在 DirectShow 中读取音频、视频设备和编解码器






3.93/5 (9投票s)
如何通过 DirectShow 读取 PC 上安装的音频、视频设备和编解码器。
引言
要获取系统上可用的音频设备、视频设备和编解码器的列表,您需要 DirectX 或更准确地说 DirectShow 的起点。这个极小的程序将为您提供一个起点。但是,要播放音频和视频,我们需要修改我们的程序,特别是如果我们想编写一个需要即时播放和文件保存等的程序。然而,这个小程序将为有兴趣学习 DirectShow 编程并访问输入/输出设备和音视频编解码器的初学者提供主要的起点。
背景
DirectX 使用“过滤器”一词来指定不同的硬件和软件编解码器。而在程序中,我使用了“设备”一词。当我开始编程时,我从不喜欢使用“过滤器”这个词,而是总是使用“设备”这个词,所以我觉得你们中的一些人也会有同感!异常处理有限,正如我之前的教程一样。我故意这样做是为了让代码更用户友好,而不是一堆专业代码。
如您所知,DirectX 是基于 COM 的,所以一点 COM 方面的理解会有所帮助。
代码
除了 main()
函数外,还有两个简单的函数。
void HR_Failed(HRESULT); void Device_Reader(GUID,ICreateDeviceEnum);
首先,让我们看看 main()
中有什么。程序的 main()
函数包含 COM 的初始化代码,并声明了几个变量,“hr
”和“ICreateDevEnum
”。ICreateDevEnum
是一个接口,我们在指针变量 *pDeviceEnum
中维护其引用。这个接口的作用将在解释 HR_Failed(hr) 函数后进行说明。
HR_Failed()
函数接收一个 HRESULT
类型;每当发生错误时都会调用该函数。例如:
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);// Initialise COM if (SUCCEEDED(hr)) { cout<<"COM Initialisation successful"<<endl; } else HR_Failed(hr)
在上面的代码行中,在调用初始化 COM 后返回 hr
。如果初始化失败,则调用 HR_Failed(hr)
。函数内的代码会将“hr
”的值转换为更易于人类理解的代码(老实说,我在 MSDN 上找到了这段代码!)。
void HR_Failed(HRESULT hr) { TCHAR szErr[MAX_ERROR_TEXT_LEN]; DWORD res = AMGetErrorText(hr, szErr, MAX_ERROR_TEXT_LEN); if (res == 0) { StringCchPrintf(szErr, MAX_ERROR_TEXT_LEN, L"Unknown Error: 0x%2x", hr); } MessageBox(0, szErr, TEXT("Error!"), MB_OK | MB_ICONERROR); return; }
Device_Reader(GUID,ICreateDevEnum)
函数负责读取或提取系统中安装的“过滤器”,即音频、视频和编解码器过滤器。不要将“设备”与编解码器混淆。DirectX 称之为“过滤器”,我称之为“设备”。如果您觉得方便,可以将该函数重命名为 Filter_Reader
。在我之前的一篇文章中,我曾解释过如何读取系统上的设备。您可以在 这里 找到该主题,但我仍将在此处写几行,因为 Device_Reader
函数中的代码几乎相同。
首先,我们通过调用 CoCreateInstance
来初始化设备枚举。
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC_SERVER, IID_ICreateDevEnum, (void **)&pDeviceEnum);//Initialise Device enumerator
然后,我们需要创建一个设备枚举,该枚举允许我们获取或“读取”系统中可用的音频设备。在这里,我们需要指定我们正在尝试读取或获取的设备的类别。我们通过传递 MSDN 文档定义的每个类别的 GUID 来进行此枚举。在这种情况下,我使用了几个:CLSID_AudioInputDeviceCategory
、CLSID_AudioCompressorCategory
。每个 GUID 的名称都指代一个特定的类别。
通过类 ID 使用设备枚举的另一个好处是,假设我们有两个声卡,它们都支持相同的过滤器,使用此枚举我们可以将它们作为单独的实例区别对待。枚举后,我们无法简单地使用 AudioInputDeviceCategory
中的设备的引脚属性或访问任何其他方法。要做到这一点,我们必须使用 Monikers。Moniker 是指向特定设备的接口,然后我们使用 IMoniker
和 IPropertyBag
将设备绑定到 Moniker 并分别读取设备的属性。IPropertyBag
接口保存着设备属性的键。
void Device_Reader(GUID DEVICE_CLSID,ICreateDevEnum *pDeviceEnum ) { HRESULT hr; IMoniker *pDeviceMonik = NULL;// Device moniker IEnumMoniker *pEnumCat = NULL;// Device enumeration moniker VARIANT varName; // Enumerate the specified device, distinguished by DEVICE_CLSID hr = pDeviceEnum->CreateClassEnumerator(DEVICE_CLSID, &pEnumCat, 0); if (hr == S_OK) { ULONG cFetched; while (pEnumCat->Next(1, &pDeviceMonik, &cFetched) == S_OK) //Pickup as moniker { IPropertyBag *pPropBag = NULL; //bind the properties of the moniker hr = pDeviceMonik->BindToStorage(0, 0, IID_IPropertyBag,(void **)&pPropBag); if (SUCCEEDED(hr)) { // Initialise the variant data type VariantInit(&varName); //extract the FriendlyName // - This is the name we see in GraphEdit hr = pPropBag->Read(L"FriendlyName", &varName, 0); if (SUCCEEDED(hr)) { wcout<<varName.bstrVal<<endl; // show the name } else HR_Failed(hr); //clear the variant data type VariantClear(&varName); pPropBag->Release();//release the properties } else HR_Failed(hr); pDeviceMonik->Release();//release Device moniker } pEnumCat->Release();//release category enumerator } else HR_Failed(hr); }
该函数接受 GUID 和指向 ICreateDevEnum
接口的指针。使用不同的 GUID,每次调用都会访问系统中可用的音频、视频和编解码器过滤器。下面是我系统上可用内容的屏幕截图。
当找不到过滤器时会发生什么?
会调用 HR_Failed
,然后出现以下窗口,显示错误消息“函数不正确”。每当找不到一整类过滤器时,就会调用 HR_Failed
,它会显示窗口。
关注点
GraphEdit 始终是开发人员摆脱困境的工具。虽然它看起来非常原始,但它绝对是一个杰作。只有在使用 GraphEdit 后,我才意识到它显示的“FriendlyName”与控制台输出中看到的完全相同。所以,一定要试试。如果您在系统上找不到 GraphEdit,请确保您已安装 Windows SDK,并在“Bin”目录中查找 GrapEdit.exe。
该代码使用以下内容构建:
- Windows Server 2008
- MS Visual Studio 2008
- DirectX 10.1
- Microsoft Windows SDK 6.1
历史
第一篇帖子。如有需要将更新。