DirectShow 媒体文件回放总结

DirectShow 媒体文件回放总结

MU小组文档

作者:Inkick

本文99%内容原创,转载请注明出处

 

1.概述

DirectShow中媒体文件回放的过程也就是一个为媒体文件选择相应所需的Filter、构建Filter Graph、并对Filter Graph的状态进行维持、控制的过程。这里所说的媒体文件,不仅仅是指音频、视频文件,同时也包括bmpjpeggif等图形图像格式以及midi等数字化音乐序列。

因此,使用DirectShow进行媒体文件的回放需要经过以下的步骤:

2.构建Filter Graph

Filter GraphFilter提供了一个容器,一个构建完整的Filter Graph也就是一个完整的Filter连路,这个连路对于程序是透明的,可控制的。而对于每一个媒体文件来说,Filter Graph与媒体文件存在着对应的关系。也就是说,一个Filter Graph只能实现一个(种)文件的回放。

DirectShow中,Filter Graph是由接口对象IGraphBuilder实现的,我们可以调用Win32 API函数CoCreateInstance()建立一个实体。Filter Graph实体建立之后并不具有任何的Filter,因此不具有任何实际用途。因此我们需要连接需要的Filter来完成FilterGraph的构建。

智能连接这个术语覆盖了一系列Filter Graph Manager用于构建所有或部份filter graph的算法。任何时候,当Filter Graph Manager需要添加filter来完成graph时,它大致做以下几件事情:

如果有一个filter存在于graph中,而且这个filter有至少一个没有连接的input pinFilter Graph Manager试着去试用这个filter

否则,Filter Graph Manager在已注册的filter中寻找连接时可以接受合适的媒体类型的filter。每一个filter都注册有一个Merit值,这个值用以标记哪个filter最容易被Filter Graph Manager选中来完成graphFilter Graph ManagerMerit值的顺序来选择filterMerit值越大,被选中的机会越大。对于每种流类型(如音频、视频、MIDI),默认的renderer具有一个很高的Merit值,解码器同样是,专用filter具有低Merit值。

如果Filter Graph Manager选择的filter不合适,它会返回来尝试另外的filter组合。

 

我们有三种构建graph的途径:

1.filter graph manager构建整个graph

2.filter graph manager构建部分graph

3.应用程序构建整个graph

2.1 RenderFile

IGraphBuilder提供了多种智能完成FilterGraph构建的方法。最简单的是使用接口方法IGraphBuilder::RenderFile

HRESULT RenderFile(LPCWSTR lpwstrFile, LPCWSTR lpwstrPlayList);

       第一个参数为文件的路径(祥见后文),第二个参数保留,必须为空。

这个方法需要一个表示媒体文件路径或者URLUnicode字符串参数。而我们通过界面获得的文件路径的字符串往往是ANSI字符串。我们可以使用下面方法进行转换:

包含头文件:

#include <tchar.h>

#include <atlbase.h>

这两个头文件包含了ANSI字符串与Unicode字符串相互转化的函数与宏

使用宏:USES_CONVERSION;

定义一个WCHAR的数组:WCHAR FileName[MAX_PATH];

MAX_PATH是在windef.h中定义的:#define MAX_PATH 260

这与windows路径最大字符为260个相符。这个数组保存转化后的Unicode形式表示的路径。然后可以使用下面的函数进行转换:(假定以ANSI形式给出的字符串为szFile

wcsncpy (FileName, T2W(szFile), NUMELMS(wFile)-1);

FileName[MAX_PATH-1] = 0;

 

wcsncpy的原型:

wchar_t *wcsncpy( wchar_t *strDest, const wchar_t *strSource, size_t count );

这个函数的作用类似于strcpy,是实现字符串之间的复制。只不过,这是一个用在Unicode上的版本。

第一个参数指定了字符串转化后的存放地址,也就是我们要得到的Unicode字符串,第二个参数指定了要转化的字符串的来源地址,也就是我们要转化的ANSI字符串。在第二个参数使用了宏T2W(szFile),这个宏可以把一个ANSI字符串转为一个WCHAR类型的字符串。第三个参数为转化字符串中字符的数量。

现在问题出来了,这个函数的第二个参数需要宽字符串的地址,如果我们有这样的一个地址,我们还转换什么?因此这个的关键在于T2W上面。让我们来看一下T2W的定义。这个定义在头文件ATLCONV.H里面(只保留我们比较感兴趣的部分)

 

#ifdef _UNICODE

 

inline LPWSTR T2W(LPTSTR lp) { return lp; }

inline LPTSTR W2T(LPWSTR lp) { return lp; }

 

#else

#define T2W A2W

 

#endif

 

我们可以看到,如果定义了_UNICODE,则T2W直接返回将要转化的字符串,这是因为在Unicode环境下,ANSI字符拥有和Unicode字符同样的宽度。也就是说,ANSIUnicode的一个子集。但是在非Unicode环境下,就将T2W替换成A2W,这样我们返回来看A2W的定义(这个定义在同样的头文件中):

#define A2W(lpa) (/

((LPCSTR)lpa == NULL) ? NULL : (/

_convert = (lstrlenA(lpa)+1),/

ATLA2WHELPER((LPWSTR) alloca(_convert*2), lpa, _convert)))

