IMA ADPCM による音声圧縮

音声データを圧縮する方式には MP3 のような知覚符号化(非可逆)もあれば、もっとシンプルに波形そのものの冗長性を削る方式もあります。今回はその中でも構造がシンプルで、組み込み機器やゲーム機のサウンドで長年使われ続けている「IMA ADPCM」を取り上げます。

ADPCM とは

ADPCM(Adaptive Differential Pulse-Code Modulation, 適応差分パルス符号変調)は、PCM(生の音声サンプル列)を圧縮するための符号化方式です。16bit の PCM をそのまま保存すると 1 サンプルあたり 16bit 必要ですが、ADPCM では多くの場合 4bit 程度まで圧縮できます。

ADPCM では音声信号の隣接サンプルの値が近い(相関が高い)という性質を利用しています。具体的には、以下の 4 つの要素を組み合わせて圧縮しています。

  • 予測(Prediction): 直前のサンプルから次の値を推定する
  • 差分(Difference): 実際の値と予測値の差だけを符号化する
  • 量子化(Quantization): 差分を少ないビット数のコードに丸める
  • 適応(Adaptation): 差分の大きさに応じて、次に使う量子化の粒度(ステップサイズ)を動的に変える

ADPCM には IMA ADPCM のほかにも、電話網で使われる G.726 [1]、各種ゲーム機の独自方式(Yamaha ADPCM、PSX ADPCM など)といった派生が数多く存在します。その中でも IMA ADPCM は仕様がシンプルで実装しやすく、WAV ファイルのコーデックとしても広く使われています [3]。

IMA ADPCM のフロー

IMA ADPCM の符号化は以下の手順となります [2]。

図:IMA ADPCM のエンコード処理の流れ
図:IMA ADPCM のエンコード処理の流れ

1. 差分計算
実際のサンプル値と予測値の差 diff を求め、符号(正負)を sign ビットとして分離します。

2. ステップサイズテーブル参照
その時点のステップサイズ step(全 89 段階のテーブルから参照)を使い、差分 diff を 3bit の大きさコード delta に変換します。下図のように diff > step x i/4 となる最大の整数 i を、delta=i とします。delta の最大値は 7 です。

図:delta の決め方
図:delta の決め方

ステップサイズのグラフは以下のようになり、インデックスに対して指数的に増加します。

図:ステップサイズのグラフ
図:ステップサイズのグラフ

3. 4bit コードの生成
sign(1bit)と delta(3bit)を組み合わせて、1 サンプルを 4bit コード code = delta | sign として出力します。

4. 予測値の更新(誤差フィードバック)
前回の値(valprev)と 量子化後の差分(vpdiff)の和で予測値(valpred)を更新します。

5. ステップインデックスを適応的に更新
出力した delta の値に応じて、index_table = [-1, -1, -1, -1, 2, 4, 6, 8] を使いステップサイズのインデックスを増減させます。差分が大きいコードが出た(=急激な変化があった)ときはステップを広げ、小さいコードが続く(=なだらかな信号)ときはステップを狭めて精度を上げます。

デコード側は、この逆の手順(4bit コード→ステップサイズ参照→差分復元→予測値更新→インデックス更新)を辿るだけなので、エンコーダとほぼ対称的な処理になります。

図:IMA ADPCM のデコード処理の流れ
図:IMA ADPCM のデコード処理の流れ

IMA ADPCM のヘッダー構造

実装の前に、IMA ADPCM を格納した WAV ファイルのヘッダー構造について触れておきます [3]。通常の PCM の WAV とは異なり、圧縮フォーマットならではのチャンク構成になっています。

全体構造

通常の PCM WAV は RIFFfmt data の3チャンクですが、IMA ADPCM のような圧縮フォーマットでは間に fact チャンクが必須で追加されます。

  • "RIFF" + ファイルサイズ + "WAVE"
  • "fmt " + チャンクサイズ(20) + 拡張 fmt 構造体
  • "fact" + チャンクサイズ(4) + デコード後の総サンプル数
  • "data" + チャンクサイズ + ブロック化された圧縮データ

