解决方案:dshow拉H264流+h264_qsv硬解码+d3d渲染

一 拉H264流

最近发现FFmpeg不支持从USB摄像头拉H264,只能拉到MJPEG流。而MJPEG流又不能用来推流,目测只能用H264推流。所以下面直接用DirectShow拉H264流。

dshow的用法可以参考msdn和amcap源码。

这里我创建了个CaptureWorker类,继承ISampleGrabberCB。这样就能在回调函数BufferCB中拿到H264数据包。

class CaptureVideo : public QObject, public ISampleGrabberCB
{Q_OBJECTpublic:CaptureVideo(QObject *parent = nullptr);~CaptureVideo(void);STDMETHODIMP_(ULONG) AddRef() override { return 2; }STDMETHODIMP_(ULONG) Release() override { return 1; }STDMETHODIMP QueryInterface(REFIID riid, void ** ppv) override {if (riid == IID_ISampleGrabberCB || riid == IID_IUnknown) {*ppv = (void *) static_cast<ISampleGrabberCB*> (this);return NOERROR;}return E_NOINTERFACE;}STDMETHODIMP SampleCB(double SampleTime, IMediaSample * pSample) override;STDMETHODIMP BufferCB(double dblSampleTime, BYTE * pBuffer, long lBufferSize) override;HRESULT OpenDevice(IMoniker *pMoniker);void Close();                   // close all interfaceHRESULT ExtensionCtrl(int type, ULONG flag, byte* data, ULONG length);IMoniker *GetMoniker();HRESULT StartPreview();HRESULT StopPreview();void StartRecord();void StopRecord();HRESULT RequestKeyFrame(BYTE byStream);HRESULT OpenStream(BYTE byStream);HRESULT CloseStream(BYTE byStream);HRESULT SetIDR(BYTE byStream, WORD wIDR);HRESULT SetBitrate(BYTE byStream, BYTE byRCMode, BYTE byMinQP, BYTE byMaxQP, UINT nBitrate);HRESULT SetProfile(BYTE byStream, BYTE byProfile, BYTE byFrameRateMode);HRESULT SetVideoSize(BYTE byStream, WORD wWidth, WORD wHeight);HRESULT SetFrameRate(BYTE byStream, BYTE byFrameRate);HRESULT SetEncodeFormate(BYTE bySteam, BYTE byEncode);bool initDecoder();bool yuv2Rgb(uchar *out, int outWidth, int outHeigh);bool initD3D_NV12(HWND hwnd, int img_width, int img_height);bool initD3D_YUVJ420P(HWND hwnd, int img_width, int img_height);bool setParams();private:HRESULT InitializeEnv();              // initialize environmentHRESULT BindCaptureFilter();void TearDownGraph();HRESULT BuildPreviewGraph();HRESULT BuildPreviewGraphEx();HRESULT InitSampleGrabber();signals:void updateImage(QPixmap img);void finished();public slots:void process();private:IGraphBuilder *m_pGraphBuilder;   // filter graph managerICaptureGraphBuilder2 *m_pCaptureGraphBuilder;IMediaControl *m_pMediaControl;    // 控制filter graph中的数据流IBaseFilter *m_pVCap;IKsControl *m_pKsCtrl;IMediaEventEx *m_pME;IMoniker *m_pMoniker; // 设备别名IBaseFilter* m_pGrabberFilter;ISampleGrabber *m_pSampleGrabber;IPin *m_pPinOutCapture;IPin *m_pPinInGrabber;//IPin *m_pPinOutGrabber;//#ifdef __ENABLE_RECORD__//CFile m_fileRecorder;BOOL m_bFileOpen;BOOL m_bFirstKeyFrame;
//#endif // __ENABLE_RECORD__bool m_bRecord;AVCodecContext *m_vDecodeCtx = nullptr;AVFrame *m_yuvFrame = nullptr;AVFrame *m_rgbFrame = nullptr;uint8_t * m_rgbFrameBuf = nullptr;SwsContext *m_swsCtx = nullptr;int m_dstWinWidth; // 目标窗口宽度int m_dstWinHeight;    // 目标窗口高度CD3DVidRender m_d3d;public:bool m_bPreview;
};
/* 数据回调 */
STDMETHODIMP CaptureVideo::BufferCB(double dblSampleTime, BYTE * pBuffer, long lBufferSize) {
#ifdef __ENABLE_RECORD__//if (m_bFileOpen)//{// m_fileRecorder.Write(pBuffer, lBufferSize);//}#endif // __ENABLE_RECORD__qDebug() << QString("dblSampleTime: %1, lBufferSize: %2").arg(dblSampleTime).arg(lBufferSize);//return 0;static int s_dropFrame = 2;if (s_dropFrame > 0){--s_dropFrame;return 0;}AVPacket pkt;av_init_packet(&pkt);pkt.data = pBuffer;pkt.size = lBufferSize;pkt.stream_index = 0;QTime t = QTime::currentTime();int re = 1;//while (re)//{re = avcodec_send_packet(m_vDecodeCtx, &pkt);if (0 != re) // 如果发送失败{qDebug() << QString("avcodec_send_packet failed, %1, pkt.size: %2").arg(parseError(re)).arg(lBufferSize);return 0;}re = avcodec_receive_frame(m_vDecodeCtx, m_yuvFrame);if (0 != re) // 如果解码失败{qDebug() << QString("avcodec_receive_frame failed, %1, pkt.size: %2").arg(parseError(re)).arg(lBufferSize);//if (AVERROR(EAGAIN) != re)//{return 0;//}}//}static bool s_singleshot = false;if (!s_singleshot){s_singleshot = true;//avcodec_flush_buffers(m_vDecodeCtx);}qDebug() << QStringLiteral("解码耗时:%1,frame->pkt_size: %2").arg(t.elapsed()).arg(m_yuvFrame->pkt_size);//av_hwframe_transfer_datareturn 0;
}

