PCMにWAVヘッダを付ける
WAVヘッダを付けると便利
音声データをPCMファイルとして扱う場合、再生するためには、サンプリング周波数、チャネル数、量子化ビット数を再生ツールに教えなければなりません。こんな時は、WAV【RIFF Waveform Audio Format】ヘッダを付けると便利で、簡単な方法でWAVヘッタを付けたいと思うことがあります( オーディオファイルのフォーマットについては こちら)。
WAVヘッダのテンプレート
WAVヘッダには、音源の情報やデータのサイズなどが含まれます。 今回はリニアPCM16ビット、サンプリング周波数16kHz(モノラル)のWAVヘッダを付けるプログラムを作ってみました。
リニアPCM16ビット、サンプリング周波数16kHz(モノラル)のWAVヘッダ
まずは、ヘッダ用のテンプレートを用意します。WAVヘッダにはオプションがありますが、リニアPCMでオプションを付けないと44バイトになるみたいです。
サンプルコード【C言語】
#define WAV_HEADER_SIZE 44
// リニアPCM16ビット、サンプリング周波数16kHz(モノラル) char wav_header_template[WAV_HEADER_SIZE] = { 0x52, 0x49, 0x46, 0x46, // 'RIFF' 0x00, 0x00, 0x00, 0x00, // RIFFチャンクのサイズ(size + 12 + 16 + 8) 0x57, 0x41, 0x56, 0x45, // 'WAVE' 0x66, 0x6D, 0x74, 0x20, // 'fmt' 0x10, 0x00, 0x00, 0x00, // fmtチャンクのバイト数 = 16(リニアPCM) 0x01, 0x00, // フォーマット = 1(非圧縮PCM) 0x01, 0x00, // チャネル数 = 1 (モノラル) 0x80, 0x3E, 0x00, 0x00, // サンプリング周波数 = 16000 0x00, 0x7D, 0x00, 0x00, // バイト/秒 = 32000 0x02, 0x00, // ブロックサイズ = 16bit x 1(モノラル) = 2byte 0x10, 0x00, // ビット/サンプル = 16 0x64, 0x61, 0x74, 0x61, // 'data' 0x00, 0x00, 0x00, 0x00 // size (データバイト数) };
WAVヘッダを取得するAPI
リニアPCM16ビット、サンプリング周波数16kHz(モノラル)用で、PCMのバイトサイズを設定しています。
char* get_wav_header_16kHz(long data_size) { long riff_size = data_size + 12 + 16 + 8; wav_header_template[7] = (riff_size >> 24) & 0xff; wav_header_template[6] = (riff_size >> 16) & 0xff; wav_header_template[5] = (riff_size >> 8) & 0xff; wav_header_template[4] = riff_size & 0xff; wav_header_template[43] = (data_size >> 24) & 0xff; wav_header_template[42] = (data_size >> 16) & 0xff; wav_header_template[41] = (data_size >> 8) & 0xff; wav_header_template[40] = data_size & 0xff; return wav_header_template; }
使い方
こちら FIRフィルタを作って周波数特性を検証 で紹介したFIRの検証用のサンプルコード(PCMをそのままファイル出力していたもの)にWAVヘッダを付けてみました。
ファイルサイズを求めるAPI
入力音源もPCMファイルなので、読み込むファイルのサイズを求めて、出力のデータサイズとしてWAVヘッダに埋め込むことにします。
long get_file_size(const char *FileName) { fpos_t fsize = 0; FILE *fp = fopen(FileName, "rb"); fseek(fp, 0, SEEK_END); fgetpos(fp, &fsize); fclose(fp); return (long)fsize; }
サンプルコード
ファイル出力したPCMの周波数特性を見る時に、PCMの情報を波形表示ツールに設定する必要がありますが、こんな時はWAVヘッダを付けると便利です。 WAV_FORMATのコンパイルスイッチで追加してみました。
- FIRフィルタを作って周波数特性を検証で紹介した検証用のサンプルコードにWAVヘッダを追加
#include <stdio.h> #include <stdlib.h> #include <string.h> #define WAV_FORMAT 1 // << WAVヘッダを追加 >> short fir_sample(float* coef, short *mem, short in); // サンプルコードのプロトタイプ宣言 extern float g_filter_coef[FIR_TAP]; //フィルタ係数 short mem[FIR_TAP]; //フィルタメモリ long get_file_size(const char *FileName); char* get_wav_header_16kHz(long wav_data_size); int main(int argc, char **argv) { FILE *fp_in; FILE *fp_out; #if WAV_FORMAT ///////////// WAVヘッダを追加 ////////////////// int pcm_bytes = get_file_size(argv[1]); // サイズを取得 char* wav_header = get_wav_header_16kHz(pcm_bytes); // WAVヘッダを取得 #endif if((fp_in = fopen(argv[1],"rb"))==NULL){ // PCM入力ファイル printf("can not open %s\n", argv[1]); exit(EXIT_FAILURE); } if((fp_out = fopen(argv[2],"wb"))==NULL){ // PCM出力ファイル printf("can not open %s\n", argv[2]); exit(EXIT_FAILURE); } for (int i = 0; i < FIR_TAP; i++) mem[i] = 0; // フィルタメモリ初期化 #if WAV_FORMAT ///////////// WAVヘッダを追加 ////////////////// fwrite(wav_header, WAV_HEADER_SIZE, 1, fp_out); // WAVヘッダ部分をファイル出力 #endif while(1) { short in, out; if (!fread(&in, sizeof(short), 1, fp_in))break; // PCM入力(16bitモノラル) out = fir_sample(g_filter_coef, mem, in); // FIRサンプルコード fwrite(&out, sizeof(short), 1, fp_out); // PCM出力(16bitモノラル) } fclose(fp_in); fclose(fp_out); return EXIT_SUCCESS; }
Audacityでファイル読み込み
例えばAudacityで音源ファイルを読み込む時、 *.pcmを開こうとすると、「ファイルの取り込み時にエラーが発生しました」となり失敗します。*.pcm の場合は、 【ファイル】➡【取り込み】➡【ロー (Raw) データの取り込み】という手順になりますが、*.wavではこの手順がなくなります。
Audacityについてはこちら
最後に
このようにWAVフォーマットの音源は再生ツールに読ませる場合はとても便利です。一方自分で作ったプログラムにWAVフォーマットを読み込ませようとすると、当然ながらヘッダ解析が必要になります。ヘッダは付ける側は簡単ですが、解析する側はどんなヘッダが付いてくるのか分からないので(オプションによってヘッダ長が変わるので)色々と手順がかかります。プログラムを作る人にとっては、出力側は目的に応じて、*.pcm や *.wavを簡単に使い分けられますが、入力側はヘッダなしの*.pcmが簡単ですね。