fmt チャンクの拡張

通常の PCM の fmt チャンクは16バイト固定ですが、IMA ADPCM は圧縮フォーマット固有の追加情報が必要なため、Microsoft の WAVEFORMATEX という拡張構造を使います。ソースコードの AdpcmFmtChunk がそのまま対応します。

typedef struct {
    uint16_t audio_format;       /* 0x0011 = WAVE_FORMAT_IMA_ADPCM */
    uint16_t num_channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;        /* PCMと意味が違う */
    uint16_t bits_per_sample;    /* 4 */
    uint16_t cb_size;            /* 拡張フィールドのバイト数 = 2 */
    uint16_t samples_per_block;  /* IMA ADPCM固有の追加フィールド */
} AdpcmFmtChunk;
  • cb_size: PCM の16バイトを超えて追加される拡張データのバイト数。IMA ADPCM では samples_per_block(2バイト)の分だけ cb_size = 2 になる。
  • samples_per_block: 1ブロックに何サンプル分の音声が入っているか。これが無いとデコーダはブロックの区切り位置を計算できない。
  • block_align: PCM では「1サンプルのバイト数」(モノラル16bitなら2)だが、IMA ADPCM では「1ブロックのバイト数」という別の意味になる。

fact チャンク

PCM なら「データサイズ ÷ ブロックアライン = サンプル数」で計算できますが、ADPCM は端数ブロックの扱いなどがあり、圧縮後のバイト数から元のサンプル数を単純には割り出せません。そのため fact チャンクにデコード後の総サンプル数を明示的に格納しておくのが WAV の規約です。

data チャンクのブロック構造

data チャンクは生の4bitコード列がベタ並びなのではなく、block_align バイトの「ブロック」が連続しています。1ブロックの中身は次のようになっています。

  • valprev (int16, 2byte) … このブロック最初のサンプルを生の値で格納
  • index (uint8, 1byte) … このブロック開始時点のステップテーブルindex
  • reserved (uint8, 1byte) … 常に0
  • 4bitデータ x samples_per_block

記事のコードでいうと、以下の部分です。

int16_t v0 = pcm_block[0];
state.valprev = v0;              /* このブロックの予測起点にリセット */
memcpy(out, &v0, sizeof v0);
*out++ = (uint8_t)state.index;
*out++ = 0;                      /* reserved */

ADPCM は「直前の値との差分」を積み重ねる予測符号化なので、本来は最初のサンプルから順にデコードしないと状態(valprev, index)が再現できません。もしこれを最初の1回しか埋め込まないと、途中から再生(シーク)できない、ファイルの一部が壊れるとそれ以降すべてのデコードが破綻する、といった問題が起きます。数千サンプルごとに生の値へリセットすることで、ブロック単位で独立してデコード開始できるようにしているわけです。

また、samples_per_block - 1 は偶数でなければならないのですが、それは4bitコードとして2個ずつ1byteに詰めるため(奇数だと最後のニブルが半端になる)です。

ニブル(nibble):4bit(1byteの半分)のデータ単位、1byte=2nibble。

プログラム

C言語 で IMA ADPCM のエンコーダ・デコーダを実装しました。実装したといっても Claude Code にほとんど書いていただきましたが…。

エンコード

エンコードのソースコードは以下です。

/* 1サンプルを4bitコード(符号1bit+差分3bit)にエンコードする */
static uint8_t encode_sample(int16_t sample, AdpcmState *st)
{
    int32_t step = STEP_TABLE[st->index];

    /* 1. 差分計算 */
    int32_t diff = sample - st->valprev;
    uint8_t sign = diff < 0 ? 8 : 0; if (sign) { diff = -diff; } /* 2. ステップサイズテーブル参照 */ uint8_t delta = 0; int32_t vpdiff = step >> 3;
    for (uint8_t bit = 4; bit != 0; bit >>= 1) {
        if (diff >= step) {
            delta |= bit;
            diff -= step;
            vpdiff += step;
        }
        step >>= 1;
    }

    /* 4. 予測値の更新(誤差フィードバック)*/
    int32_t valpred;
    if (sign) {
        valpred = st->valprev - vpdiff;
    }
    else {
        valpred = st->valprev + vpdiff;
    }
    st->valprev = clamp(valpred, -32768, 32767);

    /* 5. ステップインデックスを適応的に更新 */
    st->index = clamp(st->index + INDEX_TABLE[delta], 0, 88);

    /* 3. 4bit コードの生成 */
    return delta | sign;
}