然后将pBuffer和lBufferSize填入AVPacket,拿到AVPakcet后一面可以直接拿去推流,另一面可以解码得到AVFrame。

二 硬解码

实践发现,FFmpeg软解码(编码也是)在低配机器(主要是CPU)跑太耗时间和CPU占用率了,特别是解码1080P,解码一帧可能要几十甚至上百毫秒,这就知道导致非常高的延迟了。要保证FPS是30,整个解码显示的过程不能超过33.33毫秒。使用GPU硬解码,即使是低配机器也能保证1080p解码耗时低。

可以通过以下命令查看ffmpeg支持的h264硬解码器

ffmpeg -codecs | findstr "h264"

(decoders: h264 h264_qsv h264_cuvid ) (encoders: libx264 libx264rgb h264_amf h264_nvenc h264_qsv nvenc nvenc_h264 )

intel GPU用h264_qsv,nvidia GPU用h264_cuvid

FFmpeg硬解码代码和软解码差不到,主要区别是查找解码器:

decoder = avcodec_find_decoder_by_name("h264_qsv");

一般硬解码出来的帧好像都是NV12格式

三 d3d渲染

解码得到的帧,如果是硬解码是NV12格式,软解码一般是YUV格式,目前我的USB摄像头是YUVJ420P格式。但是Qt的QImage只支持RGB格式,所以要通过sws_scale转换像素格式。

同样FFmpeg的sws_scale这个API也是非常占用CPU资源的,刚开始我用libyuv代替sws_scale,libyuv耗时会低一点,但是CPU资源占用和sws_scale差不到,我的电脑跑release版本都占用10%多的CPU,这在低配机器就可定不止了。所以后面我改用d3d直接渲染nv12帧,这样就不需要经过像素格式转换,节省CPU资源。

调用方法很简单,InitD3D_NV12初始化,传一个QLabel的句柄和宽高,Render_NV12传AVFrame->data数据。

