windows directshow使用lav filter开发h264码流摄像头


前言

如题,这篇文章主要说的是在windows系统上使用directshow库和lav filter库对h264编码的摄像头做预览功能,实际效果可以参照下图。
实际效果
先提供两个后面需要用到的下载链接:
lav filter下载地址:https://github.com/Nevcairiel/LAVFilters/releases
graph studio next下载地址:https://github.com/cplussharp/graph-studio-next/releases


准备环境

首先我们安装 lav filtergraph studio next,下载链接在上面已经提供。

  1. lav filter 这里建议下载 .zip 压缩包而不是 .exe 安装包。因为压缩包能直接提供我们需要的 dll动态库,安装程序还需要自己寻找,而且压缩包提供注册表安装脚本,如果目标机器没有安装 lav filter 我们可以使用代码调用安装脚本直接给机器安装上,而不需要手动安装。
  2. 安装 graph studio next 是因为安装了 lav filter 之后就没办法使用自带的调试工具 graphedt.exe。好像是 graphedt.exe 的bug,安装了 lav filter 之后展开filter项程序会直接崩溃,所以使用 graph studio next 代替。

注意:如果这里程序是32位的就下载32位的 lav filter 和 32位的 graph studio next 否则无法正常使用。

开发流程

一、使用工具预览效果

在开发我们自己的代码之前,我们可以先用 graph studio next 设计图的流程、过滤器的参数和预览实际效果,这能让我们极大提高效率。就以预览 h264 码流摄像头为例,图流程设计可以参考下图。
图流程
lav配置
双击 Lav Video Decoder 可以打开配置界面,设计好流程和配置好参数后,点运行按键预览实际效果。当预览效果符合我们实际需求时就可以开始写代码了。

二、编写程序

1.摄像头预览

先提供官方的预览Demo:https://learn.microsoft.com/zh-cn/windows/win32/directshow/previewing-video
其实编写程序并不难,流程就跟用 graph studio next 调试一样,只是手动的操作编程使用代码控制而已。大体流程是:选择设备 -> 创建ICaptureGraphBuilder2 -> 创建IGraphBuilder -> 创建过滤器 -> IGraphBuilder添加过滤器 -> ICaptureGraphBuilder2设置IGraphBuilder -> 呈现。

  1. 枚举和选择设备(使用官方代码)
    #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;
    }
    
  2. 创建各种实例(创建所需要的实例)
    // 创建实例使用 CoCreateInstance 函数
    // 查询实例属性使用 QueryInterface 函数
    // 具体可以在微软文档查找更多信息。
    
    // 创建实例,这里只提供一个做参考
    IGraphBuilder *pGraphBuilder = nullptr;
    HRESULT hr = CoCreateInstance(CLSID_FilterGraph, 
    							  nullptr,
    							  CLSCTX_INPROC_SERVER,
                          		  IID_IGraphBuilder,
                          		  (void **)&pGraphBuilder);
                          		  
    // 查询实例属性,也只提供一个做参考
    IVideoWindow *pVideoWindow = nullptr;
    hr = pGraphBuilder->QueryInterface(IID_IVideoWindow, (LPVOID *) &pVideoWindow);
    
  3. 创建和添加过滤器
    IBaseFilter *pLavVideoFilter = nullptr;
    hr = CoCreateInstance(CLSID_LAVVIDEODECODER,
    					  nullptr,
    					  CLSCTX_INPROC_SERVER,
    					  IID_IBaseFilter,
                         	  (void **) &pLavVideoFilter);
                         	  
    // 向 IGraphBuilder 添加过滤器
    hr = pGraphBuilder->AddFilter(pLavVideoFilter,
    							  L"LAV Video Decoder Filter");
                              	  
    // 这里可以通过 QueryInterface 函数查询 lav 的接口从而配置 lav
    // 这里要将 lav 解压后里面 include 的 LAVVideoSettings.h 包含进来
    // 更多配置接口可以看 LAVVideoSettings.h 源码
    ILAVVideoSettings *pLavSettings = nullptr;
    hr = pLavVideoFilter->QueryInterface(IID_ILAVVideoSettings,
    									 (void **)&pLavSettings);
    hr = pLavSettings->SetHWAccel(HWAccel_DXVA2Native);
    
  4. ICaptureGraphBuilder2设置IGraphBuilder
    ICaptureGraphBuilder2 *pCaptureGraphBuilder2 = nullptr;
    hr = CoCreateInstance(CLSID_CaptureGraphBuilder2,
    					  nullptr,
    					  CLSCTX_INPROC_SERVER,
                          IID_ICaptureGraphBuilder2,
                          (void **)&pCaptureGraphBuilder2);
                          
    // ICaptureGraphBuilder2 设置 IGraphBuilder
    // IGraphBuilder 实际上就是图流程
    hr = pCaptureGraphBuilder2->SetFiltergraph(pGraphBuilder);
    
  5. 创建渲染器并渲染
    //CLSID_VideoMixingRenderer9 !CLSID_EnhancedVideoRenderer
    IBaseFilter *pMixingFilter = nullptr;
    hr = CoCreateInstance(CLSID_VideoMixingRenderer9,
    					  nullptr,
    					  CLSCTX_INPROC_SERVER,
    					  IID_IBaseFilter,
                          (void **) &pMixingFilter);
                          
    // 将呈现过滤器添加到 IGraphBuilder 中
    hr = pGraphBuilder->AddFilter(pMixingFilter ,
    						      L"EVR Filter");
    
    // 呈现渲染
    hr = pCaptureGraphBuilder2->RenderStream(&PIN_CATEGORY_PREVIEW,
                                             &MEDIATYPE_Video,
                                             // 这个是枚举到的设备
                                             pVideoCaptureFilter,
                                             nullptr,
                                             m_pMixingFilter);
    

