チュートリアル: オーディオのデコード

このチュートリアルでは、 ソース リーダー を使用してメディア ファイルからオーディオをデコードし、WAVE ファイルにオーディオを書き込む方法について説明します。 このチュートリアルは、 オーディオ クリップ のサンプルに基づいています。

概要

このチュートリアルでは、2 つのコマンド ライン引数 (オーディオ ストリームを含む入力ファイルの名前と出力ファイル名) を受け取るコンソール アプリケーションを作成します。 アプリケーションは、入力ファイルから 5 秒のオーディオ データを読み取り、オーディオを WAVE データとして出力ファイルに書き込みます。

デコードされたオーディオ データを取得するために、アプリケーションはソース リーダー オブジェクトを使用します。 ソース リーダーは 、IMFSourceReader インターフェイスを公開します。 デコードされたオーディオを WAVE ファイルに書き込むには、アプリケーションは Windows I/O 関数を使用します。 次の図は、このプロセスを示しています。

ソース ファイルからオーディオ データを取得するソース リーダーを示す図。

最も単純な形式では、WAVE ファイルの構造は次のとおりです。

データ型 サイズ (バイト)
Fourcc 4 'RIFF'
DWORD 4 ファイル の合計サイズ (最初の 8 バイトを含まない)
Fourcc 4 'WAVE'
Fourcc 4 'fmt '
DWORD 4 次の WAVEFORMATEX データのサイズ。
WAVEFORMATEX 場合により異なる オーディオ形式のヘッダー。
Fourcc 4 'data'
DWORD 4 オーディオ データのサイズ。
BYTE[] 場合により異なる オーディオ データ。

 

注意

FOURCC は、4 つの ASCII 文字を連結して形成された DWORD です。

 

この基本的な構造は、ファイル メタデータやその他の情報を追加することで拡張できます。これは、このチュートリアルの範囲を超えています。

ヘッダー ファイルとライブラリ ファイル

プロジェクトに次のヘッダー ファイルを含めます。

#define WINVER _WIN32_WINNT_WIN7

#include <windows.h>
#include <mfapi.h>
#include <mfidl.h>
#include <mfreadwrite.h>
#include <stdio.h>
#include <mferror.h>

次のライブラリへのリンク:

  • mfplat.lib
  • mfreadwrite.lib
  • mfuuid.lib

wmain を実装する

次のコードは、アプリケーションのエントリ ポイント関数を示しています。

int wmain(int argc, wchar_t* argv[])
{
    HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

    if (argc != 3)
    {
        printf("arguments: input_file output_file.wav\n");
        return 1;
    }

    const WCHAR *wszSourceFile = argv[1];
    const WCHAR *wszTargetFile = argv[2];

    const LONG MAX_AUDIO_DURATION_MSEC = 5000; // 5 seconds

    HRESULT hr = S_OK;

    IMFSourceReader *pReader = NULL;
    HANDLE hFile = INVALID_HANDLE_VALUE;

    // Initialize the COM library.
    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

    // Initialize the Media Foundation platform.
    if (SUCCEEDED(hr))
    {
        hr = MFStartup(MF_VERSION);
    }

    // Create the source reader to read the input file.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateSourceReaderFromURL(wszSourceFile, NULL, &pReader);
        if (FAILED(hr))
        {
            printf("Error opening input file: %S\n", wszSourceFile, hr);
        }
    }

    // Open the output file for writing.
    if (SUCCEEDED(hr))
    {
        hFile = CreateFile(wszTargetFile, GENERIC_WRITE, FILE_SHARE_READ, NULL,
            CREATE_ALWAYS, 0, NULL);

        if (hFile == INVALID_HANDLE_VALUE)
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
            printf("Cannot create output file: %S\n", wszTargetFile, hr);
        }
    }

    // Write the WAVE file.
    if (SUCCEEDED(hr))
    {
        hr = WriteWaveFile(pReader, hFile, MAX_AUDIO_DURATION_MSEC);
    }

    if (FAILED(hr))
    {
        printf("Failed, hr = 0x%X\n", hr);
    }

    // Clean up.
    if (hFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(hFile);
    }

    SafeRelease(&pReader);
    MFShutdown();
    CoUninitialize();

    return SUCCEEDED(hr) ? 0 : 1;
};

