用QT做一个rtsp / rtmp实时流的播放器 ffmpeg

这是一个使用Qt结合FFmpeg编写的GUI播放器,支持输入RTSP或RTMP流地址,具备播放、停止、全屏等功能。代码中包含了初始化、解码和显示视频帧的逻辑,特别指出在Ubuntu环境下能播放H265的RTMP流。用户界面简洁,可调整窗口大小,双击切换全屏。
摘要由CSDN通过智能技术生成

老早之前用qt集成ffmpeg 做过一个播放器 那个是基于sdl的命令行窗口  

这次做成GUI的方式 直接做成一个播放器  可以输入rtsp或者rtmp流地址

效果图如下 那个长的输入框输入rtsp地址 然后点击下面的播放按钮

测试时再ubuntu下面测试的 只有视频  没有音频

由于ffmpeg是支持rtmp h265的 所以 这个也可以播放h265的rtmp流

注意 是ubuntu下面运行的

点击这里可以下载 编译的可执行文件

 点击播放之后 按钮会变成停止模式

 可以只有缩放 也会随着窗口变化而变化

 双击支持全屏显示 再次双击会变回之前的模式

 关闭是给出提示

代码 头文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>


#include <thread>
extern "C"
{
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libswscale/swscale.h"
    #include "libavdevice/avdevice.h"
    #include "libswresample/swresample.h"
    #include "libavutil/imgutils.h"
}



namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

    int init();
    void play();



private:
    Ui::MainWindow *ui;

    bool m_run_flag = false;
    bool m_fullscreen_flag = false;
    std::thread m_decodecThread;
    AVFormatContext *pAVFormatCtx;
    AVCodecContext *pAVCodecCtx;
    SwsContext *pSwsCtx;
    uint8_t *pRgbBuffer;
    AVPacket packet;
    AVFrame *pAVFrame = NULL;
    AVFrame *pAVFrameRGB;
    int iVideoIndex = -1;
    QImage m_image;
    bool isFinish  =false;
    void decodec();

    signals:
    void signalDraw();



protected:
    void mouseDoubleClickEvent( QMouseEvent * e ) override;
    void paintEvent(QPaintEvent *event) override;
    void closeEvent(QCloseEvent * e) override;


private slots:
    void slotDraw();
    void slotButtonClicked();
    void onPlayClicked();
    void onExitClicked();
    void onStopClicked();

};

#endif // MAINWINDOW_H

cpp源文件

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QString>

#include "button.h"
#include <QPainter>
#include <QHBoxLayout>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_run_flag = false;
    connect(this,&MainWindow::signalDraw,this,&MainWindow::slotDraw);

    connect(ui->buttonplay, SIGNAL(clicked()), this, SLOT(onPlayClicked()));
    //connect(ui->buttonstop, SIGNAL(clicked()), this, SLOT(onStopClicked()));

    //ui->groupVideo->installEventFilter(this);

    ui->inputrtsp->setText("rtsp://uer:gd123456@192.168.2.126:554/Streaming/Channels/101");



    ui->buttonplay->setIcon(QIcon(":/new/image/play.png"));
    ui->buttonplay->setIconSize(QSize(50,50));
    ui->buttonplay->setMinimumSize(50,50);
    ui->buttonplay->setMaximumSize(50,50);
    ui->buttonplay->setFlat(true);




    //lay->addWidget(m_pBtnSearch); //。。。。 添加按钮。。。。。。。
    //lay->setContentsMargins(0, 0, 0, 0);
    //lay->setAlignment(Qt::AlignRight);

    //ui->inputrtsp->setFrame(false);
    ui->inputrtsp->setStyleSheet("QLineEdit{ background-color: rgba(128,128,128,120); }QLineEdit:focus{background-color: rgb(222,222,222)}"); // 设置样式
    //ui->inputrtsp->setTextMargins(0, 0, 30, 0); // 注意这里的30  因为图一的输入框最右边有个按钮。不能让光标在此区域出现。。需要设置文字显示范围
    //searchEdit->setPlaceholderText(tr("许嵩"));  // 设置默认文字


   //ui->buttonplay->setStyleSheet("background-color: yellow");

   //ui->buttonstop->setStyleSheet("background-color: red");


