教學課程:使用 WMContainer 物件撰寫 WMA 檔案

本教學課程示範如何從未壓縮的音訊檔案 (.wav) 擷取媒體內容,然後以 ASF 格式壓縮新的音訊檔案 (.wma) 。 用於轉換的編碼模式是 固定位元速率編碼 (CBR) 。 在此模式中,在編碼會話之前,應用程式會指定編碼器必須達到的目標位元速率。

在本教學課程中,您將建立主控台應用程式,以接受輸入和輸出檔案名作為引數。 應用程式會從波浪檔案剖析應用程式取得未壓縮的媒體範例,本教學課程會提供此範例。 這些範例會傳送至編碼器,以轉換成 Windows Media Audio 9 格式。 編碼器設定為 CBR 編碼,並使用媒體類型交涉期間提供的第一個位元速率作為目標位元速率。 編碼的樣本會傳送至多工器,以 ASF 資料格式進行封包化。 這些封包會寫入代表 ASF 資料物件的位元組資料流程。 資料區段準備就緒之後,您將建立 ASF 音訊檔案,並寫入新的 ASF 標頭物件,以合併所有標頭資訊,然後附加 ASF 資料物件位元組資料流程。

本教學課程包含下列各節:

必要條件

本教學課程假設您已句備下列條件:

  • 您已熟悉 ASF 檔案的結構,以及 Media Foundation 所提供的元件,以使用 ASF 物件。 這些元件包括 ContentInfo、分割器、多工器和設定檔物件。 如需詳細資訊,請參閱 WMContainer ASF 元件
  • 您已熟悉 Windows Media 編碼器,以及各種編碼類型,特別是 CBR。 如需詳細資訊,請參閱 Windows 媒體編碼器
  • 您已熟悉 媒體緩衝區 和位元組資料流程:特別是使用位元組資料流程的檔案作業,以及將媒體緩衝區的內容寫入位元組資料流程。

詞彙

本教學課程使用下列詞彙:

  • 來源媒體類型:媒體類型物件,會公開 IMFMediaType 介面,其中描述輸入檔的內容。
  • 音訊設定檔:設定檔物件,會公開 IMFASFProfile 介面,其中只包含輸出檔案的音訊資料流程。
  • 串流範例:媒體範例公開 IMFSample 介面,代表從編碼器取得的輸入檔媒體資料,其處於壓縮狀態。
  • ContentInfo 物件:ASF ContentInfo 物件,會公開IMFASFContentInfo介面,代表輸出檔的 ASF 標頭物件。
  • 資料位元組資料流程:位元組資料流程物件,會公開 IMFByteStream 介面,代表輸出檔案的整個 ASF 資料物件部分。
  • 資料封包:媒體範例,公開由 ASF Multiplexer產生的IMFSample介面;代表將寫入資料位元組資料流程的 ASF 資料封包。
  • 輸出位元組資料流程:Byte 資料流程物件,會公開 IMFByteStream 介面,其中包含輸出檔案的內容。

1.設定專案

  1. 在您的原始程式檔中包含下列標頭:

    #include <new>
    #include <stdio.h>       // Standard I/O
    #include <windows.h>     // Windows headers
    #include <mfapi.h>       // Media Foundation platform
    #include <wmcontainer.h> // ASF interfaces
    #include <mferror.h>     // Media Foundation error codes
    
  2. 連結至下列程式庫檔案:

    • mfplat.lib
    • mf.lib
    • mfuuid.lib
  3. 宣告 SafeRelease 函式。

  4. 在您的專案中加入 CWmaEncoder 類別。 如需此類別的完整原始程式碼,請參閱 編碼器範例程式碼

2.宣告協助程式函式

本教學課程使用下列協助程式函式從位元組資料流程讀取和寫入。

  • AppendToByteStream:將一個位元組資料流程的內容附加至另一個位元組資料流程。
  • WriteBufferToByteStream:將資料從媒體緩衝區寫入位元組資料流程。

如需詳細資訊,請參閱 IMFByteStream::Write。 下列程式碼顯示這些協助程式函式:

//-------------------------------------------------------------------
// AppendToByteStream
//
// Reads the contents of pSrc and writes them to pDest.
//-------------------------------------------------------------------

