MediaFoundation --- 音声読み書き
下記記事でMediaFoundationを用いて動画の読み書きを行いました。
MediaFoundation --- 動画の読み込み - 何でもプログラミング
MediaFoundation --- 動画書き出し - 何でもプログラミング
今回は音声データの読み書きを行ってみたいと思います。
分かりやすさ重視のため、エラー処理は省いてあります。
アプリケーションコード
今回はOpenCVのサンプルデータ、Megamind.aviから音声データだけ抽出します。
音声データはAAC形式で保存してみます。
出力bytesPerSecは12000、16000、20000、24000のいずれかとなります。(詳しくは下記を参照してください)
AAC Encoder (Windows)
ConfigureAudioDecoder、GetAudioInfo、ConfigureAudioEncoder、CaptureAudio、WriteAudioは後述いたします。
#include <mfapi.h> #include <mfidl.h> #include <mfreadwrite.h> #pragma comment(lib, "mfplat.lib") #pragma comment(lib, "mfuuid.lib") #pragma comment(lib, "mfreadwrite.lib") int main() { CoInitialize(NULL); MFStartup(MF_VERSION); // Reader作成 IMFSourceReader* reader; MFCreateSourceReaderFromURL(L"c:\\lib\\opencv3.2\\sources\\samples\\data\\megamind.avi", NULL, &reader); ConfigureAudioDecoder(reader, MFAudioFormat_PCM); UINT32 channels, samplePerSec, bitsPerSample, bytesPerSec; GetAudioInfo(reader, &channels, &samplePerSec, &bitsPerSample, &bytesPerSec); // Writer作成 IMFSinkWriter* writer; MFCreateSinkWriterFromURL(L"output.mp4", NULL, NULL, &writer); auto streamIndex = ConfigureAudioEncoder(writer, MFAudioFormat_PCM, MFAudioFormat_AAC, channels, samplePerSec, bitsPerSample, 24000); // 書き出し writer->BeginWriting(); while (true) { LONGLONG time, duration; auto data = CaptureAudio(reader, &time, &duration); if (data.empty()) break; WriteAudio(writer, streamIndex, data.data(), data.size(), time, duration); } writer->Finalize(); MFShutdown(); CoUninitialize(); return 0; }
ConfigureAudioDecoder
デコード先のフォーマットを指定して、readerにセットします。
void ConfigureAudioDecoder(IMFSourceReader* reader, GUID format) { IMFMediaType* mediaType; MFCreateMediaType(&mediaType); mediaType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); mediaType->SetGUID(MF_MT_SUBTYPE, format); reader->SetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, mediaType); mediaType->Release(); }
GetAudioInfo
チャンネル数、サンプリング数、サンプリングサイズ、ビットレートを取得できます。
void GetAudioInfo(IMFSourceReader* reader, UINT32* channels, UINT32* samplePerSec, UINT32* bitsPerSample, UINT32* bytesPerSec)
{
IMFMediaType* mediaType;
reader->GetCurrentMediaType(MF_SOURCE_READER_FIRST_AUDIO_STREAM, &mediaType);
mediaType->GetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels);
mediaType->GetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, samplePerSec);
mediaType->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample);
mediaType->GetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bytesPerSec);
}
ConfigureAudioEncoder
入力フォーマット、エンコーダ、チャンネル数、サンプリング数、サンプリングサイズ、ビットレートを指定します。
DWORD ConfigureAudioEncoder(IMFSinkWriter* writer, GUID inputFormat, GUID outputFormat, UINT32 channels, UINT32 samplePerSec, UINT32 bitsPerSample, UINT32 bytesPerSec) { auto createType = [&](GUID subtype) { IMFMediaType* type; MFCreateMediaType(&type); type->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio); type->SetGUID(MF_MT_SUBTYPE, subtype); type->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, channels); type->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, samplePerSec); type->SetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample); type->SetUINT32(MF_MT_AUDIO_AVG_BYTES_PER_SECOND, bytesPerSec); return type; }; // OutputType IMFMediaType* outputType = createType(outputFormat); DWORD streamIndex; writer->AddStream(outputType, &streamIndex); outputType->Release(); // InputType IMFMediaType* inputType = createType(inputFormat); writer->SetInputMediaType(streamIndex, inputType, NULL); inputType->Release(); return streamIndex; }
CaptureAudio
1フレーム当たりの音声データを取得します。
ついでにフレームの開始時間と長さも取得します。
std::vector<BYTE> CaptureAudio(IMFSourceReader* reader, LONGLONG* time, LONGLONG* duration) { // 読み込み DWORD flags; IMFSample* sample; reader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, NULL, &flags, NULL, &sample); if (sample == nullptr) return std::vector<BYTE>(); // Buffer取得 IMFMediaBuffer* buffer; sample->GetBufferByIndex(0, &buffer); // データ取得 BYTE* p; DWORD size; buffer->Lock(&p, NULL, &size); std::vector<BYTE> data(size); memcpy(data.data(), p, size); buffer->Unlock(); // 開始時間、長さ取得 sample->GetSampleTime(time); sample->GetSampleDuration(duration); buffer->Release(); sample->Release(); return data; }
WriteAudio
音声データをwriterで書き出します。
SetSampleTime、SetSampleDurationを行わないとエラーが出力されます。
void WriteAudio(IMFSinkWriter* writer, int streamIndex, BYTE* data, int size, LONGLONG time, LONGLONG duration) { // Buffer作成 IMFMediaBuffer* buffer; MFCreateMemoryBuffer(size, &buffer); // データ書き込み BYTE* bufferPtr; buffer->Lock(&bufferPtr, NULL, NULL); memcpy(bufferPtr, data, size); buffer->Unlock(); buffer->SetCurrentLength(size); // Sample作成 IMFSample* sample; MFCreateSample(&sample); sample->AddBuffer(buffer); sample->SetSampleTime(time); sample->SetSampleDuration(duration); // 書き出し writer->WriteSample(streamIndex, sample); sample->Release(); buffer->Release(); }