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

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

一、环境介绍

操作系统: 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 效果

image.png

目录
相关文章
|
1月前
|
存储 网络协议 C语言
【C/C++ 串口编程 】深入探讨C/C++与Qt串口编程中的粘包现象及其解决策略
【C/C++ 串口编程 】深入探讨C/C++与Qt串口编程中的粘包现象及其解决策略
80 0
|
1月前
|
安全 前端开发 C++
C++视角下的Qt按钮:从基础应用到高级定制(二)
C++视角下的Qt按钮:从基础应用到高级定制
45 2
|
1月前
|
XML JSON 算法
C++视角下的Qt按钮:从基础应用到高级定制(一)
C++视角下的Qt按钮:从基础应用到高级定制
90 2
|
1月前
|
Linux 数据处理 C++
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用(一)
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用
77 0
|
1月前
|
存储 Linux API
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用(三)
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用
31 1
|
1月前
|
消息中间件 Linux 数据处理
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用(二)
Linux系统编程 C/C++ 以及Qt 中的零拷贝技术: 从底层原理到高级应用
32 1
|
1月前
|
编解码 算法 vr&ar
深度剖析FFmpeg视频解码后的帧处理到Qt显示 从AVFrame到QImage的转换(二)
深度剖析FFmpeg视频解码后的帧处理到Qt显示 从AVFrame到QImage的转换
30 1
|
1月前
|
存储 编解码 算法
深度剖析FFmpeg视频解码后的帧处理到Qt显示 从AVFrame到QImage的转换(一)
深度剖析FFmpeg视频解码后的帧处理到Qt显示 从AVFrame到QImage的转换
63 1
|
1天前
|
开发框架 Linux API
Qt:构建高效且用户友好的跨平台应用
Qt:构建高效且用户友好的跨平台应用
|
30天前
|
测试技术 API UED
【Qt 应用开发 】初步理解 Qt窗口中的模态性应用
【Qt 应用开发 】初步理解 Qt窗口中的模态性应用
70 1

热门文章

最新文章

推荐镜像

更多