文章目录
1、DriectShow 采集
1.1 DirectShow 基础
DirectShow 其主要设计目标是通过将应用程序与数据传输的复杂性、硬件差异和同步隔离,简化在 Windows 平台上创建数字媒体应用程序的任务 ,DirectShow简化媒体播放、格式转换和捕获任务
下图显示了应用程序、DirectShow组件以及应用程序支持的一些硬件和软件DirectShow之间的关系。
在这里插入图片描述
Filter是DirectShow技术体系中最基本的概念。如上图所示,DirectShow中的Filter分成三大类:Source Filter、Transform Filter、Render Filter。Source Filter就是提供数据源的Filter,所有的数据都是从Source Filter流出去的。不管是多媒体文件还是多媒体设备,Source Filter都进行了封装统一了接口,在使用方式上保持了一致。Transform Filter则是对数据进行操作处理的Filter,所有的图像操作都应该在这里进行。而Render Filter则是用来渲染图像的Filter,不管是保存到文件还是输出到其他地方,都由这个Render Filter来实现。Windows系统本身提供了非常多的Filter,我们在开发的时候可以直接使用。
下面是UI 界面配置directshow 软件(graphstudionext)的示意图,通过拖动就可以配置,filter 的概念。而整个图就 Filter Graph。如果需要就向 Filter Graph 添加filter , 并且链接 pin(引脚)
1.2 DriectShow 采集应用场景
现在市场上大多数的摄像头和采集卡在Windows系统上的驱动基于WDM架构,微软定义了采集卡设备与上层程序间的通信驱动接口,这已经成为一种标准,因此,控制摄像头和采集图像基本都通过Directshow框架来实现。
2、DirecShow 代码和原理介绍
2.1 ICaptureGraphBuilder2
因为graph manager 的接口的管理是比较复杂的,为了简化 IGraphBuilder 的使用, DirectShow 提供了一个名为 Capture Graph Builder 的辅助对象,ICaptureGraphBuilder2 接口对IGraphBuilder 进行封装。ICaptureGraphBuilder2 包含用于构建和控制捕获图的方法。
使用捕获图构建器
类图
初始化捕获图构建器
首先调用 CoCreateInstance 创建 Capture Graph Builder(ICaptureGraphBuilder2 ) 和 Filter Graph Manager (IGraphBuilder )的新实例。然后通过使用指向过滤器图管理器的IGraphBuilder接口的指针调用ICaptureGraphBuilder2::SetFiltergraph 来初始化捕获图生成器。下图说明了这个过程。
代码
HRESULT InitCaptureGraphBuilder(
IGraphBuilder **ppGraph, // Receives the pointer.
ICaptureGraphBuilder2 **ppBuild // Receives the pointer.
)
{
if (!ppGraph || !ppBuild)
{
return E_POINTER;
}
IGraphBuilder *pGraph = NULL;
ICaptureGraphBuilder2 *pBuild = NULL;
// Create the Capture Graph Builder.
HRESULT hr = CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL,
CLSCTX_INPROC_SERVER, IID_ICaptureGraphBuilder2, (void**)&pBuild );
if (SUCCEEDED(hr))
{
// Create the Filter Graph Manager.
hr = CoCreateInstance(CLSID_FilterGraph, 0, CLSCTX_INPROC_SERVER,
IID_IGraphBuilder, (void**)&pGraph);
if (SUCCEEDED(hr))
{
// 此处就是说明2者的关系。
pBuild->SetFiltergraph(pGraph);
// Return both interface pointers to the caller.
*ppBuild = pBuild;
*ppGraph = pGraph; // The caller must release both interfaces.
return S_OK;
}
else
{
pBuild->Release();
}
}
return hr; // Failed
}
2.2 DirectShow Video Capture Filters
pin 类别
video capture file 有2类的pin
- caputre pin : 带时间戳,会有一定的延迟,一般是编码和存储使用
- preview pin:不带时间错,为了实时显示,会丢帧,只是为了渲染使用
在详细的内容,大家可以自己看下微软官网DirectShow Video Capture Filters
2.3 获取设备信息
枚举设备
1、调用 CoCreateInstance 创建 System Device Enumerator 一个实例 ICreateDevEnum
2、 ICreateDevEnum::CreateClassEnumerator 并将设备类别指定为 GUID。对于捕获设备,以下类别是相关的。
3、CreateClassEnumerator方法返回一个指针IEnumMoniker接口。要枚举名字,请调用IEnumMoniker::Next。(Moniker 是 绰号的意思)
#include <windows.h>
#include <dshow.h>
#pragma comment(lib, "strmiids")
HRESULT EnumerateDevices(REFGUID category, IEnumMoniker **ppEnum)
{
// Create the System Device Enumerator.
ICreateDevEnum *pDevEnum;
HRESULT hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL,
CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pDevEnum));
if (SUCCEEDED(hr))
{
// Create an enumerator for the category.
hr = pDevEnum->CreateClassEnumerator(category, ppEnum, 0);
if (hr == S_FALSE)
{
hr = VFW_E_NOT_FOUND; // The category is empty. Treat as an error.
}
pDevEnum->Release();
}
return hr;
}
获取属性
设备含义如下:
“FriendlyName”:属性可用于每个设备。它包含设备的可读名称。
“Description”:属性仅适用于 DV 和 D-VHS/MPEG 摄像机设备。有关详细信息,请参阅MSDV 驱动程序和MSTape 驱动程序。如果可用,它包含比“FriendlyName”属性更具体的设备描述。通常它包括供应商名称。
“DevicePath”:属性不是人类可读的字符串,但保证对于系统上的每个视频捕获设备都是唯一的。您可以使用此属性来区分同一型号设备的两个或多个实例。
如果存在“WaveInID”属性,则表示 DirectShow 捕获过滤器在内部使用波形音频API 与设备进行通信。“WaveInID”:属性的值对应于waveIn*函数使用的标识符,例如waveInOpen。
代码示例
- 调用IMoniker::BindToStorage 获得IPropertyBag 的接口
- 调用IPropertyBag::Read 获取属性
void DisplayDeviceInformation(IEnumMoniker *pEnum)
{
IMoniker *pMoniker = NULL;
while (pEnum->Next(1, &pMoniker, NULL) == S_OK)
{
IPropertyBag *pPropBag;
HRESULT hr = pMoniker->BindToStorage(0, 0, IID_PPV_ARGS(&pPropBag));
if (FAILED(hr))
{
pMoniker->Release();
continue;
}
VARIANT var;
VariantInit(&var);
// Get description or friendly name.
hr = pPropBag->Read(L"Description", &var, 0);
if (FAILED(hr))
{
hr = pPropBag->Read(L"FriendlyName", &var, 0);
}
if (SUCCEEDED(hr))
{
printf("%S\n", var.bstrVal);
VariantClear(&var);
}
hr = pPropBag->Write(L"FriendlyName", &var);
// WaveInID applies only to audio capture devices.
hr = pPropBag->Read(L"WaveInID", &var, 0);
if (SUCCEEDED(hr))
{
printf("WaveIn ID: %d\n", var.lVal);
VariantClear(&var);
}
hr = pPropBag->Read(L"DevicePath", &var, 0);
if (SUCCEEDED(hr))
{
// The device path is not intended for display.
printf("Device path: %S\n", var.bstrVal);
VariantClear(&var);
}
pPropBag->Release();
pMoniker->Release();
}
}
void main()
{
HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (SUCCEEDED(hr))
{
IEnumMoniker *pEnum;
hr = EnumerateDevices(CLSID_VideoInputDeviceCategory, &pEnum);
if (SUCCEEDED(hr))
{
DisplayDeviceInformation(pEnum);
pEnum->Release();
}
hr = EnumerateDevices(CLSID_AudioInputDeviceCategory, &pEnum);
if (SUCCEEDED(hr))
{
DisplayDeviceInformation(pEnum);
pEnum->Release();
}
CoUninitialize();
}
}
如果要为一个设备创建一个Filter,设备IMoniker 指针,调用IMoniker::BindToObject 获得一个IBaseFilter 指针,然后调用IFilterGraph::AddFilter 加入到 Filter Graph中
IBaseFilter *pCap = NULL;
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pCap);
if (SUCCEEDED(hr))
{
hr = m_pGraph->AddFilter(pCap, L"Capture Filter");
}
3、预览视频
ICaptureGraphBuilder2 *pBuild; // Capture Graph Builder
// Initialize pBuild (not shown).
IBaseFilter *pCap; // Video capture filter.
/* Initialize pCap and add it to the filter graph (not shown). */
hr = pBuild->RenderStream(&PIN_CATEGORY_PREVIEW, &MEDIATYPE_Video,
pCap, NULL, NULL);
ICaptureGraphBuilder2::RenderStream方法的
第一个参数指定引脚类别;对于预览图,请使用PIN_CATEGORY_PREVIEW。
第二个参数指定媒体类型,作为主要类型 GUID。对于视频,请使用MEDIATYPE_Video。DV 设备提供交错的音频和视频,其媒体类型为MEDIATYPE_Interleaved。(有关 DV 捕获的更多信息,请参阅DirectShow 中的数字视频。)
第三个参数是指向捕获过滤器的IBaseFilter接口的指针。本示例中不需要接下来的两个参数。它们用于指定渲染流可能需要的其他过滤器。将最后一个参数设置为NULL会导致 Capture Graph Builder 根据媒体类型为流选择默认渲染器。对于视频,Capture Graph Builder 始终使用Video Renderer过滤器作为默认渲染器。
虽然 pin 类别被指定为PIN_CATEGORY_PREVIEW,但过滤器是否真的有预览 pin 并不重要;它可以有一个视频端口引脚或只是一个捕获引脚。无论哪种情况,Capture Graph Builder 都会自动构建正确的图形
4、捕获到文件
下图显示了将视频捕获到 AVI 文件的最简单的图形。
该AVI多路复用器过滤从捕捉引脚获取视频流并将其打包成AVI流。音频流也可以连接到 AVI Mux 过滤器,在这种情况下,mux 将交错两个流。该文件写入过滤器的AVI流写入到磁盘中。
要构建图形,首先调用ICaptureGraphBuilder2::SetOutputFileName方法,如下所示:
IBaseFilter *pMux;
hr = pBuild->SetOutputFileName(
&MEDIASUBTYPE_Avi, // Specifies AVI for the target file.
L"C:\\Example.avi", // File name.
&pMux, // Receives a pointer to the mux.
NULL); // (Optional) Receives a pointer to the file sink.
接下来,调用ICaptureGraphBuilder2::RenderStream方法将捕获过滤器连接到 AVI Mux,如下所示:
hr = pBuild->RenderStream(
&PIN_CATEGORY_CAPTURE, // Pin category.
&MEDIATYPE_Video, // Media type.
pCap, // Capture filter.
NULL, // Intermediate filter (optional).
pMux); // Mux or file sink filter.
// Release the mux filter.
pMux->Release();
5、Control Capture Graph
过滤器图形管理器的IMediaControl接口具有运行、停止和暂停整个图形的方法。但是,如果过滤器图具有捕获和预览流,您可能希望独立控制这两个流。例如,您可能想要预览视频而不捕获它。您可以通过ICaptureGraphBuilder2::ControlStream方法执行此操作。
以下代码将视频捕获流设置为运行四秒,在图形运行后一秒开始:
// Control the video capture stream.
REFERENCE_TIME rtStart = 10000000, rtStop = 50000000;
const WORD wStartCookie = 1, wStopCookie = 2; // Arbitrary values.
hr = pBuild->ControlStream(
&PIN_CATEGORY_CAPTURE, // Pin category.
&MEDIATYPE_Video, // Media type.
pCap, // Capture filter.
&rtStart, &rtStop, // Start and stop times.
wStartCookie, wStopCookie // Values for the start and stop events.
);
pControl->Run();