エンコードの全体のソースコードは以下です。

ソースコード【adpcm_encode.c】

adpcm_encode.c
#include 
#include 
#include 
#include 
#include 

/* 1ブロックあたりのサンプル数。
   先頭の1サンプルはpreambleで直接送るので、実際にエンコードするのは
   SAMPLES_PER_BLOCK - 1 サンプル。
   preamble: 各ブロックの先頭に置かれる、圧縮せずそのまま埋め込む初期状態のデータ
   IMA ADPCMの仕様上、(SAMPLES_PER_BLOCK - 1) は偶数でなければならない */
#define SAMPLES_PER_BLOCK 4097

typedef struct {
    char     riff[4];
    uint32_t file_size;
    char     wave[4];
} RiffHeader;

typedef struct {
    char     id[4];
    uint32_t size;
} ChunkHeader;

/* fmtチャンク(入力側、16bit PCM モノラル想定) */
typedef struct {
    uint16_t audio_format;
    uint16_t num_channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;
} FmtChunk;

/* IMA ADPCM用の拡張fmtチャンク(出力側) */
typedef struct {
    uint16_t audio_format;       /* 0x0011 (IMA ADPCM) */
    uint16_t num_channels;       /* 常に1(モノラル) */
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;    /* 4 */
    uint16_t cb_size;            /* 拡張データのバイト数 (2) */
    uint16_t samples_per_block;
} AdpcmFmtChunk;

/* ステップサイズテーブルとインデックス変化テーブル(IMA ADPCM標準) */
static const int32_t STEP_TABLE[89] = {
    7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31,
    34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143,
    157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658,
    724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
    3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767,
};
static const int32_t INDEX_TABLE[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };

/* ADPCMの状態(予測値とステップインデックス) */
typedef struct {
    int32_t valprev;
    int32_t index;
} AdpcmState;

/* 1ブロック分の作業バッファ(モノラル専用)
   ファイル全体をメモリに載せず、ブロック単位で読む→エンコードする→書くを繰り返す */
static int16_t   pcm_block[SAMPLES_PER_BLOCK];
static uint8_t   out_block[4 + ((SAMPLES_PER_BLOCK - 1) + 1) / 2];
static AdpcmState state;

static int32_t clamp(int32_t val, int32_t lo, int32_t hi)
{
    if (val < lo) { return lo; } if (val > hi) {
        return hi;
    }
    return val;
}

/* 1サンプルを4bitコード(符号1bit+差分3bit)にエンコードする */
static uint8_t encode_sample(int16_t sample, AdpcmState *st)
{
    int32_t step = STEP_TABLE[st->index];

    /* 1. 差分計算 */
    int32_t diff = sample - st->valprev;
    uint8_t sign = diff < 0 ? 8 : 0; if (sign) { diff = -diff; } /* 2. ステップサイズテーブル参照 */ uint8_t delta = 0; int32_t vpdiff = step >> 3;
    for (uint8_t bit = 4; bit != 0; bit >>= 1) {
        if (diff >= step) {
            delta |= bit;
            diff -= step;
            vpdiff += step;
        }
        step >>= 1;
    }

    /* 4. 予測値の更新(誤差フィードバック)*/
    int32_t valpred;
    if (sign) {
        valpred = st->valprev - vpdiff;
    }
    else {
        valpred = st->valprev + vpdiff;
    }
    st->valprev = clamp(valpred, -32768, 32767);

    /* 5. ステップインデックスを適応的に更新 */
    st->index = clamp(st->index + INDEX_TABLE[delta], 0, 88);

    /* 3. 4bit コードの生成 */
    return delta | sign;
}

