当前位置: 首页 > article >正文

Qt之FFmpeg播放器设计(十七)

Qt开发 系列文章 - FFmpeg-Player(十七)



前言

Qt进行播放器设计,采用其自带的多媒体模块QMediaPlayer设计时,其底层操作系统提供的解码器有些格式不一定支持,且解码效果有限。这时我们一般采用第三方音视频解码器库进行操作,这里推荐的是FFmpeg库,用的人比较多,采用纯C编写,保证高可移植性和编解码质量,且支持多种音频和视频格式,提供了录制、转换以及流化音视频的完整解决方案。在Qt平台上开发涉及FFmpeg的应用程序,需要将FFmpeg库集成到你的Qt项目中,并编写代码来利用FFmpeg的功能进行音视频处理,本篇文章将在Qt平台上利用FFmpeg实现视频、视频流播放。


一、FFmpeg

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

FFmpeg具有强大的功能,包括但不限于视频采集、视频格式转换、视频抓图、视频水印、音频提取、视频压缩、流媒体处理等等。

FFmpeg框架的基本组成包括AVFormat、AVCodec、AVFilter、AVDevice、AVUtil、libavdevice等模块库。

FFmpeg的主要工作流程相对简单,具体如下:读取输入源、进行音视频的解封装、解码每一帧音视频数据、编码每一帧音视频数据、进行音视频的重新封装、输出到目标。

凭借其丰富的功能和高效的性能,FFmpeg被广泛应用于多个领域,如直播类:音视频会议、教育直播、娱乐/游戏直播等;短视频类:抖音、快手等;网络视频类:优酷、腾讯视频、爱奇艺等;音视频通话:微信、QQ等实时互动软件;视频监控类:摄像头监控等。

用户需要根据自己的操作系统选择相应的FFmpeg安装方法,可以访问官网下载 FFmpeg,选择不同的版本进行安装。

关于ffmpeg 命令使用介绍可以参考Windows环境 ffmpeg 命令使用介绍-CSDN博客写的挺详细的。

以上只是粗略介绍了下FFmpeg的功能、构成框架、工作流程、应用领域、下载安装,后面会有文章详细介绍,下面将介绍Qt平台上采用FFmpeg实现视频播放。

二、实现方式

1.效果演示

动画效果演示,如下所示。

可跟随全屏播放,效果如下。

打开界面上的监控按钮,效果如下。

视频采集画面动画效果演示,如下所示。

打开1#播放,可以输入视频地址,可以输入是本地视频目录地址、也可以是网络视频流地址,效果如下。

   

其它几号视频窗口操作可在画面上设置,效果如下。

因CSND上传视频不能超过5M,所以截成多个图片。

2.代码实现

代码比较多,给出主要关键实现代码,项目工程实现方式及全部代码见文末链接处。

1.添加ffmpng模块

在Qt项目上添加一个ffmpng.pri模块文件,具体实现如下。

2.ffmpng功能模块

基于Qt,依靠FFmpeg内核驱动,通过代码自建一个需要视频播放窗口FFmpegWidget,大小可以自己定义,代码如下(示例)。

#include "ffmpeg.h"
//实时视频显示窗体类
FFmpegWidget::FFmpegWidget(QWidget *parent) : QWidget(parent)
{
    thread = new FFmpegThread(this);
    image = QImage();
    connect(thread, SIGNAL(receiveImage(QImage)), this, SLOT(updateImage(QImage)));
    QString ImagePath = "images/video.jpeg";
    initWidget(ImagePath);
}

FFmpegWidget::~FFmpegWidget()
{
    close();
}
void FFmpegWidget::initWidget(QString str)
{
    image = QImage(str);
    this->update();
}

