
音声データを圧縮する方式には 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]。

1. 差分計算
実際のサンプル値と予測値の差 diff を求め、符号(正負)を sign ビットとして分離します。
2. ステップサイズテーブル参照
その時点のステップサイズ step(全 89 段階のテーブルから参照)を使い、差分 diff を 3bit の大きさコード delta に変換します。下図のように diff > step x i/4 となる最大の整数 i を、delta=i とします。delta の最大値は 7 です。

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

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 を格納した WAV ファイルのヘッダー構造について触れておきます [3]。通常の PCM の WAV とは異なり、圧縮フォーマットならではのチャンク構成になっています。
全体構造
通常の PCM WAV は RIFF → fmt → 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
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 に更新