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();
}