void FFmpegWidget::paintEvent(QPaintEvent *)
{
    if (image.isNull()) {
        return;
    }
    //qDebug() << TIMEMS << "paintEvent" << objectName();
    QPainter painter(this);
#if 1
    //image = image.scaled(this->size(), Qt::KeepAspectRatio);
    //按照比例自动居中绘制
    int pixX = rect().center().x() - image.width() / 2;
    int pixY = rect().center().y() - image.height() / 2;
    QPoint point(pixX, pixY);
    painter.drawImage(point, image);
#else
    painter.drawImage(this->rect(), image);
#endif
}
void FFmpegWidget::updateImage(const QImage &image)
{
    //this->image = image.copy();
    this->image = image;
    this->update();
}
void FFmpegWidget::open()
{
    //qDebug() << TIMEMS << "open video" << objectName();
    //clear();
    QString ImagePath = "images/video.jpeg";
    initWidget(ImagePath);
    thread->play();
}
void FFmpegWidget::pause()
{
    thread->pause();
}
void FFmpegWidget::next()
{
    thread->next();
}
void FFmpegWidget::close()
{
    if (thread->isRunning())
        thread->stop();
    //QString ImagePath = "images/video.jpeg";
    //QTimer::singleShot(1, this, SLOT(initWidget(ImagePath)));
}
void FFmpegWidget::clear()
{
    image = QImage();
    update();
}

FFmpegThread::FFmpegThread(QObject *parent) : QThread(parent)
{
    setObjectName("FFmpegThread");
    stopped = true;

    frameFinish = false;
    videoWidth = 0;
    videoHeight = 0;
    oldWidth = 0;
    oldHeight = 0;
    videoStreamIndex = -1;

    PictureBuf = nullptr;
    avPacket = nullptr;
    avFrameInput = nullptr;
    avFramePicture = nullptr;
    videoCodec = nullptr;
    swsContext = nullptr;
    options = nullptr;
    videoDecoder = nullptr;

    //初始化注册,一个软件中只注册一次即可
    FFmpegThread::initlib();
}

//一个软件中只需要初始化一次就行
void FFmpegThread::initlib()
{
    static QMutex mutex;
    QMutexLocker locker(&mutex);
    static bool isInit = false;
    if (!isInit) {
        //注册库中所有可用的文件格式和解码器
        av_register_all();
        //注册所有设备,主要用于本地摄像机播放支持
#ifdef ffmpegdevice
        avdevice_register_all();
#endif
        //初始化网络流格式,使用网络流时必须先执行
        avformat_network_init();

        isInit = true;
        qDebug() << TIMEMS << "init ffmpeg lib ok" << " version:" << FFMPEG_VERSION;
#if 0
        //输出所有支持的解码器名称
        QStringList listCodeName;
        AVCodec *code = av_codec_next(NULL);
        while (code != NULL) {
            listCodeName << code->name;
            code = code->next;
        }

        qDebug() << TIMEMS << listCodeName;
#endif
    }
}