この関数は、次の処理を行います。

  1. CoInitializeEx を呼び出して COM ライブラリを初期化します。
  2. MFStartup を呼び出して Media Foundation プラットフォームを初期化します。
  3. MFCreateSourceReaderFromURL を呼び出してソース リーダーを作成します。 この関数は、入力ファイルの名前を受け取り、 IMFSourceReader インターフェイス ポインターを受け取ります。
  4. ファイル ハンドルを返す CreateFile 関数を呼び出して、出力ファイルを作成します。
  5. アプリケーション定義の WriteWavFile 関数を呼び出します。 この関数は、オーディオをデコードし、WAVE ファイルを書き込みます。
  6. IMFSourceReader ポインターとファイル ハンドルを解放します。
  7. MFShutdown を呼び出して Media Foundation プラットフォームをシャットダウンします。
  8. CoUninitialize を呼び出して COM ライブラリを解放します。

WAVE ファイルを書き込む

ほとんどの作業は、 からwmain呼び出される 関数でWriteWavFile行われます。

//-------------------------------------------------------------------
// WriteWaveFile
//
// Writes a WAVE file by getting audio data from the source reader.
//
//-------------------------------------------------------------------

HRESULT WriteWaveFile(
    IMFSourceReader *pReader,   // Pointer to the source reader.
    HANDLE hFile,               // Handle to the output file.
    LONG msecAudioData          // Maximum amount of audio data to write, in msec.
    )
{
    HRESULT hr = S_OK;

    DWORD cbHeader = 0;         // Size of the WAVE file header, in bytes.
    DWORD cbAudioData = 0;      // Total bytes of PCM audio data written to the file.
    DWORD cbMaxAudioData = 0;

    IMFMediaType *pAudioType = NULL;    // Represents the PCM audio format.

    // Configure the source reader to get uncompressed PCM audio from the source file.

    hr = ConfigureAudioStream(pReader, &pAudioType);

    // Write the WAVE file header.
    if (SUCCEEDED(hr))
    {
        hr = WriteWaveHeader(hFile, pAudioType, &cbHeader);
    }

    // Calculate the maximum amount of audio to decode, in bytes.
    if (SUCCEEDED(hr))
    {
        cbMaxAudioData = CalculateMaxAudioDataSize(pAudioType, cbHeader, msecAudioData);

        // Decode audio data to the file.
        hr = WriteWaveData(hFile, pReader, cbMaxAudioData, &cbAudioData);
    }

    // Fix up the RIFF headers with the correct sizes.
    if (SUCCEEDED(hr))
    {
        hr = FixUpChunkSizes(hFile, cbHeader, cbAudioData);
    }

    SafeRelease(&pAudioType);
    return hr;
}

