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[i71], 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);
}
}
#endifif(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文库。