教學課程:使用 WMContainer 物件複製 ASF 資料流程

建立 ASF 檔案的其中一種方式是從現有的檔案複製 ASF 資料流程。 若要這樣做,您可以從來源檔案擷取媒體資料,並寫入輸出檔案。 如果來源檔案是 ASF 檔案,您可以複製資料流程範例,而不解壓縮並重新壓縮它們。

本教學課程示範此案例,方法是從 ASF 音訊視訊檔案擷取第一個音訊資料流程 (.wmv) ,並將其複製到新的 ASF 音訊檔案 (.wma) 。 在本教學課程中,您將建立主控台應用程式,以接受輸入和輸出檔案名作為引數。 應用程式會使用 ASF 分割器來剖析輸入資料流程樣本,然後將其傳送至 ASF 多工器,以寫入音訊資料流程的 ASF 資料封包。

本教學課程包含下列步驟:

必要條件

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

  • 您已熟悉 ASF 檔案的結構,以及媒體基礎所提供的元件,以使用 ASF 物件。 這些元件包括 ContentInfo、分割器、多工器和設定檔物件。 如需詳細資訊,請參閱 WMContainer ASF 元件
  • 您已熟悉剖析 ASF 標頭物件和現有檔案的 ASF 資料封包,以及使用分割器產生壓縮資料流程範例的程式。 如需詳細資訊,請參閱 教學課程:讀取 ASF 檔案
  • 您熟悉媒體緩衝區和位元組資料流程:具體而言,使用位元組資料流程進行檔案作業,以及將媒體緩衝區的內容寫入位元組資料流程。 (請參閱 2。宣告 Helper Functions.)

詞彙

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

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

1.設定專案

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

#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

連結至下列程式庫檔案:

  • mfplat.lib
  • mf.lib
  • mfuuid.lib

宣告 SafeRelease 函式:

template <class T> void SafeRelease(T **ppT)
{
    if (*ppT)
    {
        (*ppT)->Release();
        *ppT = NULL;
    }
}

2. 宣告協助程式函式

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

函式 AllocReadFromByteStream 會從位元組資料流程讀取資料,並配置新的媒體緩衝區來保存資料。 如需詳細資訊,請參閱 IMFByteStream::Read

//-------------------------------------------------------------------
// AllocReadFromByteStream
//
// Reads data from a byte stream and returns a media buffer that
// contains the data.
//-------------------------------------------------------------------

HRESULT AllocReadFromByteStream(
    IMFByteStream *pStream,         // Pointer to the byte stream.
    DWORD cbToRead,                 // Number of bytes to read.
    IMFMediaBuffer **ppBuffer       // Receives a pointer to the media buffer. 
    )
{
    HRESULT hr = S_OK;
    BYTE *pData = NULL;
    DWORD cbRead = 0;   // Actual amount of data read.

    IMFMediaBuffer *pBuffer = NULL;

    // Create the media buffer. 
    // This function allocates the memory for the buffer.
    hr = MFCreateMemoryBuffer(cbToRead, &pBuffer);

    // Get a pointer to the memory buffer.
    if (SUCCEEDED(hr))
    {
        hr = pBuffer->Lock(&pData, NULL, NULL);
    }

    // Read the data from the byte stream.
    if (SUCCEEDED(hr))
    {
        hr = pStream->Read(pData, cbToRead, &cbRead);
    }

    // Update the size of the valid data in the buffer.
    if (SUCCEEDED(hr))
    {
        hr = pBuffer->SetCurrentLength(cbRead);
    }

    // Return the pointer to the caller.
    if (SUCCEEDED(hr))
    {
        *ppBuffer = pBuffer;
        (*ppBuffer)->AddRef();
    }

    if (pData)
    {
        pBuffer->Unlock();
    }
    SafeRelease(&pBuffer);
    return hr;
}

函式會將 WriteBufferToByteStream 資料從媒體緩衝區寫入位元組資料流程。 如需詳細資訊,請參閱 IMFByteStream::Write

//-------------------------------------------------------------------
// 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;
}

AppendToByteStream 式會將一個位元組資料流程的內容附加至另一個位元組資料流程:

//-------------------------------------------------------------------
// 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;
}