#if 0
    MyButton *play = new MyButton(this);
    connect(play, SIGNAL(clicked()), this, SLOT(onPlayClicked()));
    play->move(100,200);


    QPushButton *test = new QPushButton(this);
    connect(test,SIGNAL(clicked()),this,SLOT(slotButtonClicked()));
    test->setText("test button");
    test->move(200,300);




    connect(ui->buttonexit, SIGNAL(clicked()), this, SLOT(onExitClicked()));




#endif
}

void MainWindow::closeEvent(QCloseEvent *e)
{
    QMessageBox::StandardButton result=QMessageBox::question(this, "确认", "确定要退出本程序吗?",
                          QMessageBox::Yes|QMessageBox::No,
                          QMessageBox::No);

        if (result==QMessageBox::Yes)
            e->accept();
        else
            e->ignore();

}

#if 0
bool eventFilter(QObject  *obj, QEvent *event)
{
    int i = 0;
    if (obj == ui->groupVideo)//当事件发生在u1(为Qlabel型)控件上
    {
        if (event->type() == QEvent::MouseButtonPress)//当为双击事件时
        {
            i++;
            if (i % 2 == 0) //此处为双击一次全屏,再双击一次退出
            {
                ui->groupVideo->setWindowFlags(Qt::Dialog);
                ui->groupVideo->showFullScreen();//全屏显示
            }
            else
            {
                ui->groupVideo->setWindowFlags(Qt::SubWindow);
                ui->groupVideo->showNormal();//退出全屏
            };

        }
        return QObject::eventFilter(obj, event);
    }

}
#endif

int MainWindow::init()
{
    //std::string file = "rtsp://uer:gd123456@192.168.2.121:554/Streaming/Channels/101";

    QString file =ui->inputrtsp->text();
    //描述多媒体文件的构成及其基本信息

     pAVFormatCtx = avformat_alloc_context(); //Init handle

    if (avformat_open_input(&pAVFormatCtx, file.toUtf8(), NULL, NULL) != 0)
        {
            qDebug() <<"open file fail";
            avformat_free_context(pAVFormatCtx);
            return -1;
        }

    //读取一部分视音频数据并且获得一些相关的信息
    if (avformat_find_stream_info(pAVFormatCtx, NULL) < 0)
    {
        qDebug() <<"vformat find stream fail";
        avformat_close_input(&pAVFormatCtx);
        return -1;
    }
   // 根据解码器枚举类型找到解码器
       AVCodec *pAVCodec;
       int ret = av_find_best_stream(pAVFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pAVCodec, 0);
       if (ret < 0) {
           qDebug()<< "av_find_best_stream faliture";
           avformat_close_input(&pAVFormatCtx);
           return -1;
       }
       iVideoIndex = ret;

        pAVCodec = avcodec_find_decoder(pAVFormatCtx->streams[iVideoIndex]->codecpar->codec_id);
        if (pAVCodec == NULL)
        {
            qDebug()<<"not find decoder";
            return -1;
        }

    qDebug()<<"avcodec_open2 pAVCodec->name:" << QString::fromStdString(pAVCodec->name);


       if(pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.den != 0) {
           float fps_ = pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.num / pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.den;
              qDebug() <<"fps:" << fps_;
       }
         int64_t video_length_sec_ = pAVFormatCtx->duration/AV_TIME_BASE;
          qDebug() <<"video_length_sec_:" << video_length_sec_;
   pAVCodecCtx = avcodec_alloc_context3(pAVCodec);
        if (pAVCodecCtx == NULL)
        {
            qDebug() <<"get pAVCodecCtx fail";
            avformat_close_input(&pAVFormatCtx);
            return -1;
        }
   ret = avcodec_parameters_to_context(pAVCodecCtx,pAVFormatCtx->streams[iVideoIndex]->codecpar);
        if (ret < 0)
        {
            qDebug() <<"avcodec_parameters_to_context fail";
            avformat_close_input(&pAVFormatCtx);
            return -1;
        }
     if (avcodec_open2(pAVCodecCtx, pAVCodec, NULL) < 0)
        {
            qDebug()<<"avcodec_open2 fail";
            return -1;
        }
        //为解码帧分配内存
        //AVFrame 存放从AVPacket中解码出来的原始数据
            pAVFrame = av_frame_alloc();
            pAVFrameRGB = av_frame_alloc();
      //用于视频图像的转换,将源数据转换为RGB32的目标数据
        pSwsCtx = sws_getContext(pAVCodecCtx->width, pAVCodecCtx->height, pAVCodecCtx->pix_fmt,
                                             pAVCodecCtx->width, pAVCodecCtx->height, AV_PIX_FMT_RGB32,
                                             SWS_BICUBIC, NULL, NULL, NULL);

        int  m_size = av_image_get_buffer_size(AVPixelFormat(AV_PIX_FMT_RGB32), pAVCodecCtx->width, pAVCodecCtx->height, 1);
        pRgbBuffer = (uint8_t *)(av_malloc(m_size));
        //为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间
        avpicture_fill((AVPicture *)pAVFrameRGB, pRgbBuffer, AV_PIX_FMT_BGR32, pAVCodecCtx->width, pAVCodecCtx->height);
        //av_image_fill_arrays
        //AVpacket 用来存放解码数据
        av_new_packet(&packet, pAVCodecCtx->width * pAVCodecCtx->height);
        return 0;
}

