教學課程:使用 WMContainer 物件複製 ASF 數據流
建立 ASF 檔案的其中一個方法是從現有的檔案複製 ASF 數據流。 若要這樣做,您可以從來源檔案擷取媒體數據,並寫入輸出檔案。 如果來源檔案是 ASF 檔案,您可以複製資料流範例,而不需解壓縮和重新壓縮它們。
本教學課程示範此案例,方法是從 ASF 音訊視訊檔案 (.wmv) 擷取第一個音訊串流,並將其複製到新的 ASF 音訊檔案 (.wma)。 在本教學課程中,您將建立主控台應用程式,以輸入和輸出檔名作為自變數。 應用程式會使用 ASF 分隔器來剖析輸入資料流範例,然後將它們傳送至 ASF 多任務器,以寫入音訊數據流的 ASF 數據封包。
本教學課程包含下列步驟:
- 必要條件
- 術語
- 1. 設定專案
- 2. 宣告輔助函式
- 3. 開啟輸入的 ASF 檔案
- 4.初始化輸入檔的物件
- 5.建立音效設定檔
- 6.初始化輸出檔 的物件
- 7.產生新的 ASF 數據封包
- 8.在新檔案 中寫入 ASF 物件
- 9 撰寫 Entry-Point 函式
- 相關主題
先決條件
本教學課程假設以下條件:
- 您熟悉 ASF 檔案的結構,以及媒體基礎所提供的元件,以使用 ASF 物件。 這些元件包括 ContentInfo、分割器、多工器和配置檔物件。 如需詳細資訊,請參閱 WMContainer ASF 元件。
- 您已熟悉剖析 ASF 標頭對象和現有檔案的 ASF 數據封包,以及使用分割器產生壓縮數據流範例的程式。 如需詳細資訊,請參閱 教學課程:讀取 ASF 檔案。
- 您熟悉媒體緩衝區和位元組數據流:具體而言,使用位元組數據流進行檔案作業,並將媒體緩衝區的內容寫入位元組數據流。 (見 2. 宣告輔助函式。)
術語
本教學課程使用下列詞彙:
- 來源位元組數據流:位元組數據流物件會公開 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 元件:
- 呼叫 MFCreateASFContentInfo 來建立 ContentInfo 物件。 此函式會傳回 指標,指向 IMFASFContentInfo 介面。
- 呼叫 IMFASFContentInfo::ParseHeader 解析 ASF 標頭。 如需此步驟的詳細資訊,請參閱 讀取現有檔案的 ASF 標頭物件。
- 呼叫 MFCreateASFSplitter 來建立 ASF 分割器物件。 此函式會傳回 IMFASFSplitter 介面的指標。
- 呼叫 IMFASFSplitter::Initialize,傳入 IMFASFContentInfo指標。 如需此步驟的詳細資訊,請參閱 建立 ASF 分割器物件。
- 呼叫 IMFASFContentInfo::GeneratePresentationDescriptor,以取得 ASF 檔案的簡報描述元。
- 從簡報描述元取得 MF_PD_ASF_DATA_START_OFFSET 屬性的值。 此值是檔案中 ASF 資料物件的位置,以位元組為單位從檔案開頭起的位移。
- 從簡報描述元取得 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 物件。
建立音訊配置檔
- 從來源 ContentInfo 物件取得輸入檔的設定檔物件,方法是呼叫 IMFASFContentInfo::GetProfile。 方法會傳回配置檔物件的指標,其中包含輸入檔中的所有數據流。 如需詳細資訊,請參閱 建立 ASF 設定檔。
- 從配置檔中移除任何互斥物件。 此步驟是必要的,因為會從設定檔中移除非音訊串流,這可能會使互斥物件失效。
- 從配置檔中移除所有非音訊串流,如下所示:
- 呼叫 IMFASFProfile::GetStreamCount 以取得數據流數目。
- 在迴圈中,呼叫 IMFASFProfile::GetStream,依索引取得每個數據流。
- 呼叫 IMFASFStreamConfig::GetStreamType 以取得數據流類型。
- 針對非音訊串流,呼叫 IMFASFProfile::RemoveStream 以移除數據流。
- 儲存第一個音訊數據流的數據流編號。 這會在分隔器上選取,以產生數據流範例。 如果數據流號碼為零,呼叫端可以假設沒有音訊串流檔案。
下列程式碼這些步驟:
//-------------------------------------------------------------------
// 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 元件
- 藉由呼叫 MFCreateASFContentInfo 來建立空的 ContentInfo 物件,並藉由呼叫 IMFASFContentInfo::SetProfile,從步驟 3 中建立的音訊配置檔填入資訊。 如需詳細資訊,請參閱 初始化新 ASF 檔案的 ContentInfo 物件。
- 使用輸出 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 資料物件。
產生音訊串流樣本
- 呼叫 IMFASFSplitter::SelectStreams ,以選取分割器上的第一個音訊串流。
- 將固定大小的媒體數據塊從來源字節流讀取到媒體緩衝區。
- 只要在 pdwStatusFlags 參數中收到 ASF_STATUSFLAGS_INCOMPLETE 旗標,即可在迴圈中呼叫 IMFASFSplitter::GetNextSample,以從分割器收集串流範例作為媒體樣本。 如需詳細資訊,請參閱從現有 ASF 資料對象產生數據流範例 中的產生 ASF 數據封包範例。
- 針對每個媒體範例,呼叫 IMFASFMultiplexer::ProcessSample,將媒體範例傳送至多工器。 多任務器會產生 ASF 資料對象的數據封包。
- 將多工器所產生的數據封包寫入位元組資料流。
- 產生所有數據封包之後,呼叫 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;
}
相關主題