天天看點

基于FFmpeg設計的流媒體播放器(rtmp/rtsp)

作者:音視訊開發老舅

一、環境介紹

作業系統: win10 64位

QT版本: QT5.12.6

編譯器: MinGW 32

ffmpeg版本: 4.2.2

二、功能介紹

使用QT+ffmpeg設計的流媒體播放器,實時播放RTMP、RTSP視訊流渲染顯示。

測試大華攝像頭、海康攝像頭、CCTV直播頻道等視訊均可正常播放,實測延遲時間在1秒以内。

部分工程代碼截圖:

基于FFmpeg設計的流媒體播放器(rtmp/rtsp)

軟體運作效果:

基于FFmpeg設計的流媒體播放器(rtmp/rtsp)

CCVT的RTMP流拉取效果:

基于FFmpeg設計的流媒體播放器(rtmp/rtsp)

大華攝像頭的RTMP流拉取效果:

基于FFmpeg設計的流媒體播放器(rtmp/rtsp)

大華攝像頭的RTSP流拉取效果:

基于FFmpeg設計的流媒體播放器(rtmp/rtsp)

C++音視訊配套學習資料:點選莬費領取→音視訊開發(資料文檔+視訊教程+面試題)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

三、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加速。

【相關學習資料推薦,點選下方連結免費報名,先碼住不迷路~】

【免費】FFmpeg/WebRTC/RTMP/NDK/Android音視訊流媒體進階開發-學習視訊教程-騰訊課堂

C++音視訊配套學習資料:點選莬費領取→音視訊開發(資料文檔+視訊教程+面試題)(FFmpeg+WebRTC+RTMP+RTSP+HLS+RTP)

五、核心代碼

基于FFmpeg設計的流媒體播放器(rtmp/rtsp)

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:[email protected]: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);
 }           

如果你對音視訊開發感興趣,覺得文章對您有幫助,别忘了點贊、收藏哦!或者對本文的一些闡述有自己的看法,有任何問題,歡迎在下方評論區與我讨論!

繼續閱讀