void MainWindow::play()
{
    m_decodecThread = std::thread([this]()
    {
        init();
        decodec();
    });
    m_decodecThread.detach();
}



void MainWindow::decodec()
{
    //读取码流中视频帧
        while (m_run_flag)
        {
            int ret = av_read_frame(pAVFormatCtx, &packet);
            if(ret != 0)
            {
                qDebug()<<"file end";
                isFinish = !isFinish;
                 return;
            }
            if (packet.stream_index != iVideoIndex)
            {
                av_packet_unref(&packet);
                continue;
            }
           int iGotPic = AVERROR(EAGAIN);
//             //解码一帧视频数据
            iGotPic = avcodec_send_packet(pAVCodecCtx, &packet);
            if(iGotPic!=0){
                qDebug()<<"avcodec_send_packet error";
                      continue;
            }
            iGotPic = avcodec_receive_frame(pAVCodecCtx, pAVFrame);
   if(iGotPic == 0){
                   //转换像素
                   sws_scale(pSwsCtx, (uint8_t const * const *)pAVFrame->data, pAVFrame->linesize, 0, pAVCodecCtx->height, pAVFrameRGB->data, pAVFrameRGB->linesize);

                   //构造QImage
                   QImage img(pRgbBuffer, pAVCodecCtx->width, pAVCodecCtx->height, QImage::Format_RGB32);
                   //qDebug()<<"decode img";
                   m_image = img;
                   emit signalDraw();
  }else {
           qDebug()<<"decode error";
           }

            av_packet_unref(&packet);
            //std::this_thread::sleep_for(std::chrono::milliseconds(25));
        }
  //资源回收
        av_free(pAVFrame);
        av_free(pAVFrameRGB);
        sws_freeContext(pSwsCtx);
        avcodec_close(pAVCodecCtx);
        avformat_close_input(&pAVFormatCtx);
}

void MainWindow::slotDraw()
{
    update();
}

void MainWindow::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setBrush(Qt::black);
    painter.drawRect(0, 0, this->width(), this->height());



    int appWindowWidth = this->geometry().width();

    int appWindowHeight = this->geometry().height();


    int button_w = ui->buttonplay->geometry().width();
    int button_h = ui->buttonplay->geometry().height();


    int intpu_w = ui->groupInput->geometry().width();
    int input_h = ui->groupInput->geometry().height();


    auto center_x = (appWindowWidth-button_w)/2;
    auto center_y = (appWindowHeight-button_h-50);

    ui->buttonplay->setGeometry(center_x, center_y, button_w,button_h);



    center_x = (appWindowWidth-intpu_w)/2;
    center_y = (appWindowHeight-input_h-120);

    ui->groupInput->setGeometry(center_x, center_y, intpu_w,input_h);


    if(m_run_flag == false)
    {
        return ;
    }

    if (m_image.size().width() <= 0)
        return;

    //比例缩放
    QImage img = m_image.scaled(this->size(),Qt::KeepAspectRatio);
    int x = this->width() - img.width();
    int y = this->height() - img.height();

    x /= 2;
    y /= 2;

    //QPoint(x,y)为中心绘制图像
    painter.drawImage(QPoint(x,y),img);
}


void MainWindow::slotButtonClicked()
{
    QMessageBox::information(NULL,"Click","Double click",QMessageBox::Accepted);
}


