
MSDN翻译系列
作者:黄丁杰
说明:如果有任何问题请联系din-je@163.com。是本人业余翻译的如果有错误请直接发到我邮箱我会立刻改正,directshow的其他文章我会在以后不断翻译出来。如果觉得好请帮我作作宣传。欢迎转载,但是禁止修改。
Directshow起步。
本章主要介绍diirectshow在编写程序时所需的基本概念。在本章你可以总揽下directshow技术。你必须在总体上对本章的编程和媒体有所了解。
本章将介绍
。设置编译环境
。介绍directshow应用程序编程
。如何播放一个文件
设置编译环境:
你可以编译工程在命令行模式下或者在vs集成环境中
头文件
所有的directshow应用程序都使用下面的头文件。
Dshow。H
部分directshow接口需要附加的头文件。这些文件会在接口的注意中给出。
所需的库文件
directshow需要如下的库文件
strmiidl。Lib 该库包含CLSIDs和IIDs的接口。所有的directshow都需要该库。
Quartz。Lib 该库包含AMGetErrorText函数。如果你没有调用该函数,该库没有用。
记住在你的编译环境中必须把directx SDK 的头文件库和连接库的目录作为vs优先搜索的路径。这样可以保证编译的时候用的是最新版本的文件。
介绍directshow应用程序编程
本章介绍编写一个directshow程序的基本技术和概念,读完本章你就可以写个你自己的directshow的应用程序了。
滤镜和图像滤镜
directshow是由被称作滤镜的东西构成的。滤镜是一个软件包用来执行对多媒体流的一些操作。比如directshow的滤镜可以
读出一个文件
从视频捕捉设备获取视频流
解码各种各样的多媒体格式,比如MPEG-1S视频。
传输视频和音频到屏幕和声卡。
滤镜几乎完成视频播放所需要的从输入到输出的所有功能。比如一个MPEG-1的滤镜。它可以输入MPEG码流然后输出图像帧用于后续显示
在directshow程序中只要执行把滤镜连接在一起就可以了。就是把一个滤镜的输入和另外一个滤镜的输出连接在一起就可以了。一组连接在一起的滤镜叫做图像滤镜。比如下面的图示就是一个播放AVI文件的图像滤镜的例子
正如上面图示的,文件滤镜把AVI文件从硬盘读入。AVI分离滤镜分离文件流为两个数据流。一个是视频压缩流,一个是硬盘流。AVI解压滤镜用于解压视频流生成视频帧。视频显示滤镜用于把视频帧用directdraw或者GDI显示到屏幕上。音频留输入到默认声音设备滤镜把声音通过directsound播放出来。
因此应用程序就不用管理任何数据流。代替的,滤镜被叫做高端组件叫做图像滤镜的进行控制。程序只要创建一些高级API。就像RUN(用于在图像控件中移动数据)或者STOP(用于停止数据流)。但是如果你要求更多的数据流控制,那你也可以直接存取控制的COM接口。图像滤镜也会反馈一定的事件消息给应用程序。
图像滤镜可以服务另外的目的。提供给应用程序创建图像滤镜的方法,还有把各个滤镜连接在一起的方法。(directshow同时也提供一些帮助的组件可以简单的处理。这些全部都有在文档中描述。
编写一个directshow程序
总的来说,写个directshow程序必须进行这三个步骤。就像下面的图例。
1.创建个图像滤镜管理的实例
2.然后用图像理解管理实例建立一个图像滤镜。外部滤镜间依赖于这个图像滤镜。
3.应用程序应用图像滤镜控制图像滤镜和数字流在滤镜中的流动。通过这些处理。应用程序也应该回应滤镜管理事件。
当处理结束后。应用程序释放图像滤镜管理器和所有的滤镜。因为directshow是基于COM的,因此图像滤镜管理和滤镜都是COM对象。因此你必须对COM客户端编程有所了解。具体的可以在DirectX SDK中的一篇文章叫做《Using COM》中有很好的介绍COM对象的资料。而且是有很多COM编程的书籍可以参考。
如何播放一个文件
本文章是想让你喜欢上Directshwo编程。我们要创建一个简单的命令行下的程序用于播放音频或者视频文件,这个程序实在是太简单了,可是它所完成的功能却是非常强大。这个程序只要短短的几行的代码就可以搞定了。可是它所完成的功能却是以前只有专门公司才能开发出来的软件所能实现的。它可以用来演示播放任何的多媒体文件。用它你可以立刻喜欢上directshow的编程。
就像上面的文章说描述的那样,一个directshow应用程序总是有三个基本的步骤。
1.建立个图像滤镜管理器的实例
2.用图像滤镜管理建立一个图像滤镜
3.给它运行的消息,让数据流在各个滤镜中移动。
好了,我们开始编写我们直接的多媒体演示程序。
首先我们必须调用CoInitialize来初始化COM库。
(记住directshow在编程的时候全部都没有传统编写COM的影子,因此初学者很容易被它的假象所欺骗,以为directshow根本就不是COM的,或者认为COM就是那么简单。其实directshow就有点象MFC,通过c+++的封装技术和其他大量的技术把COM技术的很多细节都隐藏其来。其实在背后微软已经为我们写了很多很多的代码。我们应该感谢微软为我们作的一切,但是我们也应该佩服微软。居然可以把代码的复用技术发挥到如此的境界。这些是我们国内软件企业所要学习的地方。废话了)
HRESULT hr = CoInitialize(NULL);
if (FAILED(hr))
{
// Add error-handling code here. (Omitted for clarity.)
}
为了保持程序的简单,这个例子忽略了返回值。但是你必须在你自己编写的代码中始终检查从任何方法中返回的HERSULT值。这个是很重要的因为他可能是空值。那在后续的调用的时候将产生很多莫名其妙的错误。问题严重哦。
接下来,我们调用CoCreateInstance用来建立滤镜管理器
IGraphBuilder *pGraph;
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL,
CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph);
如上所示,传入的第一个参数类的标识是CLSID_FilterGraph。按照字面的意思就是用于创建图像滤镜。返回我们就得到了一个HERSULT了,这个是指向滤镜的一个句柄。这个东西就是一个图像滤镜了,它将来要用到所有其他的后续处理之中。还有那个执行描述参数传入的是CLSCTX_INPROC_SERVER。Directshow支持释放线程模型。所以你可以调用CoinitializeEx函数用COINIT_MULTITHREADED参数。
该函数还返回了个IgraphBuilder接口。该接口包含了大多数的用于构建图像滤镜的方法。还用两个方法被用于这个例子。
。ImediaControl接口,该接口用于控制数据流。可以用来停止和启动数据流。
。ImediaEvent接口,该接口用于获取图像滤镜的的事件。在这个例子中我们用来等待播放结束。
这两个滤镜都是从图像滤镜引出来的。我们可以用刚才返回的IgraphBuilder来得到他们。下面是范例。
IMediaControl *pControl;
IMediaEvent *pEvent;
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
接下来你就可以建立个图像滤镜。用来播放文件。可是这也是很简单的调用
hr = pGraph->RenderFile(L"C:\\\\Example.avi", NULL);
IgrapBuilder::RenderFile方法可以用来播放特定的文件。第一次参数就是文件名,不过必须是UNICODE的。第二个参数是用来保留的,现在只要填NULL就可以了。
当然了,如果输入的文件名有错误,或者是根本就不认识的格式,它就会返回错误。这个时候就应该通过一些错误的代码来报告错误。因为这个例子只是简单的例子,因此没有这些代码,我们始终假设文件是存在的,并且格式是可以被解码的。要是代用成功图像滤镜就会准备播放了。现在我们可以调用ImediaControl::Run函数让他运行。
hr = pControl->Run();
一旦调用成功,数据就开始在滤镜中移动。并且将其播放出来。播放是在另外的线程后台进行的。我们只要等待它播放结束就可以了。我们可以调用ImediaEvent::WaitForCompletion还等待播放结束。
long evCode = 0;
pEvent->WaitForCompletion(INFINITE, &evCode);
该方法会一直等待知道播放解说或者播放出错返回。那个参数INFINITE是告诉该函数,我要等待直到播放结束才返回。不过还有其他的参数,可以方便的使用可以参考Responding to Events这篇文章。
当应用程序结束就可以释放这些接口的指针用于关闭.COM库
pControl->Release();
pEvent->Release();
pGraph->Release();
CoUninitialize();
下面是完整的代码,真的很少。
#include void main(void) { IGraphBuilder *pGraph = NULL; IMediaControl *pControl = NULL; IMediaEvent *pEvent = NULL; // Initialize the COM library. HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { printf("ERROR - Could not initialize COM library"); return; } // Create the filter graph manager and query for interfaces. hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph); if (FAILED(hr)) { printf("ERROR - Could not create the Filter Graph Manager."); return; } hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl); hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent); // Build the graph. IMPORTANT: Change this string to a file on your system. hr = pGraph->RenderFile(L"C:\\\\Example.avi", NULL); if (SUCCEEDED(hr)) { // Run the graph. hr = pControl->Run(); if (SUCCEEDED(hr)) { // Wait for completion. long evCode; pEvent->WaitForCompletion(INFINITE, &evCode); // Note: Do not use INFINITE in a real application, because it // can block indefinitely. } } pControl->Release(); pEvent->Release(); pGraph->Release(); CoUninitialize(); } 关于directshow 本章将介绍directshow的整体架构。 。directshow系统介绍 。图像滤镜及其组件 。图像滤镜的流量控制 。directshow事件响应 。directshow的时序 。构建动态图像 。插件发行。 Directshow系统介绍 多媒体的挑战 编写多媒体应用会有如下的挑战: 。多媒体流通常包含大量的数据,必须非常快的被处理 。音频和视频必须同步,也就是说必须同时开始或停止,而且必须被以同样的速率播放出来。 。数据可能来自各种数据源,包括文件,网络,电视广播和摄像头。 。数据流可能包含各种不同的格式,比如AVI,ASF,MPEG和DV 。程序编写者可能并不知道最用用户的硬件是否拥有各种硬件的高级功能。这些功能对流畅的播放是很有帮助的。 在以往的多媒体播放软件中这些问题通常是很难解决的。现在有了directshow就不一样了。 Directshow的解决方案 Directshow被设计用来解决以上问题。这意味着设计一个windows下的多媒体播放软件将是非常简单的事情。他可以把复杂的数据传输,硬件的区别和同步问题同你的应用程序完全隔离开。让微软或者其他人来帮助你解决这些问题,你只要解决你同其他播放器不同的地方。 为了在屏幕上产生视频图像和在声卡里面再现音乐,directshow调用了同样是direct里面的directdraw和directsound。记住directshow不过是directdraw和directsound的一个包装。把多媒体这个特殊应用抽象出来。让这个应用更加简单罢了。和以前前辈们用directdraw和driectsound辛苦的构建多媒体播放器其实本质是一样的。但是现在微软为我们做了很多,就不用象以前那么辛苦了。 Directshow通过媒体流里面的时间戳来对播放数据进行同步。为了能够播放各种不同的格式和获取数据从不同的硬件设备,它把各种不同的软件合成在一起称作“滤镜”,来完成这些功能。 Directshow提供了各种的滤镜。它提供了基于WDM设计驱动模型的摄像头视频捕捉和设置的功能。同时有些滤镜也支持Vfw模型的视频捕捉卡。支持ACM的音频解压和VCM接口。 如下是directshow组件、应用程序和硬件之间的关系。示意图 如上所示,directshow与几乎所有的设备组件通讯,并且对其进行控制。这些包括本地文件系统,电视系统、视频捕捉卡,Vfw解码器,、视频播放(可以通过directshow或者GDI)和声卡(通过directshow)。这样directshow就完全把这些部分和应用程序隔开了。你只要对directshow进行控制就可以完成多媒体软件所要求的所有功能。当然directshow同时也提供各种压缩和解压缩滤镜。用于各种不同的压缩格式。 。图像滤镜及其组件 本章介绍大部分主要的directshow组件。本章介绍如何开发自己的directshow滤镜。虽然应用程序开发者经常忽略大多数directshow滤镜的底层具体细节,但是作为一个好的相反最好还是大概了解下本章对开发者绝对是有好处的。 本章包括如下小结 。关于directshow滤镜 。关于图像滤镜管理 。关于媒体类型 。关于媒体采样和分配 。如何让硬件设备加速图像滤镜 。关于directshow滤镜 directshow用的是COM技术构建成的。每个构件被称为滤镜。Directshow提供一整套的标准滤镜给开发者使用,然而用户也可以编写自己的滤镜用于完成扩展的功能。为了说明问题下面有个示意图用来说明如何播放一个AVI文件。 。从硬盘读入数据流(File Source filter) 。检查AVI文件头和把音频和视频数据分离出来(AVI Splitter filter) 。解码视频帧(拥有各种的视频解码滤镜,他可以自动选择使用) 。显示视频帧(Video Renderer filter) 。发送声音到声卡(Default DirectSound Device filter) 就像上面的图例似的,每个滤镜一个接一个互相连接。连接点同样是COM对象。叫做引脚。滤镜使用引脚移动数据从一个滤镜到另外一。箭头指明了数据流动的方向。在directshow中,一组的滤镜被称作图像滤镜。 滤镜拥有下面三个可能的状态:运行,停止和暂停。当滤镜运行是时候不断的出来发送进来的数据。当停止的时候就停止数据处理。暂停就是全部准备停当就,只要一声令下立刻运行。除非一些非常少的情况,图像滤镜一般都是同时转换状态。也就是说,所有的滤镜都是在运行状态,都是在停止状态,都是在暂停状态。 滤镜可能有以下可能的类型 。数据源滤镜,用于数据输入。数据源可能来自文件,网络,摄像头或者其他任何设备。每种数据源滤镜都有不同的数据来源。 。转换滤镜,具有一个输入端,处理数据,并且输出数据流。解码器和编码器就是一个典型的转换滤镜。 。执行滤镜,是滤镜链的终端。他们用来接收数据并且展现给用户。比如,一个视频执行滤镜用来向屏幕画视频图像,音频执行滤镜用来向声卡播放声音,文件写执行滤镜用来把数据写到文件中等等。 。分离滤镜,用来分离输入流为一个或者多个不同的数据流。例如,AVI分离滤镜分离数据流为视频和音频流。这通常用于数据流解压缩的时候。 。混合滤镜,用来混合多个输入为一个输出。比如AVI混合滤镜就是执行分离滤镜相反的操作。也就是说把视频流和音频流重新合成为一个固定格式的数据流。当然这通常是用在AVI压缩的时候 不过他们之间的关系并不能很单独的看待。比如,ASF读滤镜就完成了数据源滤镜和分离滤镜的功能。 当然了,所有directshow滤镜都具有IbaseFilter结合和引脚接口Ipin。而且可能还有其他的接口用来完成某些特定的功能。 。关于图像滤镜管理: 图像滤镜管理器是一个COM的对象在图像滤镜中用来控制图像滤镜。它用来执行大部分的功能,包括: 。滤镜间的协作状态改变 。建立一个参考时钟 。图像事件传递给应用程序 。提供给应用程序构件图像滤镜的方法 下面对每个功能进行一个简单的描述,至于具体描述将在下面的文章中给出。 状态改变:在滤镜中的状态必须以特定的顺序改变。因此应用程序不能直接控制滤镜来改变滤镜的状态。因此必须给滤镜管理器一个信号,同滤镜管理器来改变滤镜间的状态。该信号通过应用程序发给滤镜管理器,然后通过滤镜管理器搜寻具有该命令定义的滤镜。并且使其按照事先定义好的顺序逐个改变状态。 参照时钟:所有的滤镜都采用同样的时钟频率,这就是所谓的参考时钟 这个时钟的作用就是保证各个滤镜之间能够保持同步。视频和音频样本被执行的时间被称为执行时间。执行时间是该帧在整个播放片断中播放的时间,而参考时间是在本次播放的时候相对于开始播放的时间。图像滤镜管理器通常把声卡的时钟或者系统时钟用来作为参照时间。 。图像事件:图像管理器用一个事件队列通知应用程序图像滤镜中的事件。这个机制有点象WINDOWS的消息机制。 。图像滤镜构件方法:图像滤镜管理器提供给应用程序增加滤镜到图像滤镜中的方法。这样你就可以向现有的图像滤镜中添加各种滤镜。包括你自己写的滤镜。 图像滤镜管理器是无法处理数据从一个滤镜移动到另外的一个滤镜的。这些完全是通过滤镜自己来完成的。这些是通过引脚间的连接完成的。这些数据的移动通常是在单独的线程中完成的。 注意:滤镜通常是在由进程管理器生成的滤镜管理器进行下的一个自由的线程。因此滤镜管理器是无法通过调用滤镜中的内置函数来管理滤镜的 关于媒体类型 由于directshow是由各个标准组件构成的。因此他要求每个图像滤镜都有描述数据流格式的方法。比如在AVI播放中,数据输入图像滤镜中的是一组按照特定格式压缩的RIFF块。然后进过解压缩,视频流变成了一系列的图片显示在屏幕上,同样音频流也别解压还原为了声音。当然这个过程需要知道是用什么格式压缩的,才能把它正确的解压缩出来。 Media Type:Directshow用来描述压缩格式 Media type是一个普遍的可扩充的描述数字媒体的格式。当两个滤镜相连接时必须协商相同媒体类型。媒体类型定义了何种类型的数据从一个滤镜输入到另外一个滤镜,或者物理层的数据类型。如果两个滤镜进行连接时协商的媒体类型不同就无法进行连接。也就是说输入引脚的媒体类型必须和输出引脚的媒体类型相同这两个滤镜才能互相连接。 在某些的应用中,其实你根本就不用考虑媒体类型。比如上面的文件播放器中,你根本就不用考虑播放器读到的是什么文件类型。只要你指到文件名它就自动给你播放。当然前提是该文件格式是可识别的,并且directshow有支持的。 媒体类型被AM _MEDIA _TYPE的结构来进行定义。该结构包含如下信息: typedef struct _MediaType { GUID majortype; GUID subtype; BOOL bFixedSizeSamples; BOOL bTemporalCompression; ULONG lSampleSize; GUID formattype; IUnknown *pUnk; ULONG cbFormat; [size_is(cbFormat)] BYTE *pbFormat; } AM_MEDIA_TYPE; 。Major type:它是一个GUID定义的全局数据类型。它包含了视频,音频,MIDI数据等等。 。Subtype:它也是另外一个GUID,用来进一步定义数据格式。比如Major type是视频的时候它的Subtype有可能是RGB-24,RGB-32,UYVY等等,如果是音频那就有可能是PCM,MPEG-1等等。Subtype比Major type提供了更多的信息。但是这并不是多媒体的所有信息。还有些信息,比如帧的速率,图像大小。这些信息在下面的Format block中定义。 。Format block:它用来详细的定义数据的格式。因为它实在太重要了。因此允许脱离AM_MEDIA_TYPE结构。AM_MEDIA_TYPE的成员pbFormat就是保存指向Format block的指针。 成员pbFormat是一个void *的数据类型,因为具体的类型指针依赖于具体的数据格式。比如,PCM音频使用WAVEFORMATEX结构,视频使用更多的结构,包括VIDEOINFOHEADER和VIDEOINFOHEADER2.结构。 成员formattype也是一个GUID定义的全局数据类型。他包含了该格式的格式块的信息。每种格式的数据都被分配的一个GUID。 成员cbFormat用来保存format block的大小,用来检查pbFormat指针所指向的数据会是否大小和它相等。 因为Format block,包含的机会所有的格式信息,当然如果该信息被填充,major type和subtype包含的信息就会显得多余了。但是directshow这样设计主要是为了能够让设计者一目了然的良苦用心。比如你可以立刻知道这个是视频流,而且是24位RGB图像。这样你就可以知道用什么样的数据结构来读取pbFormat所指向的结构的信息,读取里面的信息,比如图像信息或者帧率的信息 例如下面就是滤镜里面用来确定媒体类型的代码: HRESULT CheckMediaType(AM_MEDIA_TYPE *pmt) { if (pmt == NULL) return E_POINTER; // Check the major type. We're looking for video. if (pmt->majortype != MEDIATYPE_Video) { return VFW_E_INVALIDMEDIATYPE; } // Check the subtype. We're looking for 24-bit RGB. if (pmt->subtype != MEDIASUBTYPE_RGB24) { return VFW_E_INVALIDMEDIATYPE; } // Check the format type and the size of the format block. if ((pmt->formattype == FORMAT_VideoInfo) && (pmt->cbFormat >= sizeof(VIDEOINFOHEADER) && (pmt->pbFormat != NULL)) { // Now it's safe to coerce the format block pointer to the // correct structure, as defined by the formattype GUID. VIDEOINFOHEADER *pVIH = (VIDEOINFOHEADER*)pmt->pbFormat; // Examine pVIH (not shown). If it looks OK, return S_OK. return S_OK; } return VFW_E_INVALIDMEDIATYPE; } AM_MEDIA_TYPE结构同事也包含以下可选的参数。这些参数通常用来提供附加信息。但滤镜通常并不要求这些信息。 。ISampleSize,这个参数不能为0。它用来定义样本的大小,如果为0说明该样本不存在。 。bFixedSizeSamplie。如果该布尔值是真的话,说明ISampleSize是有效的,否则放弃该值。 。bTemporalCompression。如果该布尔值为假的话说明所有的帧都是关键帧。 (未完待续) 说明:如果有任何问题请联系din-je@163.com。是本人业余翻译的如果有错误请直接发到我邮箱我会立刻改正,directshow的其他文章我会在以后不断翻译出来。如果觉得好请帮我作作宣传。欢迎转载,但是禁止修改。 版本历史: 0.1 2006-3-3 0.11 2006-3-6
