今後いろいろと加工してみたいので最初に入出力をできるようにしておきます. Waveファイルからチャンネル数とサンプリング周波数,量子化ビット,ブロック数の情報だけを抽出します.それをそのままヘッダ部分を付けてWaveファイルにしてみました. Waveファイルにもいろいろな種類があるようなのですが,扱うのが一番簡単であることや,比較的入手しやすいデータ形式であるリニアPCMに限定します.これは圧縮もされていないので簡単ですし,音楽CDからリッピングすると16ビットステレオのものが得られます.ここで,チャンネル数とはモノラルであるかステレオであるかの情報で,サンプリング周波数とはアナログの音声データから離散に変換するときに, 1秒間にいくつのデータを取ってくるかということです.音楽CDではサンプリング周波数は44.1kHzとなっています.量子化ビットはアナログ信号の強さを何段階に分類するかということで, waveでは8ビットか16ビットですが,それぞれ256段階,65536段階となります. 8ビットでは128が無音,16ビットでは0が無音を表します.ブロック数はモノラルでは1サンプルを1ブロックとして何ブロックのデータがあるか,ステレオでは左右の1サンプル合わせて1ブロックとして何ブロックあるかということです.
今回RIFFフォーマットを扱いますが,ヘッダ及びデータの並びは次のようになります.
RIFFヘッダ | オフセット(0) | ファイルタイプ | RIFFといれる |
オフセット(4) | ファイルサイズ-8 | ファイル全体のサイズから8を引いたものをバイト単位でいれる | |
オフセット(8) | RIFFのタイプ | WAVEと入れる | |
fmtチャンク | オフセット(12) | FormatチャンクID | fmt と入れる(fmtの後に空白が1つ入る) |
オフセット(16) | fmtチャンクのサイズ | フォーマットID以降のfmtチャンクのサイズ(確実に存在するのは16バイト) | |
オフセット(20) | フォーマットID | リニアPCMなので1を入れる | |
オフセット(22) | チャンネル数 | モノラルであれば1,ステレオならば2 | |
オフセット(24) | サンプリング周波数 | Hz単位 | |
オフセット(28) | データ速度 | サンプリング周波数×チャンネル数×量子化ビット/2 | |
オフセット(32) | ブロックサイズ | チャンネル数×量子化ビット/2 | |
オフセット(34) | 量子化ビット数 | 8もしくは16 | |
dataチャンク | オフセット(36) | dataチャンクID | dataといれる |
オフセット(40) | dataチャンクのサイズ | 周波数データのサイズをいれる | |
オフセット(44) | 周波数データ | 量子化ビット,チャンネル数によって決められた順に並ぶ |
以上がリニアPCMに限定したwaveファイルのヘッダ部分ですが, dataチャンクの前にfactチャンクや,fmtチャンクの拡張部分が入る場合があります.これらはあっても無くても関係ないようなのですが,存在する場合読み取る時に考慮していないとリニアPCMなのに読み取れないということになります.
factチャンクや拡張部分が存在しても読み取れるようにしてみます.ヘッダの部分が一定になりませんのでソースが汚くなってしまいましたが,とりあえず,ソースコードを載せておきます.
/*----------------------wave.h----------------------*/ typedef struct{ signed short l; signed short r; }Soundsample16; typedef struct{ unsigned char l; unsigned char r; }Soundsample8; typedef struct{ unsigned short channelnum; //モノラルなら1、ステレオなら2 unsigned long samplingrate; //Hz単位 unsigned short bit_per_sample; //1サンプルあたりのbit数 unsigned long datanum; //ブロック数 unsigned char *monaural8; //8ビットモノラルのデータならこれを使う signed short *monaural16; //16ビットモノラルならばこれを使う Soundsample8 *stereo8; //8ビットステレオならばこれを使う Soundsample16 *stereo16; //16ビットステレオならばこれを使う }Sound;
音声を読み込むにあたってリニアPCMならば,チャンネル数,サンプリング周波数,量子化ビット,ブロック数の4つがあれば,残りのヘッダは再現できますので,その他のヘッダ情報は保持しません.また,ステレオの場合は左右のデータで分かれていますので構造体でまとめています.
/*--------------------------bitmap.c----------------------*/ //フォーマットチャンクサイズまでのヘッダ部分を取り込む fread(header_buf, sizeof(unsigned char), 20, fp); //ファイルがRIFF形式であるか if(strncmp(header_buf, "RIFF", 4)){ fprintf(stderr, "Error: %s is not RIFF.", filename); fclose(fp); return NULL; } //ファイルがWAVEファイルであるか if(strncmp(header_buf + 8, "WAVE", 4)){ fprintf(stderr, "Error: %s is not WAVE.", filename); fclose(fp); return NULL; } //fmt のチェック if(strncmp(header_buf + 12, "fmt ", 4)){ fprintf(stderr, "Error: %s fmt not found.", filename); fclose(fp); return NULL; }
一気にヘッダ情報を全て読み込みたいところですが,factチャンクや,拡張部分が存在する場合,ヘッダの長さが変わってしまうため,fmtチャンクサイズまでの20バイトを読み込んでいます.書き込むときはfactや拡張部分はなくていいのでヘッダサイズを44バイトで固定しています.そこからfmtチャンクサイズ分を読み込めば拡張部分の有無に関わらず次に読み込むのはfactもしくはdataチャンクとなります.
memcpy(&fmtsize, header_buf + 16, sizeof(fmtsize)); if((buf = (unsigned char *)malloc( sizeof(unsigned char)*fmtsize)) == NULL){ fprintf(stderr, "Allocation error\n"); fclose(fp); return NULL; } //フォーマットIDから拡張部分までのヘッダ部分を取り込む fread(buf, sizeof(unsigned char), fmtsize, fp);
fmtsizeにfmtチャンクサイズを取り込み,bufに動的にfmtsize分の領域を割り当て,拡張部分までを取り込んでいます.
//factもしくはdataのIDとサイズを取得8バイト fread(buf, sizeof(unsigned char), 8, fp); if(!strncmp(buf, "fact", 4)){ //残った4バイト切捨て fread(buf, sizeof(unsigned char), 4, fp); fread(buf, sizeof(unsigned char), 8, fp); } if(strncmp(buf, "data", 4)){ fprintf(stderr, "Error: %s data part not found.", filename); fclose(fp); return NULL; } memcpy(&datasize, buf + 4, sizeof(datasize)); //波形データのサイズの取得
拡張部分の後がfactであろうとdataであろうとID,チャンクサイズという順で並んでいますのでとりあえず8バイト取得しています.最初の4バイトがfactである場合,factチャンクはID,サイズ,サンプル数と全部で12バイトが存在しますので,後4バイト残っているため読み込んで破棄しています.その後8バイト取得してdataのIDとサイズを読み込んでいます.
if(channelnum==1 && bit_per_sample==8){ fread(snd->monaural8, sizeof(unsigned char), snd->datanum, fp); }else if(channelnum==1 && bit_per_sample==16){ fread(snd->monaural16, sizeof(signed short), snd->datanum, fp); }else if(channelnum==2 && bit_per_sample==8){ for(i=0; i<snd->datanum; i++){ fread(&(snd->stereo8[i].l), sizeof(unsigned char), 1, fp); fread(&(snd->stereo8[i].r), sizeof(unsigned char), 1, fp); } }else if(channelnum==2 && bit_per_sample==16){ for(i=0; i<snd->datanum; i++){ fread(&(snd->stereo16[i].l), sizeof(signed short), 1, fp); fread(&(snd->stereo16[i].r), sizeof(signed short), 1, fp); } }else{ fprintf(stderr, "Header is destroyed."); fclose(fp); Free_Sound(snd); }
この部分は自分でも気に入らないのですが,チャンネル数と量子化ビット毎に保存先を変えているためこのような場合分けをしています.また,モノラルであればそのまま1つずつサンプリングデータを取っていけばいいですが,ステレオであれば「左」「右」「左」・・・とならんでいますので注意してください.汚いですが全ソースコードを載せておきます.
これらのファイルを同じディレクトリに入れ,
$gcc main.c wave.c
とすれば実行可能ファイルができ,
$./a.out inputfile.wav outputfile.wav
で実行できます. main関数のRead_WaveとWrite_Waveの関数の間でデータ弄れば音声が加工できます.