65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.52/5 (12投票s)

2007 年 5 月 8 日

5分钟阅读

viewsIcon

116948

downloadIcon

3780

本文介绍了使用 Windows Media SDK 读取 WMV、WMA、ASF 文件的基础知识。

Screenshot - Article.gif

引言

我当时正在做一个项目,需要从 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:创建读取器接口

有两个读取器对象:AsyncronusReaderSyncronusReaderAsyncronusReader 使用回调函数接收下一帧的信息。我更喜欢使用 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 日 - 初次发布

© . All rights reserved.