天天看点

qt designer设置顶级布局后混乱_Qt编写的RTSP播放器+视频监控(ffmpeg版本)一、前言二、实现的功能三、效果图四、核心代码五、其他说明

一、前言

记得四年前就写了个简易版本的,当时写得非常粗糙,也比较吃内存,代码实在惨不忍睹,时隔多年后,重新写了个版本,同时还解决了以前不支持1画面+4画面+6画面+8画面+13画面+16画面切换等异形布局的问题。这次直接将所有的处理全部封装到一个类中,这样用起来就很方便,直接引入一个头文件即可,而且整个控件继承自qwidget,也可以界面上放一个widget鼠标右键提升为即可。

二、实现的功能

* 1:多线程实时播放rtsp视频流

* 2:支持X86和嵌入式linux

* 3:多线程显示图像,不卡主界面

* 4:自动重连网络摄像头

* 5:可设置边框大小即偏移量和边框颜色

* 6:可设置是否绘制OSD标签即标签文本或图片和标签位置

* 7:可设置两种OSD位置和风格

* 8:可设置是否保存到文件以及文件名

* 9:可设置间隔时间段保存文件到指定目录

* 10:可播放本地视频文件,支持设置帧率

* 11:支持h265视频流+rtmp等常见视频流

* 12:可暂停播放和继续播放

* 13:支持定时存储文件,包括音频和视频

* 14:支持sdl播放音频

* 15:支持外部拖曳文件+拖曳节点数据进行播放

* 16:自定义顶部悬浮条,发送单击信号通知,可设置是否启用

* 17:支持qsv dxva d3d 硬解码

三、效果图

qt designer设置顶级布局后混乱_Qt编写的RTSP播放器+视频监控(ffmpeg版本)一、前言二、实现的功能三、效果图四、核心代码五、其他说明
qt designer设置顶级布局后混乱_Qt编写的RTSP播放器+视频监控(ffmpeg版本)一、前言二、实现的功能三、效果图四、核心代码五、其他说明
qt designer设置顶级布局后混乱_Qt编写的RTSP播放器+视频监控(ffmpeg版本)一、前言二、实现的功能三、效果图四、核心代码五、其他说明
qt designer设置顶级布局后混乱_Qt编写的RTSP播放器+视频监控(ffmpeg版本)一、前言二、实现的功能三、效果图四、核心代码五、其他说明

四、核心代码

