教學課程:使用 WMContainer 物件撰寫 WMA 檔案
本教學課程示範如何從未壓縮的音訊檔案 (.wav) 擷取媒體內容,然後以 ASF 格式壓縮新的音訊檔案 (.wma) 。 用於轉換的編碼模式是 固定位元速率編碼 (CBR) 。 在此模式中,在編碼會話之前,應用程式會指定編碼器必須達到的目標位元速率。
在本教學課程中,您將建立主控台應用程式,以接受輸入和輸出檔案名作為引數。 應用程式會從波浪檔案剖析應用程式取得未壓縮的媒體範例,本教學課程會提供此範例。 這些範例會傳送至編碼器,以轉換成 Windows Media Audio 9 格式。 編碼器設定為 CBR 編碼,並使用媒體類型交涉期間提供的第一個位元速率作為目標位元速率。 編碼的樣本會傳送至多工器,以 ASF 資料格式進行封包化。 這些封包會寫入代表 ASF 資料物件的位元組資料流程。 資料區段準備就緒之後,您將建立 ASF 音訊檔案,並寫入新的 ASF 標頭物件,以合併所有標頭資訊,然後附加 ASF 資料物件位元組資料流程。
本教學課程包含下列各節:
- 先決條件
- 術語
- 1.設定專案
- 2.宣告協助程式函式
- 3.開啟音訊檔案
- 4.設定編碼器
- 5.建立 ASF ContentInfo 物件。
- 6.建立 ASF 多工器
- 7.產生新的 ASF 資料封包
- 8.寫入 ASF 檔案
- 9.定義Entry-Point函數
- 相關主題
必要條件
本教學課程假設您已句備下列條件:
- 您已熟悉 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.設定專案
在您的原始程式檔中包含下列標頭:
#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
連結至下列程式庫檔案:
- mfplat.lib
- mf.lib
- mfuuid.lib
宣告 SafeRelease 函式。
在您的專案中加入 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 設定檔:
- 呼叫 MFCreateASFProfile 以建立空的 ASF 設定檔物件。 ASF 設定檔會公開 IMFASFProfile 介面。 如需詳細資訊,請參閱 建立和設定 ASF 資料流程。
- 從 物件取得編碼的
CWmaEncoder
音訊格式。 - 呼叫 IMFASFProfile::CreateStream 以建立 ASF 設定檔的新資料流程。 傳入代表資料流程格式之 IMFMediaType 介面的指標。
- 呼叫 IMFASFStreamConfig::SetStreamNumber 以指派資料流程識別碼。
- 在資料流程物件上設定 MF_ASFSTREAMCONFIG_LEAKYBUCKET1 屬性,以設定「流失貯體」參數。
- 呼叫 IMFASFProfile::SetStream ,將新的資料流程新增至設定檔。
現在建立 ASF ContentInfo 物件,如下所示:
- 呼叫 MFCreateASFContentInfo 以建立空的 ContentInfo 物件。
- 呼叫 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 資料封包。
- 呼叫 MFCreateASFMultiplexer 以建立 ASF 多工器。
- 呼叫 IMFASFMultiplexer::Initialize 以初始化多工器。 傳入在上一節中建立的 ASF 內容資訊物件的指標。
- 呼叫 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 資料封包:
- 呼叫 MFCreateTempFile 以建立暫存位元組資料流程來保存 ASF 資料封包。
- 呼叫應用程式定義的
GetNextAudioSample
函式,以取得編碼器的未壓縮音訊資料。 - 將未壓縮的音訊傳遞至編碼器以進行壓縮。 如需詳細資訊,請參閱 在編碼器中處理資料
- 呼叫 IMFASFMultiplexer::P rocessSample ,將壓縮的音訊樣本傳送至 ASF 多工器以進行封包化。
- 從多工器取得 ASF 封包,並將其寫入暫存位元組資料流程。 如需詳細資訊,請參閱 產生新的 ASF 資料封包。
- 當您到達來來源資料流的結尾時,請清空編碼器,並從編碼器提取其餘壓縮的樣本。 如需清空 MFT 的詳細資訊,請參閱 基本 MFT 處理模型。
- 將所有樣本傳送至多工器之後,請呼叫 IMFASFMultiplexer::Flush ,並從多工器提取其餘的 ASF 封包。
- 呼叫 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;
}
相關主題