教程:使用 WMContainer 对象复制 ASF 流

创建 ASF 文件的一种方法是从现有文件复制 ASF 流。 为此,可以从源文件检索媒体数据并写入输出文件。 如果源文件是 ASF 文件,则可以复制流示例,而无需解压缩和重新压缩它们。

本教程演示了此方案,从 ASF 音频-视频文件 (.wmv) 中提取第一个音频流,并将其复制到 (.wma) 的新 ASF 音频文件。 在本教程中,你将创建一个控制台应用程序,该应用程序将输入和输出文件名作为参数。 应用程序使用 ASF 拆分器分析输入流样本,然后将其发送到 ASF 多路复用器,以写入音频流的 ASF 数据包。

本教程包含以下步骤:

先决条件

本教程的假设条件如下:

  • 你熟悉 ASF 文件的结构以及 Media Foundation 提供的用于处理 ASF 对象的组件。 这些组件包括 ContentInfo、拆分器、多路复用器和配置文件对象。 有关详细信息,请参阅 WMContainer ASF 组件
  • 你熟悉分析 ASF 标头对象和现有文件的 ASF 数据包以及使用拆分器生成压缩流示例的过程。 有关详细信息,请参阅 教程:读取 ASF 文件
  • 你熟悉媒体缓冲区和字节流:具体而言,是使用字节流的文件操作,以及将媒体缓冲区的内容写入字节流。 (请参阅 2.声明帮助程序 Functions.)

术语

本教程使用以下术语:

  • 源字节流:字节流对象,公开 IMFByteStream 接口,其中包含输入文件的内容。
  • 源 ContentInfo 对象:ContentInfo 对象,公开 IMFASFContentInfo 接口,该接口表示输入文件的 ASF 标头对象。
  • 音频配置文件:配置文件对象,公开 IMFASFProfile 接口,该接口仅包含输入文件的音频流。
  • 流示例:媒体示例,公开由拆分器生成的 IMFSample 接口,表示从处于压缩状态的输入文件获取的选定流的媒体数据。
  • 输出 ContentInfo 对象:ContentInfo 对象公开 IMFASFContentInfo 接口,该接口表示输出文件的 ASF 标头对象。
  • 数据字节流:字节流对象,公开 IMFByteStream 接口,该接口表示输出文件的整个 ASF 数据对象部分。
  • 数据包:媒体示例,公开多路复用器生成的 IMFSample 接口,表示将写入数据字节流的 ASF 数据包。
  • 输出字节流:字节流对象,公开 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 对象创建和初始化多路复用器对象。 有关详细信息,请参阅 创建多路复用器对象

以下示例代码显示了合并步骤的函数。 此函数采用指向配置文件对象的指针,并返回指向输出 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. 通过在循环中调用 IMFASFSplitter::GetNextSample ,从拆分器中收集流样本作为媒体样本,前提是它在 pdwStatusFlags 参数中收到ASF_STATUSFLAGS_INCOMPLETE标志。 有关详细信息,请参阅从现有 ASF 数据 对象生成流样本中的为 ASF 数据包生成示例
  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 标头对象后,通过调用帮助程序函数 WriteBufferToByteStream,首先将标头对象写入步骤 2 中创建的输出字节流,从而写入输出文件。 在数据字节流中包含的数据对象后跟标头对象。 示例代码演示了一个函数,该函数将数据字节流的内容传输到输出字节流。

//-------------------------------------------------------------------
// 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 支持