BOOL CD3DVidRender::InitD3D_NV12(HWND hwnd, int img_width, int img_height)
{m_nColor = 1;HRESULT lRet;if (m_pDirect3D9){m_pDirect3D9->Release();m_pDirect3D9 = NULL;}m_pDirect3D9 = Direct3DCreate9( D3D_SDK_VERSION );  if( m_pDirect3D9 == NULL ) {return FALSE; }ZeroMemory( &d3dpp, sizeof(d3dpp) ); d3dpp.Windowed = TRUE;  d3dpp.hDeviceWindow = hwnd;d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;  d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; d3dpp.EnableAutoDepthStencil = FALSE;d3dpp.Flags = D3DPRESENTFLAG_VIDEO;d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; D3DCAPS9 caps;DWORD BehaviorFlags = D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED;HRESULT hr = m_pDirect3D9->GetDeviceCaps ( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ;if ( SUCCEEDED(hr) ){if ( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ){BehaviorFlags = D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE ;}else{BehaviorFlags = D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE;}}if (m_pDirect3DDevice){m_pDirect3DDevice->Release();m_pDirect3DDevice = NULL;}lRet = m_pDirect3D9->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, BehaviorFlags, &d3dpp, &m_pDirect3DDevice );if(FAILED(lRet)) {return FALSE; }if (m_pDirect3DSurfaceRender){m_pDirect3DSurfaceRender->Release();m_pDirect3DSurfaceRender = NULL;}lRet = m_pDirect3DDevice->CreateOffscreenPlainSurface( img_width, img_height, (D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2'), D3DPOOL_DEFAULT, &m_pDirect3DSurfaceRender, NULL); if(FAILED(lRet))  {return FALSE;}return TRUE;
}BOOL CD3DVidRender::Render_NV12(unsigned char * pdata, int img_width, int img_height)
{HRESULT lRet = -1; D3DLOCKED_RECT d3d_rect;  lRet = m_pDirect3DSurfaceRender->LockRect(&d3d_rect, NULL, D3DLOCK_DONOTWAIT);  if(FAILED(lRet))  {return FALSE; }byte *pSrc = pdata;  byte * pDest = (BYTE *)d3d_rect.pBits;  int stride = d3d_rect.Pitch;  unsigned long i = 0;  for(i = 0; i < img_height; i ++){  memmove(pDest + i * stride,pSrc + i * img_width, img_width);  }  for (i = 0; i < img_height/2; i++){memmove(pDest + stride*img_height + i * stride, pSrc + img_width*img_height + i * img_width, img_width);}lRet = m_pDirect3DSurfaceRender->UnlockRect();   if(FAILED(lRet))  {return FALSE; }if (!m_pDirect3DDevice)return false;m_pDirect3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,0), 1.0f, 0 ); m_pDirect3DDevice->BeginScene(); if (m_pBackBuffer){m_pBackBuffer->Release();m_pBackBuffer = NULL;}m_pDirect3DDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &m_pBackBuffer); GetClientRect(d3dpp.hDeviceWindow, &m_rtViewport);m_pDirect3DDevice->StretchRect(m_pDirect3DSurfaceRender, NULL, m_pBackBuffer, &m_rtViewport, D3DTEXF_LINEAR);m_pDirect3DDevice->EndScene(); m_pDirect3DDevice->Present( NULL, NULL, NULL, NULL ); RECT rcCurrentClient;GetClientRect(d3dpp.hDeviceWindow, &rcCurrentClient);if (rcCurrentClient.bottom>0 && rcCurrentClient.right>0 && ((UINT)rcCurrentClient.right != d3dpp.BackBufferWidth ||( UINT )rcCurrentClient.bottom != d3dpp.BackBufferHeight )){d3dpp.BackBufferWidth = rcCurrentClient.right;d3dpp.BackBufferHeight = rcCurrentClient.bottom;InitD3D_NV12(d3dpp.hDeviceWindow, img_width, img_height);}if (m_pDirect3DDevice && m_pDirect3DDevice->TestCooperativeLevel() == D3DERR_DEVICENOTRESET){InitD3D_NV12(d3dpp.hDeviceWindow, img_width, img_height);}return TRUE;
}

将Qt控件交给native渲染要添加以下两个标志:

 // 避免闪屏ui.imgLbl->setUpdatesEnabled(false);// 避免程序无响应(鼠标指针转圈)ui.imgLbl->setAttribute(Qt::WA_NativeWindow, true);

源码