HRESULT AppendToByteStream(IMFByteStream *pSrc, IMFByteStream *pDest)
{
    HRESULT hr = S_OK;

    const DWORD READ_SIZE = 1024;

    BYTE buffer[READ_SIZE];

    while (1)
    {
        ULONG cbRead;

        hr = pSrc->Read(buffer, READ_SIZE, &cbRead);

        if (FAILED(hr)) { break; }

        if (cbRead == 0)
        {
            break;
        }

        hr = pDest->Write(buffer, cbRead, &cbRead);

        if (FAILED(hr)) { break; }
    }

    return hr;
}
//-------------------------------------------------------------------
// WriteBufferToByteStream
//
// Writes data from a media buffer to a byte stream.
//-------------------------------------------------------------------

HRESULT WriteBufferToByteStream(
    IMFByteStream *pStream,   // Pointer to the byte stream.
    IMFMediaBuffer *pBuffer,  // Pointer to the media buffer.
    DWORD *pcbWritten         // Receives the number of bytes written.
    )
{
    HRESULT hr = S_OK;
    DWORD cbData = 0;
    DWORD cbWritten = 0;
    BYTE *pMem = NULL;

    hr = pBuffer->Lock(&pMem, NULL, &cbData);

    if (SUCCEEDED(hr))
    {
        hr = pStream->Write(pMem, cbData, &cbWritten);
    }

    if (SUCCEEDED(hr))
    {
        if (pcbWritten)
        {
            *pcbWritten = cbWritten;
        }
    }

    if (pMem)
    {
        pBuffer->Unlock();
    }
    return hr;
}

3.開啟音訊檔案

本教學課程假設您的應用程式會產生未壓縮的音訊以進行編碼。 為了達到此目的,本教學課程中會宣告兩個函式:

HRESULT OpenAudioFile(PCWSTR pszURL, IMFMediaType **ppAudioFormat);
HRESULT GetNextAudioSample(BOOL *pbEOS, IMFSample **ppSample);

這些函式的實作會保留給讀取器。

  • OpenAudioFile 式應該會開啟 pszURL 指定的媒體檔案,並傳回描述音訊資料流程之媒體類型的指標。
  • GetNextAudioSample 式應該從 所 OpenAudioFile 開啟的檔案讀取未壓縮的 PCM 音訊。 到達檔案結尾時, pbEOS 會收到 TRUE值。 否則, ppSample 會接收包含音訊緩衝區的媒體範例。

4.設定編碼器

接下來,建立編碼器、設定它以產生 CBR 編碼的串流範例,以及交涉輸入和輸出媒體類型。

在媒體基礎中, (公開 IMFTransform 介面) 的編碼器會實作為 媒體基礎轉換 (MFT) 。

在本教學課程中,編碼器會在類別中 CWmaEncoder 實作,以提供 MFT 的包裝函式。 如需此類別的完整原始程式碼,請參閱 編碼器範例程式碼

注意