/* fread失敗時にエラーを表示して終了する */
static void freadn(void *buf, size_t size, size_t n, FILE *fp, const char *what)
{
    if (fread(buf, size, n, fp) != n) {
        fprintf(stderr, "%s read error\n", what);
        exit(1);
    }
}

int main(void)
{
    FILE *fp_in = fopen("input.wav", "rb");
    if (!fp_in) {
        perror("fopen");
        return 1;
    }

    RiffHeader riff;
    freadn(&riff, sizeof riff, 1, fp_in, "RIFF header");

    /* fmtチャンクを読み、dataチャンクの手前で読み込みを止める。
       PCMサンプル本体はここでは読み込まず、あとでブロック単位に読み進める
       (WAVの仕様上、fmtチャンクはdataチャンクより前に置かれる) */
    FmtChunk fmt = {0};
    uint32_t data_size = 0;
    bool found_data = false;
    ChunkHeader chunk;
    while (!found_data && fread(&chunk, sizeof chunk, 1, fp_in) == 1) {
        if (memcmp(chunk.id, "fmt ", 4) == 0) {
            freadn(&fmt, sizeof fmt, 1, fp_in, "fmt");
            if (chunk.size > sizeof fmt) {
                fseek(fp_in, chunk.size - sizeof fmt, SEEK_CUR);
            }
        }
        else if (memcmp(chunk.id, "data", 4) == 0) {
            data_size = chunk.size;
            found_data = true; /* ファイル位置はPCMサンプルの先頭 */
        }
        else {
            fseek(fp_in, chunk.size, SEEK_CUR);
        }
    }
    if (!found_data) {
        fprintf(stderr, "data chunk not found\n");
        return 1;
    }

    /* このプログラムはモノラル(1ch)・16bit PCM専用。それ以外はエラーにする */
    if (fmt.num_channels != 1) {
        fprintf(stderr, "only mono (1ch) WAV is supported (got %u channels)\n", fmt.num_channels);
        return 1;
    }
    if (fmt.bits_per_sample != 16) {
        fprintf(stderr, "only 16-bit PCM WAV is supported (got %u-bit)\n", fmt.bits_per_sample);
        return 1;
    }
    uint32_t num_samples = data_size / sizeof(int16_t);

    /* SAMPLES_PER_BLOCKごとにブロック分割する。
       各ブロックの先頭サンプルは量子化せず、そのままpreambleとして埋め込む
       (ここでvalprevを実際のサンプル値にリセットすることで、途中のブロックだけを
         取り出しても復号できるようにしている) */
    uint32_t num_blocks = (num_samples + SAMPLES_PER_BLOCK - 1) / SAMPLES_PER_BLOCK;
    uint32_t preamble_bytes = 4; /* valprev(2) + index(1) + reserved(1) */
    uint32_t nibbles_per_block = SAMPLES_PER_BLOCK - 1;
    uint32_t code_bytes_per_block = (nibbles_per_block + 1) / 2;
    uint32_t block_align = preamble_bytes + code_bytes_per_block;
    uint32_t data_bytes = num_blocks * block_align;

    /* 出力WAV(IMA ADPCM)のfmtチャンクを組み立てる。
       サイズ類はここまでの情報だけですべて計算できているので、
       サンプル本体を処理する前にヘッダー一式を先に書き出してしまう */
    AdpcmFmtChunk adpcm_fmt;
    adpcm_fmt.audio_format      = 0x0011;
    adpcm_fmt.num_channels      = 1;
    adpcm_fmt.sample_rate       = fmt.sample_rate;
    adpcm_fmt.bits_per_sample   = 4;
    adpcm_fmt.cb_size           = 2;
    adpcm_fmt.samples_per_block = SAMPLES_PER_BLOCK;
    adpcm_fmt.block_align       = (uint16_t)block_align;
    adpcm_fmt.byte_rate         = fmt.sample_rate * block_align / SAMPLES_PER_BLOCK;

    uint32_t fact_sample_length = num_samples;

    FILE *fp_out = fopen("output_adpcm.wav", "wb");
    if (!fp_out) {
        perror("fopen");
        return 1;
    }

    RiffHeader out_riff;
    memcpy(out_riff.riff, "RIFF", 4);
    memcpy(out_riff.wave, "WAVE", 4);
    out_riff.file_size = 4
                        + (uint32_t)(8 + sizeof adpcm_fmt)
                        + (uint32_t)(8 + sizeof(uint32_t))
                        + (uint32_t)(8 + data_bytes);
    fwrite(&out_riff, sizeof out_riff, 1, fp_out);

    ChunkHeader fmt_header = { "fmt ", sizeof adpcm_fmt };
    fwrite(&fmt_header, sizeof fmt_header, 1, fp_out);
    fwrite(&adpcm_fmt, sizeof adpcm_fmt, 1, fp_out);

    /* factチャンク:デコード後の全サンプル数 */
    ChunkHeader fact_header = { "fact", sizeof fact_sample_length };
    fwrite(&fact_header, sizeof fact_header, 1, fp_out);
    fwrite(&fact_sample_length, sizeof fact_sample_length, 1, fp_out);

    /* dataチャンク:これから1ブロックずつ書き足していく */
    ChunkHeader data_header = { "data", data_bytes };
    fwrite(&data_header, sizeof data_header, 1, fp_out);

    /* ここから1ブロックずつ「読む→エンコードする→書く」を繰り返す。 */
    for (uint32_t b = 0; b < num_blocks; b++) {
        uint32_t remain = num_samples - b * SAMPLES_PER_BLOCK;
        uint32_t block_len;
        if (remain < SAMPLES_PER_BLOCK) {
            block_len = remain;
        }
        else {
            block_len = SAMPLES_PER_BLOCK;
        }

        freadn(pcm_block, sizeof(int16_t), block_len, fp_in, "data");

        /* 最後のブロックがSAMPLES_PER_BLOCKに満たない場合、余ったニブル領域を0埋め
           するため、出力バッファを毎回クリアしておく */
        memset(out_block, 0, block_align);
        uint8_t *out = out_block;

        /* preamble : valprev(2byte) + index(1byte) + reserved(1byte) */
        int16_t v0 = pcm_block[0];
        state.valprev = v0; /* このブロックの予測起点にリセット */
        memcpy(out, &v0, sizeof v0);
        out += sizeof v0;
        *out++ = (uint8_t)state.index;
        *out++ = 0; /* reserved */

        /* ブロック内の2サンプル目以降を4bitコードに変換し、2つで1byteに詰める */
        uint32_t code_index = 0;
        for (uint32_t i = 1; i < block_len; i++, code_index++) {
            uint8_t code = encode_sample(pcm_block[i], &state);
            if (code_index % 2 == 0) {
                out[code_index / 2] = code;                  /* 下位ニブル */
            }
            else {
                out[code_index / 2] |= (uint8_t)(code << 4); /* 上位ニブル */
            }
        }
        /* block_len < SAMPLES_PER_BLOCK の場合、余ったニブル領域は0埋めのまま */

        fwrite(out_block, 1, block_align, fp_out);
    }

    fclose(fp_in);
    fclose(fp_out);
    return 0;
}

