자습서: 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 파일의 구조와 ASF 개체 작업을 위해 Media Foundation에서 제공하는 구성 요소에 대해 잘 알고 있습니다. 이러한 구성 요소에는 ContentInfo, 분할자, 멀티플렉서 및 프로필 개체가 포함됩니다. 자세한 내용은 WMContainer ASF 구성 요소를 참조하세요.
- Windows Media 인코더 및 다양한 인코딩 유형, 특히 CBR에 대해 잘 알고 있습니다. 자세한 내용은 Windows Media 인코더 를 참조하세요.
- 미디어 버퍼 및 바이트 스트림에 익숙합니다. 특히 바이트 스트림을 사용하여 파일 작업을 수행하고 미디어 버퍼의 내용을 바이트 스트림에 기록합니다.
용어
이 자습서에서는 다음 용어를 사용합니다.
- 원본 미디어 형식: 미디어 형식 개체는 입력 파일의 내용을 설명하는 IMFMediaType 인터페이스를 노출합니다.
- 오디오 프로필: 프로필 개체는 출력 파일의 오디오 스트림만 포함하는 IMFASFProfile 인터페이스를 노출합니다.
- 스트림 샘플: IMFSample 인터페이스를 노출하는 미디어 샘플은 압축된 상태의 인코더에서 가져온 입력 파일의 미디어 데이터를 나타냅니다.
- ContentInfo 개체: ASF ContentInfo 개체는 출력 파일의 ASF 헤더 개체를 나타내는 IMFASFContentInfo 인터페이스를 노출합니다.
- 데이터 바이트 스트림: 바이트 스트림 개체는 출력 파일의 전체 ASF 데이터 개체 부분을 나타내는 IMFByteStream 인터페이스를 노출합니다.
- 데이터 패킷: 미디어 샘플은 ASF 멀티플렉서에서 생성된 IMFSample 인터페이스를 노출합니다. 는 데이터 바이트 스트림에 기록될 ASF 데이터 패킷을 나타냅니다.
- 출력 바이트 스트림: 바이트 스트림 개체는 출력 파일의 내용을 포함하는 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
에서 연 파일에서 압축되지 않은 PCM 오디오를OpenAudioFile
읽어야 합니다. 파일의 끝에 도달하면 pbEOS 는 TRUE 값을 받습니다. 그렇지 않으면 ppSample 은 오디오 버퍼가 포함된 미디어 샘플을 받습니다.
4. 인코더 구성
다음으로, 인코더를 만들고, CBR로 인코딩된 스트림 샘플을 생성하도록 구성하고, 입력 및 출력 미디어 형식을 협상합니다.
Media Foundation에서 인코더( IMFTransform 인터페이스 노출)는 MFT( Media Foundation Transforms )로 구현됩니다.
이 자습서에서는 MFT에 대한 래퍼를 CWmaEncoder
제공하는 클래스에서 인코더가 구현됩니다. 이 클래스의 전체 소스 코드는 인코더 예제 코드를 참조하세요.
참고
필요에 따라 인코딩 형식을 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 멀티플렉서는 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을 호출합니다. 자세한 내용은 Media Foundation 초기화를 참조하세요.
다음 코드는 전체 콘솔 애플리케이션을 보여줍니다. 명령줄 인수는 변환할 파일의 이름과 새 오디오 파일의 이름을 지정합니다.
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;
}
관련 항목