适用于低配机器,从USB摄像头拉H264流的Qt播放器相关推荐

  1. usb dac Linux免驱,飞傲播放器系列USB DAC全新专用驱动玩法攻略

    针对全新USB DAC驱动的玩法给大家介绍一下,相比原来的驱动用起来会更加方便,播放器不插电脑就可以直接安装DAC驱动,安装好之后,将播放器设置为DAC模式连接电脑即可直接识别,本驱动适用于X3.X5 ...

  2. r7 270 linux,装个puppy linux 低配机器也能流畅运行

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 这些日子正折腾一台单位的老机器 翻了许多网站 提供大家这个软件 线程动态调节还有负载平衡软件 ProcessLasso_Portable_32 Proce ...

  3. C#Winform开发可播放摄像头及任意格式视频的播放器

    准备工作 首先,我们创建一个项目WpfVLC,然后,进入Neget搜索Vlc.DotNet,得到如下界面: 我们选择Vlc.DotNet.Wpf,点击安装(这里我已经安装了,所以图中显示为卸载). 然 ...

  4. 和平精英手搓别扭?低配电脑又带不动模拟器?教你一招轻松搞定

    自从刺激战场下架.和平精英上线之后,虽然有不少玩家吐槽甚至转战国际服,但是丝毫不影响和平精英在ios和安卓应用市场手游下载量第一.其实和平精英基本上是还原了80%以上的刺激战场,除了操作感没有那么顺手 ...

  5. WinCE平台USB摄像头驱动开发

    (转载)http://tech.e800.com.cn/articles/2009/116/1257487620781_1.html 由于良好的性能.低廉的价格和灵活方便的特性,USB 摄像头正被广泛 ...

  6. 浅谈WinCE平台USB摄像头驱动开发流程

    转自http://tech.e800.com.cn/articles/2009/116/1257487620781_1.html 由于良好的性能.低廉的价格和灵活方便的特性,USB 摄像头正被广泛的集 ...

  7. CM10.1源码的下载和编译(支持CyanogenMod官方列出的机型和三星S5660、三星S5830、LG-P509等低配机型)...

    不得不说,Android升级的速度非常之快,手机厂商也在不停的追赶Android的步伐,力求更快更多的抢占市场!每次Android升级之后,用不了多久,三星.HTC等一些大厂商就会发布搭载最新andr ...

  8. OpenCV 打开USB摄像头帧率低问题解决

    采用OpenCV对USB摄像头进行视频图像抓取时,如果图像帧抓取帧率低,可采用以下方式提高帧率. cv::VideoCapture camera_capture; camera_capture.ope ...

  9. OpenCV+V4L实现MJPG格式拉取USB摄像头

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.为什么YUV帧数低? 二.OpenCV+V4l编译 1.安装必要环境 2.下载源代码 3.开始编译 3.测试代码 ...

  10. linux系统下 USB 摄像头1080分辨率采集帧率低问题的解决方法

    linux系统上一般使用 video4linux2来操作系统摄像. 1.安装一个 v4l2-ctrl工具: sudo apt install v4l-utils 2.查看摄像头支持的视频参数 sudo ...

最新文章

  1. 深度丨走向人工智能时代,百度的延承和蜕变
  2. 求伯君:向暴雪学习 金山不求一夜暴富
  3. asp.net记录错误日志的方法
  4. 分组后统计总数_大数据时代看排球:排球技术统计能告诉你什么?
  5. javascript 获取控件坐标
  6. 【HNOI2017】礼物
  7. 条码的应用在管理上具有便捷的特点
  8. MySQL 5.7临时表空间
  9. vue基础之v-for,key
  10. cmake 学习笔记(三)
  11. 大学python笔记整理_python 笔记整理
  12. java面试题关于servlet_关于Java servlet的面试题目
  13. 深度学习常用数据集汇总
  14. 侙程序错误怎么找c语言,log4j 施用 - 汉字转换成拼音的种(转) - 遏止EditText弹出输入法_169IT.COM...
  15. matlab gui设计入门与实战,matlab gui编程教程
  16. Python将单一数字标签进行one-hot编码
  17. Vue3中TSX和h函数的用法
  18. 印象笔记:部分Mac用户因为故障而丢失数据
  19. 是不是顺子【C语言保姆级讲解】
  20. 学习Python很难?过来人给你分享学习经验

热门文章

  1. Matlab里的数据类型
  2. 淘宝客淘点金代码自动生成跳转
  3. wav转换mp3简单图文教程
  4. plc变频器c语言,PLC控制变频器的几种方法
  5. 视频教程-Excel项目实战从入门到精通(兼容2007、2010、2013、2016)-Office/WPS
  6. Android同步时出错,Android Studio中的Gradle给出错误项目同步失败
  7. 【ENVI遥感影像分类】 监督、非监督分类
  8. base ring shell skirt skirt 压力容器_压力容器工程规定(中英文版)
  9. 【URLOS应用开发基础】10分钟制作一个nginx静态网站环境应用
  10. 【阿里云生活物联网架构师专题 ⑥】ESP8266接入阿里生活飞燕平台国际版,实现亚马逊Alexa Echo音响语音控制;