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

一、前言

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线程中处理图像。但这种说法并不能完全立足,如果大家有兴趣,也可以告知或推断可能的原因。

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

你可能感兴趣的:(C++/Qt)