libmp3lameのAPIを使ったデコード
LAMEはオープンソースのMP3エンコーダー/デコーダーを含むソフトウェア(ライブラリ)です。マルチメディアファイルを再生する多くのアプリケーションで用いられています。
ここでは、LAMEに含まれている「libmp3lame」ライブラリを使ってMP3をデコードし、WAVEデータとして再生する手順を簡単に紹介します。
準備
まずLAMEライブラリをビルドし、生成物からlibmp3lameのライブラリ(Windowsの場合は「libmp3lame.dll」と「libmp3lame.lib」)と「lame.h」を取り出します。この手順に関しては省略しますが、付属ドキュメント「INSTALL」に紹介されているのでそちらを参照してください。
初期化
デコードの関数を使うには、「hip_decode_init」関数を呼び出してhip_t型のデータ(以降便宜上「hipハンドル」と呼びます)を取得します。また、サンプルデータのスキップを計算する場合は「lame_init」を呼び出してその戻り値を保持しておきます。
lame_global_flags* lgf; hip_t hip; lgf = lame_init(); if (!lgf) { perror("lametest: init_lame_buffers: lame_init"); return -1; } /* init decoding */ hip = hip_decode_init(); if (!hip) { perror("lametest: init_lame_buffers: hip_decode_init"); lame_close(lgf); return -1; }
MP3ヘッダーの解析
hipハンドルを取得したら解析を始めます。ただし、MP3ファイルの先頭にID3データが含まれている場合は、解析の前にそれらを読み飛ばします。
FILE* pFile; void* pvBuffer; size_t nSize, nPos; size_t headerSize; nSize = fread(pvBuffer, 1, FILE_BUFFER_SIZE, pFile); if (nSize < 4) { if (!nSize) perror("lametest: fread"); else fprintf(stderr, "lametest: too small file size"); return -1; } nPos = 0; /* check if the file has ID3 header */ if (memcmp(pvBuffer, "ID3", 3) == 0) { if (nSize < 10) { nPos = fread(((unsigned char*) pvBuffer) + nSize, 1, 10 - nSize, pFile); if (nPos + nSize < 10) { if (!nPos) perror("lametest: fread"); else fprintf(stderr, "lametest: too small file size"); return -1; } nSize += nPos; } /* retrieve size of ID3 data */ headerSize = ((size_t)(((unsigned char*) pvBuffer)[6] & 0x7F) << 21) | ((size_t)(((unsigned char*) pvBuffer)[7] & 0x7F) << 14) | ((size_t)(((unsigned char*) pvBuffer)[8] & 0x7F) << 7) | (size_t)(((unsigned char*) pvBuffer)[9] & 0x7F); /* if entire ID3 data has been read, then set current position */ if (nSize - 10 >= headerSize) { nPos = 10 + headerSize; nSize -= nPos; } else { /* skip existing ID3 data */ while (headerSize) { if (headerSize > FILE_BUFFER_SIZE) nSize = FILE_BUFFER_SIZE; else nSize = headerSize; nPos = fread(pvBuffer, 1, nSize, pFile); if (!nPos) { perror("lametest: fread"); return -1; } headerSize -= (int) nPos; } if (!headerSize) { nPos = 0; nSize = 0; } } }
データをスキップしたら、hip_decode1_headersB関数を使ってヘッダー情報などを取得します。(サンプルデータのスキップを計算しない場合はhip_decode1_headers関数を使います。)
short buffL[1152], buffR[1152]; /* max count of samples per frame is 1152 */ int decodeCount, nEncDelay, nEncPadding; mp3data_struct mp3data; decodeCount = -1; nEncDelay = nEncPadding = -1; mp3data.header_parsed = 0; while (1) { if (nSize) { /* retrieve the MP3 file info */ decodeCount = hip_decode1_headersB(hip, (unsigned char*) pvBuffer + nPos, nSize, buffL, buffR, &mp3data, &nEncDelay, &nEncPadding); if (decodeCount < 0) { perror("lametest: hip_decode1_headersB"); break; } nPos = 0; if (mp3data.header_parsed) break; } nSize = fread(pvBuffer, 1, FILE_BUFFER_SIZE, pFile); if (!nSize) { perror("lametest: fread"); break; } } if (decodeCount < 0) return -1;
WAVE出力の準備
※ ここからWindows特有のコードを含みます。
取得したヘッダー情報を元にWAVEFORMATEX構造体を初期化します。
WAVEFORMATEX* pwfx; pwfx = (WAVEFORMATEX*) malloc(sizeof(WAVEFORMATEX)); if (!pwfx) { perror("lametest: init_wave_format_ex: malloc[1]"); return -1; } /* build WAVEFORMATEX data from MP3 info */ pwfx->wFormatTag = WAVE_FORMAT_PCM; pwfx->nChannels = (WORD) mp3data.stereo; pwfx->nSamplesPerSec = (DWORD) mp3data.samplerate; pwfx->wBitsPerSample = 16; pwfx->nBlockAlign = pwfx->nChannels * pwfx->wBitsPerSample / 8; pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign; pwfx->cbSize = 0;
初期化したら、この構造体を使ってWAVEデバイスをオープンします。
MMRESULT mr; HWAVEOUT hWaveOut; size_t n; /* open wave device */ mr = waveOutOpen(&hWaveOut, WAVE_MAPPER, pwfx, 0, 0, CALLBACK_NULL); if (mr != MMSYSERR_NOERROR) { wave_perror(mr, "lametest: init_wave_format_ex: waveOutOpen"); free((void*) pwfx); *ppwfx = NULL; *phWaveOut = NULL; return -1; }
さらに、出力用のバッファのメモリを割り当て、waveOutPrepareHeaderを使って初期化します。
size_t hdrCount; WAVEHDR* pwavehdr; /* must be allocated (= malloc(sizeof(WAVEHDR) * hdrCount)) */ /* init WAVEHDR data */ memset(pwavehdr, 0, sizeof(WAVEHDR) * hdrCount); for (n = 0; n < hdrCount; n++) { pwavehdr[n].lpData = (LPSTR) malloc(WAVE_BUFFER_SIZE); if (!pwavehdr[n].lpData) { perror("lametest: init_wave_format_ex: malloc[2]"); while (n--) { waveOutUnprepareHeader(hWaveOut, &pwavehdr[n], sizeof(WAVEHDR)); free((void*) pwavehdr[n].lpData); pwavehdr[n].lpData = NULL; } waveOutClose(hWaveOut); free(pwfx); return -1; } mr = waveOutPrepareHeader(hWaveOut, &pwavehdr[n], sizeof(WAVEHDR)); if (mr != MMSYSERR_NOERROR) { wave_perror(mr, "lametest: init_wave_format_ex: waveOutPrepareHeader"); free(pwavehdr[n].lpData); while (n--) { waveOutUnprepareHeader(hWaveOut, &pwavehdr[n], sizeof(WAVEHDR)); free((void*) pwavehdr[n].lpData); pwavehdr[n].lpData = NULL; } waveOutClose(hWaveOut); free(pwfx); return -1; } }
デコードとデータの出力
準備が整ったらデータをデコードしてそれを出力します。なお、ヘッダーを解析した際に既に一部データがデコードされているので、先にそのデータを出力するように処理を書きます。
データのデコードはhip_decode1関数を用います。デコードする際、以前の呼び出し時に使用されなかったデータが内部で保持されている場合があるため、それを取得するには入力データサイズを0にして呼び出す必要があります。
データを取得したら出力用バッファに転送し、それをwaveOutWrite関数を使ってデバイスに転送します。
※「hip_decode」関数も利用できますが、こちらは1フレーム単位ではないため、取得に必要なバッファのサイズを計算する手間が発生します。「hip_decode1」関数は1フレーム単位でデータが返るため、必要なバッファサイズは最大でも1152(×2(L-R)×2(16ビット))となります。
/* copies samples to buffer */ static size_t frame_buffer_to_wave_buffer(int frameCount, int stereo, const short* buffL, const short* buffR, void* pvBuffer, size_t _buffer_size) { short* p; size_t n; /* check buffer size */ if (_buffer_size < ((size_t) frameCount) * 2 * stereo) { _set_errno(EINVAL); return 0; } p = (short*) pvBuffer; n = 0; while (frameCount--) { *p++ = *buffL++; n += sizeof(short); if (stereo) { *p++ = *buffR++; n += sizeof(short); } } return n; } int hdrPos, nowSample; hdrPos = 0; nowSample = 0; while (!s_nInterrupted) { nSize = 1; /* check whether the decoded data is not available */ if (!decodeCount) { /* flush buffers that is not decoded */ decodeCount = hip_decode1(hip, (unsigned char*) pvBuffer, 0, buffL, buffR); while (!decodeCount) { /* there is no buffer left, so read from file again */ nSize = fread(pvBuffer, 1, FILE_BUFFER_SIZE, pFile); if (!nSize) { /* file is EOF, so wait until finishing the play */ while (!(wavehdr[hdrPos].dwFlags & WHDR_DONE) && (wavehdr[hdrPos].dwFlags != WHDR_PREPARED)) { if (s_nInterrupted) break; my_sleep(1); } if (!s_nInterrupted) { fprintf(stderr, "Finished.\n"); exitCode = 0; } break; } /* decode */ decodeCount = hip_decode1(hip, (unsigned char*) pvBuffer, nSize, buffL, buffR); if (decodeCount < 0) { perror("lametest: hip_decode1[2]"); break; } } } if (!nSize || decodeCount < 0) break; /* skip first samples */ if (skipSamples) { if (decodeCount < skipSamples) { skipSamples -= decodeCount; continue; } memmove(&buffL[0], &buffL[skipSamples], sizeof(short) * (decodeCount - skipSamples)); memmove(&buffR[0], &buffR[skipSamples], sizeof(short) * (decodeCount - skipSamples)); decodeCount -= skipSamples; skipSamples = 0; } /* skip last samples */ if (nEncPadding > 0 && feof(pFile)) { if (decodeCount < nEncPadding) decodeCount = 0; else decodeCount -= nEncPadding; } if (nowSample) tty_move_lineup(stderr, 1); nowSample += decodeCount; fprintf(stderr, "now sample = %d\n", nowSample); /* set frame buffer to output buffer */ nSize = frame_buffer_to_wave_buffer(decodeCount, mp3data.stereo, buffL, buffR, (void*) pwavehdr[hdrPos].lpData, WAVE_BUFFER_SIZE); if (!nSize) { perror("lametest: _frame_buffer_to_wave_buffer_s"); break; } /* output buffer to device */ wavehdr[hdrPos].dwBufferLength = (DWORD) nSize; mr = waveOutWrite(hWaveOut, &pwavehdr[hdrPos], sizeof(WAVEHDR)); if (mr != MMSYSERR_NOERROR) { wave_perror(mr, "lametest: waveOutWrite"); break; } /* use next buffer */ decodeCount = 0; hdrPos++; if (hdrPos == hdrCount) hdrPos = 0; /* wait until the buffer can use */ while (!(pwavehdr[hdrPos].dwFlags & WHDR_DONE) && (pwavehdr[hdrPos].dwFlags != WHDR_PREPARED)) { if (s_nInterrupted) break; my_sleep(1); } }
後処理
今まで使ったデータをすべて解放します。
while (hdrCount--) { if (pwavehdr[hdrCount].lpData) { waveOutUnprepareHeader(hWaveOut, &pwavehdr[hdrCount], sizeof(WAVEHDR)); free((void*) pwavehdr[hdrCount].lpData); } } if (hWaveOut) waveOutClose(hWaveOut); if (pwfx) free((void*) pwfx); if (hip) hip_decode_exit(hip); if (lgf) lame_close(lgf);
サンプルプログラム
以上のコードを使ったサンプルプログラム(一部修正)は以下のリンクからダウンロードできます。
- lametest.c (VC++とMinGWでビルドできることを確認しています)
- lametest.zip (lametest.cのみを含んでいます)
最終更新日: 2011/12/18