DirectShow编程基础1

DirectShow用于视频音频播放,视频音频捕获,视频音频文件创作。还可以用于视频音频文件格式转换,编码方式转换等。

windows系统中带有过滤器图编辑器(graphedt.exe),使用它我们可以直观的构建过滤器图,进行运行和测试。

过滤器图编辑器在哪里?

它在Windows7系统中的位置是:
C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\graphedt.exe
C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\x64\graphedt.exe

它在Windows8系统中的位置是:
C:\Program Files (x86)\Windows Kits\8.1\bin\x86\graphedt.exe
C:\Program Files (x86)\Windows Kits\8.1\bin\x64\graphedt.exe

在Windows10系统中graphedt.exe所在的路径:
C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\graphedt.exe
C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\graphedt.exe
C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\arm64\graphedt.exe

第一个是32位,第二个是64位。

根据系统安装SDK版本的不同,可能在其它目录中还有graphedt.exe。

因为我们将编写32位DirectShow应用程序,所以选择32位GraphEdit,双击运行GraphEdit,GraphEdit界面如下:
在这里插入图片描述
点击“文件”(File)菜单,在下拉菜单中选择“渲染一个媒体文件”(Render Media File…);弹出“选择媒体文件对话框”,在对话框中选择并打开一个WAV音频文件。GraphEdit自动创建了一个过滤器图,界面图像如下:
在这里插入图片描述
GraphEdit客户区中的每个矩形窗口都统称为过滤器。

GraphEdit创建了3个过滤器:
第一个是文件源过滤器,它有一个输出引脚。
第二个是WAV解析过滤器,它有一个输入引脚和一个输出引脚。
第三个是默认DirectSound音频渲染过滤器,它只有一个输入引脚。

3个过滤器已自动连接。点击播放按钮,就可以播放该WAV音频文件。

使用同样方法,渲染一个avi视频文件,过滤器图如下:
在这里插入图片描述
当然我们也可以打开过滤器列表,手动添加过滤器,连接过滤器,然后运行。还可以保存当前过滤器图到grf文件。

查看过滤器属性

要查看过滤器属性,引脚属性;需在过滤器图编辑器所在目录下注册以下DLL:
proppage.dll
evrprop.dll

方法是:

在windows7系统打开“运行”窗口。
在“打开(O):”编辑框输入:
regsvr32.exe /s “C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\proppage.dll”
点击确定。

再次在在“打开(O):”编辑框输入:
regsvr32.exe /s “C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\evrprop.dll”
点击确定。

如果是windows8系统,则为:
regsvr32.exe /s “C:\Program Files (x86)\Windows Kits\8.1\bin\x86\proppage.dll”
regsvr32.exe /s “C:\Program Files (x86)\Windows Kits\8.1\bin\x86\evrprop.dll”

如果是windows10系统,则为:
regsvr32.exe /s “C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\proppage.dll”
regsvr32.exe /s “C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x86\evrprop.dll”

这样就可以查看过滤器属性和引脚属性了。在过滤器上点击右键,选择“过滤器属性”菜单项。

过滤器图的代码实现