bool FFmpegThread::init()
{
    //在打开码流前指定各种参数比如:探测时间/超时时间/最大延时等
    //设置缓存大小,1080p可将值调大
    av_dict_set(&options, "buffer_size", "8192000", 0);
    //以tcp方式打开,如果以udp方式打开将tcp替换为udp
    av_dict_set(&options, "rtsp_transport", "tcp", 0);
    //设置超时断开连接时间,单位微秒,3000000表示3秒
    av_dict_set(&options, "stimeout", "3000000", 0);
    //设置最大时延,单位微秒,1000000表示1秒
    av_dict_set(&options, "max_delay", "1000000", 0);
    //自动开启线程数
    av_dict_set(&options, "threads", "auto", 0);

    videoDecoder = avcodec_find_decoder(AV_CODEC_ID_HEVC);
    videoCodec = avcodec_alloc_context3(videoDecoder);
    CodecParserCtx = av_parser_init(AV_CODEC_ID_HEVC);
    //设置加速解码
    videoCodec->lowres = videoDecoder->max_lowres;
    videoCodec->flags2 |= AV_CODEC_FLAG2_FAST;
    //打开视频解码器
    if (avcodec_open2(videoCodec, videoDecoder, nullptr) < 0) {
        qDebug() << TIMEMS << "open video codec error";
        return false;
    }
    //预分配好内存
    avPacket = av_packet_alloc();
    avFrameInput = av_frame_alloc();
    avFramePicture = av_frame_alloc();
    //----------以下为用来初始化qt显示时所用的图片----------
    initImageObjects();
    QString videoInfo = QString("视频信息: %1  解码: %2  格式: %3  时长: %4 秒  分辨率: %5*%6")
                        .arg("3#camera").arg(videoDecoder->name).arg("H265").arg("***").arg(videoWidth).arg(videoHeight);
    qDebug() << TIMEMS << videoInfo;
    qDebug() << TIMEMS << "init ffmpeg finsh";
    return true;
}
bool FFmpegThread::initImageObjects()
{
    //获取分辨率大小
    //videoWidth = videoStream->codec->width;
    //videoHeight = videoStream->codec->height;
    videoWidth = 720;
    videoHeight = 576;
    //如果没有获取到宽高则返回
    if (videoWidth == 0 || videoHeight == 0) {
        qDebug() << TIMEMS << "find width height error";
        return false;
    }
    //比较上一次文件的宽度高度,当改变时,需要重新分配内存
    if (oldWidth != videoWidth || oldHeight != videoHeight){
        int byte = avpicture_get_size(AV_PIX_FMT_RGB32, videoWidth, videoHeight);
        PictureBuf = (uint8_t *)av_malloc(byte * sizeof(uint8_t));
        oldWidth = videoWidth;
        oldHeight = videoHeight;
    }
    //定义像素格式
    AVPixelFormat srcFormat = AV_PIX_FMT_YUV420P;
    AVPixelFormat dstFormat = AV_PIX_FMT_RGB32;
    //通过解码器获取解码格式
    //srcFormat = videoCodec->pix_fmt;
    //默认最快速度的解码采用的SWS_FAST_BILINEAR参数,可能会丢失部分图片数据,可以自行更改成其他参数
    int flags = SWS_FAST_BILINEAR;
    //开辟缓存存储一帧数据
    //以下两种方法都可以,avpicture_fill已经逐渐被废弃
    //avpicture_fill((AVPicture *)avFramePicture, PictureBuf, dstFormat, videoWidth, videoHeight);
    av_image_fill_arrays(avFramePicture->data, avFramePicture->linesize, PictureBuf, dstFormat, videoWidth, videoHeight, 1);
    //图像转换
    swsContext = sws_getContext(videoWidth, videoHeight, srcFormat, videoWidth,
                                videoHeight, dstFormat, flags, nullptr, nullptr, nullptr);
    return true;
}

