コアダンプの数だけ強くなれるよ

見習いエンジニアの備忘log

WAVEファイルの波形をgnuplotで表示してみる

サウンドエフェクト、信号処理に興味があるが前提知識が全く無い状態。とっかかりとして、まずはWAVEファイルを理解することから初めてみることにした。今回はかじり得た知識を使ってWAVEファイルから音データを抜き出して波形を表示してみた。実行環境はLinux(CentOS 7)。

WAVEファイルとは

WAVEファイルとはWindowsで音情報をディジタル信号に変換したデータのこと。例えばWindowsの警告音やシャットダウン時の音で使用されている。

波形表示対象のWAVEファイル

新しく用意するのも面倒なので下記のWindows PCのエラー音(Windows エラー.wav)を使うことにした。

f:id:segmentation-fault:20170806222158p:plain

ファイルフォーマット

公式な文書は見つけらなかったので、下記を参考にさせていただいた。

www.youfit.co.jp


ファイルフォーマットと照らし合わせながら実際のパラメータ値がどうなっているか確認する。

#
# 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

f:id:segmentation-fault:20170806225814p:plain