Qt+libvlc+rtsp:视频层上绘图探究

来源:互联网 发布:触宝输入法软件 编辑:程序博客网 时间:2024/06/05 16:41

一、前言

1.起源

最近负责项目中的一个部分,需要从摄像头获取rtsp流,同时作tcp的客户端收取人脸识别的信息,用qt显示视频并绘制人脸识别框。

2.成果


3.环境

系统环境:windows 10
Qt版本:5.8.0
vlc版本:2.2.4(这里建议使用libvlc 3以上的版本,3以下很难找到debug库,windows下自行编译较麻烦)
开发工具:vs2015

4.致谢与声明

向本文所有引用的作者表示感谢,也要感谢帮助过我的朋友们,正是他们的分享开源精神帮助我解决了问题。
同时感谢菊花怪,从学习编程至今一直互勉,包括本次的程序也是在他的帮助下,深夜解决的。

在此声明:本文对所引用的所有文章的主观观点、言论,均不负法律责任。

二、准备

1.了解视频传输的原理

如果你对视频传输充满兴趣,可以参考以下博文:

(1)流媒体客户端的传送原理

http://blog.csdn.net/jianren994/article/details/8133395

(2)RTSP/RTP媒体传输和控制协议

http://blog.csdn.net/ww506772362/article/details/52609379

(3) RTSP详解

http://blog.csdn.net/yangzhiloveyou/article/details/10161269

2.为什么使用vlc

vlc是开源的多媒体播放器及框架,它支持大多数的媒体格式播放,体积虽不算非常精简,但功能非常强大。
你可以访问VideoLAN的官方网站,并且下载这款播放器:http://www.videolan.org/
不仅仅是体验,这对你的测试工作也非常有帮助。

3.下载并配置libvlc

本文不提供下载地址,不过你可以非常容易找到libvlc的sdk,将include/和lib/放入工程目录,配置你的项目属性。
请不要忘记与sdk同目录下的plugins文件夹,它是libvlc发挥能力的重要基础,请将它放在程序的输出目录下。

三、开始

1.使用库播放一个rtsp流视频

首先,你需要这么几个变量:
libvlc_instance_t *_inst;// libvlc的运行实例libvlc_media_t *_media;// 用于准备播放的媒体libvlc_media_player_t *_media_player;// 用于使用vlc媒体播放器

(1)初始化你的rtsp流媒体

值得注意的是,用于初始化本地媒体与rtsp流媒体所调用的api并不相同。
_media = libvlc_media_new_location(_inst, addr);

(2)为播放添加一些配置

(未实测)对于实时的rtsp流可能存在延迟,添加如下代码可能会减少延迟。
libvlc_media_add_option(_media, ":network-caching=300 :live-caching=300;");

(3)初始化播放器