您可以選擇性地將編碼類型指定為 CBR。 根據預設,編碼器會設定為使用 CBR 編碼。 如需詳細資訊,請參閱 常數位元速率編碼。 您可以根據編碼類型來設定其他屬性,如需編碼模式特定屬性的相關資訊,請參閱 品質型變數位元速率編碼未限制的變數位元速率編碼,以及 尖峰限制變數位元速率編碼

 

    CWmaEncoder* pEncoder = NULL; //Pointer to the Encoder object.

    hr = OpenAudioFile(sInputFileName, &pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Initialize the WMA encoder wrapper.

    pEncoder = new (std::nothrow) CWmaEncoder();
    if (pEncoder == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pEncoder->Initialize();
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetEncodingType(EncodeMode_CBR);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetInputType(pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

5.建立 ASF ContentInfo 物件。

ASF ContentInfo 物件包含輸出檔之各種標頭物件的相關資訊。

首先,建立音訊資料流程的 ASF 設定檔:

  1. 呼叫 MFCreateASFProfile 以建立空的 ASF 設定檔物件。 ASF 設定檔會公開 IMFASFProfile 介面。 如需詳細資訊,請參閱 建立和設定 ASF 資料流程
  2. 從 物件取得編碼的 CWmaEncoder 音訊格式。
  3. 呼叫 IMFASFProfile::CreateStream 以建立 ASF 設定檔的新資料流程。 傳入代表資料流程格式之 IMFMediaType 介面的指標。
  4. 呼叫 IMFASFStreamConfig::SetStreamNumber 以指派資料流程識別碼。
  5. 在資料流程物件上設定 MF_ASFSTREAMCONFIG_LEAKYBUCKET1 屬性,以設定「流失貯體」參數。
  6. 呼叫 IMFASFProfile::SetStream ,將新的資料流程新增至設定檔。

現在建立 ASF ContentInfo 物件,如下所示:

  1. 呼叫 MFCreateASFContentInfo 以建立空的 ContentInfo 物件。
  2. 呼叫 IMFASFContentInfo::SetProfile 以設定 ASF 設定檔。

下列程式碼顯示這些步驟:

HRESULT CreateASFContentInfo(
    CWmaEncoder* pEncoder,
    IMFASFContentInfo** ppContentInfo
    )
{
    HRESULT hr = S_OK;
    
    IMFASFProfile* pProfile = NULL;
    IMFMediaType* pMediaType = NULL;
    IMFASFStreamConfig* pStream = NULL;
    IMFASFContentInfo* pContentInfo = NULL;

    // Create the ASF profile object.

    hr = MFCreateASFProfile(&pProfile); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Create a stream description for the encoded audio.

    hr = pEncoder->GetOutputType(&pMediaType); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pProfile->CreateStream(pMediaType, &pStream); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pStream->SetStreamNumber(DEFAULT_STREAM_NUMBER); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Set "leaky bucket" values.

    LeakyBucket bucket;

    hr = pEncoder->GetLeakyBucket1(&bucket);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pStream->SetBlob(
        MF_ASFSTREAMCONFIG_LEAKYBUCKET1, 
        (UINT8*)&bucket, 
        sizeof(bucket)
        );

    if (FAILED(hr))
    {
        goto done;
    }

    //Add the stream to the profile

    hr = pProfile->SetStream(pStream);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create the ASF ContentInfo object.

    hr = MFCreateASFContentInfo(&pContentInfo); 
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pContentInfo->SetProfile(pProfile); 
    if (FAILED(hr))
    {
        goto done;
    }

    // Return the pointer to the caller.

    *ppContentInfo = pContentInfo;
    (*ppContentInfo)->AddRef();

done:
    SafeRelease(&pProfile);
    SafeRelease(&pStream);
    SafeRelease(&pMediaType);
    SafeRelease(&pContentInfo);
    return hr;
}

6.建立 ASF 多工器

ASF Multiplexer會產生 ASF 資料封包。

  1. 呼叫 MFCreateASFMultiplexer 以建立 ASF 多工器。
  2. 呼叫 IMFASFMultiplexer::Initialize 以初始化多工器。 傳入在上一節中建立的 ASF 內容資訊物件的指標。
  3. 呼叫 IMFASFMultiplexer::SetFlags 以設定 MFASF_MULTIPLEXER_AUTOADJUST_BITRATE 旗標。 使用此設定時,多工器會自動調整 ASF 內容的位元速率,以符合正在多工處理的資料流程特性。
HRESULT CreateASFMux( 
    IMFASFContentInfo* pContentInfo,
    IMFASFMultiplexer** ppMultiplexer
    )
{
    HRESULT hr = S_OK;
    
    IMFMediaType* pMediaType = NULL;
    IMFASFMultiplexer *pMultiplexer = NULL;

    // Create and initialize the ASF Multiplexer object.

    hr = MFCreateASFMultiplexer(&pMultiplexer);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pMultiplexer->Initialize(pContentInfo);
    if (FAILED(hr))
    {
        goto done;
    }

    // Enable automatic bit-rate adjustment.

    hr = pMultiplexer->SetFlags(MFASF_MULTIPLEXER_AUTOADJUST_BITRATE);
    if (FAILED(hr))
    {
        goto done;
    }

    *ppMultiplexer = pMultiplexer;
    (*ppMultiplexer)->AddRef();

done:
    SafeRelease(&pMultiplexer);
    return hr;
}

7.產生新的 ASF 資料封包

接下來,產生新檔案的 ASF 資料封包。 這些資料封包會構成新檔案的最終 ASF 資料物件。 若要產生新的 ASF 資料封包:

  1. 呼叫 MFCreateTempFile 以建立暫存位元組資料流程來保存 ASF 資料封包。
  2. 呼叫應用程式定義的 GetNextAudioSample 函式,以取得編碼器的未壓縮音訊資料。
  3. 將未壓縮的音訊傳遞至編碼器以進行壓縮。 如需詳細資訊,請參閱 在編碼器中處理資料
  4. 呼叫 IMFASFMultiplexer::P rocessSample ,將壓縮的音訊樣本傳送至 ASF 多工器以進行封包化。
  5. 從多工器取得 ASF 封包,並將其寫入暫存位元組資料流程。 如需詳細資訊,請參閱 產生新的 ASF 資料封包
  6. 當您到達來來源資料流的結尾時,請清空編碼器,並從編碼器提取其餘壓縮的樣本。 如需清空 MFT 的詳細資訊,請參閱 基本 MFT 處理模型
  7. 將所有樣本傳送至多工器之後,請呼叫 IMFASFMultiplexer::Flush ,並從多工器提取其餘的 ASF 封包。
  8. 呼叫 IMFASFMultiplexer::End

下列程式碼會產生 ASF 資料封包。 函式會傳回包含 ASF 資料物件的位元組資料流程指標。

HRESULT EncodeData(
    CWmaEncoder* pEncoder, 
    IMFASFContentInfo* pContentInfo,
    IMFASFMultiplexer* pMux, 
    IMFByteStream** ppDataStream) 
{
    HRESULT hr = S_OK;

    IMFByteStream* pStream = NULL;
    IMFSample* pInputSample = NULL;
    IMFSample* pWmaSample = NULL;

    BOOL bEOF = FALSE;

   // Create a temporary file to hold the data stream.
   hr = MFCreateTempFile(
        MF_ACCESSMODE_READWRITE, 
        MF_OPENMODE_DELETE_IF_EXIST,
        MF_FILEFLAGS_NONE,
        &pStream
        );

   if (FAILED(hr))
   {
       goto done;
   }

    BOOL bNeedInput = TRUE;

    while (TRUE)
    {
        if (bNeedInput)
        {
            hr = GetNextAudioSample(&bEOF, &pInputSample);
            if (FAILED(hr))
            {
                goto done;
            }

            if (bEOF)
            {
                // Reached the end of the input file.
                break;
            }

            // Encode the uncompressed audio sample.
            hr = pEncoder->ProcessInput(pInputSample);
            if (FAILED(hr))
            {
                goto done;
            }

            bNeedInput = FALSE;
        }

        if (bNeedInput == FALSE)
        {
            // Get data from the encoder.

            hr = pEncoder->ProcessOutput(&pWmaSample);
            if (FAILED(hr))
            {
                goto done;
            }

            // pWmaSample can be NULL if the encoder needs more input.

            if (pWmaSample)
            {
                hr = pMux->ProcessSample(DEFAULT_STREAM_NUMBER, pWmaSample, 0);
                if (FAILED(hr))
                {
                    goto done;
                }

                //Collect the data packets and write them to a stream
                hr = GenerateASFDataPackets(pMux, pStream);
                if (FAILED(hr))
                {
                    goto done;
                }
            }
            else
            {
                bNeedInput = TRUE;
            }
        }
        
        SafeRelease(&pInputSample);
        SafeRelease(&pWmaSample);
    }

    // Drain the MFT and pull any remaining samples from the encoder.

    hr = pEncoder->Drain();
    if (FAILED(hr))
    {
        goto done;
    }

    while (TRUE)
    {
        hr = pEncoder->ProcessOutput(&pWmaSample);
        if (FAILED(hr))
        {
            goto done;
        }

        if (pWmaSample == NULL)
        {
            break;
        }

        hr = pMux->ProcessSample(DEFAULT_STREAM_NUMBER, pWmaSample, 0);
        if (FAILED(hr))
        {
            goto done;
        }

        //Collect the data packets and write them to a stream
        hr = GenerateASFDataPackets(pMux, pStream);
        if (FAILED(hr))
        {
            goto done;
        }

        SafeRelease(&pWmaSample);
    }

    // Flush the mux and get any pending ASF data packets.
    hr = pMux->Flush();
    if (FAILED(hr))
    {
        goto done;
    }

    hr = GenerateASFDataPackets(pMux, pStream);
    if (FAILED(hr))
    {
        goto done;
    }
    
    // Update the ContentInfo object
    hr = pMux->End(pContentInfo);
    if (FAILED(hr))
    {
        goto done;
    }

    //Return stream to the caller that contains the ASF encoded data.
    *ppDataStream = pStream;
    (*ppDataStream)->AddRef();

done:
    SafeRelease(&pStream);
    SafeRelease(&pInputSample);
    SafeRelease(&pWmaSample);
    return hr;
}

函式的程式 GenerateASFDataPackets 代碼會顯示在 產生新的 ASF 資料封包主題中。

8.寫入 ASF 檔案

接下來,呼叫 IMFASFContentInfo::GenerateHeader,將 ASF 標頭寫入媒體緩衝區。 此方法會將儲存在 ContentInfo 物件中的資料轉換成 ASF 標頭物件格式的二進位資料。 如需詳細資訊,請參閱 產生新的 ASF 標頭物件

產生新的 ASF 標頭物件之後,請建立輸出檔案的位元組資料流程。 首先,將 Header 物件寫入輸出位元組資料流程。 遵循標頭物件,其中包含資料位元組資料流程中的資料物件。

HRESULT WriteASFFile( 
     IMFASFContentInfo *pContentInfo, 
     IMFByteStream *pDataStream,
     PCWSTR pszFile
     )
{
    HRESULT hr = S_OK;
    
    IMFMediaBuffer* pHeaderBuffer = NULL;
    IMFByteStream* pWmaStream = NULL;

    DWORD cbHeaderSize = 0;
    DWORD cbWritten = 0;

    //Create output file
    hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_DELETE_IF_EXIST,
        MF_FILEFLAGS_NONE, pszFile, &pWmaStream);

    if (FAILED(hr))
    {
        goto done;
    }


    // Get the size of the ASF Header Object.
    hr = pContentInfo->GenerateHeader (NULL, &cbHeaderSize);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create a media buffer.
    hr = MFCreateMemoryBuffer(cbHeaderSize, &pHeaderBuffer);
    if (FAILED(hr))
    {
        goto done;
    }

    // Populate the media buffer with the ASF Header Object.
    hr = pContentInfo->GenerateHeader(pHeaderBuffer, &cbHeaderSize);
    if (FAILED(hr))
    {
        goto done;
    }

    // Write the ASF header to the output file.
    hr = WriteBufferToByteStream(pWmaStream, pHeaderBuffer, &cbWritten);
    if (FAILED(hr))
    {
        goto done;
    }

    // Append the data stream to the file.

    hr = pDataStream->SetCurrentPosition(0);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = AppendToByteStream(pDataStream, pWmaStream);

done:
    SafeRelease(&pHeaderBuffer);
    SafeRelease(&pWmaStream);
    return hr;
}

9.定義Entry-Point函式

現在,您可以將先前的步驟放在完整的應用程式中。 在使用任何 Media Foundation 物件之前,請先呼叫 MFStartup來初始化 Media Foundation 平臺。 完成後,請呼叫 MFShutdown。 如需詳細資訊,請參閱 初始化媒體基礎

下列程式碼顯示完整的主控台應用程式。 命令列引數會指定要轉換的檔案名,以及新音訊檔的名稱。

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

    if (argc != 3)
    {
        wprintf_s(L"Usage: %s input.wmv, %s output.wma");
        return 0;
    }

    const WCHAR* sInputFileName = argv[1];    // Source file name
    const WCHAR* sOutputFileName = argv[2];  // Output file name
    
    IMFMediaType* pInputType = NULL;
    IMFASFContentInfo* pContentInfo = NULL;
    IMFASFMultiplexer* pMux = NULL;
    IMFByteStream* pDataStream = NULL;

    CWmaEncoder* pEncoder = NULL; //Pointer to the Encoder object.

    HRESULT hr = CoInitializeEx(
        NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);

    if (FAILED(hr))
    {
        goto done;
    }

    hr = MFStartup(MF_VERSION);
    if (FAILED(hr))
    {
        goto done;
    }

    CWmaEncoder* pEncoder = NULL; //Pointer to the Encoder object.

    hr = OpenAudioFile(sInputFileName, &pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Initialize the WMA encoder wrapper.

    pEncoder = new (std::nothrow) CWmaEncoder();
    if (pEncoder == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    hr = pEncoder->Initialize();
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetEncodingType(EncodeMode_CBR);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = pEncoder->SetInputType(pInputType);
    if (FAILED(hr))
    {
        goto done;
    }

    // Create the WMContainer objects.
    hr = CreateASFContentInfo(pEncoder, &pContentInfo);
    if (FAILED(hr))
    {
        goto done;
    }

    hr = CreateASFMux(pContentInfo, &pMux);
    if (FAILED(hr))
    {
        goto done;
    }

    // Convert uncompressed data to ASF format.
    hr = EncodeData(pEncoder, pContentInfo, pMux, &pDataStream);
    if (FAILED(hr))
    {
        goto done;
    }

    // Write the ASF objects to the output file.
    hr = WriteASFFile(pContentInfo, pDataStream, sOutputFileName);

done:
    SafeRelease(&pInputType);
    SafeRelease(&pContentInfo);
    SafeRelease(&pMux);
    SafeRelease(&pDataStream);
    delete pEncoder;

    MFShutdown();
    CoUninitialize();

    if (FAILED(hr))
    {
        wprintf_s(L"Error: 0x%X\n", hr);
    }

    return 0;
} 

WMContainer ASF 元件

媒體基礎中的 ASF 支援