一、环境介绍
操作系统: win10 64位
QT版本: QT5.12.6
编译器: MinGW 32
FFMPEG版本: 4.2.2
win32下使用FFMPEG 4.2.2库下载地址:https://download.csdn.net/download/xiaolong1126626497/12321684
完整项目源码下载地址(下载就可编译运行,不懂可以私信): https://download.csdn.net/download/xiaolong1126626497/19354698
二、功能介绍
基于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
//指定文件的编码为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 效果