读取 WMV ASF, WMA (Windows Media) 文件






4.52/5 (12投票s)
2007 年 5 月 8 日
5分钟阅读

116948

3780
本文介绍了使用 Windows Media SDK 读取 WMV、WMA、ASF 文件的基础知识。
引言
我当时正在做一个项目,需要从 WMV 文件中读取视频帧。起初,我在 CodeProject 和其他网站上搜索相关文章,但一无所获。于是,我开始学习 Windows Media SDK 的帮助文档,并利用 Google 讨论组来解决遇到的问题。
本文介绍了从 Windows Media 文件读取的基础知识。我将添加一个类,用于从文件中读取 WMV 视频帧。
尽管我专注于读取视频帧,但本文所述的步骤对于读取音频缓冲区也基本相同。
注意:Windows Media SDK 需要对 COM 接口有基本了解。
基本准备
您应该从 Microsoft 网站下载 Windows Media SDK 文件。我使用的是 9.5 版本。
非常重要:请按照说明设置环境以运行 WM SDK。
与任何使用 COM 的程序一样,您需要记住调用 CoIntialize(NULL);
。这会实例化基本的 IUnknown
接口,并允许使用 COM 对象。
现在我将描述打开 WMV 文件所采取的步骤。
打开文件
步骤 1:创建读取器接口
有两个读取器对象:AsyncronusReader
和 SyncronusReader
。AsyncronusReader
使用回调函数接收下一帧的信息。我更喜欢使用 SyncReader
,因为它更容易理解和实现。
//Step 1: Create the Reader Object
IWMSyncReader* m_ISyncReader;
hr = WMCreateSyncReader(NULL,0,&m_ISyncReader);
步骤 2:打开文件
现在我们有了读取器对象,就可以调用 Open
函数打开一个文件进行读取。文件名应该是 w_char
类型,所以您可以使用 CString
对象并调用 AllocSysString()
方法。
//Step 2: Open the file
hr = m_ISyncReader->Open(m_filename.AllocSysString());
步骤 3:接收输出和流编号
每个 WMV 文件都有多个流(流是压缩的音频或视频)。输出是未压缩的数据,从文件中读取。输出编号从 0 开始,流编号从 1 开始。如果我们有一个输出编号,就可以得到它的流编号。所以,基本上,我们读取输出(它指的是存储在文件中的压缩流)。注意:输出也可以称为输出流。
这部分步骤较多,我将将其分解为几个部分。
3.1 获取文件中的输出数量
//3.1 get the number of outputs
DWORD m_theOutputCount;
m_ISyncReader->GetOutputCount(&m_theOutputsCount);
3.2 识别音频流和视频流
我们使用 IWMOutputMediaProps
接口来接收输出流属性。我们还使用 WM_MEDIA_TYPE
结构。
首先,我们通过调用 m_ISyncReader.GetOutputProps(outputNum,&IVideoOutputProps)
函数来获取输出属性,该函数为输出编号返回 IVideoOutputProps
。然后,我们需要获取详细信息。这通过两次调用 m_IVideoOutputProps->GetMediaType()
函数来完成,第一次是为了获取分配 WM_MEDIA_TYPE
所需的大小,第二次是实际获取信息。
DWORD theSize;
m_ISyncReader->GetOutputProps(i,&m_IVideoOutputProps);
m_IVideoOutputProps->GetMediaType(NULL,&theSize);
m_theMediaType = ( WM_MEDIA_TYPE* ) new BYTE[theSize ];
m_IVideoOutputProps->GetMediaType(m_theMediaType,&theSize);
现在我们可以检查这是音频流还是视频流。
音频流检查
if( WMMEDIATYPE_Audio == m_theMediaType->majortype)
视频流检查
if( WMMEDIATYPE_Video == m_theMediaType->majortype)
if(m_theMediaType->formattype == WMFORMAT_VideoInfo)
现在我们可以接收视频或音频输出的流编号。
m_ISyncReader->GetStreamNumberForOutput(OutputNumber,(WORD*)&StreamNumber);
另外,对于视频流,我们应该读取视频头。
WMVIDEOINFOHEADER m_theVideoInfoHeader;
memcpy(&m_theVideoInfoHeader,m_theMediaType->pbFormat,
sizeof(WMVIDEOINFOHEADER));
m_BitmapInfoHdr= m_theVideoInfoHeader.bmiHeader;
我们可以接收到更多关于视频的信息,例如:时长、片名等。不过,我们暂时搁置。
步骤 4:设置读取器以接收正确的样本持续时间
设置以接收正确的样本持续时间。为了确保同步读取器为视频流提供正确的样本持续时间,您必须先配置输出流。调用 IWMSyncReader::SetOutputSetting
方法来设置 g_wszVideoSampleDurations
,并将其设置为 TRUE。如果为 TRUE,读取器将提供准确的样本持续时间。
BYTE* pValue = new BYTE[5];
strcpy((char*)pValue,"TRUE");
hr = m_ISyncReader->SetOutputSetting(m_iVideoOutputNumber,
g_wszVideoSampleDurations,WMT_TYPE_BOOL,pValue,sizeof(pValue));
步骤 5:设置为接收未压缩样本
SetReadStreamSamples
方法指定是否会传递流中的样本(压缩或未压缩)。以下是如何设置为接收未压缩样本。
m_ISyncReader->SetReadStreamSamples(m_iVideoStreamNumber,FALSE);
从文件中读取样本
使用同步读取器读取样本非常简单。
我们调用 IWMSyncReader::GetNextSample()
函数。该函数填充一个 INSSBuffer 接口指针,您可以选择要从中读取的流,并获取下一个样本。您还可以获得其持续时间和在视频中的位置时间。
接收样本时,您应该检查它是否是 CLEANPOINT 样本。这个样本是您想要读取的图像。
当您到达视频末尾时,HRESULT
将会收到 NS_E_NO_MORE_SAMPLES
。
有关详细信息,请参见下面的代码片段。
QWORD cnsSampleTime = 0;
QWORD cnsSampleDuration = 0;
DWORD dwFlags = 0;
DWORD dwOutputNumber;
HRESULT hr = m_ISyncReader->GetNextSample(m_iVideoStreamNumber,
&m_pINSSBuffer,
&cnsSampleTime,
&cnsSampleDuration,
&dwFlags,
NULL,//&dwOutputNumber,
NULL);
if(hr== NS_E_NO_MORE_SAMPLES)
{
//finished reading the file
}
if(SUCCEEDED(hr))
{
if(dwFlags ==WM_SF_CLEANPOINT) //this a clean point frame, a picture to
//take read sdk for explantion
{
//1. Get the Bitmap from the frame
m_pINSSBuffer->GetBufferAndLength(&m_bitmapBuffer,
&m_dwrdBitmapBufferLength);
}
m_pINSSBuffer->Release();
m_pINSSBuffer = NULL;
}
我们在这里收到的缓冲区只是 DIB,即位图数据,没有标题(标题在早期阶段已收到)。
现在您可以将图片保存到磁盘,在窗口中显示帧,或分析帧用于其他目的。
读取其他文件属性
如前所述,您可以获取更多文件信息,例如其时长。这需要几个步骤。
为了接收关于文件的额外信息,如文件时长、标题、帧数等,我们执行以下操作。
注意:这些信息只有在创建文件时包含这些信息才存在。因此,即使您尝试获取某些信息(例如 numberOfFrames
),也可能无法获得。
为了接收信息,您需要创建两个对象。
IWMMetadataEditor *pEditor; IWMHeaderInfo3* pHdrInfo;
首先创建编辑器,然后就可以接收 HeaderInfo。
以下是接收文件时长的代码。
//step 6: Get wmv Duration (total time)
//6.1 create a MetaData Editor
IWMMetadataEditor *pEditor;
hr= WMCreateEditor(&pEditor);
if(hr==S_OK)
{
pEditor->Open(m_filename.AllocSysString());
//6.2 create a HeaderInfo interface.
IWMHeaderInfo3* pHdrInfo;
pHdrInfo = NULL;
hr = pEditor->QueryInterface(IID_IWMHeaderInfo3,(void**)&pHdrInfo);
WORD wStream =0;// for any stream;
WMT_ATTR_DATATYPE dType;
QWORD dwDuration;
WORD wSize =0;
//first Call for receiving the buffer size
hr = pHdrInfo->GetAttributeByName(
&wStream,L"Duration",&dType,(BYTE*)NULL,&wSize);
//know that we have the size we allocate the memory and read the attribute
BYTE* pValue;
if(wSize>0)
pValue = new BYTE[wSize];
hr = pHdrInfo->GetAttributeByName(&wStream,L"Duration",
&dType,pValue,&wSize);
dwDuration =*((QWORD*)pValue);
m_qwTotalTimeInSeconds = (dwDuration*100)/1000000000;
SAFE_ARRAYDELETE(pValue);
SAFE_RELEASE(pHdrInfo);
SAFE_RELEASE(pEditor);
}
请查阅 Windows Media SDK 以获取更多关于您可以接收的属性以及在 GetAttributeByName()
函数中应使用哪些名称的信息。
好了,就这些了。希望您觉得这篇文章有用。
历史
5 月 8 日 - 初次发布