QT软件开发 基于FFMPGE设计的流媒体播放器(rtmprtsp)

672 阅读7分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第3篇文章,点击查看活动详情

一、环境介绍

操作系统: win10 64位

QT版本: QT5.12.6

编译器: MinGW 32

ffmpeg版本: 4.2.2

二、功能介绍

使用QT+ffmpeg设计的流媒体播放器,实时播放RTMP、RTSP视频流渲染显示。

测试大华摄像头、海康摄像头、CCTV直播频道等视频均可正常播放,实测延迟时间在1秒以内。

部分工程代码截图:

img

软件运行效果: img

CCVT的RTMP流拉取效果: img

CCVT的RTMP流拉取效果--隐藏日志窗口: img

大华摄像头的RTMP流拉取效果: img

大华摄像头的RTSP流拉取效果:

img

三、RTMP与RTSP协议介绍

RTMP

RTMP是Real Time Messaging Protocol(实时消息传输协议)的首字母缩写。该协议基于TCP,是一个协议族,包括RTMP基本协议及RTMPT/RTMPS/RTMPE等多种变种。RTMP是一种设计用来进行实时数据通信的网络协议,主要用来在Flash/AIR平台和支持RTMP协议的流媒体/交互服务器之间进行音视频和数据通信。支持该协议的软件包括Adobe Media Server/Ultrant Media Server/red5等。RTMP与HTTP一样,都属于TCP/IP四层模型的应用层。

RTMP又是Routing Table Maintenance Protocol(路由选择表维护协议)的缩写。 在 AppleTalk 协议组中,路由选择表维护协议(RTMP,Routing Table Maintenance Protocol)是一种传输层协议,它在 AppleTalk 路由器中建立并维护路由选择表。RTMP 基于路由选择信息协议(RIP)。正如 RIP 一样,RTMP 使用跳数作为路由计量标准。一个数据包从源 网络发送到目标网络,必须通过的路由器或其它中间介质节点数目的计算结果即为跳数。

RTSP

RTSP(Real Time Streaming Protocol),RFC2326,实时流传输协议,是TCP/IP协议体系中的一个应用层协议,由哥伦比亚大学、网景和RealNetworks公司提交的IETF RFC标准。该协议定义了一对多应用程序如何有效地通过IP网络传送多媒体数据。RTSP在体系结构上位于RTP和RTCP之上,它使用TCP或UDP完成数据传输。HTTP与RTSP相比,HTTP请求由客户机发出,服务器作出响应;使用RTSP时,客户机和服务器都可以发出请求,即RTSP可以是双向的。RTSP是用来控制声音或影像的多媒体串流协议,并允许同时多个串流需求控制,传输时所用的网络通讯协定并不在其定义的范围内,服务器端可以自行选择使用TCP或UDP来传送串流内容,它的语法和运作跟HTTP 1.1类似,但并不特别强调时间同步,所以比较能容忍网络延迟。而前面提到的允许同时多个串流需求控制(Multicast),除了可以降低服务器端的网络用量,更进而支持多方视讯会议(Video Conference)。因为与HTTP1.1的运作方式相似,所以代理服务器〈Proxy〉的快取功能〈Cache〉也同样适用于RTSP,并因RTSP具有重新导向功能,可视实际负载情况来转换提供服务的服务器,以避免过大的负载集中于同一服务器而造成延迟。

四、FFMPEG介绍

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。

多媒体视频处理工具FFmpeg有非常强大的功能包括视频采集功能、视频格式转换、视频抓图、给视频加水印等。

FFmpeg在Linux平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括Windows、Mac OS X等。这个项目最早由Fabrice Bellard发起,2004年至2015年间由Michael Niedermayer主要负责维护。许多FFmpeg的开发人员都来自MPlayer项目,而且当前FFmpeg也是放在MPlayer项目组的服务器上。项目的名称来自MPEG视频编码标准,前面的"FF"代表"Fast Forward"。FFmpeg编码库可以使用GPU加速。

五、核心代码

