WAVEファイルの波形をgnuplotで表示してみる
サウンドエフェクト、信号処理に興味があるが前提知識が全く無い状態。とっかかりとして、まずはWAVEファイルを理解することから初めてみることにした。今回はかじり得た知識を使ってWAVEファイルから音データを抜き出して波形を表示してみた。実行環境はLinux(CentOS 7)。
WAVEファイルとは
WAVEファイルとはWindowsで音情報をディジタル信号に変換したデータのこと。例えばWindowsの警告音やシャットダウン時の音で使用されている。
波形表示対象のWAVEファイル
新しく用意するのも面倒なので下記のWindows PCのエラー音(Windows エラー.wav)を使うことにした。
ファイルフォーマット
公式な文書は見つけらなかったので、下記を参考にさせていただいた。
ファイルフォーマットと照らし合わせながら実際のパラメータ値がどうなっているか確認する。
# # Windows エラー.wavはLinuxに転送してWindows_Error.wavにリネームしている # [user@localhost wav]$ hexdump -C Windows_Error.wav |head 00000000 52 49 46 46 24 9e 02 00 57 41 56 45 66 6d 74 20 |RIFF$...WAVEfmt | 00000010 10 00 00 00 01 00 02 00 44 ac 00 00 10 b1 02 00 |........D.......| 00000020 04 00 10 00 64 61 74 61 00 9e 02 00 00 00 00 00 |....data........| 00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| * 000006a0 00 00 00 00 00 00 00 00 00 00 ff ff 00 00 fe ff |................| 000006b0 00 00 fc ff 00 00 fa ff 00 00 fd ff 00 00 09 00 |................| 000006c0 00 00 26 00 00 00 4f 00 00 00 82 00 00 00 bb 00 |..&...O.........| 000006d0 00 00 f6 00 00 00 2e 01 00 00 65 01 00 00 95 01 |..........e.....| 000006e0 00 00 bd 01 00 00 db 01 00 00 e9 01 00 00 e4 01 |................|
RIFFチャンク
パラメータ | サイズ(byte) | 値(16進数) | 補足 |
---|---|---|---|
チャンク識別子 | 4 | 0x52494646 | 'RIFF' |
チャンクサイズ | 4 | 0x00029e24 | 171556byte ヘッダサイズ(36byte)+dataチャンクのチャンクサイズ |
フォーマットタイプ | 4 | 0x57415645 | 'WAVE' |
fmtチャンク
パラメータ | サイズ(byte) | 値(16進数) | 補足 |
---|---|---|---|
チャンク識別子 | 4 | 0x666D7420 | 'fmt' |
チャンクサイズ | 4 | 0x00000010 | 16bit |
フォーマットタイプ | 2 | 0x0001 | PCM |
チャンネル数 | 2 | 0x0002 | ステレオ |
サンプリング周波数 | 4 | 0x0000ac44 | 44.1Khz |
1秒あたりバイト数 | 4 | 0x0002b110 | サンプリング周波数×ブロックサイズ |
ブロックサイズ | 2 | 0x0004 | 4byte |
量子化精度 | 2 | 0x0010 | 16bit |
dataチャンク
パラメータ | サイズ(byte) | 値(16進数) | 補足 |
---|---|---|---|
チャンク識別子 | 4 | 0x64617461 | 'data' |
チャンクサイズ | 4 | 0x00029e00 | 171520byte |
16bitステレオなのでデータを抽出するときは4byte(L,Rの順で2byte)ずつ読み込んでいけば良いようだ。
データ抽出プログラム
構造体は下記のように定義しておく。
/* RIFFチャンク */ typedef struct { char chunk_id[4]; /* チャンク識別子('RIFF'固定) */ uint32_t chunk_size; /* チャンクサイズ */ char format_type[4]; /* フォーマットタイプ('WAVE'固定) */ } RIFF_chunk_t; /* fmt チャンク */ typedef struct { char chunk_id[4]; /* チャンク識別子('fmt '固定) */ uint32_t chunk_size; /* チャンクサイズ */ uint16_t format_type; /* フォーマットタイプ */ uint16_t channel; /* チャンネル数 */ uint32_t sample_per_sec; /* サンプリング周波数 */ uint32_t byte_per_sec; /* 1秒あたりバイト数 */ uint16_t block_size; /* ブロックサイズ */ uint16_t bit_per_sample; /* 量子化精度 */ } fmt_chunk_t; /* dataチャンク */ typedef struct { char chunk_id[4]; /* チャンク識別子('data')固定 */ uint32_t chunk_size; /* チャンクサイズ(データ長) */ uint8_t dat[0]; /* データ(可変長) */ } data_chunk_t; typedef struct { RIFF_chunk_t riff; fmt_chunk_t fmt; data_chunk_t data; } wave_format_t; typedef struct { int16_t left; int16_t right; } wave_stereo_t;
読み込んだデータは3列(時間 L(左) R(右))で表示する(後でgnuplotに食わせる為)。
static void printWaveData(wave_format_t* wave) { int t = 0; for(t = 0; t < wave->data.chunk_size/sizeof(wave_stereo_t); t++) { /* time left right */ wave_stereo_t* val = (wave_stereo_t* )&wave->data.dat[t * sizeof(*val)]; printf("%-08d %-05d %-05d\n", t, val->left, val->right); } }
ソースコード全体(plotwav.c)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <unistd.h> /* RIFFチャンク */ typedef struct { char chunk_id[4]; /* チャンク識別子('RIFF'固定) */ uint32_t chunk_size; /* チャンクサイズ */ char format_type[4]; /* フォーマットタイプ('WAVE'固定) */ } RIFF_chunk_t; /* fmt チャンク */ typedef struct { char chunk_id[4]; /* チャンク識別子('fmt '固定) */ uint32_t chunk_size; /* チャンクサイズ */ uint16_t format_type; /* フォーマットタイプ */ uint16_t channel; /* チャンネル数 */ uint32_t sample_per_sec; /* サンプリング周波数 */ uint32_t byte_per_sec; /* 1秒あたりバイト数 */ uint16_t block_size; /* ブロックサイズ */ uint16_t bit_per_sample; /* 量子化精度 */ } fmt_chunk_t; /* dataチャンク */ typedef struct { char chunk_id[4]; /* チャンク識別子('data')固定 */ uint32_t chunk_size; /* チャンクサイズ(データ長) */ uint8_t dat[0]; /* データ(可変長) */ } data_chunk_t; typedef struct { RIFF_chunk_t riff; fmt_chunk_t fmt; data_chunk_t data; } wave_format_t; typedef struct { int16_t left; int16_t right; } wave_stereo_t; static wave_format_t* getWaveData(char* path); static void delWaveData(wave_format_t* wave); static void printWaveData(wave_format_t* wave); int main(int argc, char*argv[]) { if (argc != 2) { fprintf(stderr, "usage:%s <wave file>\n", argv[0]); exit(EXIT_FAILURE); } if (access(argv[1], R_OK) != 0) { perror("access"); exit(EXIT_FAILURE); } wave_format_t *wave = getWaveData(argv[1]); if (NULL == wave) { fprintf(stderr, "getWaveData(%s) failed.\n", argv[1]); exit(EXIT_FAILURE); } printWaveData(wave); delWaveData(wave); return 0; } static wave_format_t* getWaveData(char* path) { FILE* fp = NULL; char buff[sizeof(wave_format_t)] = {0}; wave_format_t* wave = NULL; fp = fopen(path, "rb"); if (NULL == fp ) { goto error_end; } if (fread(buff, 1, sizeof(buff), fp) <= 0) { goto error_close_end; } wave = (wave_format_t* )malloc(((wave_format_t* )buff)->riff.chunk_size); if (NULL == wave) { goto error_close_end; } *wave = *((wave_format_t* )buff); fread(wave->data.dat, 1, wave->data.chunk_size, fp); error_close_end: fclose(fp); error_end: return wave; } static void delWaveData(wave_format_t* wave) { if (NULL != wave) { free(wave); } } static void printWaveData(wave_format_t* wave) { int t = 0; for(t = 0; t < wave->data.chunk_size/sizeof(wave_stereo_t); t++) { /* time left right */ wave_stereo_t* val = (wave_stereo_t* )&wave->data.dat[t * sizeof(*val)]; printf("%-08d %-05d %-05d\n", t, val->left, val->right); } }
波形表示
抽出したデータ(LR両方)をグラフ化すると下記になった。
[user@localhost wav]$ gcc -o plotwav plotwav.c [user@localhost wav]$ ./plotwav Windows_Error.wav > wave.plt [user@localhost wav]$ gnuplot G N U P L O T Version 4.6 patchlevel 2 last modified 2013-03-14 Build System: Linux x86_64 Copyright (C) 1986-1993, 1998, 2004, 2007-2013 Thomas Williams, Colin Kelley and many others gnuplot home: http://www.gnuplot.info faq, bugs, etc: type "help FAQ" immediate help: type "help" (plot window: hit 'h') Terminal type set to 'x11' gnuplot> plot "wave.plt" using 1:2 with lines, "wave.plt" using 1:3 with lines