デコード

デコード側は、コードからステップサイズと差分を逆算するだけなので、エンコード処理とほぼ対称の実装になります。

/* 4bitコード(符号1bit+差分3bit)から1サンプルを復号する */
static int16_t decode_sample(uint8_t code, AdpcmState *st)
{
    /* 1. コード分解 */
    uint8_t sign = code & 8;

    /* 2. ステップテーブル参照 */
    int32_t step = STEP_TABLE[st->index];

    /* 3. 差分復元 */
    int32_t vpdiff = step >> 3;
    for (uint8_t bit = 4; bit != 0; bit >>= 1) {
        if (code & bit) {
            vpdiff += step;
        }
        step >>= 1;
    }

    /* 4. 予測値を更新 */
    int32_t valpred;
    if (sign) {
        valpred = st->valprev - vpdiff;
    }
    else {
        valpred = st->valprev + vpdiff;
    }
    st->valprev = clamp(valpred, -32768, 32767);

    /* 5. ステップインデックスを適応的に更新 */
    st->index = clamp(st->index + INDEX_TABLE[code & 7], 0, 88);
    return (int16_t)st->valprev;
}

デコードの全体のソースコードは以下です。

ソースコード【adpcm_decode.c】

adpcm_decode.c
#include 
#include 
#include 
#include 
#include 

