PythonでWAVEファイルのバイナリデータを解析する
音データのファイルであるWAVEファイルの中身を調べてみました。WAVEファイルはバイナリで書き込まれてますが、Pythonやシェルを使えば比較的簡単にバイナリを解析できます。この技術は、WAVEファイル以外のバイナリファイルにも応用できるので役立つはずです。
WAVEファイルの構造
ヘッダー
パラメータ | byte | 内容 |
---|---|---|
chunkId | 4 | "RIFF" |
chunkSize | 4 | size + 36 |
formType | 4 | "WAVE" |
fmtチャンク
パラメータ | byte | 内容 |
---|---|---|
chunkID | 4 | "fmt " |
chunkSize | 4 | 16 |
waveFormatType | 2 | PCM=1 |
channel | 2 | mono=1, stereo=2 |
samplePerSec | 4 | ex. 44100 |
bytePerSec | 2 | |
blockSize | 2 | 音データあたりのbyte数 |
bitsPerSample | 2 | 量子化精度、8=8bit,16=16bit |
dataチャンク
パラメータ | byte | 内容 |
---|---|---|
chunkID | 4 | "data" |
chunkSize | 4 | size |
data | size | 音データ |
音データ
WAVEファイルの構造より、つまりは44byte目からが音データとなる。音データはsigned int(例外もあり)で表現されている。
channel | blockSize | 内容 |
---|---|---|
1 | 1 | 8bitモノラル |
2 | 1 | 8bitステレオ |
2 | 1 | 16bitモノラル |
2 | 2 | 16bitステレオ |
たとえば、16bitモノラルでは音データ単位あたりの数値が2byteで表現される。 つまり1サンプルは-32768〜32768の範囲内の値となる。
つぎはaudacityで作った440Hzの正弦波をodコマンドを使って音データを表示した場合の例だ。
od -j 44 -td2 raw/sin440.wav | more0000054 -1 1644 3276 4904 6501 8087 9625 11143
0000074 12597 14023 15375 16679 17913 19075 20166 21175
0000114 22100 22943 23689 24349 24907 25372 25734 25997
0000134 26156 26215 26169 26019 25770 25418 24964 24418
0000154 23768 23033 22201 21282 20287 19201 18052 16818
0000174 15531 14174 12768 11305 9803 8261 6685 5085
0000214 3463 1828 187 -1455 -3094 -4716 -6325 -7904
0000234 -9456 -10969 -12438 -13860 -15226 -16534 -17776 -18947
0000254 -20046 -21063 -22002 -22849 -23611 -24277 -24850 -25323
0000274 -25699 -25971 -26144 -26214 -26178 -26043 -25803 -25461
0000314 -25025 -24480 -23851 -23119 -22299 -21394 -20401 -19331
0000334 -18183 -16964 -15680 -14331 -12930 -11474 -9976 -8438
Pythonで音バイナリデータを数値に変換する
open(filename, 'rb')でwavファイルを読み込み、44byte移行の音データをstruct.unpack('<B', wav_file.read(1))[0] でsamples配列に格納する。 unpackのBオブションは1byteをunsigned charとして読み込む意味である。しかしグラフ表示にしたりフィルター処理をしたい場合、このままだと不便なので、-32768から32768の数値データへ変換する。ここで注意したいのが 16bitモノラルの場合、1サンプル(2byte)はリトルエンディアンで並んでいる。つまり次のようにして2byteの数値を格納している。
1byte目 | 2byte目 |
---|---|
下の桁 | 上の桁 |
このデータ配列を変換するために次のプログラムを作った。
- 配列内のunsigned char値を2進数に変換し、2byte目を8bit分シフトする。
- それを1byte目と足し合わせる。32767より大きい値は負の値であるので65536を引いてあげる。
def convert_int16_samples(samples):
data = []
for i in range(0, int(len(samples) / 2), 2):
s1 = samples[i]
s2 = samples[i + 1]
left_b = s2 << 8
right_b = s1
s = (left_b | right_b)
if s > 32767:
s = s - 65536
data.append(s)
return data
※numpyのnp.array(samples, dtype=np.int16)を使えばもっと簡単に書くことができてしまうが、WAVEの構造を学ぶためには上記のプログラムが役に立つだろう。
この配列データであれば、pyplotなどで簡単にグラフ表示ができる。
その他メモ
1byte (8bit) -> 256 まで表現可能
2byte (16bit) -> 65,536 まで表現可能
4byte (32bit) -> 4,294,967,296 まで表現可能