자습서: WMContainer 개체를 사용하여 ASF 파일 읽기
이 자습서에서는 ASF 분할자를 사용하여 ASF(Advanced Systems Format) 파일에서 데이터 패킷을 가져오는 방법을 보여 줍니다. 이 자습서에서는 ASF 파일을 읽고 파일의 첫 번째 비디오 스트림에 대한 압축된 미디어 샘플을 생성하는 간단한 콘솔 애플리케이션을 만듭니다. 애플리케이션은 비디오 스트림의 키 프레임에 대한 정보를 표시합니다.
이 자습서에는 다음 단계가 포함되어 있습니다.
- 필수 구성 요소
- 1. 프로젝트 설정
- 2. ASF 파일 열기
- 3. ASF 헤더 개체 읽기
- 4. ASF 분할기 만들기
- 5. 구문 분석용 스트림 선택
- 6. 압축된 미디어 샘플 생성
- 7. Entry-Point 함수 작성
- 프로그램 목록
- 관련 항목
이 자습서에서는 애플리케이션이 ASF 분할자에서 가져오는 압축 데이터를 디코딩하는 방법을 다루지 않습니다.
사전 요구 사항
이 자습서에서는 다음을 가정합니다.
- ASF 파일의 구조와 ASF 개체 작업을 위해 Media Foundation에서 제공하는 구성 요소에 대해 잘 알고 있습니다. 이러한 구성 요소에는 ContentInfo 개체, 분할자, 멀티플렉서 및 프로필이 포함됩니다. 자세한 내용은 WMContainer ASF 구성 요소를 참조하세요.
- 미디어 버퍼 및 바이트 스트림에 익숙합니다. 특히 바이트 스트림을 사용하여 파일 작업을 수행하고, 바이트 스트림에서 미디어 버퍼로 읽고, 미디어 버퍼의 콘텐츠를 바이트 스트림에 기록합니다.
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>
다음 라이브러리 파일에 연결합니다.
- mfplat.lib
- mf.lib
- mfuuid.lib
SafeRelease 함수를 선언합니다.
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
2. ASF 파일 열기
다음으로 , MFCreateFile 함수를 호출하여 지정된 파일을 엽니다. 메서드는 파일의 내용을 포함하는 바이트 스트림 개체에 대한 포인터를 반환합니다. 파일 이름은 애플리케이션의 명령줄 인수를 통해 사용자가 지정합니다.
다음 예제 코드는 파일 이름을 사용하고 파일을 읽는 데 사용할 수 있는 바이트 스트림 개체에 대한 포인터를 반환합니다.
// Open the file.
hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST,
MF_FILEFLAGS_NONE, pszFileName, &pStream);
3. ASF 헤더 개체 읽기
다음으로 ASF ContentInfo 개체 를 만들고 이를 사용하여 지정된 파일의 ASF 헤더 개체를 구문 분석합니다. ContentInfo 개체는 전역 파일 특성 및 각 스트림에 대한 정보를 포함하여 ASF 헤더의 정보를 저장합니다. 자습서의 뒷부분에 있는 ContentInfo 개체를 사용하여 ASF 분할자를 초기화하고 비디오 스트림의 스트림 번호를 가져옵니다.
ASF ContentInfo 개체를 만들려면 다음을 수행합니다.
- MFCreateASFContentInfo 함수를 호출하여 ContentInfo 개체를 만듭니다. 메서드는 IMFASFContentInfo 인터페이스에 대한 포인터를 반환합니다.
- ASF 파일에서 미디어 버퍼로 처음 30바이트의 데이터를 읽습니다.
- 미디어 버퍼를 IMFASFContentInfo::GetHeaderSize 메서드에 전달합니다. 이 메서드는 ASF 파일에서 Header 개체의 총 크기를 반환합니다.
- 동일한 미디어 버퍼를 IMFASFContentInfo::P arseHeader 메서드에 전달합니다 .
- Header 개체의 나머지 부분을 새 미디어 버퍼로 읽습니다.
- 두 번째 버퍼를 ParseHeader 메서드에 전달합니다. ParseHeader의 cbOffsetWithinHeader 매개 변수에서 30 바이트 오프셋을 지정합니다. ParseHeader 메서드는 Header 개체에 포함된 다양한 ASF 개체에서 수집된 정보를 사용하여 ContentInfo 개체를 초기화합니다.
// Read the ASF Header Object from a byte stream and return a pointer to the
// populated ASF ContentInfo object.
//
// The current read position of the byte stream must be at the start of the
// ASF Header Object.
HRESULT CreateContentInfo(IMFByteStream *pStream,
IMFASFContentInfo **ppContentInfo)
{
const DWORD MIN_ASF_HEADER_SIZE = 30;
QWORD cbHeader = 0;
DWORD cbBuffer = 0;
IMFASFContentInfo *pContentInfo = NULL;
IMFMediaBuffer *pBuffer = NULL;
// Create the ASF ContentInfo object.
HRESULT hr = MFCreateASFContentInfo(&pContentInfo);
// Read the first 30 bytes to find the total header size.
if (SUCCEEDED(hr))
{
hr = MFCreateMemoryBuffer(MIN_ASF_HEADER_SIZE, &pBuffer);
}
if (SUCCEEDED(hr))
{
hr = ReadFromByteStream(pStream, pBuffer,MIN_ASF_HEADER_SIZE);
}
if (SUCCEEDED(hr))
{
hr = pContentInfo->GetHeaderSize(pBuffer, &cbHeader);
}
// Pass the first 30 bytes to the ContentInfo object.
if (SUCCEEDED(hr))
{
hr = pContentInfo->ParseHeader(pBuffer, 0);
}
SafeRelease(&pBuffer);
if (SUCCEEDED(hr))
{
cbBuffer = (DWORD)(cbHeader - MIN_ASF_HEADER_SIZE);
hr = MFCreateMemoryBuffer(cbBuffer, &pBuffer);
}
// Read the rest of the header and finish parsing the header.
if (SUCCEEDED(hr))
{
hr = ReadFromByteStream(pStream, pBuffer, cbBuffer);
}
if (SUCCEEDED(hr))
{
hr = pContentInfo->ParseHeader(pBuffer, MIN_ASF_HEADER_SIZE);
}
if (SUCCEEDED(hr))
{
// Return the pointer to the caller.
*ppContentInfo = pContentInfo;
(*ppContentInfo)->AddRef();
}
SafeRelease(&pBuffer);
SafeRelease(&pContentInfo);
return hr;
}
이 함수는 함수를 ReadFromByteStream
사용하여 바이트 스트림에서 미디어 버퍼로 읽습니다.
// Read data from a byte stream into a media buffer.
//
// This function reads a maximum of cbMax bytes, or up to the size size of the
// buffer, whichever is smaller. If the end of the byte stream is reached, the
// actual amount of data read might be less than either of these values.
//
// To find out how much data was read, call IMFMediaBuffer::GetCurrentLength.
HRESULT ReadFromByteStream(
IMFByteStream *pStream, // Pointer to the byte stream.
IMFMediaBuffer *pBuffer, // Pointer to the media buffer.
DWORD cbMax // Maximum amount to read.
)
{
DWORD cbBufferMax = 0;
DWORD cbRead = 0;
BYTE *pData= NULL;
HRESULT hr = pBuffer->Lock(&pData, &cbBufferMax, NULL);
// Do not exceed the maximum size of the buffer.
if (SUCCEEDED(hr))
{
if (cbMax > cbBufferMax)
{
cbMax = cbBufferMax;
}
// Read up to cbMax bytes.
hr = pStream->Read(pData, cbMax, &cbRead);
}
// Update the size of the valid data in the buffer.
if (SUCCEEDED(hr))
{
hr = pBuffer->SetCurrentLength(cbRead);
}
if (pData)
{
pBuffer->Unlock();
}
return hr;
}
4. ASF 분할기 만들기
다음으로 ASF Splitter 개체를 만듭니다. ASF 분할기를 사용하여 ASF 파일에 대한 패킷화된 미디어 데이터가 포함된 ASF 데이터 개체를 구문 분석합니다.
ASF 파일에 대한 분할자 개체를 만들려면 다음을 수행합니다.
- MFCreateASFSplitter 함수를 호출하여 ASF 분할기를 만듭니다. 함수는 IMFASFSplitter 인터페이스에 대한 포인터를 반환합니다.
- IMFASFSplitter::Initialize를 호출하여 ASF 분할기를 초기화합니다. 이 메서드는 프로시저 3에서 만든 ContentInfo 개체에 대한 포인터를 사용합니다.
// Create and initialize the ASF splitter.
HRESULT CreateASFSplitter (IMFASFContentInfo* pContentInfo,
IMFASFSplitter** ppSplitter)
{
IMFASFSplitter *pSplitter = NULL;
// Create the splitter object.
HRESULT hr = MFCreateASFSplitter(&pSplitter);
// Initialize the splitter to work with specific ASF data.
if (SUCCEEDED(hr))
{
hr = pSplitter->Initialize(pContentInfo);
}
if (SUCCEEDED(hr))
{
// Return the object to the caller.
*ppSplitter = pSplitter;
(*ppSplitter)->AddRef();
}
SafeRelease(&pSplitter);
return hr;
}
5. 구문 분석용 스트림 선택
다음으로, ASF 파일의 스트림을 열거하고 구문 분석할 첫 번째 비디오 스트림을 선택합니다. 스트림을 열거하려면 ASF 프로필 개체를 사용하고 비디오 미디어 유형이 있는 스트림을 검색합니다.
비디오 스트림을 선택하려면 다음을 수행합니다.
- ContentInfo 개체에서 IMFASFContentInfo::GetProfile 을 호출하여 ASF 프로필을 만듭니다. 다른 정보 중에서 프로필은 ASF 파일의 스트림을 설명합니다.
- IMFASFProfile::GetStreamCount를 호출하여 ASF 파일의 스트림 수를 가져옵니다.
- 루프에서 IMFASFProfile::GetStream 을 호출하여 스트림을 열거합니다. 메서드는 IMFASFStreamConfig 인터페이스에 대한 포인터를 반환합니다. 스트림 식별자도 반환합니다.
- IMFASFStreamConfig::GetStreamType을 호출하여 스트림의 주요 형식 GUID를 가져옵니다. 주 형식 GUID가 MFMediaType_Video 경우 스트림에 비디오가 포함됩니다.
- 4단계에서 비디오 스트림을 찾은 경우 IMFASFSplitter::SelectStreams 를 호출하여 스트림을 선택합니다. 이 메서드는 스트림 식별자 배열을 사용합니다. 이 자습서에서는 애플리케이션이 단일 스트림을 구문 분석하므로 배열 크기는 1입니다.
다음 예제 코드는 ASF 파일의 스트림을 열거하고 ASF 분할기에서 첫 번째 비디오 스트림을 선택합니다.
// Select the first video stream for parsing with the ASF splitter.
HRESULT SelectVideoStream(IMFASFContentInfo *pContentInfo,
IMFASFSplitter *pSplitter, BOOL *pbHasVideo)
{
DWORD cStreams = 0;
WORD wStreamID = 0;
IMFASFProfile *pProfile = NULL;
IMFASFStreamConfig *pStream = NULL;
// Get the ASF profile from the ContentInfo object.
HRESULT hr = pContentInfo->GetProfile(&pProfile);
// Loop through all of the streams in the profile.
if (SUCCEEDED(hr))
{
hr = pProfile->GetStreamCount(&cStreams);
}
if (SUCCEEDED(hr))
{
for (DWORD i = 0; i < cStreams; i++)
{
GUID streamType = GUID_NULL;
// Get the stream type and stream identifier.
hr = pProfile->GetStream(i, &wStreamID, &pStream);
if (FAILED(hr))
{
break;
}
hr = pStream->GetStreamType(&streamType);
if (FAILED(hr))
{
break;
}
if (streamType == MFMediaType_Video)
{
*pbHasVideo = TRUE;
break;
}
SafeRelease(&pStream);
}
}
// Select the video stream, if found.
if (SUCCEEDED(hr))
{
if (*pbHasVideo)
{
// SelectStreams takes an array of stream identifiers.
hr = pSplitter->SelectStreams(&wStreamID, 1);
}
}
SafeRelease(&pStream);
SafeRelease(&pProfile);
return hr;
}
6. 압축된 미디어 샘플 생성
다음으로 ASF 분할기를 사용하여 ASF 데이터 개체를 구문 분석하고 선택한 비디오 스트림에 대한 데이터 패킷을 가져옵니다. 애플리케이션은 고정 크기 블록의 ASF 파일에서 데이터를 읽고 데이터를 ASF 분할기로 전달합니다. 분할은 데이터를 구문 분석하고 압축된 비디오 데이터가 포함된 미디어 샘플을 생성합니다. 애플리케이션은 각 샘플이 키 프레임을 나타내는지 여부를 확인합니다. 이 경우 애플리케이션은 샘플에 대한 몇 가지 기본 정보를 표시합니다.
- 미디어 버퍼 수
- 데이터의 총 크기
- 타임스탬프
압축된 미디어 샘플을 생성하려면 다음을 수행합니다.
- 새 미디어 버퍼를 할당합니다.
- 바이트 스트림에서 미디어 버퍼로 데이터를 읽습니다.
- 미디어 버퍼를 IMFASFSplitter::P arseData 메서드에 전달합니다 . 메서드는 버퍼의 ASF 데이터를 구문 분석합니다.
- 루프에서 IMFASFSplitter::GetNextSample을 호출하여 분할자에서 미디어 샘플을 가져옵니다. ppISample 매개 변수가 유효한 IMFSample 포인터를 수신하는 경우 ASF 분할기에서 하나 이상의 데이터 패킷을 구문 분석했음을 의미합니다. ppISample이 NULL 값을 받으면 루프에서 중단하고 1단계로 돌아갑니다.
- 샘플에 대한 정보를 표시합니다.
- 다음 조건에서 루프에서 중단합니다.
- ppISample 매개 변수는 NULL 값을 받습니다.
- pdwStatusFlags 매개 변수는 ASF_STATUSFLAGS_INCOMPLETE 플래그를 받지 않습니다.
파일의 끝에 도달할 때까지 이 단계를 반복합니다. 다음은 이러한 단계를 보여 주는 코드입니다.
// Parse the video stream and display information about the video samples.
//
// The current read position of the byte stream must be at the start of the ASF
// Data Object.
HRESULT DisplayKeyFrames(IMFByteStream *pStream, IMFASFSplitter *pSplitter)
{
const DWORD cbReadSize = 2048; // Read size (arbitrary value)
IMFMediaBuffer *pBuffer = NULL;
IMFSample *pSample = NULL;
HRESULT hr = S_OK;
while (SUCCEEDED(hr))
{
// The parser must get a newly allocated buffer each time.
hr = MFCreateMemoryBuffer(cbReadSize, &pBuffer);
if (FAILED(hr))
{
break;
}
// Read data into the buffer.
hr = ReadFromByteStream(pStream, pBuffer, cbReadSize);
if (FAILED(hr))
{
break;
}
// Get the amound of data that was read.
DWORD cbData;
hr = pBuffer->GetCurrentLength(&cbData);
if (FAILED(hr))
{
break;
}
if (cbData == 0)
{
break; // End of file.
}
// Send the data to the ASF splitter.
hr = pSplitter->ParseData(pBuffer, 0, 0);
SafeRelease(&pBuffer);
if (FAILED(hr))
{
break;
}
// Pull samples from the splitter.
DWORD parsingStatus = 0;
do
{
WORD streamID;
hr = pSplitter->GetNextSample(&parsingStatus, &streamID, &pSample);
if (FAILED(hr))
{
break;
}
if (pSample == NULL)
{
// No samples yet. Parse more data.
break;
}
if (IsRandomAccessPoint(pSample))
{
DisplayKeyFrame(pSample);
}
SafeRelease(&pSample);
} while (parsingStatus & ASF_STATUSFLAGS_INCOMPLETE);
}
SafeRelease(&pSample);
SafeRelease(&pBuffer);
return hr;
}
IsKeyFrame 함수는 MFSampleExtension_CleanPoint 특성의 값을 가져오면 샘플이 키 프레임인지 여부를 테스트합니다.
inline BOOL IsRandomAccessPoint(IMFSample *pSample)
{
// Check for the "clean point" attribute. Default to FALSE.
return MFGetAttributeUINT32(pSample, MFSampleExtension_CleanPoint, FALSE);
}
설명을 위해 이 자습서에서는 다음 함수를 호출하여 각 비디오 키 프레임에 대한 몇 가지 정보를 표시합니다.
void DisplayKeyFrame(IMFSample *pSample)
{
DWORD cBuffers = 0; // Buffer count
DWORD cbTotalLength = 0; // Buffer length
MFTIME hnsTime = 0; // Time stamp
// Print various information about the key frame.
if (SUCCEEDED(pSample->GetBufferCount(&cBuffers)))
{
wprintf_s(L"Buffer count: %d\n", cBuffers);
}
if (SUCCEEDED(pSample->GetTotalLength(&cbTotalLength)))
{
wprintf_s(L"Length: %d bytes\n", cbTotalLength);
}
if (SUCCEEDED(pSample->GetSampleTime(&hnsTime)))
{
// Convert the time stamp to seconds.
double sec = static_cast<double>(hnsTime / 10000) / 1000;
wprintf_s(L"Time stamp: %f sec.\n", sec);
}
wprintf_s(L"\n");
}
일반적인 애플리케이션은 디코딩, 리믹스, 네트워크를 통해 전송 또는 기타 작업에 데이터 패킷을 사용합니다.
7. Entry-Point 함수 작성
이제 이전 단계를 전체 애플리케이션에 함께 배치할 수 있습니다. Media Foundation 개체를 사용하기 전에 MFStartup을 호출하여 Media Foundation 플랫폼을 초기화합니다. 완료되면 MFShutdown을 호출합니다. 자세한 내용은 Media Foundation 초기화를 참조하세요.
int wmain(int argc, WCHAR* argv[])
{
if (argc != 2)
{
_s(L"Usage: %s input.wmv");
return 0;
}
// Start the Media Foundation platform.
HRESULT hr = MFStartup(MF_VERSION);
if (SUCCEEDED(hr))
{
PCWSTR pszFileName = argv[1];
BOOL bHasVideo = FALSE;
IMFByteStream *pStream = NULL;
IMFASFContentInfo *pContentInfo = NULL;
IMFASFSplitter *pSplitter = NULL;
// Open the file.
hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST,
MF_FILEFLAGS_NONE, pszFileName, &pStream);
// Read the ASF header.
if (SUCCEEDED(hr))
{
hr = CreateContentInfo(pStream, &pContentInfo);
}
// Create the ASF splitter.
if (SUCCEEDED(hr))
{
hr = CreateASFSplitter(pContentInfo, &pSplitter);
}
// Select the first video stream.
if (SUCCEEDED(hr))
{
hr = SelectVideoStream(pContentInfo, pSplitter, &bHasVideo);
}
// Parse the ASF file.
if (SUCCEEDED(hr))
{
if (bHasVideo)
{
hr = DisplayKeyFrames(pStream, pSplitter);
}
else
{
wprintf_s(L"No video stream.\n");
}
}
SafeRelease(&pSplitter);
SafeRelease(&pContentInfo);
SafeRelease(&pStream);
// Shut down the Media Foundation platform.
MFShutdown();
}
if (FAILED(hr))
{
wprintf_s(L"Error: 0x%X\n", hr);
}
return 0;
}
프로그램 목록
다음 코드는 자습서의 전체 목록을 보여 줍니다.
#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>
#pragma comment(lib, "mfplat")
#pragma comment(lib, "mf")
#pragma comment(lib, "mfuuid")
template <class T> void SafeRelease(T **ppT)
{
if (*ppT)
{
(*ppT)->Release();
*ppT = NULL;
}
}
// Read data from a byte stream into a media buffer.
//
// This function reads a maximum of cbMax bytes, or up to the size size of the
// buffer, whichever is smaller. If the end of the byte stream is reached, the
// actual amount of data read might be less than either of these values.
//
// To find out how much data was read, call IMFMediaBuffer::GetCurrentLength.
HRESULT ReadFromByteStream(
IMFByteStream *pStream, // Pointer to the byte stream.
IMFMediaBuffer *pBuffer, // Pointer to the media buffer.
DWORD cbMax // Maximum amount to read.
)
{
DWORD cbBufferMax = 0;
DWORD cbRead = 0;
BYTE *pData= NULL;
HRESULT hr = pBuffer->Lock(&pData, &cbBufferMax, NULL);
// Do not exceed the maximum size of the buffer.
if (SUCCEEDED(hr))
{
if (cbMax > cbBufferMax)
{
cbMax = cbBufferMax;
}
// Read up to cbMax bytes.
hr = pStream->Read(pData, cbMax, &cbRead);
}
// Update the size of the valid data in the buffer.
if (SUCCEEDED(hr))
{
hr = pBuffer->SetCurrentLength(cbRead);
}
if (pData)
{
pBuffer->Unlock();
}
return hr;
}
// Read the ASF Header Object from a byte stream and return a pointer to the
// populated ASF ContentInfo object.
//
// The current read position of the byte stream must be at the start of the
// ASF Header Object.
HRESULT CreateContentInfo(IMFByteStream *pStream,
IMFASFContentInfo **ppContentInfo)
{
const DWORD MIN_ASF_HEADER_SIZE = 30;
QWORD cbHeader = 0;
DWORD cbBuffer = 0;
IMFASFContentInfo *pContentInfo = NULL;
IMFMediaBuffer *pBuffer = NULL;
// Create the ASF ContentInfo object.
HRESULT hr = MFCreateASFContentInfo(&pContentInfo);
// Read the first 30 bytes to find the total header size.
if (SUCCEEDED(hr))
{
hr = MFCreateMemoryBuffer(MIN_ASF_HEADER_SIZE, &pBuffer);
}
if (SUCCEEDED(hr))
{
hr = ReadFromByteStream(pStream, pBuffer,MIN_ASF_HEADER_SIZE);
}
if (SUCCEEDED(hr))
{
hr = pContentInfo->GetHeaderSize(pBuffer, &cbHeader);
}
// Pass the first 30 bytes to the ContentInfo object.
if (SUCCEEDED(hr))
{
hr = pContentInfo->ParseHeader(pBuffer, 0);
}
SafeRelease(&pBuffer);
if (SUCCEEDED(hr))
{
cbBuffer = (DWORD)(cbHeader - MIN_ASF_HEADER_SIZE);
hr = MFCreateMemoryBuffer(cbBuffer, &pBuffer);
}
// Read the rest of the header and finish parsing the header.
if (SUCCEEDED(hr))
{
hr = ReadFromByteStream(pStream, pBuffer, cbBuffer);
}
if (SUCCEEDED(hr))
{
hr = pContentInfo->ParseHeader(pBuffer, MIN_ASF_HEADER_SIZE);
}
if (SUCCEEDED(hr))
{
// Return the pointer to the caller.
*ppContentInfo = pContentInfo;
(*ppContentInfo)->AddRef();
}
SafeRelease(&pBuffer);
SafeRelease(&pContentInfo);
return hr;
}
// Create and initialize the ASF splitter.
HRESULT CreateASFSplitter (IMFASFContentInfo* pContentInfo,
IMFASFSplitter** ppSplitter)
{
IMFASFSplitter *pSplitter = NULL;
// Create the splitter object.
HRESULT hr = MFCreateASFSplitter(&pSplitter);
// Initialize the splitter to work with specific ASF data.
if (SUCCEEDED(hr))
{
hr = pSplitter->Initialize(pContentInfo);
}
if (SUCCEEDED(hr))
{
// Return the object to the caller.
*ppSplitter = pSplitter;
(*ppSplitter)->AddRef();
}
SafeRelease(&pSplitter);
return hr;
}
// Select the first video stream for parsing with the ASF splitter.
HRESULT SelectVideoStream(IMFASFContentInfo *pContentInfo,
IMFASFSplitter *pSplitter, BOOL *pbHasVideo)
{
DWORD cStreams = 0;
WORD wStreamID = 0;
IMFASFProfile *pProfile = NULL;
IMFASFStreamConfig *pStream = NULL;
// Get the ASF profile from the ContentInfo object.
HRESULT hr = pContentInfo->GetProfile(&pProfile);
// Loop through all of the streams in the profile.
if (SUCCEEDED(hr))
{
hr = pProfile->GetStreamCount(&cStreams);
}
if (SUCCEEDED(hr))
{
for (DWORD i = 0; i < cStreams; i++)
{
GUID streamType = GUID_NULL;
// Get the stream type and stream identifier.
hr = pProfile->GetStream(i, &wStreamID, &pStream);
if (FAILED(hr))
{
break;
}
hr = pStream->GetStreamType(&streamType);
if (FAILED(hr))
{
break;
}
if (streamType == MFMediaType_Video)
{
*pbHasVideo = TRUE;
break;
}
SafeRelease(&pStream);
}
}
// Select the video stream, if found.
if (SUCCEEDED(hr))
{
if (*pbHasVideo)
{
// SelectStreams takes an array of stream identifiers.
hr = pSplitter->SelectStreams(&wStreamID, 1);
}
}
SafeRelease(&pStream);
SafeRelease(&pProfile);
return hr;
}
inline BOOL IsRandomAccessPoint(IMFSample *pSample)
{
// Check for the "clean point" attribute. Default to FALSE.
return MFGetAttributeUINT32(pSample, MFSampleExtension_CleanPoint, FALSE);
}
void DisplayKeyFrame(IMFSample *pSample)
{
DWORD cBuffers = 0; // Buffer count
DWORD cbTotalLength = 0; // Buffer length
MFTIME hnsTime = 0; // Time stamp
// Print various information about the key frame.
if (SUCCEEDED(pSample->GetBufferCount(&cBuffers)))
{
wprintf_s(L"Buffer count: %d\n", cBuffers);
}
if (SUCCEEDED(pSample->GetTotalLength(&cbTotalLength)))
{
wprintf_s(L"Length: %d bytes\n", cbTotalLength);
}
if (SUCCEEDED(pSample->GetSampleTime(&hnsTime)))
{
// Convert the time stamp to seconds.
double sec = static_cast<double>(hnsTime / 10000) / 1000;
wprintf_s(L"Time stamp: %f sec.\n", sec);
}
wprintf_s(L"\n");
}
// Parse the video stream and display information about the video samples.
//
// The current read position of the byte stream must be at the start of the ASF
// Data Object.
HRESULT DisplayKeyFrames(IMFByteStream *pStream, IMFASFSplitter *pSplitter)
{
const DWORD cbReadSize = 2048; // Read size (arbitrary value)
IMFMediaBuffer *pBuffer = NULL;
IMFSample *pSample = NULL;
HRESULT hr = S_OK;
while (SUCCEEDED(hr))
{
// The parser must get a newly allocated buffer each time.
hr = MFCreateMemoryBuffer(cbReadSize, &pBuffer);
if (FAILED(hr))
{
break;
}
// Read data into the buffer.
hr = ReadFromByteStream(pStream, pBuffer, cbReadSize);
if (FAILED(hr))
{
break;
}
// Get the amound of data that was read.
DWORD cbData;
hr = pBuffer->GetCurrentLength(&cbData);
if (FAILED(hr))
{
break;
}
if (cbData == 0)
{
break; // End of file.
}
// Send the data to the ASF splitter.
hr = pSplitter->ParseData(pBuffer, 0, 0);
SafeRelease(&pBuffer);
if (FAILED(hr))
{
break;
}
// Pull samples from the splitter.
DWORD parsingStatus = 0;
do
{
WORD streamID;
hr = pSplitter->GetNextSample(&parsingStatus, &streamID, &pSample);
if (FAILED(hr))
{
break;
}
if (pSample == NULL)
{
// No samples yet. Parse more data.
break;
}
if (IsRandomAccessPoint(pSample))
{
DisplayKeyFrame(pSample);
}
SafeRelease(&pSample);
} while (parsingStatus & ASF_STATUSFLAGS_INCOMPLETE);
}
SafeRelease(&pSample);
SafeRelease(&pBuffer);
return hr;
}
int wmain(int argc, WCHAR* argv[])
{
if (argc != 2)
{
_s(L"Usage: %s input.wmv");
return 0;
}
// Start the Media Foundation platform.
HRESULT hr = MFStartup(MF_VERSION);
if (SUCCEEDED(hr))
{
PCWSTR pszFileName = argv[1];
BOOL bHasVideo = FALSE;
IMFByteStream *pStream = NULL;
IMFASFContentInfo *pContentInfo = NULL;
IMFASFSplitter *pSplitter = NULL;
// Open the file.
hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST,
MF_FILEFLAGS_NONE, pszFileName, &pStream);
// Read the ASF header.
if (SUCCEEDED(hr))
{
hr = CreateContentInfo(pStream, &pContentInfo);
}
// Create the ASF splitter.
if (SUCCEEDED(hr))
{
hr = CreateASFSplitter(pContentInfo, &pSplitter);
}
// Select the first video stream.
if (SUCCEEDED(hr))
{
hr = SelectVideoStream(pContentInfo, pSplitter, &bHasVideo);
}
// Parse the ASF file.
if (SUCCEEDED(hr))
{
if (bHasVideo)
{
hr = DisplayKeyFrames(pStream, pSplitter);
}
else
{
wprintf_s(L"No video stream.\n");
}
}
SafeRelease(&pSplitter);
SafeRelease(&pContentInfo);
SafeRelease(&pStream);
// Shut down the Media Foundation platform.
MFShutdown();
}
if (FAILED(hr))
{
wprintf_s(L"Error: 0x%X\n", hr);
}
return 0;
}
관련 항목