/* 1ブロックあたりの最大サンプル数。
   このプログラムはモノラル(1ch)専用 */
#define MAX_SAMPLES_PER_BLOCK 4097

typedef struct {
    char     riff[4];
    uint32_t file_size;
    char     wave[4];
} RiffHeader;

typedef struct {
    char     id[4];
    uint32_t size;
} ChunkHeader;

/* IMA ADPCM用の拡張fmtチャンク(入力側) */
typedef struct {
    uint16_t audio_format;       /* 0x0011 (IMA ADPCM) */
    uint16_t num_channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;    /* 4 */
    uint16_t cb_size;
    uint16_t samples_per_block;
} AdpcmFmtChunk;

/* fmtチャンク(出力側、16bit PCM モノラル) */
typedef struct {
    uint16_t audio_format;
    uint16_t num_channels;
    uint32_t sample_rate;
    uint32_t byte_rate;
    uint16_t block_align;
    uint16_t bits_per_sample;
} FmtChunk;

/* ステップサイズテーブルとインデックス変化テーブル(IMA ADPCM標準) */
static const int32_t STEP_TABLE[89] = {
    7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31,
    34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143,
    157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658,
    724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
    3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
    15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767,
};
static const int32_t INDEX_TABLE[8] = { -1, -1, -1, -1, 2, 4, 6, 8 };

/* ADPCMの状態(予測値とステップインデックス) */
typedef struct {
    int32_t valprev;
    int32_t index;
} AdpcmState;

/* 1ブロック分の作業バッファ(モノラル専用。すべて静的確保。malloc/callocは使わない)。
   ファイル全体をメモリに載せず、ブロック単位で読む→デコードする→書くを繰り返す */
static uint8_t   in_block[4 + ((MAX_SAMPLES_PER_BLOCK - 1) + 1) / 2];
static int16_t   pcm_block[MAX_SAMPLES_PER_BLOCK];
static AdpcmState state;

static int32_t clamp(int32_t val, int32_t lo, int32_t hi)
{
    if (val < lo) { return lo; } if (val > hi) {
        return hi;
    }
    return val;
}

/* 4bitコード(符号1bit+差分3bit)から1サンプルを復号する */
static int16_t decode_sample(uint8_t code, AdpcmState *st)
{
    /* 1. コード分解 */
    uint8_t sign = code & 8;

    /* 2. ステップテーブル参照 */
    int32_t step = STEP_TABLE[st->index];

    /* 3. 差分復元 */
    int32_t vpdiff = step >> 3;
    for (uint8_t bit = 4; bit != 0; bit >>= 1) {
        if (code & bit) {
            vpdiff += step;
        }
        step >>= 1;
    }

    /* 4. 予測値を更新 */
    int32_t valpred;
    if (sign) {
        valpred = st->valprev - vpdiff;
    }
    else {
        valpred = st->valprev + vpdiff;
    }
    st->valprev = clamp(valpred, -32768, 32767);

    /* 5. ステップインデックスを適応的に更新 */
    st->index = clamp(st->index + INDEX_TABLE[code & 7], 0, 88);
    return (int16_t)st->valprev;
}

/* fread失敗時にエラーを表示して終了する */
static void freadn(void *buf, size_t size, size_t n, FILE *fp, const char *what)
{
    if (fread(buf, size, n, fp) != n) {
        fprintf(stderr, "%s read error\n", what);
        exit(1);
    }
}