以上面播放wav音频文件过滤器图为例,它的代码实现为:

    IGraphBuilder* pGraph = NULL;
    IMediaControl* pControl = NULL;
    IBasicAudio* pIBasicAudio = NULL;
    HRESULT hr;
    IBaseFilter* pFileSourceAsync = NULL;
    IFileSourceFilter* pIFileSourceFilter1 = NULL;
    IPin* pFileSourceAsync_Output = NULL;
    IBaseFilter* pWaveParser = NULL;
    GUID WaveParser_guid = { 0xd51bd5a1, 0x7548, 0x11cf, 0xa5, 0x20, 0x00, 0x80, 0xc7, 0x7e, 0xf5, 0x8a };
    IPin* pWaveParser_inputpin = NULL;
    IPin* pWaveParser_output = NULL;
    IBaseFilter* pDefaultDirectSoundDevice = NULL;
    IPin* pDefaultDirectSoundDevice_AudioInputpinrendered = NULL;

    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pGraph));//创建过滤器图管理器
    if (hr != S_OK)goto Exit;
    hr = pGraph->QueryInterface(IID_IMediaControl, (void**)&pControl);//获取媒体控制接口
    if (hr != S_OK)goto Exit;
    hr = pGraph->QueryInterface(IID_IBasicAudio, (void**)&pIBasicAudio);//获取音量和平衡控制接口
    if (hr != S_OK)goto Exit;
    hr = CoCreateInstance(CLSID_AsyncReader, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (LPVOID*)&pFileSourceAsync);//创建FileSourceAsync过滤器
    if (hr != S_OK)goto Exit;
    hr = pGraph->AddFilter(pFileSourceAsync, L"FileSourceAsync");
    if (hr != S_OK)goto Exit;
    hr = pFileSourceAsync->QueryInterface(IID_PPV_ARGS(&pIFileSourceFilter1));//获取IFileSourceFilter接口
    if (hr != S_OK)goto Exit;
    if (pIFileSourceFilter1 != NULL)
    {
        hr = pIFileSourceFilter1->Load(L"D:\\音频视频\\音频\\不同编码方式的WAV\\PCM.wav", NULL);//加载媒体文件
        if (hr != S_OK)goto Exit;
        pIFileSourceFilter1->Release(); pIFileSourceFilter1 = NULL;
    }
    hr = pFileSourceAsync->FindPin(L"Output", &pFileSourceAsync_Output);
    if (hr != S_OK)goto Exit;
    hr = CoCreateInstance(WaveParser_guid, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (LPVOID*)&pWaveParser);//创建WaveParser过滤器
    if (hr != S_OK)goto Exit;
    hr = pGraph->AddFilter(pWaveParser, L"WaveParser");
    if (hr != S_OK)goto Exit;
    hr = pWaveParser->FindPin(L"input pin", &pWaveParser_inputpin);
    if (hr != S_OK)goto Exit;
    hr = pGraph->ConnectDirect(pFileSourceAsync_Output, pWaveParser_inputpin, NULL);//连接引脚
    if (hr != S_OK)goto Exit;
    hr = pWaveParser->FindPin(L"output", &pWaveParser_output);
    if (hr != S_OK)goto Exit;
    hr = CoCreateInstance(CLSID_DSoundRender, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (LPVOID*)&pDefaultDirectSoundDevice);//创建DefaultDirectSoundDevice过滤器
    if (hr != S_OK)goto Exit;
    hr = pGraph->AddFilter(pDefaultDirectSoundDevice, L"DefaultDirectSoundDevice");
    if (hr != S_OK)goto Exit;
    hr = pDefaultDirectSoundDevice->FindPin(L"Audio Input pin (rendered)", &pDefaultDirectSoundDevice_AudioInputpinrendered);
    if (hr != S_OK)goto Exit;
    hr = pGraph->ConnectDirect(pWaveParser_output, pDefaultDirectSoundDevice_AudioInputpinrendered, NULL);//连接引脚
    if (hr != S_OK)goto Exit;

    pControl->Run();//运行过滤器图
    return;

Exit:
    if (pFileSourceAsync_Output != NULL)
    {
        pFileSourceAsync_Output->Release(); pFileSourceAsync_Output = NULL;
    }
    if (pFileSourceAsync != NULL)
    {
        pFileSourceAsync->Release(); pFileSourceAsync = NULL;
    }
    if (pWaveParser_inputpin != NULL)
    {
        pWaveParser_inputpin->Release(); pWaveParser_inputpin = NULL;
    }
    if (pWaveParser_output != NULL)
    {
        pWaveParser_output->Release(); pWaveParser_output = NULL;
    }
    if (pWaveParser != NULL)
    {
        pWaveParser->Release(); pWaveParser = NULL;
    }
    if (pDefaultDirectSoundDevice_AudioInputpinrendered != NULL)
    {
        pDefaultDirectSoundDevice_AudioInputpinrendered->Release();
        pDefaultDirectSoundDevice_AudioInputpinrendered = NULL;
    }
    if (pDefaultDirectSoundDevice != NULL)
    {
        pDefaultDirectSoundDevice->Release(); pDefaultDirectSoundDevice = NULL;
    }
    if (pIBasicAudio != NULL)
    {
        pIBasicAudio->Release(); pIBasicAudio = NULL;
    }
    if (pControl != NULL)
    {
        pControl->Release(); pControl = NULL;
    }
    if (pGraph != NULL)
    {
        pGraph->Release(); pGraph = NULL;
    }

代码中每一步都添加了判断代码,和安全退出代码。实际应用程序必须这样做,因为,即使某些代码没有成功,也不至于导致程序崩溃,只是任务没有完成。但下面的示例都省略了判断代码,目的是为了代码简洁。后面的所有示例,全部在32位MFC对话框应用程序中实现。

编写DirectShow应用程序需要

