天天看点

QT应用编程:基于FFmpeg设计的精简版视频播放器

作者:音视频开发老舅

一、环境介绍

操作系统: win10 64位

QT版本: QT5.12.6

编译器: MinGW 32

FFMPEG版本: 4.2.2

二、功能介绍

基于ffmpeg设计的视频播放器,只解码处理了图像,没有处理音频。写这个例子方便在其他平台移植播放视频。

2.1 xxx.pro文件

QT += core gui

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 += \

ReverseDecodThread.cpp \

VideoFrameDisplay.cpp \

main.cpp \

widget.cpp

HEADERS += \

ReverseDecodThread.h \

VideoFrameDisplay.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*

}

2.2 ReverseDecodThread.cpp

【文章福利】免费领取Qt开发学习资料包、大厂面试题、技术视频和学习路线图,资料包括(Qt C++基础,数据库编程,Qt项目实战、Qt框架、qt线程等等)有需要的可以加714620761群领取哦~

//指定文件的编码为UTF-8

#pragma execution_character_set("utf-8")

#include "ReverseDecodThread.h"

ReverseDecodThread::ReverseDecodThread()

{

//注册解码器

av_register_all();

//分配上下文

format_ctx= avformat_alloc_context();

qDebug() << "FFMPEG版本信息:" << av_version_info();

}

ReverseDecodThread::~ReverseDecodThread()

{

FreeRAM();

if(format_ctx)

{

avformat_close_input(&format_ctx);//释放解封装器的空间,以防空间被快速消耗完

avformat_free_context(format_ctx);

}

}

void ReverseDecodThread::FreeRAM()

{

if(SRC_VIDEO_pFrame) av_frame_free(&SRC_VIDEO_pFrame);

if(RGB24_pFrame) av_frame_free(&RGB24_pFrame);

if(img_convert_ctx)sws_freeContext(img_convert_ctx);

if(out_buffer_rgb)av_free(out_buffer_rgb);

SRC_VIDEO_pFrame=nullptr;

RGB24_pFrame=nullptr;

img_convert_ctx=nullptr;

out_buffer_rgb=nullptr;

}

/*

工程: FFMPE_ReversePlay

日期: 2021-04-06

作者: DS小龙哥

环境: win10 QT5.12.6 MinGW32

功能: 加载媒体文件

*/

int ReverseDecodThread::LoadVideoFile(QString media)

{

//释放空间

FreeRAM();

//打开媒体文件

strncpy(m_MediaFile, media.toUtf8().data(), sizeof(m_MediaFile));

if(avformat_open_input(&format_ctx, m_MediaFile, nullptr, nullptr) != 0)

{

LogSend(tr("无法打开视频文件: %1").arg(m_MediaFile));

return -1;

}

//读取媒体文件的数据包以获取流信息

if(avformat_find_stream_info(format_ctx, nullptr) < 0)

{

LogSend(tr("无法获取流信息.\n"));

return -1;

}

LogSend(tr("视频中流的数量: %1\n").arg(format_ctx->nb_streams));

for(int i = 0; i < format_ctx->nb_streams; ++i)

{

const AVStream* stream = format_ctx->streams[i];

if(stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)

{

//查找解码器

AVCodec *video_pCodec=avcodec_find_decoder(stream->codecpar->codec_id);

//打开解码器

if(avcodec_open2(stream->codec,video_pCodec,nullptr)!=0)

{

LogSend(tr("解码器打开失败.\n"));

return -1;

}

video_stream_index = i;

//得到视频帧的宽高

video_width=stream->codecpar->width;

video_height=stream->codecpar->height;

LogSend(tr("视频帧的尺寸(以像素为单位): (宽X高)%1x%2 像素格式: %3\n").arg(

stream->codecpar->width).arg(stream->codecpar->height).arg(stream->codecpar->format));

}

}

if (video_stream_index == -1)

{

LogSend("没有检测到视频流.\n");

return -1;

}

AVRational frameRate = format_ctx->streams[video_stream_index]->avg_frame_rate;

/*设置视频转码器*/

SRC_VIDEO_pFrame = av_frame_alloc();

RGB24_pFrame = av_frame_alloc();// 存放解码后YUV数据的缓冲区

//将解码后的YUV数据转换成RGB24

img_convert_ctx = sws_getContext(video_width, video_height,

format_ctx->streams[video_stream_index]->codec->pix_fmt,video_width, video_height,

AV_PIX_FMT_RGB24, SWS_BICUBIC, nullptr, nullptr, nullptr);

//计算RGB图像所占字节大小

int numBytes=avpicture_get_size(AV_PIX_FMT_RGB24,video_width,video_height);

//申请空间存放RGB图像数据

out_buffer_rgb = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));

// avpicture_fill函数将ptr指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了ptr的数据

avpicture_fill((AVPicture *) RGB24_pFrame, out_buffer_rgb, AV_PIX_FMT_RGB24,

video_width, video_height);

qDebug()<<"format_ctx->duration:"<<format_ctx->duration;

}

void ReverseDecodThread::SetSate(int run)

{

m_run = run;

}

int ReverseDecodThread::GetSate()

{

return m_run;

}

//跳转视频帧

void ReverseDecodThread::SetSeekPos(qint64 pos)

{

is_CurrentSeekPos = 1;

m_n64CurrentSeekPos = pos;

m_run=1; //运行状态

//获取系统本地时间

play_base_time=QDateTime::currentMSecsSinceEpoch();

}