3.開啟輸入 ASF 檔案

呼叫 MFCreateFile 函式以開啟輸入檔。 方法會傳回包含檔案內容的位元組資料流程物件的指標。 檔案名是由使用者透過應用程式的命令列引數所指定。

下列範例程式碼會採用檔案名,並傳回可用來讀取檔案的位元組資料流程物件的指標。

        // Open the file.
        hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST, 
            MF_FILEFLAGS_NONE, pszFileName, &pStream);

4. 初始化輸入檔的物件

接下來,您將建立並初始化來源 ContentInfo 物件和用於產生資料流程範例的分割器。

在步驟 2 中建立的這個來源位元組資料流程將用來剖析 ASF 標頭物件,並填入來源 ContentInfo 物件。 這個物件將用來初始化分割器,以協助剖析輸入檔中音訊資料流程的 ASF 資料封包。 您也會擷取輸入檔中的 ASF 資料物件長度,以及相對於檔案開頭的第一個 ASF 資料封包的位移。 分割器會使用這些屬性來產生音訊串流範例。

若要建立和初始化輸入檔的 ASF 元件:

  1. 呼叫 MFCreateASFContentInfo 以建立 ContentInfo 物件。 此函式會傳回 IMFASFContentInfo 介面的指標。
  2. 呼叫 IMFASFContentInfo::P arseHeader 來剖析 ASF 標頭。 如需此步驟的詳細資訊,請參閱 讀取現有檔案的 ASF 標頭物件
  3. 呼叫 MFCreateASFSplitter 以建立 ASF 分割器物件。 此函式會傳回 IMFASFSplitter 介面的指標。
  4. 呼叫 IMFASFSplitter::Initialize,傳入 IMFASFContentInfo指標。 如需此步驟的詳細資訊,請參閱 建立 ASF 分割器物件
  5. 呼叫 IMFASFContentInfo::GeneratePresentationDescriptor 以取得 ASF 檔案的簡報描述元。
  6. 從簡報描述項取得 MF_PD_ASF_DATA_START_OFFSET 屬性的值。 這個值是檔案中 ASF 資料物件的位置,做為檔案開頭的位元組位移。
  7. 從簡報描述項取得 MF_PD_ASF_DATA_LENGTH 屬性值。 這個值是以位元組為單位的 ASF 資料物件大小總計。 如需詳細資訊,請參閱 從 ASF 標頭物件取得資訊

下列範例程式碼顯示合併所有步驟的函式。 此函式會取得來源位元組資料流程的指標,並傳回來源 ContentInfo 物件和分割器的指標。 此外,它會接收 ASF 資料物件的長度和位移。

//-------------------------------------------------------------------
// CreateSourceParsers
//
// Creates the ASF splitter and the ASF Content Info object for the 
// source file.
// 
// This function also calulates the offset and length of the ASF 
// Data Object.
//-------------------------------------------------------------------

