Smile Engineering Blog

ジェイエスピーからTipsや技術特集、プロジェクト物語を発信します

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のコンパイルスイッチで追加してみました。

#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が簡単ですね。