void FFmpegThread::run()
{
    this->init();
    while (!stopped)
    {
        //1.取出缓存中数据显示 一帧编码数据应不少于4000字节
        if (!m_CamDataArray.isEmpty()) {
            CamData.append(m_CamDataArray.dequeue());
        }
        int nDataSize = CamData.size();
        if (nDataSize <= 0){
            msleep(20);
            continue;
        }
        //2.循环显示
        while (nDataSize > 0)
        {
            size_t nBufferSize = size_t(nDataSize + FF_BUG_NO_PADDING);
            unsigned char* pData = new unsigned char[nBufferSize];
            memcpy_s(pData, nBufferSize, CamData, size_t(nDataSize));
            //通过CodecParserCtx = av_parser_init(AV_CODEC_ID_HEVC)函数,按照h265视频格式解析
            //通过videoCodec = avcodec_alloc_context3(videoDecoder)函数,按照videoDecoder编解码器编解
            int nLength = av_parser_parse2(CodecParserCtx, videoCodec, &avPacket->data,
                        &avPacket->size, pData, nDataSize, AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
            nDataSize -= nLength;
            //CamData.remove(0, nLength);
            CamData = CamData.mid(nLength);

            if (avPacket->size == 0){
                if(pData != nullptr){
                    delete[] pData;
                    pData = nullptr;
                }
                continue;
            }
            switch (CodecParserCtx->pict_type){
                case AV_PICTURE_TYPE_I: /*qInfo() << QString("收到I帧");*/ break;
                case AV_PICTURE_TYPE_P: /*qInfo() << QString("收到P帧");*/ break;
                case AV_PICTURE_TYPE_B: qInfo() << QString("收到B帧"); break;
                default: qInfo() << QString::fromLocal8Bit("收到未知帧"); break;
            }
            avcodec_send_packet(videoCodec, avPacket);
            int ret = avcodec_receive_frame(videoCodec, avFrameInput);
            if (ret < 0){
                if (ret == -11){
                    if(pData != nullptr){
                        delete[] pData;
                        pData = nullptr;
                    }
                    continue;
                }
                qInfo() << QString("解码错误");
                continue;
            }
            else {
                // initImageObjects();
                sws_scale(swsContext, (const uint8_t *const *)avFrameInput->data, avFrameInput->linesize,
                          0, videoHeight, avFramePicture->data, avFramePicture->linesize);
                //以下两种方法都可以
                QImage image(avFramePicture->data[0], videoWidth, videoHeight, QImage::Format_RGB32);
                //QImage image((uchar *)PictureBuf, videoWidth, videoHeight, QImage::Format_RGB32);
                if (!image.isNull())
                    emit receiveImage(image);
            }
            if(pData != nullptr){
                delete[] pData;
                pData = nullptr;
            }
        }
        av_packet_unref(avPacket);
        av_freep(avPacket);
        msleep(1);
    }
    //线程结束后释放资源
    free();
}
void FFmpegThread::free()
{
    if (swsContext != nullptr) {
        sws_freeContext(swsContext);
        swsContext = nullptr;
    }

    if (avPacket != nullptr) {
        av_packet_unref(avPacket);
        av_freep(avPacket);
        avPacket = nullptr;
    }

    if (avFrameInput != nullptr) {
        av_frame_free(&avFrameInput);
        avFrameInput = nullptr;
    }

    if (avFramePicture != nullptr) {
        av_frame_free(&avFramePicture);
        avFramePicture = nullptr;
    }

    if (videoCodec != nullptr) {
        avcodec_close(videoCodec);
        videoCodec = nullptr;
    }
    av_dict_free(&options);
    //qDebug() << TIMEMS << "close ffmpeg ok";
}
void FFmpegThread::play()
{
    //通过标志位让线程执行初始化
    stopped = false;
    this->start();
}
void FFmpegThread::pause()
{

}
void FFmpegThread::next()
{

}
void FFmpegThread::stop()
{
    //通过标志位让线程停止
    stopped = true;
    this->quit();
    this->wait(100);
    if(!m_CamDataArray.isEmpty())
        m_CamDataArray.clear();
    if(!CamData.isEmpty())
        CamData.clear();
}

这段代码完成了自定义一个需要视频播放窗口FFmpegWidget和FFmpegThread对象类,其中FFmpegThread线程类属于FFmpegWidget的公共变量,完成对音视频数据流的解析。

3.ffmpng类自定义

上一步完成视频播放窗口的功能模块设计,这一步将给出该视频播放窗口对象类FFmpegWidget的定义,和线程类FFmpegThread定义,并将FFmpegThread类作为FFmpegWidget的公共变量,代码如下(示例)。

#include <QtGui>
#include <QtWidgets>
#include "ffmpeginclude.h"

class FFmpegThread : public QThread
{
    Q_OBJECT
public:
    explicit FFmpegThread(QObject *parent = nullptr);
    static void initlib();

    QQueue<QByteArray> m_CamDataArray;
protected:
    void run();
    bool initImageObjects();
private:
    volatile bool stopped;          //线程停止标志位

    int frameFinish;                //一帧完成
    int videoWidth;                 //视频宽度
    int videoHeight;                //视频高度
    int oldWidth;                   //上一次视频宽度
    int oldHeight;                  //上一次视频高度
    int videoStreamIndex;           //视频流索引

    uint8_t *PictureBuf;            //存储解码后图片buffer
    AVPacket *avPacket;             //包对象 存储一帧(一般情况下)压缩编码数据
    AVFrame *avFrameInput;          //帧对象
    AVFrame *avFramePicture;        //帧对象
    AVCodecContext *videoCodec;     //视频解码器
    AVCodecParserContext *CodecParserCtx;
    SwsContext *swsContext;         //处理图片数据对象

    AVDictionary *options;          //参数对象
    AVCodec *videoDecoder;          //视频解码
    QByteArray CamData;             //临时缓存视频数据量

signals:
    //收到图片信号
    void receiveImage(const QImage &image);

public slots:
    //初始化视频对象
    bool init();
    //释放对象
    void free();
    //播放视频对象
    void play();
    //暂停播放
    void pause();
    //继续播放
    void next();
    //停止采集线程
    void stop();
};

//实时视频显示窗体类
class FFmpegWidget : public QWidget
{
    Q_OBJECT
public:
    explicit FFmpegWidget(QWidget *parent = nullptr);
    ~FFmpegWidget();
    //初始图片
    void initWidget(QString);
    FFmpegThread *thread;
protected:   
    void paintEvent(QPaintEvent *);
private:
    QImage image;
private slots:
    //接收图像并绘制
    void updateImage(const QImage &image);
public slots:
    //打开设备
    void open();
    //暂停
    void pause();
    //继续
    void next();
    //关闭设备
    void close();
    //清空
    void clear();
};

4.UI设计

上一步完成播放窗口FFmpegWidget对象类的创建,这一步将实现其UI窗口设计,UI窗口对象名称为PlotGraphic,可视化界面如下。

在上图UI界面PlotGraphic上,使用QWidget将其提升为FFmpegWidget,名称为CAM1widget。FFmpegWidget为上一步自定义的一个播放窗口,它提供了一个用于显示视频的自定义窗口。

5.用户使用

创建完上面的FFmpegWidget对象后,用户对象MainWindow需要调用/使用它。实现方式为,首先定义一个线程对象类udpthread(用于一些高频处理图像数据环境,例如网络图像传输时),然后将PlotGraphic对象设置为udpthread的私有变量,具体实现如下。

  • MainWindow对象定义
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private slots:
    void on_BtnStart_clicked();
    void on_pushButton_2_clicked();
    void on_pushButton_3_clicked();
    void ListWidgetRecvShow(QString);
private:
    Ui::MainWindow *ui;
    QListWidget *listWidgetRecv;
    udpthread *CAMthread;
};

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    CAMthread = new udpthread;
    connect(CAMthread,SIGNAL(ListWidgetRecvShow(QString)),this,SLOT(ListWidgetRecvShow(QString)));
    //setStyleSheet样式表中的属性包括边框样式、圆角、背景颜色、最小宽度、字体、字体大小、字体粗细和文字颜色
    ui->BtnStart->setStyleSheet("QPushButton{border: 0.5px solid white;\
                           border-radius: 6px;background-color: rgb(90,194,198);\
                           min-width: 80px;font-family: \"Microsoft YaHei\";font-size:11pt;\
                           font-weight: bold;color:white;} QPushButton:hover{\
                           border: 0.5px solid white;border-radius: 6px;background-color: #1fab89;\
                           min-width: 80px;font-family: \"Microsoft YaHei\";\
                           font-size:10pt;font-weight: bold;color:white;}");
    // 背景动画
    QString url = "images/rocket.gif";
    if (url.isEmpty()) {
        qInfo() << "rocket.gif is null.";
    }
    QString ImageSheet = QString("border-image: url(%1); border-radius: 8px;").arg(url);
    ui->listWidgetRecv->setStyleSheet(ImageSheet);
    /*
    首先说明一下background-image、border-image、image三种区别
    background-image:简单理解就是将图片从部件的左上角开始贴图,部件的大小限制了显示图片范围;好比是我们按照部件的大小来裁剪图片
    border-image:就是将贴图缩放进到部件里,部件能看到完整图片,但是此时图片会被压缩的变形
    iamge:部件会按照图片的原始大小进行填充
    */
    ui->listWidgetRecv->setUrl(url);
    ui->listWidgetRecv->open();
    QVBoxLayout *verticalLayout;
    verticalLayout = new QVBoxLayout(this);  //创建一个垂直布局verticalLayout
    ui->listWidgetRecv->setLayout(verticalLayout); //将垂直布局verticalLayout设置为ui->listWidgetRecv的局
    listWidgetRecv = new QListWidget(ui->listWidgetRecv); //创建一个listWidgetRecv到ui->listWidgetRecv中
    QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Expanding);
    sizePolicy1.setHorizontalStretch(0);
    sizePolicy1.setVerticalStretch(0);
    sizePolicy1.setHeightForWidth(ui->listWidgetRecv->sizePolicy().hasHeightForWidth());
    listWidgetRecv->setSizePolicy(sizePolicy1);
    listWidgetRecv->setStyleSheet("background-color: transparent;");
    listWidgetRecv->setWindowFlags(listWidgetRecv->windowFlags() |Qt::WindowStaysOnTopHint);  //总是在前
    listWidgetRecv->setAttribute(Qt::WA_TranslucentBackground);
    listWidgetRecv->setWindowFlags(Qt::FramelessWindowHint);
    verticalLayout->addWidget(listWidgetRecv); // 将listWidgetRecv添加到垂直布局verticalLayout中 
}
MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::on_BtnStart_clicked()
{
    QString url = "images/rocket.gif";
    if(ui->BtnStart->text() == tr("停止播放")){
        ui->BtnStart->setText("继续播放");
        ui->listWidgetRecv->pause(true);
    }
    else if(ui->BtnStart->text() == tr("继续播放")) {
        ui->BtnStart->setText("更换视频");
        ui->listWidgetRecv->pause(false);
    }
    else {
        ui->BtnStart->setText("停止播放");
        if (ui->listWidgetRecv->thread->url == url)
            url = "http://vd3.bdstatic.com/mda-jennyc5ci1ugrxzi/mda-jennyc5ci1ugrxzi.mp4";
        else
            //url = "images/rocket.gif";
            url = "images/test.mp4";
        ui->listWidgetRecv->setUrl(url.trimmed());
        ui->listWidgetRecv->open();
    }
}
void MainWindow::on_pushButton_2_clicked()
{
    if(ui->pushButton_2->text() == tr("打开监控画面")){
        ui->pushButton_2->setText("关闭监控");
        CAMthread->showffmpegUi(true);
    }
    else {
        ui->pushButton_2->setText("打开监控画面");
        CAMthread->showffmpegUi(false);
    }
}
void MainWindow::ListWidgetRecvShow(QString str)
{
    if(!str.isEmpty()){
        listWidgetRecv->addItem(str);
        listWidgetRecv->setCurrentRow(listWidgetRecv->count() - 1);
    }
}
void MainWindow::on_pushButton_3_clicked()
{
    QString str = "测试下背景字效果!";
    listWidgetRecv->addItem(str);
    listWidgetRecv->setCurrentRow(listWidgetRecv->count() - 1);
}
  • udpthread对象定义