HRESULT CreateSourceParsers(
    IMFByteStream *pSourceStream,
    IMFASFContentInfo **ppSourceContentInfo,
    IMFASFSplitter **ppSplitter,
    UINT64 *pcbDataOffset,
    UINT64 *pcbDataLength
    )
{
    const DWORD MIN_ASF_HEADER_SIZE = 30;

    IMFMediaBuffer *pBuffer = NULL;
    IMFPresentationDescriptor *pPD = NULL;
    IMFASFContentInfo *pSourceContentInfo = NULL;
    IMFASFSplitter *pSplitter = NULL;

    QWORD cbHeader = 0;

    /*------- Parse the ASF header. -------*/

    // Create the ASF ContentInfo object.
    HRESULT hr = MFCreateASFContentInfo(&pSourceContentInfo);
    
    // Read the first 30 bytes to find the total header size.
    if (SUCCEEDED(hr))
    {
        hr = AllocReadFromByteStream(
            pSourceStream, 
            MIN_ASF_HEADER_SIZE, 
            &pBuffer
            );
    }

    // Get the header size.
    if (SUCCEEDED(hr))
    {
        hr = pSourceContentInfo->GetHeaderSize(pBuffer, &cbHeader);
    }

    // Release the buffer; we will reuse it.
    SafeRelease(&pBuffer);
    
    // Read the entire header into a buffer.
    if (SUCCEEDED(hr))
    {
        hr = pSourceStream->SetCurrentPosition(0);
    }

    if (SUCCEEDED(hr))
    {
        hr = AllocReadFromByteStream(
            pSourceStream, 
            (DWORD)cbHeader, 
            &pBuffer
            );
    }

    // Parse the buffer and populate the header object.
    if (SUCCEEDED(hr))
    {
        hr = pSourceContentInfo->ParseHeader(pBuffer, 0);
    }

    /*------- Initialize the ASF splitter. -------*/

    // Create the splitter.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateASFSplitter(&pSplitter);
    }
    
    // initialize the splitter with the ContentInfo object.
    if (SUCCEEDED(hr))
    {
        hr = pSplitter->Initialize(pSourceContentInfo);
    }


    /*------- Get the offset and size of the ASF Data Object. -------*/

    // Generate the presentation descriptor.
    if (SUCCEEDED(hr))
    {
        hr =  pSourceContentInfo->GeneratePresentationDescriptor(&pPD);
    }

    // Get the offset to the start of the Data Object.
    if (SUCCEEDED(hr))
    {
        hr = pPD->GetUINT64(MF_PD_ASF_DATA_START_OFFSET, pcbDataOffset);
    }

    // Get the length of the Data Object.
    if (SUCCEEDED(hr))
    {
        hr = pPD->GetUINT64(MF_PD_ASF_DATA_LENGTH, pcbDataLength);
    }

    // Return the pointers to the caller.
    if (SUCCEEDED(hr))
    {
        *ppSourceContentInfo = pSourceContentInfo;
        (*ppSourceContentInfo)->AddRef();

        *ppSplitter = pSplitter;
        (*ppSplitter)->AddRef();

    }

    SafeRelease(&pPD);
    SafeRelease(&pBuffer);
    SafeRelease(&pSourceContentInfo);
    SafeRelease(&pSplitter);

    return S_OK;
}

5.建立音訊設定檔

接下來,您會從來源 ContentInfo 物件取得輸入檔來建立輸入檔的設定檔物件。 然後,您將設定設定檔,使其只包含輸入檔的音訊資料流程。 若要這樣做,請列舉資料流程,並從設定檔中移除非音訊資料流程。 本教學課程稍後會使用音訊設定檔物件來初始化輸出 ContentInfo 物件。

建立音訊設定檔

  1. 藉由呼叫 IMFASFContentInfo::GetProfile,從來源 ContentInfo 物件取得輸入檔的設定檔物件。 方法會傳回設定檔物件的指標,其中包含輸入檔中的所有資料流程。 如需詳細資訊,請參閱 建立 ASF 設定檔
  2. 從設定檔中移除任何相互排除物件。 此步驟是必要的,因為非音訊資料流程將會從設定檔中移除,這可能會導致相互排除物件失效。
  3. 從設定檔中移除所有非音訊資料流程,如下所示:
  4. 儲存第一個音訊資料流程的資料流程編號。 這會在分隔器上選取,以產生串流範例。 如果資料流程號碼為零,則呼叫端可以假設沒有音訊資料流程檔案。

下列步驟的程式碼:

//-------------------------------------------------------------------
// GetAudioProfile
//
// Gets the ASF profile from the source file and removes any video
// streams from the profile.
//-------------------------------------------------------------------