1.包含dshow.h头文件,导入strmiids.lib库文件。

#include "dshow.h"
#pragma comment(lib, "strmiids")//导入库,包含类标识定义

2.启动程序时初始化COM库,在程序退出时,关闭COM库。

    HRESULT hr = CoInitialize(NULL);//初始化COM库
    if (hr != S_OK)
    {
        AfxMessageBox(_T("COM库初始化失败!"));
    }

    CoUninitialize();//关闭COM库

因为过滤器图管理器,过滤器都是COM对象。编者将CoInitialize放在了应用程序类的InitInstance函数中,在主对话框DoModal函数前;将CoUninitialize放在ExitInstance函数中。

编写DirectShow应用程序

编写DirectShow应用程序,就是做以下工作:

1.创建过滤器图管理器。

2.创建过滤器。

3.将过滤器添加到图中。

4.配置过滤器。一些过滤器需要配置代码。

5.连接引脚。

6.处理过滤器图事件。

7.运行和测试过滤器图。

创建过滤器图管理器

    IGraphBuilder* pGraph=NULL;//声明过滤器图管理器接口指针
    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pGraph));
    //创建过滤器图管理器,如果成功返回S_OK
    或
    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder,(LPVOID*)&pGraph);

    //参数1,CLSID_FilterGraph 是过滤器图管理器的类标识
    //参数2,IUnknown接口指针。如果为NULL,则表示该对象不是作为聚合的一部分创建的
    //参数3,CLSCTX_INPROC_SERVER  为CLSCTX枚举值,表示创建和管理此类对象的代码是一个DLL
    //参数4,要获取的接口的标识
    //参数5,指针变量的地址,接收获取到的接口指针
    //IID_PPV_ARGS  宏,根据所使用的接口指针的类型自动提供所请求接口的IID值

过滤器图管理器为过滤器图创建了一个工作线程,工作线程并不是这些创建代码所在的线程。

创建过滤器

DirectShow Filters类别下的过滤器都可以使用CoCreateInstance函数创建。但音频压缩类别(Audio Compressors)视频压缩类别(Video Compressors)下的过滤器必须使用系统设备枚举器创建。原因是这些过滤器使用了硬件,目的是获得较高的转换速度。

使用CoCreateInstance函数创建过滤器:

    IBaseFilter *pFilter = NULL;
    GUID guid = { 0xd51bd5a1, 0x7548, 0x11cf, 0xa5, 0x20, 0x00, 0x80, 0xc7, 0x7e, 0xf5, 0x8a };
    //要创建的过滤器的GUID
    HRESULT hr = CoCreateInstance(guid, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFilter));
    或
    hr = CoCreateInstance(guid, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (LPVOID*)&pFilter);
    //创建过滤器,要求要创建的过滤器已注册。如果创建成功,返回值为S_OK。pFilter接收过滤器的指针

    //参数1,是过滤器的GUID(类标识)
    //参数2,IUnknown接口指针。如果为NULL,则表示该对象不是作为聚合的一部分创建的
    //参数3,CLSCTX枚举值。CLSCTX_INPROC_SERVER表示创建和管理此类对象的代码是一个DLL
    //参数4,要获取的接口的标识
    //参数5,指针变量的地址,接收获取到的接口指针
    //IID_PPV_ARGS  宏,根据所使用的接口指针的类型自动提供所请求接口的IID值

使用系统设备枚举器创建过滤器:

   IBaseFilter* pFilter=NULL;//声明过滤器接口

    ICreateDevEnum* pSysDevEnum = NULL;//声明系统设备枚举器接口
    HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pSysDevEnum));//创建系统设备枚举器。
    IEnumMoniker* pEnumCat = NULL;
    hr = pSysDevEnum->CreateClassEnumerator(CLSID_AudioCompressorCategory, &pEnumCat, 0);//创建指定类别的类枚举器

    IMoniker* pMoniker = NULL;
    ULONG cFetched;
    while (pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
    {
        IPropertyBag* pPropBag;
        hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPropBag);
        if (hr == S_OK)
        {
            VARIANT varName;
            VariantInit(&varName);
            hr = pPropBag->Read(L"FriendlyName", &varName, 0);//检索过滤器的名称
            if (hr == S_OK )
            {
                CString Friendly_Name = varName.bstrVal;
                if (Friendly_Name == L"CCITT A-Law" )//如果找到指定的设备
                {
                        hr = pMoniker->BindToObject(NULL, NULL, IID_PPV_ARGS(&pFilter));//创建过滤器的实例
                        VariantClear(&varName);//清除变量
                        pPropBag->Release();//释放IPropertyBag接口
                        pMoniker->Release();//释放IMoniker接口
                        pEnumCat->Release();//释放    IEnumMoniker接口
                        pSysDevEnum->Release();//释放系统设备枚举器接口
                        return S_OK;
                }
            }
            VariantClear(&varName);//清除变量
            pPropBag->Release();//释放IPropertyBag接口
        }
        pMoniker->Release();//释放IMoniker接口
    }
    pEnumCat->Release();//释放    IEnumMoniker接口
    pSysDevEnum->Release();//释放系统设备枚举器接口