img

5.1 xxx.pro :MinGW配置方式

 QT       += core gui
 QT       += multimediawidgets
 QT       += xml
 QT       += multimedia
 QT       += network
 QT       += widgets
 QT       += serialport
 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 ​
 CONFIG += c++11
 ​
 # The following define makes your compiler emit warnings if you use
 # any Qt feature that has been marked deprecated (the exact warnings
 # depend on your compiler). Please consult the documentation of the
 # deprecated API in order to know how to port your code away from it.
 DEFINES += QT_DEPRECATED_WARNINGS
 ​
 # You can also make your code fail to compile if it uses deprecated APIs.
 # In order to do so, uncomment the following line.
 # You can also select to disable deprecated APIs only up to a certain version of Qt.
 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
 ​
 SOURCES += \
     Thread_FFMPEG_LaLiu.cpp \
     main.cpp \
     videoplayer_showvideowidget.cpp \
     widget.cpp
 ​
 HEADERS += \
     Thread_FFMPEG_LaLiu.h \
     videoplayer_showvideowidget.h \
     widget.h
 ​
 FORMS += \
     widget.ui
 ​
 # Default rules for deployment.
 qnx: target.path = /tmp/$${TARGET}/bin
 else: unix:!android: target.path = /opt/$${TARGET}/bin
 !isEmpty(target.path): INSTALLS += target
 ​
 win32
 {
     message('运行win32版本')
     INCLUDEPATH+=C:/FFMPEG/ffmpeg_x86_4.2.2/include
     LIBS+=C:/FFMPEG/ffmpeg_x86_4.2.2/bin/av*
     LIBS+=C:/FFMPEG/ffmpeg_x86_4.2.2/bin/sw*
     LIBS+=C:/FFMPEG/ffmpeg_x86_4.2.2/bin/pos*
 }
 ​
 RESOURCES += \
     image.qrc
 ​
 RC_ICONS=main.ico

点击并拖拽以移动

5.2 xxx.pro: VS编译器配置方式

 QT       += core gui
 QT       += multimediawidgets
 QT       += xml
 QT       += multimedia
 QT       += network
 QT       += widgets
 QT       += serialport
 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 ​
 CONFIG += c++11
 ​
 # The following define makes your compiler emit warnings if you use
 # any Qt feature that has been marked deprecated (the exact warnings
 # depend on your compiler). Please consult the documentation of the
 # deprecated API in order to know how to port your code away from it.
 DEFINES += QT_DEPRECATED_WARNINGS
 ​
 # You can also make your code fail to compile if it uses deprecated APIs.
 # In order to do so, uncomment the following line.
 # You can also select to disable deprecated APIs only up to a certain version of Qt.
 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
 ​
 SOURCES += \
     Thread_FFMPEG_LaLiu.cpp \
     main.cpp \
     videoplayer_showvideowidget.cpp \
     widget.cpp
 ​
 HEADERS += \
     Thread_FFMPEG_LaLiu.h \
     videoplayer_showvideowidget.h \
     widget.h
 ​
 FORMS += \
     widget.ui
 ​
 # Default rules for deployment.
 qnx: target.path = /tmp/$${TARGET}/bin
 else: unix:!android: target.path = /opt/$${TARGET}/bin
 !isEmpty(target.path): INSTALLS += target
 ​
 win32
 {
     message('运行win32版本')
     INCLUDEPATH+=C:/FFMPEG/ffmpeg_x86_x64_3.3.2/include
     LIBS+=C:/FFMPEG/ffmpeg_x86_x64_3.3.2/lib/avcodec.lib
     LIBS+=C:/FFMPEG/ffmpeg_x86_x64_3.3.2/lib/avformat.lib
     LIBS+=C:/FFMPEG/ffmpeg_x86_x64_3.3.2/lib/avfilter.lib
     LIBS+=C:/FFMPEG/ffmpeg_x86_x64_3.3.2/lib/avutil.lib
     LIBS+=C:/FFMPEG/ffmpeg_x86_x64_3.3.2/lib/swresample.lib
     LIBS+=C:/FFMPEG/ffmpeg_x86_x64_3.3.2/lib/swscale.lib
 }
 ​
 ​
 RESOURCES += \
     image.qrc
 ​
 RC_ICONS=main.ico