宏定义比较晦涩,我们转换成比较好理解的函数形式:

LPCSTR A2W(LPTSTR lpa)

{

       if(lpa == NULL)

       {

              return NULL;

       }

       _convert = (lstrlen(A)(lpa)+1);

       AtlA2WHelper ((LPWSTR)alloca(_covert*2),lpa,_convert);

       return lpa;

}

这个函数的结构比较清晰,首先判断是不是空字符串,如果是空字符串就返回空,因为ANSIUnicode意义上的空字符串都是NULL,如果不为空,则开始转换。重点是函数 ATLA2WWHELPER(),这个函数的实现部分在ATLCONV.CPP中:

LPWSTR WINAPI AtlA2WHelper(LPWSTR lpw, LPCSTR lpa, int nChars)

{

_ASSERTE(lpa != NULL);

_ASSERTE(lpw != NULL);

lpw[0] = '/0';

MultiByteToWideChar(CP_ACP, 0, lpa, -1, lpw, nChars);

return lpw;

}

这个函数除了做一些必要的安全性判定以及前序准备以外,核心工作是调用了一个函数MultiByteToWideChar,因此我们还要继续深入。这次我们发现我们找不到源代码了,但是在MSDN中我们可以得到明确的提示:

int MultiByteToWideChar(

  UINT CodePage,               // code page

  DWORD dwFlags,             // character-type options

  LPCSTR lpMultiByteStr,          // address of string to map

  int cchMultiByte,                 // number of bytes in string

  LPWSTR lpWideCharStr,        // address of wide-character buffer

  int cchWideChar                // size of buffer

);

这个API函数接受六个参数(Win32 API的风格——参数超多),第一个参数指定了CodePage(解释见附录5.1),在这里我们可以指定CP_ACP,来表示我们选择ANSI Code Page代表我们要转换的源字符串编码形式为ANSI,第二个参数是一组位标志,决定了如何处理原字符串中的控制字符或者无效字符。一般指定MB_PRECOMPOSED,第三个参数是源字符串的地址(或者指针)。第四个字参数是要转化的字符串里面包含了多少个字符,第五个参数指定了转换后的字符串的存放地址,第六个参数指定了转换后字符串的Buffer的大小,也就是转换后占用内存空间的多少。

 

现在我们逐层返回,(如果你已经忘记了我们的初衷是什么建议你听一下F.I.R的歌),根据最核心的函数MultiByteToWideChar的分析,我们可以得出AtlA2Whelper几个参数的含义:

AtlA2WHelper(       LPWSTR lpw,              //转化的目标字符串的地址

                           LPCSTR lpa,         //转化的源字符串的地址

int nChars              //源字符串中包含的字符数

)

因此在判断lpw以及lpa不为空之后便直接调用:

lpw[0] = '/0';

MultiByteToWideChar(CP_ACP, 0, lpa, -1, lpw, nChars);

 

我们已经返回到了那个宏,还是让我们来看我们改写的那个函数吧:

_convert = (lstrlen(A)(lpa)+1);

       AtlA2WHelper ((LPWSTR)alloca(_covert*2),lpa,_convert);

       return lpa;

 

lstrlen函数得到参数字符串的长度(对于ANSI字符串来说是字节数,对于Unicode字符串来说是字符数),后面的(A)表明参数字符串为ANSI字符串,经过这个参数的调用,_convert的值变成了要转换的字符串长度加一。为什么要加一呢?因为字符串的结尾要补上一个’/0’,我们总要为这个’/0’预留空间。

下一步就是要申请空间来保存转化完成的字符串了,使用(LPWSTR)alloca(_covert*2),分配字符串数的两倍空间(因为Unicode字符占用的空间是ANSI的两倍),然后将地址转为LPWSTR,当作参数传递给AtlA2Whelper,开始转换,任务完成!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值