この関数は、他の一連のアプリケーション定義関数を呼び出します。

  1. ConfigureAudioStream 関数は、ソース リーダーを初期化します。 この関数は 、IMFMediaType インターフェイスへのポインターを受け取ります。これは、サンプリング レート、チャネル数、ビット深度 (サンプルあたりのビット数) など、デコードされたオーディオ形式の説明を取得するために使用されます。
  2. WriteWaveHeader 関数は、ヘッダーと 'data' チャンクの開始を含む WAVE ファイルの最初の部分を書き込みます。
  3. CalculateMaxAudioDataSize 関数は、ファイルに書き込むオーディオの最大量をバイト単位で計算します。
  4. WriteWaveData 関数は、PCM オーディオ データをファイルに書き込みます。
  5. FixUpChunkSizes 関数は、WAVE ファイル内の 'RIFF' 値と 'data' FOURCC 値の後に表示されるファイル サイズ情報を書き込みます。 (これらの値は完了するまで WriteWaveData 認識されません。

これらの関数については、このチュートリアルの残りのセクションで説明します。

ソース リーダーを構成する

この関数は ConfigureAudioStream 、ソース ファイル内のオーディオ ストリームをデコードするようにソース リーダーを構成します。 また、デコードされたオーディオの形式に関する情報も返します。

Media Foundation では、メディア形式は メディアタイプ オブジェクトを使用して記述されます。 メディア型オブジェクトは、IMFAttributes インターフェイスを継承する IMFMediaType インターフェイスを公開します。 基本的に、メディアの種類は、形式を記述するプロパティのコレクションです。 詳細については、「 メディアの種類」を参照してください。

//-------------------------------------------------------------------
// ConfigureAudioStream
//
// Selects an audio stream from the source file, and configures the
// stream to deliver decoded PCM audio.
//-------------------------------------------------------------------

HRESULT ConfigureAudioStream(
    IMFSourceReader *pReader,   // Pointer to the source reader.
    IMFMediaType **ppPCMAudio   // Receives the audio format.
    )
{
    IMFMediaType *pUncompressedAudioType = NULL;
    IMFMediaType *pPartialType = NULL;

    // Select the first audio stream, and deselect all other streams.
    HRESULT hr = pReader->SetStreamSelection(
        (DWORD)MF_SOURCE_READER_ALL_STREAMS, FALSE);

    if (SUCCEEDED(hr))
    {
        hr = pReader->SetStreamSelection(
            (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM, TRUE);
    }

    // Create a partial media type that specifies uncompressed PCM audio.
    hr = MFCreateMediaType(&pPartialType);

    if (SUCCEEDED(hr))
    {
        hr = pPartialType->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
    }

    if (SUCCEEDED(hr))
    {
        hr = pPartialType->SetGUID(MF_MT_SUBTYPE, MFAudioFormat_PCM);
    }

    // Set this type on the source reader. The source reader will
    // load the necessary decoder.
    if (SUCCEEDED(hr))
    {
        hr = pReader->SetCurrentMediaType(
            (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
            NULL, pPartialType);
    }

    // Get the complete uncompressed format.
    if (SUCCEEDED(hr))
    {
        hr = pReader->GetCurrentMediaType(
            (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
            &pUncompressedAudioType);
    }

    // Ensure the stream is selected.
    if (SUCCEEDED(hr))
    {
        hr = pReader->SetStreamSelection(
            (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
            TRUE);
    }

    // Return the PCM format to the caller.
    if (SUCCEEDED(hr))
    {
        *ppPCMAudio = pUncompressedAudioType;
        (*ppPCMAudio)->AddRef();
    }

    SafeRelease(&pUncompressedAudioType);
    SafeRelease(&pPartialType);
    return hr;
}

関数は ConfigureAudioStream 次の処理を行います。

  1. IMFSourceReader::SetStreamSelection メソッドを呼び出してオーディオ ストリームを選択し、他のすべてのストリームの選択を解除します。 この手順により、ソース リーダーがアプリケーションで使用しないビデオ フレームを保持できなくなるため、パフォーマンスが向上します。
  2. PCM オーディオを指定する 部分 メディアの種類を作成します。 関数は、次のように部分型を作成します。
    1. MFCreateMediaType を呼び出して、空のメディア型オブジェクトを作成します。
    2. MF_MT_MAJOR_TYPE属性を MFMediaType_Audio に設定します。
    3. MF_MT_SUBTYPE属性をMFAudioFormat_PCMに設定します。
  3. IMFSourceReader::SetCurrentMediaType を呼び出して、ソース リーダーに部分型を設定します。 ソース ファイルにエンコードされたオーディオが含まれている場合、ソース リーダーは必要なオーディオ デコーダーを自動的に読み込みます。
  4. IMFSourceReader::GetCurrentMediaType を呼び出して、実際の PCM メディアの種類を取得します。 このメソッドは、オーディオ サンプル レートやチャネル数など、すべての形式の詳細が入力されたメディアの種類を返します。
  5. IMFSourceReader::SetStreamSelection を呼び出してオーディオ ストリームを有効にします。

WAVE ファイル ヘッダーを書き込む

関数は WriteWaveHeader WAVE ファイル ヘッダーを書き込みます。

この関数から呼び出される Media Foundation API は、メディアの種類を WAVEFORMATEX 構造体に変換する MFCreateWaveFormatExFromMFMediaType だけです。

//-------------------------------------------------------------------
// WriteWaveHeader
//
// Write the WAVE file header.
//
// Note: This function writes placeholder values for the file size
// and data size, as these values will need to be filled in later.
//-------------------------------------------------------------------

HRESULT WriteWaveHeader(
    HANDLE hFile,               // Output file.
    IMFMediaType *pMediaType,   // PCM audio format.
    DWORD *pcbWritten           // Receives the size of the header.
    )
{
    HRESULT hr = S_OK;
    UINT32 cbFormat = 0;

    WAVEFORMATEX *pWav = NULL;

    *pcbWritten = 0;

    // Convert the PCM audio format into a WAVEFORMATEX structure.
    hr = MFCreateWaveFormatExFromMFMediaType(pMediaType, &pWav, &cbFormat);

    // Write the 'RIFF' header and the start of the 'fmt ' chunk.
    if (SUCCEEDED(hr))
    {
        DWORD header[] = {
            // RIFF header
            FCC('RIFF'),
            0,
            FCC('WAVE'),
            // Start of 'fmt ' chunk
            FCC('fmt '),
            cbFormat
        };

        DWORD dataHeader[] = { FCC('data'), 0 };

        hr = WriteToFile(hFile, header, sizeof(header));

        // Write the WAVEFORMATEX structure.
        if (SUCCEEDED(hr))
        {
            hr = WriteToFile(hFile, pWav, cbFormat);
        }

        // Write the start of the 'data' chunk

        if (SUCCEEDED(hr))
        {
            hr = WriteToFile(hFile, dataHeader, sizeof(dataHeader));
        }

        if (SUCCEEDED(hr))
        {
            *pcbWritten = sizeof(header) + cbFormat + sizeof(dataHeader);
        }
    }


    CoTaskMemFree(pWav);
    return hr;
}

関数は WriteToFile 、Windows WriteFile 関数をラップし、 HRESULT 値を返す単純なヘルパー関数です。

//-------------------------------------------------------------------
//
// Writes a block of data to a file
//
// hFile: Handle to the file.
// p: Pointer to the buffer to write.
// cb: Size of the buffer, in bytes.
//
//-------------------------------------------------------------------

HRESULT WriteToFile(HANDLE hFile, void* p, DWORD cb)
{
    DWORD cbWritten = 0;
    HRESULT hr = S_OK;

    BOOL bResult = WriteFile(hFile, p, cb, &cbWritten, NULL);
    if (!bResult)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
    return hr;
}

最大データ サイズを計算する

ファイル サイズはファイル ヘッダーに 4 バイトの値として格納されるため、WAVE ファイルの最大サイズは0xFFFFFFFF バイト (約 4 GB) に制限されます。 この値には、ファイル ヘッダーのサイズが含まれます。 PCM オーディオには一定のビット レートがあるため、次のようにオーディオ形式から最大データ サイズを計算できます。

//-------------------------------------------------------------------
// CalculateMaxAudioDataSize
//
// Calculates how much audio to write to the WAVE file, given the
// audio format and the maximum duration of the WAVE file.
//-------------------------------------------------------------------

DWORD CalculateMaxAudioDataSize(
    IMFMediaType *pAudioType,    // The PCM audio format.
    DWORD cbHeader,              // The size of the WAVE file header.
    DWORD msecAudioData          // Maximum duration, in milliseconds.
    )
{
    UINT32 cbBlockSize = 0;         // Audio frame size, in bytes.
    UINT32 cbBytesPerSecond = 0;    // Bytes per second.

    // Get the audio block size and number of bytes/second from the audio format.

    cbBlockSize = MFGetAttributeUINT32(pAudioType, MF_MT_AUDIO_BLOCK_ALIGNMENT, 0);
    cbBytesPerSecond = MFGetAttributeUINT32(pAudioType, MF_MT_AUDIO_AVG_BYTES_PER_SECOND, 0);

    // Calculate the maximum amount of audio data to write.
    // This value equals (duration in seconds x bytes/second), but cannot
    // exceed the maximum size of the data chunk in the WAVE file.

        // Size of the desired audio clip in bytes:
    DWORD cbAudioClipSize = (DWORD)MulDiv(cbBytesPerSecond, msecAudioData, 1000);

    // Largest possible size of the data chunk:
    DWORD cbMaxSize = MAXDWORD - cbHeader;

    // Maximum size altogether.
    cbAudioClipSize = min(cbAudioClipSize, cbMaxSize);

    // Round to the audio block size, so that we do not write a partial audio frame.
    cbAudioClipSize = (cbAudioClipSize / cbBlockSize) * cbBlockSize;

    return cbAudioClipSize;
}

オーディオ フレームの一部を回避するために、サイズはブロックアラインメントに丸められ、 MF_MT_AUDIO_BLOCK_ALIGNMENT 属性に格納されます。

オーディオをデコードする

関数は WriteWaveData 、デコードされたオーディオをソース ファイルから読み取り、WAVE ファイルに書き込みます。

//-------------------------------------------------------------------
// WriteWaveData
//
// Decodes PCM audio data from the source file and writes it to
// the WAVE file.
//-------------------------------------------------------------------

HRESULT WriteWaveData(
    HANDLE hFile,               // Output file.
    IMFSourceReader *pReader,   // Source reader.
    DWORD cbMaxAudioData,       // Maximum amount of audio data (bytes).
    DWORD *pcbDataWritten       // Receives the amount of data written.
    )
{
    HRESULT hr = S_OK;
    DWORD cbAudioData = 0;
    DWORD cbBuffer = 0;
    BYTE *pAudioData = NULL;

    IMFSample *pSample = NULL;
    IMFMediaBuffer *pBuffer = NULL;

    // Get audio samples from the source reader.
    while (true)
    {
        DWORD dwFlags = 0;

        // Read the next sample.
        hr = pReader->ReadSample(
            (DWORD)MF_SOURCE_READER_FIRST_AUDIO_STREAM,
            0, NULL, &dwFlags, NULL, &pSample );

        if (FAILED(hr)) { break; }

        if (dwFlags & MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED)
        {
            printf("Type change - not supported by WAVE file format.\n");
            break;
        }
        if (dwFlags & MF_SOURCE_READERF_ENDOFSTREAM)
        {
            printf("End of input file.\n");
            break;
        }

        if (pSample == NULL)
        {
            printf("No sample\n");
            continue;
        }

        // Get a pointer to the audio data in the sample.

        hr = pSample->ConvertToContiguousBuffer(&pBuffer);

        if (FAILED(hr)) { break; }


        hr = pBuffer->Lock(&pAudioData, NULL, &cbBuffer);

        if (FAILED(hr)) { break; }


        // Make sure not to exceed the specified maximum size.
        if (cbMaxAudioData - cbAudioData < cbBuffer)
        {
            cbBuffer = cbMaxAudioData - cbAudioData;
        }

        // Write this data to the output file.
        hr = WriteToFile(hFile, pAudioData, cbBuffer);

        if (FAILED(hr)) { break; }

        // Unlock the buffer.
        hr = pBuffer->Unlock();
        pAudioData = NULL;

        if (FAILED(hr)) { break; }

        // Update running total of audio data.
        cbAudioData += cbBuffer;

        if (cbAudioData >= cbMaxAudioData)
        {
            break;
        }

        SafeRelease(&pSample);
        SafeRelease(&pBuffer);
    }

    if (SUCCEEDED(hr))
    {
        printf("Wrote %d bytes of audio data.\n", cbAudioData);

        *pcbDataWritten = cbAudioData;
    }

    if (pAudioData)
    {
        pBuffer->Unlock();
    }

    SafeRelease(&pBuffer);
    SafeRelease(&pSample);
    return hr;
}

関数は WriteWaveData ループ内で次の処理を行います。

  1. IMFSourceReader::ReadSample を呼び出して、ソース ファイルからオーディオを読み取ります。 dwFlags パラメーターは、MF_SOURCE_READER_FLAG列挙体からフラグのビットごとの OR を受け取ります。 pSample パラメーターは、オーディオ データへのアクセスに使用される IMFSample インターフェイスへのポインターを受け取ります。 場合によっては、 ReadSample の呼び出しでデータが生成されません。その場合、 IMFSample ポインターは NULL です
  2. dwFlags で次のフラグをチェックします。
    • MF_SOURCE_READERF_CURRENTMEDIATYPECHANGED。 このフラグは、ソース ファイルの形式の変更を示します。 WAVE ファイルでは、形式の変更はサポートされていません。
    • MF_SOURCE_READERF_ENDOFSTREAM。 このフラグは、ストリームの末尾を示します。
  3. IMFSample::ConvertToContiguousBuffer を呼び出して、バッファー オブジェクトへのポインターを取得します。
  4. IMFMediaBuffer::Lock を呼び出して、バッファー メモリへのポインターを取得します。
  5. オーディオ データを出力ファイルに書き込みます。
  6. IMFMediaBuffer::Unlock を呼び出してバッファー オブジェクトのロックを解除します。

関数は、次のいずれかが発生するとループから抜け出します。

  • ストリームの形式が変更されます。
  • ストリームの末尾に到達しました。
  • オーディオ データの最大量は、出力ファイルに書き込まれます。
  • エラーが発生しました。

ファイル ヘッダーの最終処理

WAVE ヘッダーに格納されているサイズ値は、前の関数が完了するまで認識されません。 は FixUpChunkSizes 、次の値を入力します。

//-------------------------------------------------------------------
// FixUpChunkSizes
//
// Writes the file-size information into the WAVE file header.
//
// WAVE files use the RIFF file format. Each RIFF chunk has a data
// size, and the RIFF header has a total file size.
//-------------------------------------------------------------------

HRESULT FixUpChunkSizes(
    HANDLE hFile,           // Output file.
    DWORD cbHeader,         // Size of the 'fmt ' chuck.
    DWORD cbAudioData       // Size of the 'data' chunk.
    )
{
    HRESULT hr = S_OK;

    LARGE_INTEGER ll;
    ll.QuadPart = cbHeader - sizeof(DWORD);

    if (0 == SetFilePointerEx(hFile, ll, NULL, FILE_BEGIN))
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    // Write the data size.

    if (SUCCEEDED(hr))
    {
        hr = WriteToFile(hFile, &cbAudioData, sizeof(cbAudioData));
    }

    if (SUCCEEDED(hr))
    {
        // Write the file size.
        ll.QuadPart = sizeof(FOURCC);

        if (0 == SetFilePointerEx(hFile, ll, NULL, FILE_BEGIN))
        {
            hr = HRESULT_FROM_WIN32(GetLastError());
        }
    }

    if (SUCCEEDED(hr))
    {
        DWORD cbRiffFileSize = cbHeader + cbAudioData - 8;

        // NOTE: The "size" field in the RIFF header does not include
        // the first 8 bytes of the file. (That is, the size of the
        // data that appears after the size field.)

        hr = WriteToFile(hFile, &cbRiffFileSize, sizeof(cbRiffFileSize));
    }

    return hr;
}

オーディオ メディアの種類

ソース リーダー

IMFSourceReader