创建DMO过滤器:

#include "dmodshow.h"
#include "dmoreg.h"
#pragma comment(lib, "dmoguids.lib")

    IBaseFilter* pFilter=NULL;
    hr = CoCreateInstance(CLSID_DMOWrapperFilter, NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, reinterpret_cast<void**>(&pFilter));
    //创建DMO包装器过滤器
    IDMOWrapperFilter* pDmoWrapper = NULL;
    hr = pFilter->QueryInterface(IID_IDMOWrapperFilter,reinterpret_cast<void**>(&pDmoWrapper));//查询IDMOWrapperFilter接口
    GUID G_DMO = { 0x2eeb4adf, 0x4578, 0x4d10, 0xbc, 0xa7, 0xbb, 0x95, 0x5f, 0x56, 0x32, 0x0a };//DMO的类标识
    hr = pDmoWrapper->Init(G_DMO, DMOCATEGORY_AUDIO_DECODER);//初始化过滤器
    pDmoWrapper->Release();

将过滤器添加到图中

    hr = pGraph->AddFilter(pFilter, L"WMAudio DMO");//添加到过滤器图中

配置过滤器

不同的过滤器有着不同的配置代码,一些过滤器不需要配置带码。以上面的播放wav音频文件代码为例,为异步文件源过滤器指定读取文件,就是它的配置代码:

    IFileSourceFilter* pIFileSourceFilter1 = NULL;
    hr = pFileSourceAsync->QueryInterface(IID_PPV_ARGS(&pIFileSourceFilter1));//获取IFileSourceFilter接口
    if (pIFileSourceFilter1 != NULL)
    {
        hr = pIFileSourceFilter1->Load(L"D:\\音频视频\\音频\\不同编码方式的WAV\\PCM.wav", NULL);//加载媒体文件
        pIFileSourceFilter1->Release(); pIFileSourceFilter1 = NULL;
    }

连接引脚

要连接引脚,首先需获取引脚。如果知道引脚的标识,可以使用IBaseFilter接口的FindPin方法获取引脚:

    IPin* pOutPin = NULL;
    hr = pFilter1->FindPin(L"Output", &pOutPin);//如果成功,返回S_OK。pFilter1的类型为IBaseFilter*
    //参数1,引脚的标识。大部分过滤器其引脚标识就是引脚名称
    //参数2,接收引脚IPin接口指针的指针变量

如果不知道过滤器引脚标识,可以枚举过滤器所有引脚,获取其标识:

    IEnumPins* pEnum = NULL;
    hr = pFilter1->EnumPins(&pEnum);//获取IEnumPins接口指针
    IPin* pPin = NULL;
    while (pEnum->Next(1, &pPin, 0) == S_OK)
    {
        LPWSTR lp;
        pPin->QueryId(&lp);//如果成功,lp包含引脚标识
        CoTaskMemFree(lp);//必须使用CoTaskMemFree释放它
        pPin->Release();//在下一次循环前释放该引脚
    }
    pEnum->Release();//释放枚举引脚接口

连接引脚:

    hr = pGraph->Connect(pOutPin, pInPin);//pGraph为过滤器图管理器指针,类型为IGraphBuilder*
    //参数1,输出引脚的指针。类型为IPin*
    //参数2,输入引脚的指针。类型为IPin*

如果引脚连接成功,IGraphBuilder的Connect方法返回S_OK。此方法可能拉入中间过滤器。如果直接连接两个引脚,可以使用下面方法:

    AM_MEDIA_TYPE mt;
    mt.majortype = MEDIATYPE_Video;
    mt.subtype = MEDIASUBTYPE_RGB24;
    hr = pGraph->ConnectDirect(pOutPin, pInPin, &mt);//可以提供媒体类型的所有信息;也可以只指定部分信息