#include <QIODevice>
#include "plotgraphic.h"

typedef struct {
    quint16 frameSize;		// 一帧图像的大小
    quint8 key_f;			// 关键帧还是普通帧
    quint8 packetSize;		// 每个包的大小(video data size)
    quint16 total_packets;	// 每帧里面包的个数
    quint16 index;			// 包的序号
}CAM_Head;
typedef struct
{
    quint8 CameraNo;    // 摄像头编号
    quint8 DesAddr;     // 目的地址
    quint8 VType;		// 视频流
    CAM_Head Head;      // 帧头
    quint8 Data[61];    // 数据内容
}__attribute__((packed)) CAM_Message;
class udpthread : public QThread
{
    Q_OBJECT
public:
    udpthread();
    ~udpthread();

    bool m_isYes;
    void showffmpegUi(bool);
    void closePltGrapUi(void);
    void pushcameradata(void);
    void openffmpegthread(void);
    void closeffmpegthread(void);
    int m_pluseTimeid;
    QByteArray test_data;
    void timerEvent(QTimerEvent *t);
private slots:
    void LocalVideoPlayback(QString,quint8);
    void OpenMonitorVideo(bool);
signals:
    void ListWidgetRecvShow(QString);
protected:
    void run();
    QDateTime m_zerot;
    mutable QMutex m_getDataMutex;
    int getNowSystemTime();
    void VideoDataProcess(quint8*, int);
    void DealCameraData(quint8*, int, quint8);
private:
    PlotGraphic *PltGrapUi;
    bool isfirst;
    int addcnt=0;
};