void ReverseDecodThread::PausePlay()

{

m_run = 2;

}

void ReverseDecodThread::StopPlay()

{

m_run = 0;

}

void ReverseDecodThread::LogSend(QString text)

{

qDebug() << text;

}

//线程执行起点

void ReverseDecodThread::run()

{

LogSend("开始播放视频.\n");

StartPlay();

}

//播放视频

int ReverseDecodThread::StartPlay()

{

//获取系统本地时间

play_base_time=QDateTime::currentMSecsSinceEpoch();

//表示视频加载成功

while(m_run)

{

if(m_run == 2)

{

msleep(100); //暂停播放

continue;

}

if (is_CurrentSeekPos)

{

is_CurrentSeekPos = 0;

//偏移到指定位置再开始解码 AVSEEK_FLAG_BACKWARD 向后找最近的关键帧

av_seek_frame(format_ctx, -1, m_n64CurrentSeekPos* AV_TIME_BASE, AVSEEK_FLAG_BACKWARD);

qDebug()<<"跳转的位置:"<<m_n64CurrentSeekPos;

}

double video_clock;

AVPacket pkt;

//读取一帧数据

if(av_read_frame(format_ctx, &pkt) < 0)

{

m_run=2; //设置为暂停状态

qDebug()<<"数据读取完毕.";

continue;

}

if(pkt.stream_index == video_stream_index)

{

//当前时间

video_clock = av_q2d(format_ctx->streams[video_stream_index]->time_base) * pkt.pts;

qDebug()<<"pkt.pts:"<<pkt.pts<<"video_clock:"<<video_clock;

//解码视频 frame

//发送视频帧

if ( avcodec_send_packet(format_ctx->streams[video_stream_index]->codec,&pkt) != 0)

{

av_packet_unref(&pkt);//不成功就释放这个pkt

continue;

}

//接受后对视频帧进行解码

if (avcodec_receive_frame(format_ctx->streams[video_stream_index]->codec, SRC_VIDEO_pFrame) != 0)

{

av_packet_unref(&pkt);//不成功就释放这个pkt

continue;

}

//转格式

sws_scale(img_convert_ctx,

(uint8_t const **) SRC_VIDEO_pFrame->data,

SRC_VIDEO_pFrame->linesize, 0, video_height, RGB24_pFrame->data,

RGB24_pFrame->linesize);

//释放包

av_packet_unref(&pkt);

//加载图片数据

QImage image(out_buffer_rgb,video_width,video_height,QImage::Format_RGB888);

//通过pts同步视频帧--显示视频帧

while (true)

{

qint64 t1=QDateTime::currentMSecsSinceEpoch();

qint64 t2=t1-play_base_time;

qDebug()<<"t1:"<<t1;

qDebug()<<"t2:"<<t2;

qDebug()<<"video_clock:"<<video_clock*1000;

if(t2>=video_clock*1000)

{

break;

}

else

{

QThread::msleep(1);

}

}

//通知界面更新

VideoDataOutput(image.copy());

//时间信号

sig_getCurrentTime(video_clock, format_ctx->duration *1.0 / AV_TIME_BASE);

// QThread::msleep(40);

}

}

LogSend("视频音频解码播放器的线程退出成功.\n");

return 0;

}

2.3 ReverseDecodThread

#ifndef VIDEO_PLAY_H

#define VIDEO_PLAY_H

#include <QThread>

#include <qdebug.h>

#include <QImage>

#include <QDateTime>

extern "C" {

#include <libavutil/opt.h>

#include <libavutil/mem.h>

#include <libavutil/fifo.h>

#include <libavutil/pixfmt.h>

#include <libavutil/log.h>

#include <libavutil/opt.h>

#include <libavcodec/avcodec.h>

#include <libavformat/avformat.h>

#include <libswscale/swscale.h>

#include <libswresample/swresample.h>

#include <libavfilter/avfilter.h>

#include <libavfilter/buffersrc.h>

#include <libavfilter/buffersink.h>

}

//视频音频解码线程

class ReverseDecodThread: public QThread

{

Q_OBJECT

public:

//构造函数

ReverseDecodThread();

~ReverseDecodThread();

char m_MediaFile[1024];

int m_run; //1表示运行 0表示停止 2表示暂停

double m_n64CurrentSeekPos = 0; //当前seek位置

bool is_CurrentSeekPos = 0; //1需要跳转 0不需要

void SetSate(int run);

int GetSate();

void SetSeekPos(qint64 pos);

void PausePlay();

void StopPlay();

void LogSend(QString text);

//加载视频文件

int LoadVideoFile(QString media);

//释放内存

void FreeRAM();

protected:

void run();

int StartPlay();

signals:

void sig_getCurrentTime(double Sec, double total_Sec);

void VideoDataOutput(QImage); //输出信号

private:

int video_width=0;

int video_height=0;

AVFormatContext *format_ctx=nullptr;

int video_stream_index = -1;

AVFrame *RGB24_pFrame = nullptr;

AVFrame *SRC_VIDEO_pFrame= nullptr;

uint8_t *out_buffer_rgb= nullptr;

struct SwsContext *img_convert_ctx=nullptr; //用于解码后的视频格式转换

double video_clock_tmp;

qint64 play_base_time=0;

};

#endif // VIDEO_PLAY_H

2.3 效果

QT应用编程:基于FFmpeg设计的精简版视频播放器

继续阅读