void MainWindow::onPlayClicked()
{

    if(m_run_flag == false)
    {

        ui->buttonplay->setIcon(QIcon(":/new/image/stop.png"));
        ui->buttonplay->setIconSize(QSize(50,50));
        ui->buttonplay->setMinimumSize(50,50);
        ui->buttonplay->setMaximumSize(50,50);
        ui->buttonplay->setFlat(true);
        m_run_flag = true;

        play();



    }
    else
    {
        m_run_flag = false;
        ui->buttonplay->setIcon(QIcon(":/new/image/play.png"));
        ui->buttonplay->setIconSize(QSize(50,50));
        ui->buttonplay->setMinimumSize(50,50);
        ui->buttonplay->setMaximumSize(50,50);
        ui->buttonplay->setFlat(true);
        //QMessageBox::information(NULL,"Click","Allready in playing...",QMessageBox::Accepted);
    }

}

void MainWindow::onExitClicked()
{
    QMessageBox::information(NULL,"Click","onexitClicked",QMessageBox::Accepted);
    //ui->buttonexit->setText("I am exit button");
}

void MainWindow::onStopClicked()
{
    //QMessageBox::information(NULL,"Click","onStopClicked",QMessageBox::Accepted);
    m_run_flag = false;
    update();
    //qApp->exit();
}


void MainWindow::mouseDoubleClickEvent( QMouseEvent * e )
{
    if ( e->button() == Qt::LeftButton )
    {
        m_fullscreen_flag = 1-m_fullscreen_flag;
        if(m_fullscreen_flag)
        {
            showFullScreen();
        }
        else
        {
            showNormal();
        }

    }

    // You may have to call the parent's method for other cases
    QMainWindow::mouseDoubleClickEvent( e );
}



MainWindow::~MainWindow()
{
    m_run_flag = false;
    delete ui;
}

这个时个demo很多异常处理都没有做

算是QT入个门

项目配置文件  利用ffmpeg软解码


QMAKE_CXXFLAGS += -std=c++0x

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = qtplayer
TEMPLATE = app


SOURCES += main.cpp\
        mainwindow.cpp \
    button.cpp



INCLUDEPATH +=  /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/include
INCLUDEPATH +=  /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubunt_rmtp_h265/include


LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavformat.a
LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavcodec.a

LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavdevice.a
LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavfilter.a

LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libavutil.a
LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libswresample.a
LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/FFmpeg-n4.3.2/install_ubunt_so/lib/libswscale.a
LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubuntu/lib/libSDL2.a
LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubuntu/lib/libSDL2main.a
LIBS +=     /home/jason/HS/Hi3531DV200_SDK_V2.0.0.3/mpp/sample/SDL2-2.0.16/install_ubuntu/lib/libSDL2_test.a

LIBS += -lz

HEADERS  += mainwindow.h \
    button.h

FORMS    += mainwindow.ui

RESOURCES += \
    image.qrc

Qt软件开发是指使用Qt框架进行软件开发的过程。Qt是一种用于开发跨平台软件的工具,提供了丰富的图形界面和功能模块,能够快速开发高质量的软件应用。 基于FFmpeg设计的媒体播放器是一种能够播放RTMP(Real-Time Messaging Protocol)和RTSP(Real-Time Streaming Protocol)媒体协议的软件应用。FFmpeg一个开源的多媒体框架,能够处理音频和视频编解码、格式转换等多种功能。 在使用Qt进行开发时,首先需要将FFmpeg框架集成到Qt项目中,以便能够使用FFmpeg提供的功能。可以通过静态库、动态库或者源码方式引入FFmpeg。接下来,需要设计并实现媒体播放器的界面,可以使用Qt提供的控件来创建播放器的UI界面,包括播放按钮、进度条、音量控制等。 在播放器的逻辑功能上,需要使用FFmpeg解码视频和音频,并将图像渲染到界面上,同时实现控制功能,如播放、暂停、快进快退等。 对于RTMP协议,需要建立与服务端的连接,并通过RTMP协议发送请求来获取媒体,然后使用FFmpeg进行解码和播放。 对于RTSP协议,需要建立与服务器的连接,并通过RTSP协议的SDP描述文件来获取媒体信息,然后使用FFmpeg进行解码和播放。 除了基本的播放功能外,还可以增加一些高级功能,如全屏、截图、倍速播放等,来满足不同用户的需求。 总之,基于FFmpeg设计的媒体播放器(支持RTMPRTSP协议)是Qt软件开发中的一个具体应用场景,通过QtFFmpeg的结合,能够快速开发出功能丰富、稳定可靠的媒体播放器软件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QMCY_jason

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值