ConnectDirect是IFilterGraph方法,直接连接两个引脚。参数3为媒体类型结构AM_MEDIA_TYPE的指针,指定连接使用的媒体类型。可以指定结构的全部参数,也可以只指定部分参数,也可为NULL。为NULL时,由两个引脚协商连接使用的媒体类型。

少部分过滤器须指定媒体类型的全部参数,这时可以枚举输出引脚的媒体类型,当获取到希望的类型时,使用该媒体类型连接引脚:

    IEnumMediaTypes* pEnum = NULL;
    hr = pOutPin->EnumMediaTypes(&pEnum);
    AM_MEDIA_TYPE* pmt = NULL; 
    while (hr = pEnum->Next(1, &pmt, NULL), hr == S_OK)
    {
        if (pmt->majortype == MEDIATYPE_Audio && pmt->subtype == MEDIASUBTYPE_PCM)break;
        _DeleteMediaType(pmt);//必须删除获取到的媒体类型结构,包括它的格式块
    }
    pEnum->Release();
    hr= pGraph->ConnectDirect(pOutPin, pInPin,pmt);
 
//下面是_DeleteMediaType和_FreeMediaType函数定义
void _FreeMediaType(AM_MEDIA_TYPE& mt)//释放媒体类型的格式块。
{
    if (mt.cbFormat != 0)
    {
        CoTaskMemFree((PVOID)mt.pbFormat);
        mt.cbFormat = 0;
        mt.pbFormat = NULL;
    }
    if (mt.pUnk != NULL)
    {
        mt.pUnk->Release();
        mt.pUnk = NULL;
    }
}

void _DeleteMediaType(AM_MEDIA_TYPE* pmt)//删除在堆上分配的媒体类型结构。
{
    if (pmt != NULL)
    {
        _FreeMediaType(*pmt);
        CoTaskMemFree(pmt);
    }
}

连接是否成功主要在于,要连接的输入引脚和输出引脚允许类型中,是否具有相同的媒体类型;或指定的媒体类型是否为两引脚共同允许的类型。如果是,大部分情况下连接可以成功。但极少情况下,即便有共同的媒体类型,仍无法连接。

如果连接成功,大部分情况下可以正常运行。但存在连接即便成功仍无法运行的情况,这种情况比较少见,但是存在。原因是两个过滤器功能不匹配。

处理过滤器图事件

要处理过滤器图事件,首先要获取图事件。
获取过滤器图事件:
1.获取媒体事件接口;获取媒体事件扩展接口。

	IMediaEvent* 	pEvent = NULL;
	IMediaEventEx* 	pEventEx = NULL;
	hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEvent));//获取媒体事件接口
	hr = pGraph->QueryInterface(IID_PPV_ARGS(&pEventEx));//获取媒体事件扩展接口

2.定义一个私人消息(WM_GRAPHNOTIFY),如果过滤器图有事件通知,将WM_GRAPHNOTIFY消息发送到指定窗口。

	#define WM_GRAPHNOTIFY WM_APP + 1   //应用程序可以将WM_APP到0xBFFF范围内的消息编号用作私有消息
	HWND g_hwnd=GetSafeHwnd();//接收WM_GRAPHNOTIFY消息的窗口的句柄
	hr=pEventEx->SetNotifyWindow((OAHWND)g_hwnd, WM_GRAPHNOTIFY, 0);//指定接收WM_GRAPHNOTIFY消息的窗口

3.重写接收窗口(这里是对话框)的WindowProc函数,在函数中处理过滤器图事件。
处理过滤器图事件:
最常见的要处理的事件之一是EC_COMPLETE(流渲染完成),即使全部流渲染已完成,过滤器图状态仍为运行状态,这时我们需要停止过滤器图。

		LRESULT CMFCApplication1Dlg::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
		{ 
			long evCode; LONG_PTR param1, param2;
			switch(message)
			{
			case WM_GRAPHNOTIFY:
				pEvent->GetEvent(&evCode, &param1, &param2, 0);
				switch(evCode) 
				{ 
                case EC_COMPLETE://播放完成
                    pControl->Stop();//停止过滤器图
                    break;
                case EC_ACTIVATE://播放窗口已激活
                    break;
                case EC_ERRORABORT://发生错误,操作被中止
                    break;
				}
				pEvent->FreeEventParams(evCode, param1, param2);//释放为事件参数分配的资源
				break;
			}
			return CDialogEx::WindowProc(message, wParam, lParam);
		}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

h3974

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值