对于数字音频和视频的DirectShow编程(一)
关键词: DirectShow
Programming DirectShow Applications
DirectShow应用程序的设计是很简单的,只要掌握三种方法就可以.
1) 初始化,这一步由DirectShow filter graph来执行
2) 执行,当filter graph进入了运行状态并且开始处理数据流的时候
3) 清空,当数据结构被解除分配并且系统被释放的时候
在我们用DirectShow产生一个简单的“media player“之前,我们先需要了解一下COM.
DirectShow编程能将filters和filter graphs结合在一起是因为把它们当作了COM对象,所以大部分的DirectShow都是COM-intensive.
COM基础
从1990年起,操作系统和应用程序发展地更为复杂化,software architects尽力去生产一种新的可重用的代码.这些代码可以通过明显的已定义过的接口再度被使用.这样的代码可以使我们的程序设计更为严重简单化.
比方说,一个文档处理器和一个mail客户端都需要对拼写进行检查,但是如果我们可以运用可重用的代码又为什么需要两个拼写检查器呢?理想的状态就是,我们只需要知道这个可重用的代码的名字.一旦程序包含了这种理想的对象,程序就可以查询它并且也可以中止它.正如一个一般的对象一样,这个对象也会有数据和方法.
在1990年中期,Microsoft推出来了COM,它就是那个可重用的代码.一个COM对象就相当于一个C++对象一样,可以被检查,有方法,也有从基类继承来的接口.
作为一个理想的可重用的代码,我们所需要知道的关于COM的唯一的事情就是它的名字.然而它的名字不是一串罗马字符.它是全局通用字符(GUID), 一串以32bits-16bits-16bits-16bits-44bits为格式的十二进制数字.GUID必须要是保证为唯一的,所以每个COM对象都有一个唯一的名字.不过幸运的是你不需要记住这些字符.每一个被DirectShow 使用的COM对象都已经被赋予了一个名字,我们叫它CLSID(Class ID).比方说,一个表示Filter Graph的COM对象就有一个Class ID,CLSID_FilterGraph,而它所体现的GUID 就是e436ebb8-542f-11ce-9f53-0020af0ba770.这个Class ID实际上只是提供了一个象征性的名字从而使你可以初始化COM对象.
初始化和释放COM
在COM可以在DirectShow里面运行之前,我们必须要先声明一下COM对象.(如果在你的程序里有多个执行线程,你必须对每一个线程都要单独的初始化).
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED) //Initializes COM
在应用程序终止它的执行之前,必须还要再次关闭COM.
CoUninitialize(); // Releases COM
产生一个COM对象的实例
一旦在DirectShow中初始化完COM之后,你就需要产生COM对象的实例了.这个命令可以产生应用程序所需要的不同的对象,比方说filters.在任何一个DirectShow程序中第一个COM的执行都是要产生一个Filter Graph Manager,这个对象是用来处理filter graph的内部细节问题的.
CoCreateInstance就是被用于产生COM对象的.产生Filter Graph Manager的代码如下:
IGraphBuilder *graphBuilder = NULL; // Pointer to created object
HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder,
(void **)&pGraphBuilder);
CoCreateInstance方法里有5个参数,第一个是Class ID- 在这段代码中是CLSID_FilterGraph,是用于请求COM对象来产生filter graph的(实际上是用来产生Filter Graph Manager的). 第二个是NULL,表示这里不是一个集合对象. CLSCTX_INPROC_SERVER表示这个COM对象是从一个in-process DLL里载入的.
下一个参数是一个interface ID.它用于通知COM炸个唯一的接口现在被请求使用.在这里,这个值是IID_IgraphBuilder,表示你想寻回IgraphBuilder接口的对象,使我们可以得到建造filter graph的方法.之后,你还需要使用这个对象的另外一个接口IMediaControl,是用于提供可以start,stop,pause filter graph的方法.最后一个参数是返回的地址指针.这个指针被写做void **,这是一个指向指针的指针,因为这个函数是返回指向任何一个对象的指针.
如果你通过了COM接口,你现在就需要运用它的释放方法,这样对象就可以知道怎样在特定的时间里删除自己了.
pGraphBuilder->Release(); // Release the object
pGraphBuilder = NULL; // And set it to NULL
如果你不去释放你的COM接口,对象就不会被删除,这样就会占用你的内存.
对于数字音频和视频的DirectShow编程(二)
在COM对象里查询接口
在用具体的例子说明完COM对象之后,我们明白,一个DirectShow应用程序经常需要去进入其它的接口.比方说,如果程序员想要控制filter graph,那么就必须要获取filter graph Manager 的IMediaControl接口的指针.其实还是同样的对象,但是每一个接口都有自己唯一的方法.
为了获得这个接口,你必须给对象发送一个查询请求,才能使用这个你已经获得的接口.在例子中,我们已经有了IGraphBuilder接口,所以我们才可以用这个接口.
IMediaControl *pMediaControl = NULL; // Store pointer to interface
hr = pGraphBuilder->QueryInterface(IID_MediaControl,
(void**)&pMediaControl);
这个QueryInterface有两个参数.第一个是被查询的接口的interface ID(GUID).在这里,这个interface ID指的是IMediaControl接口.第二个是对于返回接口的指针.
DSRender: AdirectShow Media Player in C++
以下这个应用程序是很简单的:它显示一个打开文件的文本框,允许用户选择文件,并且能够接收这个文件.文件类型没关系,它可以是an AVI movie, a WAV sound, Windows Media, an MP3 song, or an MPEG movie.
在DSRRender.cpp里只有三个函数.
// DSRender.cpp
// A very simple program to render media files using DirectShow
//
int main(int argc, char* argv[])
{
IGraphBuilder *pGraph = NULL; // Graph builder interface
IMediaControl *pControl = NULL; // Media control interface
IMediaEvent *pEvent = NULL; // Media event interface
if (!GetMediaFileName()) { // Local function to get a file name
return(0); // If we didn't get it, exit
}
// Initialize the COM library.
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
if (FAILED(hr))
{
// We'll send our error messages to the console.
printf("ERROR - Could not initialize COM library");
return hr;
}
// Create the Filter Graph Manager and query for interfaces.
hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void **)&pGraph);
if (FAILED(hr)) // FAILED is a macro that tests the return value
{
printf("ERROR - Could not create the Filter Graph Manager.");
return hr;
}
// Use IGraphBuilder::QueryInterface (inherited from IUnknown)
// to get the IMediaControl interface.
hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
if (FAILED(hr))
{
printf("ERROR - Could not obtain the Media Control interface.");
pGraph->Release(); // Clean up after
pGraph = NULL;
CoUninitialize(); // And uninitialize COM
return hr;
}
// And get the Media Event interface, too.
hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
if (FAILED(hr))
{
printf("ERROR - Could not obtain the Media Event interface.");
pGraph->Release(); // Clean up after ourselves
CoUninitialize(); // And uninitialize COM
return hr;
}
// To build the filter graph, only one call is required.
// We make the RenderFile call to the Filter Graph Manager
// to which we pass the name of the media file.
#ifndef UNICODE
WCHAR wFileName[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, g_PathFileName, -1, wFileName,
MAX_PATH);
// This is all that's required to create a filter graph
// that will render a media file!
hr = pGraph->RenderFile((LPCWSTR)wFileName, NULL);
#else
hr = pGraph->RenderFile((LPCWSTR)g_PathFileName, NULL);
#endif
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.
}
// And stop the filter graph.
hr = pControl->Stop();
// Before we finish, save the filter graph to a file.
SaveGraphFile(pGraph, L"C:\\MyGraph.GRF");
}
// Now release everything and clean up.
pControl->Release();
pEvent->Release();
pGraph->Release();
CoUninitialize();
return 0;
}
现在我们来解释一下程序吧.
一部分的代码在一开头就已经讲过了.这里就不再重复.
用GetMediaFileName来获取文件名字,并且然后用CoInitializeEx初始化COM对象.
如果一切都进行的顺利的话,我们需要用CoCreateInstance来建造一个Filter Graph Manager的对象,这个对象提供了允许你建立filter graph的方法.
在获得IGraphBuilder接口之后,我们可以通过QueryInterface来找回被filter graph manager暴露的额外的接口.第一个命令就是用于返回一个IMediaControl接口,它的方法是用于控制filter graph. 第二个命令是用于寻求一个ImediaEvent,它的作用是通知DirectShow应用程序filter graph状态的改变.
在使用IgraphBuilder:: RenderFile之后,filter graph manager对象开始检查媒体文件的类型并决定特定的filter graph. source, transform, and renderer – 它们都需要被添加到filter graph中去.这些filters被添加到filter graph中去之后就被组合到了一起.如果RenderFile没有返回错误的话, DirectShow就能找到一条从source filter到renderer filter的路径.如果调用失败的话, DirectShow就会缺乏那种能播放媒体文件的filters.
在filter graph建立起之后,我们调用IMediaControl:: Run来执行filter graph.
在WaitForCompletion中使用参数INFINITE,目的是当这个媒体文件结束播发之后让应用程序等待.
调用stop方法使filter graph停止执行.这个stop方法很重要,因为在媒体文件结束之后filter graph不会自己停止.
把filter graph保存为.GRF文件.
在filter graph结束之后,你需要调用SaveFilterGraph.它里面含有两个参数:一个是指向IGraphBuilder的指针,另一个是文件名.
SaveFilterGraph保存的是GraphEdit里filter graph的拷贝.