#include "udpthread.h"
udpthread::udpthread()
{
    m_pluseTimeid = 0;
    PltGrapUi = new PlotGraphic;
    PltGrapUi->setWindowIcon(PltGrapUi->style()->standardIcon(QStyle::SP_ComputerIcon));
    connect(PltGrapUi,SIGNAL(LocalVideoPlayback(QString,quint8)),this,SLOT(LocalVideoPlayback(QString,quint8)));
    connect(PltGrapUi,SIGNAL(OpenMonitorVideo(bool)),this,SLOT(OpenMonitorVideo(bool)));
}

//----以下为数据回读代码-----
void udpthread::timerEvent(QTimerEvent *t)
{
    if(t->timerId() == m_pluseTimeid)
    {
        if(test_data.size() >= (512*(addcnt+1)))
        {
            VideoDataProcess(reinterpret_cast<quint8*>(test_data.data()) + addcnt*512, 512);
            addcnt++;
        }
    }
}
//----数据回读代码结束-----

udpthread::~udpthread()
{
    m_isYes = false;
    if(PltGrapUi != nullptr)
        delete PltGrapUi;
    PltGrapUi = nullptr;
}
void udpthread::showffmpegUi(bool is)
{
    if (is)
       PltGrapUi->show();
    else
       PltGrapUi->hide();
}
void udpthread::closePltGrapUi(void)
{
    PltGrapUi->close();
}
void udpthread::pushcameradata(void)
{
    PltGrapUi->ui->CAM3widget->thread->m_CamDataArray.enqueue(test_data);
    PltGrapUi->ui->CAM4widget->thread->m_CamDataArray.enqueue(test_data);
}
void udpthread::openffmpegthread(void)
{
    addcnt = 0;
    PltGrapUi->ui->CAM3widget->open();
    PltGrapUi->ui->CAM4widget->open();
}
void udpthread::closeffmpegthread(void)
{
    PltGrapUi->ui->CAM3widget->close();
    PltGrapUi->ui->CAM4widget->close();

    isfirst = true;
}