void FFmpegWidget::resizeEvent(QResizeEvent *){ //重新设置顶部工具栏的位置和宽高,可以自行设置顶部显示或者底部显示 int height = 20; flowPanel->setGeometry(borderWidth, borderWidth, this->width() - (borderWidth * 2), height); //flowPanel->setGeometry(borderWidth, this->height() - height - borderWidth, this->width() - (borderWidth * 2), height);}void FFmpegWidget::enterEvent(QEvent *){ //这里还可以增加一个判断,是否获取了焦点的才需要显示 //if (this->hasFocus()) {} if (flowEnable) { flowPanel->setVisible(true); }}void FFmpegWidget::leaveEvent(QEvent *){ if (flowEnable) { flowPanel->setVisible(false); }}void FFmpegWidget::dropEvent(QDropEvent *event){ //拖放完毕鼠标松开的时候执行 //判断拖放进来的类型,取出文件,进行播放 if(event->mimeData()->hasUrls()) { QString url = event->mimeData()->urls().first().toLocalFile(); this->close(); this->setUrl(url); this->open(); emit fileDrag(url); } else if (event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist")) { QTreeWidget *treeWidget = (QTreeWidget *)event->source(); if (treeWidget != 0) { QString url = treeWidget->currentItem()->data(0, Qt::UserRole).toString(); this->close(); this->setUrl(url); this->open(); emit fileDrag(url); } }}void FFmpegWidget::dragEnterEvent(QDragEnterEvent *event){ //拖曳进来的时候先判断下类型,非法类型则不处理 if(event->mimeData()->hasFormat("application/x-qabstractitemmodeldatalist")) { event->setDropAction(Qt::CopyAction); event->accept(); } else if(event->mimeData()->hasFormat("text/uri-list")) { event->setDropAction(Qt::LinkAction); event->accept(); } else { event->ignore(); }}void FFmpegWidget::paintEvent(QPaintEvent *){ //如果不需要绘制 if (!drawImage) { return; } //qDebug() << TIMEMS << "paintEvent" << objectName(); QPainter painter(this); painter.setRenderHints(QPainter::Antialiasing); //绘制边框 if (borderWidth > 0) { drawBorder(&painter); } if (!image.isNull()) { //绘制背景图片 drawImg(&painter, image); //绘制标签1 if (osd1Visible) { drawOSD(&painter, osd1FontSize, osd1Text, osd1Color, osd1Image, osd1Format, osd1Position); } //绘制标签2 if (osd2Visible) { drawOSD(&painter, osd2FontSize, osd2Text, osd2Color, osd2Image, osd2Format, osd2Position); } } else { //绘制背景 drawBg(&painter); }}void FFmpegWidget::drawBorder(QPainter *painter){ painter->save(); QPen pen; pen.setWidth(borderWidth); pen.setColor(hasFocus() ? focusColor : borderColor); painter->setPen(pen); painter->drawRect(rect()); painter->restore();}void FFmpegWidget::drawBg(QPainter *painter){ painter->save(); //背景图片为空则绘制文字,否则绘制背景图片 if (bgImage.isNull()) { painter->setPen(palette().foreground().color()); painter->drawText(rect(), Qt::AlignCenter, bgText); } else { //居中绘制 int pixX = rect().center().x() - bgImage.width() / 2; int pixY = rect().center().y() - bgImage.height() / 2; QPoint point(pixX, pixY); painter->drawImage(point, bgImage); } painter->restore();}void FFmpegWidget::drawImg(QPainter *painter, QImage img){ painter->save(); int offset = borderWidth * 1 + 0; img = img.scaled(width() - offset, height() - offset, Qt::KeepAspectRatio, Qt::SmoothTransformation); if (fillImage) { QRect rect(offset / 2, offset / 2, width() - offset, height() - offset); painter->drawImage(rect, img); } else { //按照比例自动居中绘制 int pixX = rect().center().x() - img.width() / 2; int pixY = rect().center().y() - img.height() / 2; QPoint point(pixX, pixY); painter->drawImage(point, img); } painter->restore();}void FFmpegWidget::drawOSD(QPainter *painter, int osdFontSize, const QString &osdText, const QColor &osdColor, const QImage &osdImage, const FFmpegWidget::OSDFormat &osdFormat, const FFmpegWidget::OSDPosition &osdPosition){ painter->save(); //标签位置尽量偏移多一点避免遮挡 QRect osdRect(rect().x() + (borderWidth * 2), rect().y() + (borderWidth * 2), width() - (borderWidth * 5), height() - (borderWidth * 5)); int flag = Qt::AlignLeft | Qt::AlignTop; QPoint point = QPoint(osdRect.x(), osdRect.y()); if (osdPosition == OSDPosition_Left_Top) { flag = Qt::AlignLeft | Qt::AlignTop; point = QPoint(osdRect.x(), osdRect.y()); } else if (osdPosition == OSDPosition_Left_Bottom) { flag = Qt::AlignLeft | Qt::AlignBottom; point = QPoint(osdRect.x(), osdRect.height() - osdImage.height()); } else if (osdPosition == OSDPosition_Right_Top) { flag = Qt::AlignRight | Qt::AlignTop; point = QPoint(osdRect.width() - osdImage.width(), osdRect.y()); } else if (osdPosition == OSDPosition_Right_Bottom) { flag = Qt::AlignRight | Qt::AlignBottom; point = QPoint(osdRect.width() - osdImage.width(), osdRect.height() - osdImage.height()); } if (osdFormat == OSDFormat_Image) { painter->drawImage(point, osdImage); } else { QDateTime now = QDateTime::currentDateTime(); QString text = osdText; if (osdFormat == OSDFormat_Date) { text = now.toString("yyyy-MM-dd"); } else if (osdFormat == OSDFormat_Time) { text = now.toString("HH:mm:ss"); } else if (osdFormat == OSDFormat_DateTime) { text = now.toString("yyyy-MM-dd HH:mm:ss"); } //设置颜色及字号 QFont font; font.setPixelSize(osdFontSize); painter->setPen(osdColor); painter->setFont(font); painter->drawText(osdRect, flag, text); } painter->restore();}QImage FFmpegWidget::getImage() const{ return this->image;}QDateTime FFmpegWidget::getLastTime() const{ return ffmpeg->getLastTime();}QString FFmpegWidget::getUrl() const{ return ffmpeg->getUrl();}bool FFmpegWidget::getCopyImage() const{ return this->copyImage;}bool FFmpegWidget::getCheckLive() const{ return this->checkLive;}bool FFmpegWidget::getDrawImage() const{ return this->drawImage;}bool FFmpegWidget::getFillImage() const{ return this->fillImage;}bool FFmpegWidget::getFlowEnable() const{ return this->flowEnable;}int FFmpegWidget::getTimeout() const{ return this->timeout;}int FFmpegWidget::getBorderWidth() const{ return this->borderWidth;}QColor FFmpegWidget::getBorderColor() const{ return this->borderColor;}QColor FFmpegWidget::getFocusColor() const{ return this->focusColor;}QString FFmpegWidget::getBgText() const{ return this->bgText;}QImage FFmpegWidget::getBgImage() const{ return this->bgImage;}bool FFmpegWidget::getOSD1Visible() const{ return this->osd1Visible;}int FFmpegWidget::getOSD1FontSize() const{ return this->osd1FontSize;}QString FFmpegWidget::getOSD1Text() const{ return this->osd1Text;}QColor FFmpegWidget::getOSD1Color() const{ return this->osd1Color;}QImage FFmpegWidget::getOSD1Image() const{ return this->osd1Image;}FFmpegWidget::OSDFormat FFmpegWidget::getOSD1Format() const{ return this->osd1Format;}FFmpegWidget::OSDPosition FFmpegWidget::getOSD1Position() const{ return this->osd1Position;}bool FFmpegWidget::getOSD2Visible() const{ return this->osd2Visible;}int FFmpegWidget::getOSD2FontSize() const{ return this->osd2FontSize;}QString FFmpegWidget::getOSD2Text() const{ return this->osd2Text;}QColor FFmpegWidget::getOSD2Color() const{ return this->osd2Color;}QImage FFmpegWidget::getOSD2Image() const{ return this->osd2Image;}FFmpegWidget::OSDFormat FFmpegWidget::getOSD2Format() const{ return this->osd2Format;}FFmpegWidget::OSDPosition FFmpegWidget::getOSD2Position() const{ return this->osd2Position;}void FFmpegWidget::updateImage(const QImage &image){ //拷贝图片有个好处,当处理器比较差的时候,图片不会产生断层,缺点是占用时间 //默认QImage类型是浅拷贝,可能正在绘制的时候,那边已经更改了图片的上部分数据 this->image = copyImage ? image.copy() : image; this->update();}void FFmpegWidget::checkVideo(){ QDateTime now = QDateTime::currentDateTime(); QDateTime lastTime = ffmpeg->getLastTime(); int sec = lastTime.secsTo(now); if (sec >= timeout) { restart(); }}void FFmpegWidget::btnClicked(){ QPushButton *btn = (QPushButton *)sender(); emit btnClicked(btn->objectName());}void FFmpegWidget::setInterval(int interval){ ffmpeg->setInterval(interval);}void FFmpegWidget::setSleepTime(int sleepTime){ ffmpeg->setSleepTime(sleepTime);}void FFmpegWidget::setCheckTime(int checkTime){ ffmpeg->setCheckTime(checkTime);}void FFmpegWidget::setCheckConn(bool checkConn){ ffmpeg->setCheckConn(checkConn);}void FFmpegWidget::setUrl(const QString &url){ ffmpeg->setUrl(url);}void FFmpegWidget::setHardware(const QString &hardware){ ffmpeg->setHardware(hardware);}void FFmpegWidget::setSaveFile(bool saveFile){ ffmpeg->setSaveFile(saveFile);}void FFmpegWidget::setSaveInterval(int saveInterval){ ffmpeg->setSaveInterval(saveInterval);}void FFmpegWidget::setSavePath(const QString &savePath){ //如果目录不存在则新建 QDir dir(savePath); if (!dir.exists()) { dir.mkdir(savePath); } ffmpeg->setSavePath(savePath);}void FFmpegWidget::setFileName(const QString &fileName){ ffmpeg->setFileName(fileName);}void FFmpegWidget::setCopyImage(bool copyImage){ this->copyImage = copyImage;}void FFmpegWidget::setCheckLive(bool checkLive){ this->checkLive = checkLive;}void FFmpegWidget::setDrawImage(bool drawImage){ this->drawImage = drawImage;}void FFmpegWidget::setFillImage(bool fillImage){ this->fillImage = fillImage;}void FFmpegWidget::setFlowEnable(bool flowEnable){ this->flowEnable = flowEnable;}void FFmpegWidget::setTimeout(int timeout){ this->timeout = timeout;}void FFmpegWidget::setBorderWidth(int borderWidth){ this->borderWidth = borderWidth;}void FFmpegWidget::setBorderColor(const QColor &borderColor){ this->borderColor = borderColor;}void FFmpegWidget::setFocusColor(const QColor &focusColor){ this->focusColor = focusColor;}void FFmpegWidget::setBgText(const QString &bgText){ this->bgText = bgText;}void FFmpegWidget::setBgImage(const QImage &bgImage){ this->bgImage = bgImage;}void FFmpegWidget::setOSD1Visible(bool osdVisible){ this->osd1Visible = osdVisible;}void FFmpegWidget::setOSD1FontSize(int osdFontSize){ this->osd1FontSize = osdFontSize;}void FFmpegWidget::setOSD1Text(const QString &osdText){ this->osd1Text = osdText;}void FFmpegWidget::setOSD1Color(const QColor &osdColor){ this->osd1Color = osdColor;}void FFmpegWidget::setOSD1Image(const QImage &osdImage){ this->osd1Image = osdImage;}void FFmpegWidget::setOSD1Format(const FFmpegWidget::OSDFormat &osdFormat){ this->osd1Format = osdFormat;}void FFmpegWidget::setOSD1Position(const FFmpegWidget::OSDPosition &osdPosition){ this->osd1Position = osdPosition;}void FFmpegWidget::setOSD2Visible(bool osdVisible){ this->osd2Visible = osdVisible;}void FFmpegWidget::setOSD2FontSize(int osdFontSize){ this->osd2FontSize = osdFontSize;}void FFmpegWidget::setOSD2Text(const QString &osdText){ this->osd2Text = osdText;}void FFmpegWidget::setOSD2Color(const QColor &osdColor){ this->osd2Color = osdColor;}void FFmpegWidget::setOSD2Image(const QImage &osdImage){ this->osd2Image = osdImage;}void FFmpegWidget::setOSD2Format(const FFmpegWidget::OSDFormat &osdFormat){ this->osd2Format = osdFormat;}void FFmpegWidget::setOSD2Position(const FFmpegWidget::OSDPosition &osdPosition){ this->osd2Position = osdPosition;}void FFmpegWidget::open(){ //qDebug() << TIMEMS << "open video" << objectName(); clear(); //如果是图片则只显示图片就行 image = QImage(ffmpeg->getUrl()); if (!image.isNull()) { update(); return; } ffmpeg->play(); ffmpeg->start(); if (checkLive) { timerCheck->start(); }}void FFmpegWidget::pause(){ ffmpeg->pause();}void FFmpegWidget::next(){ ffmpeg->next();}void FFmpegWidget::close(){ //qDebug() << TIMEMS << "close video" << objectName(); if (ffmpeg->isRunning()) { ffmpeg->stop(); ffmpeg->quit(); ffmpeg->wait(500); } if (checkLive) { timerCheck->stop(); } QTimer::singleShot(1, this, SLOT(clear()));}void FFmpegWidget::restart(){ //qDebug() << TIMEMS << "restart video" << objectName(); close(); QTimer::singleShot(10, this, SLOT(open()));}void FFmpegWidget::clear(){ image = QImage(); update();}
           

五、其他说明

1:可以用过目录下的rtsp.txt设置要显示的视频流地址。

2:支持4画面+6画面+8画面+9画面+13画面+16画面切换。

3:支持双击最大化当前画面。

4:支持右键截图。

5:支持各种视频流和本地文件播放。

6:可设置OSD标签。

7:在所有可能测试的操作系统和平台上均编译通过并完美运行。

8:XP运行报错的话请执行目录下的fixff.cmd即可。

继续阅读