WAVE ファイルとは ?
WAVE とは, 音声コーデックの 1 つです. 圧縮をしないので, ファイルサイズは, MP3 や AAC などと比較するとかなり大きくなってしまいますが, 音質の劣化がありません.
WAVE ファイルの構成
WAVE ファイルは, chunk と呼ばれるブロック構造によって音データを記録します. WAVE ファイルそのものは, RIFF (Resource Interchange File Format) チャンクと呼ばれるブロックになっています. そして, RIFF チャンクのなかに, fmt チャンクと data チャンクが格納されています.
chunk | chunk | Parameter | byte | Description |
---|---|---|---|---|
RIFF チャンク | chunkID | 4 | 'RIFF' | |
chunkSize | 4 | size + 36 | ||
formType | 4 | 'WAVE' | ||
fmt チャンク | chunkID | 4 | 'fmt ' | |
chunkSize | 4 | 16 | ||
waveFormatType | 2 | プロパティ | ||
channel | 2 | プロパティ | ||
samplesPerSec | 4 | プロパティ | ||
bytesPerSec | 4 | プロパティ | ||
blockSize | 2 | プロパティ | ||
bitsPerSample | 2 | プロパティ | ||
data チャンク | chunkID | 4 | 'data' | |
chunkSize | 4 | size | ||
data | size | 音データ |
- waveFormatType
- 音データの形式で, PCM の場合 1
- channel
- モノラルの場合 1, ステレオの場合 2
- samplesPerSec
- サンプリング周波数 (単位は, Hz)
- blockSize
- 音データの最小単位を記録するのに必要なデータ量 (例: 16 bit ステレオであれば 4 byte)
- bytesPerSec
- 1 sec の音データを記録するのに必要なデータサイズ.
blockSize
とsamplesPerSec
の積
- 1 sec の音データを記録するのに必要なデータサイズ.
- bitsPerSample
- 量子化ビット (8 bit or 16 bit)
data チャンクには, 音データそのものが記録されますが, その値は, 量子化ビットと関係しています.
Min | Max | |
---|---|---|
量子化ビット 8 bit | 0 | 255 |
量子化ビット 16 bit | -32768 | 32767 |
モノラル場合, 時間の経過にしたがって, 音データが順に記録されます. ステレオの場合, 左チャンネルと右チャンネルの音データが交互に記録されます.
クリッピング
ところで, 音データが, 最大値を上回ってしまうとオーバーフローが発生 (量子化ビット 16 なら, 32768 であれば -32768, -32769 なら 0 になってしまう) してしまい, 波形が大きく変形してしまいます.
これを防止するために, それらの値 (量子化ビットが 16 なら, 32767, -32768) を上限, 下限として振幅をうちきります. この処理をクリッピングと呼びます.
実装
C++ による, WAVE ファイルの読み書きの実装です.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
typedef struct {
int fs;
int bits;
int length;
std::vector<double> s;
} MONO_PCM;
typedef struct {
int fs;
int bits;
int length;
std::vector<double> sL;
std::vector<double> sR;
} STEREO_PCM;
void wave_read(MONO_PCM *pcm, std::string filename) {
int number_of_channels = 1;
char riff_chunk_id[4];
long riff_chunk_size;
char riff_form_type[4];
char fmt_chunk_id[4];
long fmt_chunk_size;
short fmt_wave_format_type;
short fmt_channel;
long fmt_samples_per_sec;
long fmt_bytes_per_sec;
short fmt_block_size;
short fmt_bits_per_sample;
char data_chunk_id[4];
int data_chunk_size;
short data;
std::fstream file(filename, std::ios::binary | std::ios::in);
file.read(reinterpret_cast<char *>(riff_chunk_id), 1 * 4);
file.read(reinterpret_cast<char *>(&riff_chunk_size), 4 * 1);
file.read(reinterpret_cast<char *>(riff_form_type), 1 * 4);
file.read(reinterpret_cast<char *>(fmt_chunk_id), 1 * 4);
file.read(reinterpret_cast<char *>(&fmt_chunk_size), 4 * 1);
file.read(reinterpret_cast<char *>(&fmt_wave_format_type), 2 * 1);
file.read(reinterpret_cast<char *>(&fmt_channel), 2 * 1);
file.read(reinterpret_cast<char *>(&fmt_samples_per_sec), 4 * 1);
file.read(reinterpret_cast<char *>(&fmt_bytes_per_sec), 4 * 1);
file.read(reinterpret_cast<char *>(&fmt_block_size), 2 * 1);
file.read(reinterpret_cast<char *>(&fmt_bits_per_sample), 2 * 1);
file.read(reinterpret_cast<char *>(data_chunk_id), 1 * 4);
file.read(reinterpret_cast<char *>(&data_chunk_size), 4 * 1);
pcm->fs = fmt_samples_per_sec;
pcm->bits = fmt_bits_per_sample;
pcm->length = data_chunk_size / (number_of_channels * (pcm->bits / 8));
pcm->s.resize(pcm->length);
for (int n = 0; n < pcm->length; n++) {
file.read(reinterpret_cast<char *>(&data), 2 * 1);
pcm->s[n] = static_cast<double>(data) / 32768.0;
}
file.close();
}
void wave_write(MONO_PCM *pcm, std::string filename) {
int number_of_channels = 1;
char riff_chunk_id[4];
long riff_chunk_size;
char riff_form_type[4];
char fmt_chunk_id[4];
long fmt_chunk_size;
short fmt_wave_format_type;
short fmt_channel;
long fmt_samples_per_sec;
long fmt_bytes_per_sec;
short fmt_block_size;
short fmt_bits_per_sample;
char data_chunk_id[4];
int data_chunk_size;
short data;
double s;
riff_chunk_id[0] = 'R';
riff_chunk_id[1] = 'I';
riff_chunk_id[2] = 'F';
riff_chunk_id[3] = 'F';
riff_chunk_size = 36 + (number_of_channels * (16 / 8) * pcm->length);
riff_form_type[0] = 'W';
riff_form_type[1] = 'A';
riff_form_type[2] = 'V';
riff_form_type[3] = 'E';
fmt_chunk_id[0] = 'f';
fmt_chunk_id[1] = 'm';
fmt_chunk_id[2] = 't';
fmt_chunk_id[3] = ' ';
fmt_chunk_size = 16;
fmt_wave_format_type = 1;
fmt_channel = number_of_channels;
fmt_samples_per_sec = pcm->fs;
fmt_bytes_per_sec = pcm->fs * (number_of_channels * (pcm->bits / 8));
fmt_block_size = number_of_channels * (pcm->bits / 8);
fmt_bits_per_sample = pcm->bits;
data_chunk_id[0] = 'd';
data_chunk_id[1] = 'a';
data_chunk_id[2] = 't';
data_chunk_id[3] = 'a';
data_chunk_size = (number_of_channels * (16 / 8)) * pcm->length;
std::fstream file(filename, std::ios::binary | std::ios::out);
file.write(reinterpret_cast<char *>(riff_chunk_id), 1 * 4);
file.write(reinterpret_cast<char *>(&riff_chunk_size), 4 * 1);
file.write(reinterpret_cast<char *>(riff_form_type), 1 * 4);
file.write(reinterpret_cast<char *>(fmt_chunk_id), 1 * 4);
file.write(reinterpret_cast<char *>(&fmt_chunk_size), 4 * 1);
file.write(reinterpret_cast<char *>(&fmt_wave_format_type), 2 * 1);
file.write(reinterpret_cast<char *>(&fmt_channel), 2 * 1);
file.write(reinterpret_cast<char *>(&fmt_samples_per_sec), 4 * 1);
file.write(reinterpret_cast<char *>(&fmt_bytes_per_sec), 4 * 1);
file.write(reinterpret_cast<char *>(&fmt_block_size), 2 * 1);
file.write(reinterpret_cast<char *>(&fmt_bits_per_sample), 2 * 1);
file.write(reinterpret_cast<char *>(data_chunk_id), 1 * 4);
file.write(reinterpret_cast<char *>(&data_chunk_size), 4 * 1);
for (int n = 0; n < pcm->length; n++) {
s = ((pcm->s[n] + 1.0) / 2.0) * 65536.0;
if (s > 65535.0) {
s = 65535.0;
}
if (s < 0.0) {
s = 0.0;
}
data = static_cast<short>((s + 0.5) - 32768);
file.write(reinterpret_cast<char *>(&data), 2 * 1);
}
file.close();
}
void wave_read(STEREO_PCM *pcm, std::string filename) {
int number_of_channels = 2;
char riff_chunk_id[4];
long riff_chunk_size;
char riff_form_type[4];
char fmt_chunk_id[4];
long fmt_chunk_size;
short fmt_wave_format_type;
short fmt_channel;
long fmt_samples_per_sec;
long fmt_bytes_per_sec;
short fmt_block_size;
short fmt_bits_per_sample;
char data_chunk_id[4];
int data_chunk_size;
short dataL;
short dataR;
std::fstream file(filename, std::ios::binary | std::ios::in);
file.read(reinterpret_cast<char *>(riff_chunk_id), 1 * 4);
file.read(reinterpret_cast<char *>(&riff_chunk_size), 4 * 1);
file.read(reinterpret_cast<char *>(riff_form_type), 1 * 4);
file.read(reinterpret_cast<char *>(fmt_chunk_id), 1 * 4);
file.read(reinterpret_cast<char *>(&fmt_chunk_size), 4 * 1);
file.read(reinterpret_cast<char *>(&fmt_wave_format_type), 2 * 1);
file.read(reinterpret_cast<char *>(&fmt_channel), 2 * 1);
file.read(reinterpret_cast<char *>(&fmt_samples_per_sec), 4 * 1);
file.read(reinterpret_cast<char *>(&fmt_bytes_per_sec), 4 * 1);
file.read(reinterpret_cast<char *>(&fmt_block_size), 2 * 1);
file.read(reinterpret_cast<char *>(&fmt_bits_per_sample), 2 * 1);
file.read(reinterpret_cast<char *>(data_chunk_id), 1 * 4);
file.read(reinterpret_cast<char *>(&data_chunk_size), 4 * 1);
pcm->fs = fmt_samples_per_sec;
pcm->bits = fmt_bits_per_sample;
pcm->length = data_chunk_size / (number_of_channels * (pcm->bits / 8));
pcm->sL.resize(pcm->length);
pcm->sR.resize(pcm->length);
for (int n = 0; n < pcm->length; n++) {
file.read(reinterpret_cast<char *>(&dataL), 2 * 1);
file.read(reinterpret_cast<char *>(&dataR), 2 * 1);
pcm->sL[n] = static_cast<double>(dataL) / 32768.0;
pcm->sR[n] = static_cast<double>(dataR) / 32768.0;
}
file.close();
}
void wave_write(STEREO_PCM *pcm, std::string filename) {
int number_of_channels = 2;
char riff_chunk_id[4];
long riff_chunk_size;
char riff_form_type[4];
char fmt_chunk_id[4];
long fmt_chunk_size;
short fmt_wave_format_type;
short fmt_channel;
long fmt_samples_per_sec;
long fmt_bytes_per_sec;
short fmt_block_size;
short fmt_bits_per_sample;
char data_chunk_id[4];
int data_chunk_size;
short dataL;
short dataR;
double sL;
double sR;
riff_chunk_id[0] = 'R';
riff_chunk_id[1] = 'I';
riff_chunk_id[2] = 'F';
riff_chunk_id[3] = 'F';
riff_chunk_size = 36 + (number_of_channels * (16 / 8) * pcm->length);
riff_form_type[0] = 'W';
riff_form_type[1] = 'A';
riff_form_type[2] = 'V';
riff_form_type[3] = 'E';
fmt_chunk_id[0] = 'f';
fmt_chunk_id[1] = 'm';
fmt_chunk_id[2] = 't';
fmt_chunk_id[3] = ' ';
fmt_chunk_size = 16;
fmt_wave_format_type = 1;
fmt_channel = number_of_channels;
fmt_samples_per_sec = pcm->fs;
fmt_bytes_per_sec = pcm->fs * (number_of_channels * (pcm->bits / 8));
fmt_block_size = number_of_channels * (pcm->bits / 8);
fmt_bits_per_sample = pcm->bits;
data_chunk_id[0] = 'd';
data_chunk_id[1] = 'a';
data_chunk_id[2] = 't';
data_chunk_id[3] = 'a';
data_chunk_size = (number_of_channels * (16 / 8)) * pcm->length;
std::fstream file(filename, std::ios::binary | std::ios::out);
file.write(reinterpret_cast<char *>(riff_chunk_id), 1 * 4);
file.write(reinterpret_cast<char *>(&riff_chunk_size), 4 * 1);
file.write(reinterpret_cast<char *>(riff_form_type), 1 * 4);
file.write(reinterpret_cast<char *>(fmt_chunk_id), 1 * 4);
file.write(reinterpret_cast<char *>(&fmt_chunk_size), 4 * 1);
file.write(reinterpret_cast<char *>(&fmt_wave_format_type), 2 * 1);
file.write(reinterpret_cast<char *>(&fmt_channel), 2 * 1);
file.write(reinterpret_cast<char *>(&fmt_samples_per_sec), 4 * 1);
file.write(reinterpret_cast<char *>(&fmt_bytes_per_sec), 4 * 1);
file.write(reinterpret_cast<char *>(&fmt_block_size), 2 * 1);
file.write(reinterpret_cast<char *>(&fmt_bits_per_sample), 2 * 1);
file.write(reinterpret_cast<char *>(data_chunk_id), 1 * 4);
file.write(reinterpret_cast<char *>(&data_chunk_size), 4 * 1);
for (int n = 0; n < pcm->length; n++) {
sL = ((pcm->sL[n] + 1.0) / 2.0) * 65536.0;
sR = ((pcm->sR[n] + 1.0) / 2.0) * 65536.0;
if (sL > 65535.0) {
sL = 65535.0;
}
if (sL < 0.0) {
sL = 0.0;
}
if (sR > 65535.0) {
sR = 65535.0;
}
if (sR < 0.0) {
sR = 0.0;
}
dataL = static_cast<short>((sL + 0.5) - 32768);
dataR = static_cast<short>((sR + 0.5) - 32768);
file.write(reinterpret_cast<char *>(&dataL), 2 * 1);
file.write(reinterpret_cast<char *>(&dataR), 2 * 1);
}
file.close();
}