5.3 widget.h

 #ifndef WIDGET_H
 #define WIDGET_H
 #pragma execution_character_set("utf-8")
 #include <QWidget>
 #include <QCompleter>
 #include "Thread_FFMPEG_LaLiu.h"
 QT_BEGIN_NAMESPACE
 namespace Ui { class Widget; }
 QT_END_NAMESPACE
 ​
 //主线程
 class Widget : public QWidget
 {
     Q_OBJECT
 ​
 public:
     Widget(QWidget *parent = nullptr);
     ~Widget();
     void SetStyle(const QString &qssFile);
     void Log_Text_Display(QPlainTextEdit *plainTextEdit_log,QString text);
 private slots:
     void Log_Display(QString text);
      void VideoDataDisplay(QImage image);
      void on_pushButton_start_clicked();
      void on_pushButton_stop_play_clicked();
 ​
      void on_pushButton_no_display_clicked();
 ​
 private:
     Ui::Widget *ui;
 ​
     bool log_widge_state=true;
 };
 ​
 #endif // WIDGET_H

5.4 widget.cpp

 #include "widget.h"
 #include "ui_widget.h"
 ​
 /*
  * 设置QT界面的样式
 */
 void Widget::SetStyle(const QString &qssFile) {
     QFile file(qssFile);
     if (file.open(QFile::ReadOnly)) {
         QString qss = QLatin1String(file.readAll());
         qApp->setStyleSheet(qss);
         QString PaletteColor = qss.mid(20,7);
         qApp->setPalette(QPalette(QColor(PaletteColor)));
         file.close();
     }
     else
     {
         qApp->setStyleSheet("");
     }
 }
 ​
 ​
 Widget::Widget(QWidget *parent)
     : QWidget(parent)
     , ui(new Ui::Widget)
 {
     ui->setupUi(this);
 ​
     /*基本设置*/
    // this->SetStyle(":/images/blue.css");     //设置样式表
     this->setWindowIcon(QIcon(":/main.ico")); //设置图标
     this->setWindowTitle("流媒体播放器");
 ​
     //构建默认地址
     QStringList listyear;
     listyear<<"rtmp://10.0.0.13:8888/live/video"
            <<"rtmp://58.200.131.2:1935/livetv/cctv14"
            <<"rtsp://admin:vioovi2021@10.0.0.2:554/cam/realmonitor?channel=1&subtype=0"
            ; //列表
     QCompleter *year = new QCompleter(listyear);//构建自动补全器
     ui->lineEdit_rtmp_url->setCompleter(year); //设置自动补全器功能
 ​
     //连接拉流线程的图像输出信号
     connect(&thread_laliu,SIGNAL(VideoDataOutput(QImage )),this,SLOT(VideoDataDisplay(QImage )));
     //连接拉流线程的日志信息
     connect(&thread_laliu,SIGNAL(LogSend(QString)),this,SLOT(Log_Display(QString)));
 }
 ​
 Widget::~Widget()
 {
     delete ui;
 }
 ​
 //视频刷新显示
 void Widget::VideoDataDisplay(QImage image)
 {
     ui->widget_display->slotGetOneFrame(image);
 }
 ​
 /*日志显示*/
 void Widget::Log_Text_Display(QPlainTextEdit *plainTextEdit_log,QString text)
 {
     plainTextEdit_log->insertPlainText(text);
     //移动滚动条到底部
     QScrollBar *scrollbar = plainTextEdit_log->verticalScrollBar();
     if(scrollbar)
     {
         scrollbar->setSliderPosition(scrollbar->maximum());
     }
 }
 ​
 //日志显示
 void Widget::Log_Display(QString text)
 {
     Log_Text_Display(ui->plainTextEdit_log,text);
 }
 ​
 //开始拉流
 void Widget::on_pushButton_start_clicked()
 {
     on_pushButton_stop_play_clicked();
     //设置地址
     thread_laliu.SetRTMPAddr(ui->lineEdit_rtmp_url->text());
     //开始运行线程
     thread_laliu.start();
 }
 ​
 ​
 /*
 工程: ffmpeg_Laliu
 日期: 2021-07-30
 作者: DS小龙哥
 环境: win10 QT5.12.6 MinGW32
 功能: 停止播放
 */
 void Widget::on_pushButton_stop_play_clicked()
 {
     if(thread_laliu.isRunning())
     {
         thread_laliu.Exit_process();
         thread_laliu.quit();
         thread_laliu.wait();
     }
 }
 ​
 /*
 工程: ffmpeg_Laliu
 日期: 2021-07-30
 作者: DS小龙哥
 环境: win10 QT5.12.6 MinGW32
 功能: 隐藏日志窗口
 */
 void Widget::on_pushButton_no_display_clicked()
 {
     log_widge_state=!log_widge_state;
     ui->groupBox->setVisible(log_widge_state);
 }