int main(void)
{
    FILE *fp_in = fopen("output_adpcm.wav", "rb");
    if (!fp_in) {
        perror("fopen");
        return 1;
    }

    RiffHeader riff;
    freadn(&riff, sizeof riff, 1, fp_in, "RIFF header");

    /* fmt/factチャンクを読み、dataチャンクの手前で読み込みを止める。 */
    AdpcmFmtChunk fmt = {0};
    uint32_t fact_sample_length = 0;
    bool found_data = false;
    ChunkHeader chunk;
    while (!found_data && fread(&chunk, sizeof chunk, 1, fp_in) == 1) {
        if (memcmp(chunk.id, "fmt ", 4) == 0) {
            freadn(&fmt, sizeof fmt, 1, fp_in, "fmt");
            if (chunk.size > sizeof fmt) {
                fseek(fp_in, chunk.size - sizeof fmt, SEEK_CUR);
            }
        }
        else if (memcmp(chunk.id, "fact", 4) == 0) {
            freadn(&fact_sample_length, sizeof fact_sample_length, 1, fp_in, "fact");
            if (chunk.size > sizeof fact_sample_length) {
                fseek(fp_in, chunk.size - sizeof fact_sample_length, SEEK_CUR);
            }
        }
        else if (memcmp(chunk.id, "data", 4) == 0) {
            found_data = true; /* ファイル位置はエンコード済みデータの先頭 */
        }
        else {
            fseek(fp_in, chunk.size, SEEK_CUR);
        }
    }
    if (!found_data) {
        fprintf(stderr, "data chunk not found\n");
        return 1;
    }

    /* このプログラムはモノラル(1ch)専用。2ch以上の入力はエラーにする */
    if (fmt.num_channels != 1) {
        fprintf(stderr, "only mono (1ch) IMA ADPCM is supported (got %u channels)\n", fmt.num_channels);
        return 1;
    }
    uint32_t num_samples = fact_sample_length;
    uint32_t samples_per_block = fmt.samples_per_block;
    uint32_t block_align = fmt.block_align;

    /* ファイルから読んだ値を、静的バッファのサイズに収まるかどうか検証する */
    if (samples_per_block == 0 || samples_per_block > MAX_SAMPLES_PER_BLOCK) {
        fprintf(stderr, "unsupported samples_per_block: %u (max %d)\n", samples_per_block, MAX_SAMPLES_PER_BLOCK);
        return 1;
    }
    if (block_align > sizeof in_block) {
        fprintf(stderr, "block_align too large: %u\n", block_align);
        return 1;
    }

    /* 出力WAV(16bit PCM)のヘッダーを組み立てる。
       サイズ類はここまでの情報だけですべて計算できているので、
       サンプル本体を処理する前にヘッダー一式を先に書き出してしまう */
    FmtChunk out_fmt;
    out_fmt.audio_format    = 1; /* PCM */
    out_fmt.num_channels    = 1;
    out_fmt.sample_rate     = fmt.sample_rate;
    out_fmt.bits_per_sample = 16;
    out_fmt.block_align     = sizeof(int16_t);
    out_fmt.byte_rate       = fmt.sample_rate * out_fmt.block_align;

    uint32_t out_data_bytes = num_samples * sizeof(int16_t);

    FILE *fp_out = fopen("decoded.wav", "wb");
    if (!fp_out) {
        perror("fopen");
        return 1;
    }

    RiffHeader out_riff;
    memcpy(out_riff.riff, "RIFF", 4);
    memcpy(out_riff.wave, "WAVE", 4);
    out_riff.file_size = 4
                        + (uint32_t)(8 + sizeof out_fmt)
                        + (uint32_t)(8 + out_data_bytes);
    fwrite(&out_riff, sizeof out_riff, 1, fp_out);

    ChunkHeader fmt_header = { "fmt ", sizeof out_fmt };
    fwrite(&fmt_header, sizeof fmt_header, 1, fp_out);
    fwrite(&out_fmt, sizeof out_fmt, 1, fp_out);

    ChunkHeader data_header = { "data", out_data_bytes };
    fwrite(&data_header, sizeof data_header, 1, fp_out);

    /* ここから1ブロックずつ「読む→デコードする→書く」を繰り返す。 */
    uint32_t num_blocks = (num_samples + samples_per_block - 1) / samples_per_block;
    for (uint32_t b = 0; b < num_blocks; b++) {
        uint32_t remain = num_samples - b * samples_per_block;
        uint32_t block_len;
        if (remain < samples_per_block) {
            block_len = remain;
        }
        else {
            block_len = samples_per_block;
        }

        freadn(in_block, 1, block_align, fp_in, "data");
        uint8_t *p = in_block;

        /* preambleから初期状態を読み出す */
        int16_t valprev;
        memcpy(&valprev, p, sizeof valprev);
        p += sizeof valprev;
        state.valprev = valprev;
        state.index = *p++;
        p++; /* reserved */
        pcm_block[0] = valprev; /* ブロック先頭サンプル */

        /* ブロック内の2サンプル目以降を、2つで1byteに詰まった4bitコードから復号する */
        uint32_t code_index = 0;
        for (uint32_t i = 1; i < block_len; i++, code_index++) { uint8_t code; if (code_index % 2 == 0) { code = p[code_index / 2] & 0x0F; /* 下位ニブル */ } else { code = p[code_index / 2] >> 4;   /* 上位ニブル */
            }
            pcm_block[i] = decode_sample(code, &state);
        }

        fwrite(pcm_block, sizeof(int16_t), block_len, fp_out);
    }

    fclose(fp_in);
    fclose(fp_out);
    return 0;
}