HRESULT GetAudioProfile(
    IMFASFContentInfo *pSourceContentInfo, 
    IMFASFProfile **ppAudioProfile, 
    WORD *pwSelectStreamNumber
    )
{
    IMFASFStreamConfig *pStream = NULL;
    IMFASFProfile *pProfile = NULL;

    DWORD dwTotalStreams = 0;
    WORD  wStreamNumber = 0; 
    GUID guidMajorType = GUID_NULL;
    
    // Get the profile object from the source ContentInfo object.
    HRESULT hr = pSourceContentInfo->GetProfile(&pProfile);

    // Remove mutexes from the profile
    if (SUCCEEDED(hr))
    {
        hr = RemoveMutexes(pProfile);
    }

    // Get the total number of streams on the profile.
    if (SUCCEEDED(hr))
    {
        hr = pProfile->GetStreamCount(&dwTotalStreams);
    }

    // Enumerate the streams and remove the non-audio streams.
    if (SUCCEEDED(hr))
    {
        for (DWORD index = 0; index < dwTotalStreams; )
        {
            hr = pProfile->GetStream(index, &wStreamNumber, &pStream);

            if (FAILED(hr)) { break; }

            hr = pStream->GetStreamType(&guidMajorType);

            SafeRelease(&pStream);

            if (FAILED(hr)) { break; }

            if (guidMajorType != MFMediaType_Audio)
            {
                hr = pProfile->RemoveStream(wStreamNumber);
    
                if (FAILED(hr)) { break; }

                index = 0;
                dwTotalStreams--;
            }
            else
            {
                // Store the first audio stream number. 
                // This will be selected on the splitter.

                if (*pwSelectStreamNumber == 0)
                {
                    *pwSelectStreamNumber = wStreamNumber;
                }

                index++;
            }
        }
    }

    if (SUCCEEDED(hr))
    {
        *ppAudioProfile = pProfile;
        (*ppAudioProfile)->AddRef();
    }

    SafeRelease(&pStream);
    SafeRelease(&pProfile);

    return S_OK;
}

RemoveMutexes 式會從設定檔中移除任何互斥物件:

HRESULT RemoveMutexes(IMFASFProfile *pProfile)
{
    DWORD cMutex = 0;
    HRESULT hr = pProfile->GetMutualExclusionCount(&cMutex);

    if (SUCCEEDED(hr))
    {
        for (DWORD i = 0; i < cMutex; i++)
        {
            hr = pProfile->RemoveMutualExclusion(0);

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

    return hr;
}

6.初始化輸出檔的物件

接下來,您將建立輸出 ContentInfo 物件和多工器,以產生輸出檔案的資料封包。

在步驟 4 中建立的音訊設定檔將用來填入輸出 ContentInfo 物件。 此物件包含全域檔案屬性和資料流程屬性等資訊。 輸出 ContentInfo 物件將用來初始化多工器,以產生輸出檔案的資料封包。 產生資料封包之後,必須更新 ContentInfo 物件以反映新的值。

建立和初始化輸出檔案的 ASF 元件

  1. 藉由呼叫 MFCreateASFContentInfo 建立空的 ContentInfo 物件,並藉由呼叫 IMFASFContentInfo::SetProfile,在步驟 3 中建立的音訊設定檔填入該物件中的資訊。 如需詳細資訊,請參閱 初始化新 ASF 檔案的 ContentInfo 物件
  2. 使用輸出 ContentInfo 物件建立並初始化多工器物件。 如需詳細資訊,請參閱 建立 Multiplexer 物件

下列範例程式碼顯示合併步驟的函式。 此函式會取得設定檔物件的指標,並將指標傳回輸出 ContentInfo 物件和多工器的指標。

//-------------------------------------------------------------------
// CreateOutputGenerators
//
// Creates the ASF mux and the ASF Content Info object for the 
// output file.
//-------------------------------------------------------------------

HRESULT CreateOutputGenerators(
    IMFASFProfile *pProfile, 
    IMFASFContentInfo **ppContentInfo, 
    IMFASFMultiplexer **ppMux
    )
{
    IMFASFContentInfo *pContentInfo = NULL;
    IMFASFMultiplexer *pMux = NULL;

    // Use the ASF profile to create the ContentInfo object.
    HRESULT hr = MFCreateASFContentInfo(&pContentInfo);

    if (SUCCEEDED(hr))
    {
        hr = pContentInfo->SetProfile(pProfile);
    }

    // Create the ASF Multiplexer object.
    if (SUCCEEDED(hr))
    {
        hr = MFCreateASFMultiplexer(&pMux);
    }
    
    // Initialize it using the new ContentInfo object.
    if (SUCCEEDED(hr))
    {
        hr = pMux->Initialize(pContentInfo);
    }

    // Return the pointers to the caller.
    if (SUCCEEDED(hr))
    {
        *ppContentInfo = pContentInfo;
        (*ppContentInfo)->AddRef();

        *ppMux = pMux;
        (*ppMux)->AddRef();
    }

    SafeRelease(&pContentInfo);
    SafeRelease(&pMux);

    return hr;
}

7.產生新的 ASF 資料封包

接下來,您將使用分割器從來源位元組資料流程產生音訊串流範例,並將其傳送至多工器以建立 ASF 資料封包。 這些資料封包將構成新檔案的最終 ASF 資料物件。

產生音訊串流範例

  1. 呼叫 IMFASFSplitter::SelectStreams,以選取分隔器上的第一個音訊串流。
  2. 從來源位元組資料流程讀取媒體資料的固定大社區塊到媒體緩衝區。
  3. 只要在pdwStatusFlags參數中收到ASF_STATUSFLAGS_INCOMPLETE旗標,就可以在迴圈中呼叫IMFASFSplitter::GetNextSample,以從分割器收集串流範例作為媒體樣本。 For more information, see Generating Samples for ASF Data Packets" in Generating Stream Samples from an Existing ASF Data Object.
  4. 針對每個媒體範例,呼叫 IMFASFMultiplexer::P rocessSample 將媒體範例傳送至多工器。 多工器會產生 ASF 資料物件的資料封包。
  5. 將多工器所產生的資料封包寫入資料位元組資料流程。
  6. 產生所有資料封包之後,請呼叫 IMFASFMultiplexer::End ,以在 ASF 資料封包產生期間收集的資訊來更新輸出 ContentInfo 物件。

下列範例程式碼會從 ASF 分隔器產生資料流程範例,並將其傳送至多工器。 多工器會產生 ASF 資料封包,並將它寫入資料流程。

//-------------------------------------------------------------------
// GenerateASFDataObject
// 
// Creates a byte stream that contains the ASF Data Object for the
// output file.
//-------------------------------------------------------------------

HRESULT GenerateASFDataObject(
    IMFByteStream *pSourceStream, 
    IMFASFSplitter *pSplitter, 
    IMFASFMultiplexer *pMux, 
    UINT64   cbDataOffset,
    UINT64   cbDataLength,
    IMFByteStream **ppDataStream
    )
{
    IMFMediaBuffer *pBuffer = NULL;
    IMFByteStream *pDataStream = NULL;
    
    const DWORD READ_SIZE = 1024 * 4;

    // Flush the splitter to remove any pending samples.
    HRESULT hr = pSplitter->Flush();

    if (SUCCEEDED(hr))
    {
        hr = MFCreateTempFile(
            MF_ACCESSMODE_READWRITE, 
            MF_OPENMODE_DELETE_IF_EXIST,
            MF_FILEFLAGS_NONE,
            &pDataStream
            );
    }

    if (SUCCEEDED(hr))
    {
        hr = pSourceStream->SetCurrentPosition(cbDataOffset);
    }

    if (SUCCEEDED(hr))
    {
        while (cbDataLength > 0)
        {
            DWORD cbRead = min(READ_SIZE, (DWORD)cbDataLength);

            hr = AllocReadFromByteStream(
                pSourceStream, 
                cbRead, 
                &pBuffer
                );

            if (FAILED(hr)) 
            { 
                break; 
            }

            cbDataLength -= cbRead;

            // Push data on the splitter.
            hr =  pSplitter->ParseData(pBuffer, 0, 0);

            if (FAILED(hr)) 
            { 
                break; 
            }

            // Get ASF packets from the splitter and feed them to the mux.
            hr = GetPacketsFromSplitter(pSplitter, pMux, pDataStream);

            if (FAILED(hr)) 
            { 
                break; 
            }

            SafeRelease(&pBuffer);
        }
    }

    // Flush the mux and generate any remaining samples.
    if (SUCCEEDED(hr))
    {
        hr = pMux->Flush();
    }

    if (SUCCEEDED(hr))
    {
        hr = GenerateASFDataPackets(pMux, pDataStream);
    }

     // Return the pointer to the caller.
    if (SUCCEEDED(hr))
    {
        *ppDataStream = pDataStream;
        (*ppDataStream)->AddRef();
    }

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

若要從 ASF 分隔器取得封包,先前的程式碼會呼叫 函 GetPacketsFromSplitter 式,如下所示:

//-------------------------------------------------------------------
// GetPacketsFromSplitter
//
// Gets samples from the ASF splitter.
//
// This function is called after calling IMFASFSplitter::ParseData.
//-------------------------------------------------------------------

HRESULT GetPacketsFromSplitter(
    IMFASFSplitter *pSplitter,
    IMFASFMultiplexer *pMux,
    IMFByteStream *pDataStream
    )
{
    HRESULT hr = S_OK;
    DWORD   dwStatus = ASF_STATUSFLAGS_INCOMPLETE;
    WORD    wStreamNum = 0;

    IMFSample *pSample = NULL;

    while (dwStatus & ASF_STATUSFLAGS_INCOMPLETE) 
    {
        hr = pSplitter->GetNextSample(&dwStatus, &wStreamNum, &pSample);

        if (FAILED(hr))
        {
            break;
        }

        if (pSample)
        {
            //Send to the multiplexer to convert it into ASF format
            hr = pMux->ProcessSample(wStreamNum, pSample, 0);

            if (FAILED(hr)) 
            { 
                break; 
            }

            hr = GenerateASFDataPackets(pMux, pDataStream);

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

        SafeRelease(&pSample);
    }

    SafeRelease(&pSample);
    return hr;
}

函式 GenerateDataPackets 會從多工器取得資料封包。 如需詳細資訊,請參閱 取得 ASF 資料封包

//-------------------------------------------------------------------
// GenerateASFDataPackets
// 
// Gets data packets from the mux. This function is called after 
// calling IMFASFMultiplexer::ProcessSample. 
//-------------------------------------------------------------------

HRESULT GenerateASFDataPackets( 
    IMFASFMultiplexer *pMux, 
    IMFByteStream *pDataStream
    )
{
    HRESULT hr = S_OK;

    IMFSample *pOutputSample = NULL;
    IMFMediaBuffer *pDataPacketBuffer = NULL;

    DWORD dwMuxStatus = ASF_STATUSFLAGS_INCOMPLETE;

    while (dwMuxStatus & ASF_STATUSFLAGS_INCOMPLETE)
    {
        hr = pMux->GetNextPacket(&dwMuxStatus, &pOutputSample);

        if (FAILED(hr))
        {
            break;
        }

        if (pOutputSample)
        {
            //Convert to contiguous buffer
            hr = pOutputSample->ConvertToContiguousBuffer(&pDataPacketBuffer);
            
            if (FAILED(hr))
            {
                break;
            }

            //Write buffer to byte stream
            hr = WriteBufferToByteStream(pDataStream, pDataPacketBuffer, NULL);

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

        SafeRelease(&pDataPacketBuffer);
        SafeRelease(&pOutputSample);
    }

    SafeRelease(&pOutputSample);
    SafeRelease(&pDataPacketBuffer);
    return hr;
}

8.在新檔案中寫入 ASF 物件

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

產生新的 ASF 標頭物件之後,請先將 Header 物件寫入步驟 2 中建立的輸出位元組資料流程,方法是呼叫 Helper 函式 WriteBufferToByteStream,以寫入輸出檔案。 遵循 Header 物件與資料位元組資料流程中包含的資料物件。 此範例程式碼顯示一個函式,會將資料位元組資料流程的內容傳輸至輸出位元組資料流程。

//-------------------------------------------------------------------
// WriteASFFile
//
// Writes the complete ASF file.
//-------------------------------------------------------------------

HRESULT WriteASFFile( 
    IMFASFContentInfo *pContentInfo, // ASF Content Info for the output file.
    IMFByteStream *pDataStream,      // Data stream.
    PCWSTR pszFile                   // Output file name.
    )
{
    
    IMFMediaBuffer *pHeaderBuffer = NULL;
    IMFByteStream *pWmaStream = NULL;

    DWORD cbHeaderSize = 0;
    DWORD cbWritten = 0;

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

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

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

    // Populate the media buffer with the ASF Header Object.
    if (SUCCEEDED(hr))
    {
        hr = pContentInfo->GenerateHeader(pHeaderBuffer, &cbHeaderSize);
    }
 
    // Write the header contents to the byte stream for the output file.
    if (SUCCEEDED(hr))
    {
        hr = WriteBufferToByteStream(pWmaStream, pHeaderBuffer, &cbWritten);
    }

    if (SUCCEEDED(hr))
    {
        hr = pDataStream->SetCurrentPosition(0);
    }

    // Append the data stream to the file.

    if (SUCCEEDED(hr))
    {
        hr = AppendToByteStream(pDataStream, pWmaStream);
    }

    SafeRelease(&pHeaderBuffer);
    SafeRelease(&pWmaStream);

    return hr;
}

9 撰寫 Entry-Point 函式

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

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

int wmain(int argc, WCHAR* argv[])
{
    if (argc != 3)
    {
        wprintf_s(L"Usage: %s input.wmv, %s output.wma\n");
        return 0;
    }

    HRESULT hr = MFStartup(MF_VERSION);

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

    PCWSTR pszInputFile = argv[1];      
    PCWSTR pszOutputFile = argv[2];     
    
    IMFByteStream      *pSourceStream = NULL;       
    IMFASFContentInfo  *pSourceContentInfo = NULL;  
    IMFASFProfile      *pAudioProfile = NULL;       
    IMFASFContentInfo  *pOutputContentInfo = NULL;  
    IMFByteStream      *pDataStream = NULL;         
    IMFASFSplitter     *pSplitter = NULL;           
    IMFASFMultiplexer  *pMux = NULL;                

    UINT64  cbDataOffset = 0;           
    UINT64  cbDataLength = 0;           
    WORD    wSelectStreamNumber = 0;    

    // Open the input file.

    hr = OpenFile(pszInputFile, &pSourceStream);

    // Initialize the objects that will parse the source file.

    if (SUCCEEDED(hr))
    {
        hr = CreateSourceParsers(
            pSourceStream, 
            &pSourceContentInfo,    // ASF Header for the source file.
            &pSplitter,             // Generates audio samples.
            &cbDataOffset,          // Offset to the first data packet.
            &cbDataLength           // Length of the ASF Data Object.
            );
    }

    // Create a profile object for the audio streams in the source file.

    if (SUCCEEDED(hr))
    {
        hr = GetAudioProfile(
            pSourceContentInfo, 
            &pAudioProfile,         // ASF profile for the audio stream.
            &wSelectStreamNumber    // Stream number of the first audio stream.
            );
    }

    // Initialize the objects that will generate the output data.

    if (SUCCEEDED(hr))
    {
        hr = CreateOutputGenerators(
            pAudioProfile, 
            &pOutputContentInfo,    // ASF Header for the output file.
            &pMux                   // Generates ASF data packets.
            );
    }

    // Set up the splitter to generate samples for the first
    // audio stream in the source media.

    if (SUCCEEDED(hr))
    {
        hr = pSplitter->SelectStreams(&wSelectStreamNumber, 1);
    }
    
    // Generate ASF Data Packets and store them in a byte stream.

    if (SUCCEEDED(hr))
    {
        hr = GenerateASFDataObject(
               pSourceStream, 
               pSplitter, 
               pMux, 
               cbDataOffset, 
               cbDataLength, 
               &pDataStream    // Byte stream for the ASF data packets.    
               );
    }

    // Update the header with new information if any.

    if (SUCCEEDED(hr))
    {
        hr = pMux->End(pOutputContentInfo);
    }

    //Write the ASF objects to the output file
    if (SUCCEEDED(hr))
    {
        hr = WriteASFFile(pOutputContentInfo, pDataStream, pszOutputFile);
    }

    // Clean up.
    SafeRelease(&pMux);
    SafeRelease(&pSplitter);
    SafeRelease(&pDataStream);
    SafeRelease(&pOutputContentInfo);
    SafeRelease(&pAudioProfile);
    SafeRelease(&pSourceContentInfo);
    SafeRelease(&pSourceStream);

    MFShutdown();

    if (FAILED(hr))
    {
        wprintf_s(L"Could not create the audio file: 0x%X\n", hr);
    }

    return 0;
}

WMContainer ASF 元件

媒體基礎中的 ASF 支援