5.5 widget渲染窗口--渲染视频画面

 #include "videoplayer_showvideowidget.h"
 ​
 #include <QPainter>
 ​
 VideoPlayer_ShowVideoWidget::VideoPlayer_ShowVideoWidget(QWidget *parent) :
     QWidget(parent)
 {
     m_nRotateDegree=0;
     this->setMouseTracking(true);
 }
 ​
 VideoPlayer_ShowVideoWidget::~VideoPlayer_ShowVideoWidget()
 {
 ​
 }
 ​
 void VideoPlayer_ShowVideoWidget::Set_Rotate(int Rotate)
 {
     m_nRotateDegree=Rotate;
 }
 ​
 void VideoPlayer_ShowVideoWidget::paintEvent(QPaintEvent *event)
 {
     QPainter painter(this);
 ​
     painter.setRenderHint(QPainter::Antialiasing);
     painter.setRenderHint(QPainter::TextAntialiasing);
     painter.setRenderHint(QPainter::SmoothPixmapTransform);
     painter.setRenderHint(QPainter::HighQualityAntialiasing);
 ​
     painter.setBrush(Qt::black);
     painter.drawRect(0,0,this->width(),this->height()); //先画成黑色
 ​
     if (mImage.size().width() <= 0) return;
 ​
     //将图像按比例缩放成和窗口一样大小
     QImage img = mImage.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); //这里效率比较低下  还不知道如何优化
 ​
     //画面旋转
     if(m_nRotateDegree > 0)
     {
         QTransform matrix;
         matrix.rotate(m_nRotateDegree);
         img = img.transformed(matrix, Qt::SmoothTransformation);
     }
 ​
     int x = this->width() - img.width();
     int y = this->height() - img.height();
 ​
     x /= 2;
     y /= 2;
 ​
     painter.drawImage(QPoint(x,y),img); //画出图像
 ​
 }
 ​
 ​
 void VideoPlayer_ShowVideoWidget::slotGetOneFrame(QImage img)
 {
     src_mImage =mImage = img;
     update(); //调用update将执行 paintEvent函数
 }
 ​
 /*
 工程: QtAV_VideoPlayer
 日期: 2021-03-24
 作者: DS小龙哥
 环境: win10 QT5.12.6 MinGW32
 功能: 获取原图数据
 */
 QImage VideoPlayer_ShowVideoWidget::GetImage()
 {
     return src_mImage.copy();
 }
 ​
 /*
 工程: QtAV_VideoPlayer
 日期: 2021-03-25
 作者: DS小龙哥
 环境: win10 QT5.12.6 MinGW32
 功能: 鼠标双击事件
 */
 void VideoPlayer_ShowVideoWidget::mouseDoubleClickEvent(QMouseEvent *e)
 {
     emit s_VideoWidgetEvent(1);
 }