int udpthread::getNowSystemTime()
{
//    qint64 nowTime2 = QDateTime::currentMSecsSinceEpoch();
//    return nowTime2;
    QTime nowTime1 = QTime::currentTime();
    return nowTime1.msecsSinceStartOfDay();
}

void udpthread::run()
{
    char udpBuf[1024] ={};
    int retSize = 0;
    while (m_isYes)
    {
        /* 处理发送数据 */
        // 判断网络上是否有数据 接受网络数据 返回接收数据长度
        /* 处理接收数据 */
        VideoDataProcess(reinterpret_cast<quint8*>(udpBuf), retSize);
        usleep(10);
    }
}
/*
 * 网络监控视频数据处理
 * */
void udpthread::VideoDataProcess(quint8 *p, int len)
{
    if (len <= 0)
        return;
    quint8 id;
    // 对网络上的数据进行解析,看ID几号摄像头
    id = 4;
    DealCameraData(p, len, id);
}

void udpthread::DealCameraData(quint8 *p, int valen, quint8 id)
{
#if 0
    if(id != 3 && id != 4){
        qInfo() << QString("摄像头编号错误:%1").arg(id);
        return;
    }
    if(valen != 497){
        qInfo() << QString("摄像头有效数据长度错误:%1").arg(valen);
        //return;
    }
    CAM_Message cam;
    for(quint8 i=0; i<7; i++)
    {
        cam.CameraNo = id;
        memcpy((quint8*)&cam.DesAddr, &p[i*71], sizeof(CAM_Message)-1);
        if(cam.DesAddr != 0x21 || cam.VType != 0x22){
            if(cam.DesAddr != 0x00 && cam.VType != 0x00)
                qInfo() << QString("%1#摄像头目的地址0x%2或视频流0x%3错误").arg(id).arg(QString::number(cam.DesAddr,16)).arg(QString::number(cam.VType,16));
            continue;
        }
        //判断每包图像数据长度合法性
        if(cam.Head.packetSize > 61 || cam.Head.packetSize <= 0)
            continue;
        //找到图像帧头第一帧
        if(isfirst) {
            if(cam.Head.index == 0) {
                if(cam.Data[0]==0 && cam.Data[1]==0 && cam.Data[2]==0 && cam.Data[3]==0x01 && cam.Data[4]==0x40)
                    isfirst = false;
                else
                    continue;
            }
            else
                continue;
        }
        if(cam.CameraNo == 3){
            QByteArray temp3;
            temp3.append(reinterpret_cast<char*>(cam.Data), cam.Head.packetSize);
            PltGrapUi->ui->CAM3widget->thread->m_CamDataArray.enqueue(temp3);
        }
        if(cam.CameraNo == 4){
            QByteArray temp4;
            temp4.append(reinterpret_cast<char*>(cam.Data), cam.Head.packetSize);
            PltGrapUi->ui->CAM4widget->thread->m_CamDataArray.enqueue(temp4);
        }
    }
#endif

    if(id == 3){
        QByteArray temp3;
        temp3.append(reinterpret_cast<char*>(p), valen);
        PltGrapUi->ui->CAM3widget->thread->m_CamDataArray.enqueue(temp3);
    }
    if(id == 4){
        QByteArray temp4;
        temp4.append(reinterpret_cast<char*>(p), valen);
        PltGrapUi->ui->CAM4widget->thread->m_CamDataArray.enqueue(temp4);
    }
}
void udpthread::LocalVideoPlayback(QString fileName, quint8 RunRate)
{
    //2.读取文件数据内容
    QFile *file;
    file = new QFile(fileName);
    if(!file->open(QIODevice::ReadOnly))
        return;
    QByteArray data = file->readAll();
    //3.开启视频图像线程
    PltGrapUi->ui->CAM3widget->open();
    //4.选择解析速率
    if(RunRate == 0)
        PltGrapUi->ui->CAM3widget->thread->m_CamDataArray.enqueue(data);
    else {
        test_data.append(data);
        m_pluseTimeid = startTimer(RunRate);
    }
    file->close();
    delete file;
    file = nullptr;
}
void udpthread::OpenMonitorVideo(bool isOpen)
{
    if(isOpen) {
        m_isYes = true;
        start();  // 这里设置为网络线程开启运行 一般为UDP网络高速处理图像
    }
    else
       m_isYes = false;
}