最后选择 HWND 设置渲染视频的窗口就可以了,上面提供的代码是按照流程拆开的,实际上是代码是先:创建实例 -> 查询配置接口 -> 设置过滤器 -> 设置IGraphBuilder -> 呈现 这个流程实现的。

2.参数位图

我们可以在摄像头的预览窗口添加位图,显示一些摄像头的参数。

  1. 获取摄像头参数
    // 位图的操作对象在呈现器中查询
    IVMRMixerBitmap9 *pBMP = nullptr;
    hr = pMixingFilter->QueryInterface(IID_IVMRMixerBitmap9,
    								   (void**)&pBMP);
    
    // 获取视频分辨率和码流类型
     CComPtr<IAMStreamConfig> pStreamConfig = nullptr;
     hr = pCaptureGraphBuilder2->FindInterface(nullptr, &MEDIATYPE_Video, pVideoCaptureFilter,
                                               IID_IAMStreamConfig, (void **) &pStreamConfig);
    AM_MEDIA_TYPE *pmtConfig;
    hr = pStreamConfig->GetFormat(&pmtConfig);
    // 码流类型
    // subType: MEDIASUBTYPE_MJPG|MEDIASUBTYPE_H264
    GUID subType = pmtConfig->subtype;
    // 分辨率
    VIDEOINFOHEADER *pVih = reinterpret_cast<VIDEOINFOHEADER *>(pmtConfig->pbFormat);
    pVih->bmiHeader.biWidth;
    pVih->bmiHeader.biHeight;
    
    // 在呈现器中查询视频的平均帧
    IQualProp *pQual = nullptr;
    hr = pMixingFilter->QueryInterface(IID_IQualProp, (void**)&pQual);
    
    // 获取平均帧
    int rate;
    pQual->get_AvgFrameRate(&rate);
    
  2. 添加位图
    LONG hr;
    HWND hwndApp = (HWND)winId();
    const TCHAR *szNewText = L"TEST";
    
    HDC hdc = GetDC(hwndApp);
    HDC hdcBmp = CreateCompatibleDC(hdc);
    
    // 计算字符实际大小
    int nLength, nTextBmpWidth, nTextBmpHeight;
    SIZE sz={0};
    nLength = (int) _tcslen(szNewText);
    GetTextExtentPoint32(hdcBmp, szNewText, nLength, &sz);
    nTextBmpHeight = sz.cy;
    nTextBmpWidth  = sz.cx;
    
    // 创建对象
    HBITMAP hbm = CreateCompatibleBitmap(hdc, nTextBmpWidth, nTextBmpHeight*qslPrama.size());
    ReleaseDC(hwndApp, hdc);
    
    HBITMAP hbmOld = (HBITMAP)SelectObject(hdcBmp, hbm);
    
    // 设置底色
    RECT rcText;
    SetRect(&rcText, 0, 0, nTextBmpWidth, nTextBmpHeight*qslPrama.size());
    SetBkColor(hdcBmp, RGB(0, 0, 0));
    SetTextColor(hdcBmp, RGB(0, 255, 0));
    
    // 实际展示内容
    szNewText = L"Your TEXT";
    nLength = (int) _tcslen(szNewText);
    TextOut(hdcBmp, 0, nTextBmpHeight*i, szNewText, nLength);
    
    VMR9AlphaBitmap bmpInfo;
    ZeroMemory(&bmpInfo, sizeof(bmpInfo) );
    bmpInfo.dwFlags = VMRBITMAP_HDC;
    bmpInfo.hdc = hdcBmp;
    
    //调整显示位置
    bmpInfo.rDest.left  = 0.0f;
    bmpInfo.rDest.right = 0.3f;
    bmpInfo.rDest.top = 0.0f;
    bmpInfo.rDest.bottom = 0.2f;
    bmpInfo.rSrc = rcText;
    
    bmpInfo.fAlpha = 0.6f;
    setColorRef(bmpInfo);
    
    hr = m_pBMP->SetAlphaBitmap(&bmpInfo);
    if (FAILED(hr))
        qDebug() << tr("SetAlphaBitmap FAILED!  hr=%1").arg(hr);
    
    DeleteObject(SelectObject(hdcBmp, hbmOld));
    
    DeleteObject(hbm);
    DeleteDC(hdcBmp);
    
    return hr;
    

3.开发技巧

这里提供一些结合 graph studio next 调试工具开发的技巧。在 graph studio next 里面的过滤器大多数都是可以双击点开属性的,里面能快捷的获取到很多我们写程序时需要的信息,就不需要再去官网查询,例如:CLSID,IID,头文件,文档链接等。可以参考下图(以 Video Mixing Renderer 9 呈现器为例)。
属性
CLSID
Interface
整个开发流程基本上就是这样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值