動作確認

音声に対してIMA ADPCMでエンコード後、さらにデコードした音源を元音源と比較してIMA ADPCMの音質劣化がどれくらいか確認しました。

聴感で確認

音声について聴感で確認しました。

入力音声



0:00 / 0:00

IMA ADPCM



0:00 / 0:00

スピーカーだとわからないですが、イヤホンで聴くと音が若干こもって聴こえます。ただ、よく聴かないとまったくわからないため、音声用途の場合はIMA ADPCMで十分ではないかと思います。

スペクトログラム比較

スペクトログラムを比較した結果が以下です。

図:スペクトログラム比較
図:スペクトログラム比較

上(元音源)と下(IMA ADPCM)を比べると、低域(0〜10kHz あたり)のフォルマント的な縞模様はよく保存されていて、全体としての音の骨格は壊れていないことがわかります。

一方で、下のグラフは全体的に薄い緑〜水色のノイズが乗っていて、特に元音源ではほとんど真っ暗(=エネルギーがほぼ無い)だった 10kHz より上の高域にまで、ノイズが広がっているのが見て取れます。聴感ではわかりにくかったですが、たしかに量子化誤差があることがわかります。

おわりに

本記事ではIMA ADPCMによる音声圧縮についてまとめました。基本的に音声圧縮のアルゴリズムは難しいイメージがありますが、IMA ADPCMについては処理がわかりやすく、音質劣化もあまりないため、組み込み機器などで使われる理由が理解できるなと思いました。

次の機会には G.729 のアルゴリズムについて調べてみたいと思います。

■参考文献
[1] ITU-T Recommendation G.726 (1990).
[2] IMA Digital Audio Focus and Technical Working Groups, “Recommended Practices for Enhancing Digital Audio Compatibility in Multimedia Systems.”
[3] Microsoft, “New Multimedia Data Types and Data Techniques” (WAVEFORMATEX / ADPCM 仕様).

■使用した音声について
この記事で使用した音声は Mozilla Foundation で提供されている音声を使用させていただきました。

【音声のページ】
・ “Common Voice: A Massively-Multilingual Speech Corpus” 2020 Mozilla Foundation (Licensed under CC-0) ※取得時バージョン: common_voice_14_0(現在は Mozilla Data Collective に移管、バージョンにより収録音声は異なります) https://mozilladatacollective.com/organization/cmfh0j9o10006ns07jq45h7xk

■変更履歴
・2026/08/11:使用音声のクレジットリンクを Mozilla Data Collective に更新