总结

本文基于Qt平台,依靠FFmpeg内核库,设计了一款播放器,供大家参考使用。其中FFmpeg版本为4.2.3,在上述给出的FFmpeg官网下载,并封装成动态库文件使用,下一章节将介绍FFmpeg的使用,并将下载的源码封装成库文件。

博文中相应的工程代码Qt-Case.zip 利用Qt开发软件进行编的例程,为博文提供案例-CSDN文库。


http://www.kler.cn/a/468723.html

相关文章:

  • C# 设计模式(结构型模式):外观模式
  • 用户界面软件01
  • 网络安全抓包
  • Redis--高可用(主从复制、哨兵模式、分片集群)
  • doris:基于 Arrow Flight SQL 的高速数据传输链路
  • [python3]Excel解析库-openpyxl
  • Kotlin 面向对象与函数式编程
  • 飞书企业消息实践
  • Eclipse 首选项(Preferences)
  • Spring MVC实战指南:构建高效Web应用的架构与技巧(三)
  • C++26 函数契约(Contract)概览
  • 计算机网络 —— 网络编程(TCP)
  • 基于Web的足球青训俱乐部管理后台系统的设计与开发源码(springboot+mysql+vue)
  • JAVA开发中 MyBatis XML 映射文件 的作用
  • LabVIEW语言学习过程是什么?
  • 轻量级通信协议 JSON-RPC 2.0 详解
  • GraalVM:云原生时代的Java虚拟机
  • QPainter
  • 从零开始学TiDB(8) TiFlash 主要架构
  • 通过串口通信控制led灯的亮灭
  • 如何在centos中进行有效的网络管理
  • 基于ESP32的桌面小屏幕实战[5]:PCB下单
  • 深入Android架构(从线程到AIDL)_12 Android UI 单线程程序
  • AIGC生图实战技巧分享
  • iptable限制多个端口出站
  • C++直接内存管理new和delete