_media_player = libvlc_media_player_new_from_media(_media);
记得同时释放你的媒体:
libvlc_media_release(_melibvlc_media_release(_media);

(4)播放媒体

libvlc_media_player_play(_media_player);

(5)rtsp测试链接

通过上述这些步骤,如果rtsp链接没有问题的话,你应该可以在桌面上看见媒体啦。
这里提供几条rtsp测试链接:

①某路段监控视频(非实时,1080P)

rtsp://202.104.126.35/demo?from=2017-02-07

②某节目(实时,240P)

rtsp://rtsp-v3-spbtv.msk.spbtv.com/spbtv_v3_1/214_110.sdp

③一段可爱的动画(非实时,160P)

rtsp://rtsp-v3-spbtv.msk.spbtv.com/spbtv_v3_1/214_110.sdp

2.嵌入播放窗口到你的界面

看到这里,如果你能成功播放媒体,那说明基本的环境配置都没有问题,放心大胆地走下去吧。
对于大多数开发者来说,接下来应该把它嵌入到自己的界面中啦!

(1)如果你仅仅是作为播放器

那么我建议你使用API:
libvlc_media_player_set_hwnd(_media_player, (void*)_wdgPlayer->winId());
这是最方便、快捷,并且稳定的方法,满足一般的开发需求。
至此,你可以将本文看作一篇libvlc如何连接到rtsp流媒体的简明小提示,后续内容仅供了解即可。

(2)如果你需要对视频进行二次开发

比如我的例子中添加了人脸识别的边框,或者类似的标注。更重要的是,如果你想要实现半透明在视频上提示内容,那么请继续向下看。
我尝试过各种方法,如:设置透明背景、加入QStackLayout、使用QGraphicsscene画异形窗口啊等等…
与此类似的问题:

①在qt+mplayer播放视频上层设置半透明窗体,为什么窗体透明不了,变成黑色了

http://bbs.csdn.net/topics/390482549

②QT中实现多层Widget,而下层播放视频处于不断重绘的状态,怎么实现?

http://www.qtcn.org/bbs/simple/?t58461.html

相信读到这里的大家应该也焦头烂额,找不到更好的办法。
我在程序开发时遇到的问题:为了在视频上添加透明内容,我先创建了一个QWidget,设置为透明,并raise()。运行现象是的确透明了,但也确确实实从视频“穿越”了过去。
这里看到的背景色是最底层的widget颜色。

可能的解释是:使用了这段代码后,widget的句柄交给了windows下的directX绘制。
libvlc_media_player_set_hwnd(_media_player, (void*)_wdgPlayer->winId());
要想解决这个办法:可以选择不使用这个api,自行绘制;也可以生成多个hwnd进行绘制(windows下不太了解,期间帮助过的朋友曾提过这个解决方案,不知是否可行,还希望能得到验证)。

3.将绘图统统交给Qt

那么,在不使用api的前提下,如何绘制呢?术语不甚了解还请见谅,这里我就说的通俗易懂些:
对于视频来说,由一幅幅画面组成,我们将每一张图称之为“帧”。每秒钟传输了多少帧(FPS)是评价视频是否流畅的标准。
不了解如何编码解码没关系,libvlc在前面所做的那些工作已经为我们铺垫好了基础。既然如此,我们只要取出帧,并且绘制即可。
仔细查看libvlc的api,发现可取出帧并操作的方法:
void VideoSetCallBacks(IntPtr mediaPlayInstance, VideoLockCB lockCB, VideoUnlockCB unlockCB, VideoDisplayCB displayCB, IntPtr opaque);IntPtr VideoLockCB(IntPtr opaque, IntPtr planes);void VideoUnlockCB(IntPtr opaque, IntPtr picture, IntPtr planes);void VideoDisplayCB(IntPtr opaque, IntPtr picture);
以下是我修改后的一部分代码:
typedef struct{QMutex mutex;CVLCPainter *painter;uchar *pixels;}callback_param_t;static void* lock(void* op, void** plane){callback_param_t *p = (callback_param_t *)op;p->mutex.lock();*plane = p->pixels;return NULL;}static void unlock(void* op, void* pic, void* const* plane){callback_param_t *p = (callback_param_t *)op;uchar* pp = (uchar*)*plane;unsigned char* data = (unsigned char*)*plane;QImage a(data, gWidth, gHeight, QImage::Format_RGBA8888);p->painter->updatePic(a);p->mutex.unlock();}static void display(void* op, void* pic) {}
初始化图像缓冲区(建议使用其他数据结构代替数组)
void CVLCPlayer::init_vlc_param(){param = new callback_param_t;param->painter = _vlcPainter;param->pixels = new uchar[gWidth * gHeight * 4];memset(param->pixels, 0, gWidth * gHeight * 4);}
updatePic函数
void CVLCPainter::updatePic(QImage &img){_pixmap = QPixmap::fromImage(img);_image = img;update();}
在widget中渲染:
void CVLCPainter::paintEvent(QPaintEvent *event){_mutex.lock();QPainter painter(this);if (_pixmap.isNull()){_mutex.unlock();return;}v_width = this->rect().width();multi = (float)v_width / (float)gWidth;multi = QString::number(multi, 'f', 2).toFloat();v_height = (int)((float)gHeight * multi);o_height = (this->rect().height() - v_height) / 2;painter.drawImage(QRect(0, o_height, v_width, v_height), _image);//painter.drawPixmap(QRect(0, o_height, v_width, v_height), _pixmap);if (_vecRect.count() > 0 && isFaceDist){painter.setPen(Qt::red);for (int i = 0; i < _vecRect.count(); ++i)painter.drawRect(*_vecRect.at(i));}_mutex.unlock();}

以上原理均可查看博文:http://www.cnblogs.com/smartsensor/p/4343769.html
在这里提供一篇贴子,并感谢作者提供的源码:http://bbs.csdn.net/topics/390817375

三、新问题的出现、解决与再探

使用drawPixmap()绘图可能导致程序崩溃?
在我的机器上出现了崩溃的问题,由于是release版本只能追踪到drawPixmap(),替换为drawImage()后问题不再出现。通过对于windows下两者的差异,可能是由于QImage独立于硬件,并且它也是一种QPaintDevice,不需要在UI线程中处理图像。但这种说法并不能完全立足,如果大家有兴趣,也可以告知或推断可能的原因。

博主的能力有限、经验尚浅,本文可能存在诸多问题,仅供解决问题的参考方向。如果发现错误,也请给予指正,谢谢。