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

FFmpeg + ‌Qt‌ 简单视频播放器代码

一个基于 ‌FFmpeg 4.x‌ 和 ‌Qt‌ 的简单视频播放器代码示例,实现视频解码和渲染到 Qt 窗口的功能。

1)ffmpeg库界面,视频解码支持软解和硬解方式。

2)QImage/QPixmap显示视频图片。

1. Qt 项目配置(.pro 文件)

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

INCLUDEPATH += $$PWD/ffmpeg-4.2.2-win32/include
LIBS += -L$$PWD/ffmpeg-4.2.2-win32/lib -lavcodec -lavformat -lavutil -lswscale

# 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 += \
    main.cpp \
    mainwindow.cpp \
    playimage.cpp \
    videodecode.cpp

HEADERS += \
    mainwindow.h \
    playimage.h \
    videodecode.h

FORMS += \
    mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

2. 视频解码类

‌文件 videodecode.h

#ifndef VIDEODECODE_H
#define VIDEODECODE_H

//视频解码类
#include <QString>
#include <QImage>
#include <thread>

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>
#include <libavutil/imgutils.h>
}

//流类型
enum StreamType
{
    StreamType_Video        = 0,
    StreamType_Audio        = 1,
    StreamType_Text         = 2,
};

//格式类型
enum FormatType
{
    FormatType_RGB24        =   0,
    FormatType_RGB32        =   1,
    FormatType_YUV420       =   2,
    FormatType_YUV422       =   3,
};

//文件状态
enum  FileStatus
{
    FileStatus_OverFileTail     =   0,  //达到文件尾
    FileStatus_OverFileHead     =   1,  //达到文件头
    FileStatus_TrigeException   =   2,  //发生异常
};

//流解码回调函数
typedef void (*StreamDecodeCallback)(int nStreamType, int nFormatType, long long llDecodeTs, long long llPlayTs, int width, int height, unsigned char ** pStreamData, int * linesize, void * pUserData);

//文件状态回调函数
typedef  void (*FileStatusCallback)(int FileStatus, int nErrorCode, void * pUserData);

class VideoDecode
{
public:
    VideoDecode();
    ~VideoDecode();

public:
    void globalInit();//初始化ffmpeg库(整个程序中只需加载一次)
    void globalUnInit();//反初始化ffmpeg库(整个程序中只需加载一次)

public:
    void setStreamDecodeCallback(StreamDecodeCallback funStreamDecodeCallback, void * userData);
    void setFileStatusCallback(FileStatusCallback funFileStatusCallback, void * userData);

    void setHWDecoder(bool flag);                 // 是否使用硬件解码器
    bool isHWDecoder();
    bool open(const QString& url);              // 打开媒体文件,或者流媒体rtmp、strp、http
    void close();                               // 关闭
    bool isClose();

public:
    void decodeProccessThread();                //解码线程
    static QImage ConvertRGB24FrameToQImage(unsigned char *data, int width, int height);

protected:
    void initHWDecoder(const AVCodec *codec);
    bool dataCopy();    //硬件解码完成需要将数据从GPU复制到CPU
    void freeDecode();
    qreal rationalToDouble(AVRational* rational);

private:
    // FFmpeg 相关对象
    AVFormatContext *formatCtx = nullptr;
    AVCodecContext *codecCtx = nullptr;
    AVFrame *frame = nullptr, *rgbFrame = nullptr;
    AVFrame *frameHW = nullptr;
    SwsContext *swsCtx = nullptr;
    uchar* buffer = nullptr;                      // YUV图像需要转换位RGBA图像,这里保存转换后的图形数据
    AVPacket* packet = nullptr;
    int videoStreamIndex = -1;  // 视频流索引
    qint64 totalTime    = 0;                    // 视频总时长
    qint64 totalFrames  = 0;                    // 视频总帧数
    qint64 obtainFrames = 0;                    // 视频当前获取到的帧数
    qint64 pts          = 0;                    // 图像帧的显示时间
    qreal  frameRate    = 0;                    // 视频帧率
    int  width = 0;         //视频分辨率大小width
    int  height = 0;        //视频分辨率大小height

    std::vector<int> vecHWDeviceTypes;            // 保存当前环境支持的硬件解码器
    AVBufferRef* hw_device_ctx = nullptr;         // 对数据缓冲区的引用
    bool   hwDecoderFlag = false;                 // 记录是否使用硬件解码

    std::thread threadDecode;
    bool stopWorkFlag = true;
    StreamDecodeCallback funCallbackByStreamDecode = nullptr;
    void * userDataByStreamDecode = nullptr;
    FileStatusCallback funCallbackByFileStatus = nullptr;
    void * userDataByFileStatus = nullptr;
};

#endif // VIDEODECODE_H

‌文件 videodecode.cpp

#include "videodecode.h"
#include <QTime>
#include <QDebug>
#include <QStringList>
#include <chrono>


/*********************************** FFmpeg获取GPU硬件解码帧格式的回调函数 *****************************************/
static enum AVPixelFormat g_pixelFormat;

/**
 * @brief      回调函数,获取GPU硬件解码帧的格式
 * @param s
 * @param fmt
 * @return
 */
AVPixelFormat get_hw_format(AVCodecContext* s, const enum AVPixelFormat* fmt)
{
    Q_UNUSED(s)
    const enum AVPixelFormat* p;

    for (p = fmt; *p != -1; p++)
    {
        if(*p == g_pixelFormat)
        {
            return *p;
        }
    }

    qDebug() << "无法获取硬件表面格式.";         // 当同时打开太多路视频时,如果超过了GPU的能力,可能会返回找不到解码帧格式
    return AV_PIX_FMT_NONE;
}
/************************************************ END ******************************************************/

VideoDecode::VideoDecode()
{

}

VideoDecode::~VideoDecode()
{
}

void VideoDecode::globalInit()
{
    //        av_register_all();         // 已经从源码中删除
    /**
     * 初始化网络库,用于打开网络流媒体,此函数仅用于解决旧GnuTLS或OpenSSL库的线程安全问题。
     * 一旦删除对旧GnuTLS和OpenSSL库的支持,此函数将被弃用,并且此函数不再有任何用途。
     */
    avformat_network_init();
}

void VideoDecode::globalUnInit()
{
    avformat_network_deinit();
}

qreal VideoDecode::rationalToDouble(AVRational* rational)
{
    qreal frameRate = (rational->den == 0) ? 0 : (qreal(rational->num) / rational->den);
    return frameRate;
}

void VideoDecode::setStreamDecodeCallback(StreamDecodeCallback funStreamDecodeCallback, void * userData)
{
    funCallbackByStreamDecode = funStreamDecodeCallback;
    userDataByStreamDecode = userData;
}
void VideoDecode::setFileStatusCallback(FileStatusCallback funFileStatusCallback, void * userData)
{
    funCallbackByFileStatus = funFileStatusCallback;
    userDataByFileStatus = userData;
}

//初始化硬件解码器
void VideoDecode::initHWDecoder(const AVCodec *codec)
{
    if(!codec) return;

    for(int i = 0; ; i++)
    {
        const AVCodecHWConfig* config = avcodec_get_hw_config(codec, i);    // 检索编解码器支持的硬件配置。
        if(!config)
        {
            qDebug() << "打开硬件解码器失败!";
            return;          // 没有找到支持的硬件配置
        }

        if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)       // 判断是否是设备类型
        {
            for(auto i : vecHWDeviceTypes)
            {
                if(config->device_type == AVHWDeviceType(i))                 // 判断设备类型是否是支持的硬件解码器
                {
                    g_pixelFormat = config->pix_fmt;

                    // 打开指定类型的设备,并为其创建AVHWDeviceContext。
                    int ret = av_hwdevice_ctx_create(&hw_device_ctx, config->device_type, nullptr, nullptr, 0);
                    if(ret < 0)
                    {
                        freeDecode();
                        return ;
                    }
                    qDebug() << "打开硬件解码器:" << av_hwdevice_get_type_name(config->device_type);
                    codecCtx->hw_device_ctx = av_buffer_ref(hw_device_ctx);  // 创建一个对AVBuffer的新引用。
                    codecCtx->get_format = get_hw_format;                    // 由一些解码器调用,以选择将用于输出帧的像素格式
                    return;
                }
            }
        }
    }
}

//硬件解码完成需要将数据从GPU复制到CPU
bool VideoDecode::dataCopy()
{
    if(frame->format != g_pixelFormat)
    {
        av_frame_unref(frame);
        return false;
    }

#if 1   // av_hwframe_map处理速度比av_hwframe_transfer_data快(av_hwframe_map在ffmpeg3.3以后才有)
    int ret = av_hwframe_map(frameHW, frame, 0);                   // 映射硬件数据帧
    if(ret < 0)
    {
        av_frame_unref(frame);
        return false;
    }
    frameHW->width = frame->width;
    frameHW->height = frame->height;
#else

    int ret = av_hwframe_transfer_data(frameHW, frame, 0);       // 将解码后的数据从GPU复制到CPU(frameHW) 比较耗时,但硬解码速度比软解码快很多
    if(ret < 0)
    {
        av_frame_unref(frame);
        return false;
    }
    av_frame_copy_props(frameHW, frame);   // 仅将“metadata”字段从src复制到dst。
#endif

    return true;
}

void VideoDecode::setHWDecoder(bool flag)
{
    hwDecoderFlag = flag;
}
bool VideoDecode::isHWDecoder()
{
    return hwDecoderFlag;
}

bool VideoDecode::open(const QString& url)
{
    if(url.isNull()) return false;

    AVHWDeviceType type = AV_HWDEVICE_TYPE_NONE;      // ffmpeg支持的硬件解码器
    QStringList strTypes;
    while ((type = av_hwdevice_iterate_types(type)) != AV_HWDEVICE_TYPE_NONE)       // 遍历支持的设备类型。
    {
        vecHWDeviceTypes.push_back(type);
        const char* ctype = av_hwdevice_get_type_name(type);  // 获取AVHWDeviceType的字符串名称。
        if(ctype)
        {
            strTypes.append(QString(ctype));
        }
    }
    qDebug() << "支持的硬件解码器:";
    qDebug() << strTypes;

      AVDictionary* dict = nullptr;
      av_dict_set(&dict, "rtsp_transport", "tcp", 0);      // 设置rtsp流使用tcp打开,如果打开失败错误信息为【Error number -135 occurred】可以切换(UDP、tcp、udp_multicast、http),比如vlc推流就需要使用udp打开
      av_dict_set(&dict, "max_delay", "3", 0);             // 设置最大复用或解复用延迟(以微秒为单位)。当通过【UDP】 接收数据时,解复用器尝试重新排序接收到的数据包(因为它们可能无序到达,或者数据包可能完全丢失)。这可以通过将最大解复用延迟设置为零(通过max_delayAVFormatContext 字段)来禁用。
      av_dict_set(&dict, "timeout", "1000000", 0);         // 以微秒为单位设置套接字 TCP I/O 超时,如果等待时间过短,也可能会还没连接就返回了。

      // 打开输入流并返回解封装上下文
      int ret = avformat_open_input(&formatCtx,          // 返回解封装上下文
                                    url.toStdString().data(),  // 打开视频地址
                                    nullptr,                   // 如果非null,此参数强制使用特定的输入格式。自动选择解封装器(文件格式)
                                    &dict);                    // 参数设置
      // 释放参数字典
      if(dict)
      {
          av_dict_free(&dict);
      }
      // 打开视频失败
      if(ret < 0)
      {
          qDebug() << "Failed to avformat_open_input";
          return false;
      }

      // 读取媒体文件的数据包以获取流信息。
      ret = avformat_find_stream_info(formatCtx, nullptr);
      if(ret < 0)
      {
          qDebug() << "Failed to avformat_find_stream_info";
          freeDecode();
          return false;
      }
      totalTime = formatCtx->duration / (AV_TIME_BASE / 1000); // 计算视频总时长(毫秒)
      qDebug() << QString("视频总时长:%1 ms,[%2]").arg(totalTime).arg(QTime::fromMSecsSinceStartOfDay(int(totalTime)).toString("HH:mm:ss zzz"));

      // 通过AVMediaType枚举查询视频流ID(也可以通过遍历查找),最后一个参数无用
      videoStreamIndex = av_find_best_stream(formatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
      if(videoStreamIndex < 0)
      {
          qDebug() << "Failed to av_find_best_stream";
          freeDecode();
          return false;
      }

      AVStream* videoStream = formatCtx->streams[videoStreamIndex];  // 通过查询到的索引获取视频流

      // 获取视频图像分辨率(AVStream中的AVCodecContext在新版本中弃用,改为使用AVCodecParameters)
      width = videoStream->codecpar->width;
      height = videoStream->codecpar->height;
      frameRate = rationalToDouble(&videoStream->avg_frame_rate);  // 视频帧率

      // 通过解码器ID获取视频解码器(新版本返回值必须使用const)
      const AVCodec* codec = avcodec_find_decoder(videoStream->codecpar->codec_id);
      totalFrames = videoStream->nb_frames;
      qDebug() << QString("分辨率:[w:%1,h:%2] 帧率:%3  总帧数:%4  解码器:%5")
                  .arg(width).arg(height).arg(frameRate).arg(totalFrames).arg(codec->name);

      // 分配AVCodecContext并将其字段设置为默认值。
      codecCtx = avcodec_alloc_context3(codec);
      if(!codecCtx)
      {
          qDebug() << "Failed to avcodec_alloc_context3";
          freeDecode();
          return false;
      }

      // 使用视频流的codecpar为解码器上下文赋值
      ret = avcodec_parameters_to_context(codecCtx, videoStream->codecpar);
      if(ret < 0)
       {
          qDebug() << "Failed to avcodec_parameters_to_context";
          freeDecode();
          return false;
       }

       codecCtx->flags2 |= AV_CODEC_FLAG2_FAST;    // 允许不符合规范的加速技巧。
       codecCtx->thread_count = 8;                 // 使用8线程解码

       if(isHWDecoder())
       {
           initHWDecoder(codec);     // 初始化硬件解码器(在avcodec_open2前调用)
       }

      // 初始化解码器上下文,如果之前avcodec_alloc_context3传入了解码器,这里设置NULL就可以
      ret = avcodec_open2(codecCtx, nullptr, nullptr);
      if(ret < 0)
      {
          qDebug() << "Failed to avcodec_open2";
          freeDecode();
          return false;
      }

      // 分配AVPacket并将其字段设置为默认值。
      packet = av_packet_alloc();
      if(!packet)
      {
          qDebug() << "Failed to av_packet_alloc";
          freeDecode();
          return false;
      }

      // 初始化帧和转换上下文
      frame = av_frame_alloc();
      rgbFrame = av_frame_alloc();
      frameHW = av_frame_alloc();
      int size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, codecCtx->width, codecCtx->height, 1);
      buffer = (uint8_t *)av_malloc(size + 1000);
      av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24,
                           codecCtx->width, codecCtx->height, 1);

      /*
      // 初始化 SWS 上下文(YUV -> RGB 转换)
      swsCtx = sws_getContext(codecCtx->width, codecCtx->height, codecCtx->pix_fmt,
                              codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB24,
                              SWS_BILINEAR, nullptr, nullptr, nullptr);
     */
    stopWorkFlag = false;
    std::thread t(std::bind(&VideoDecode::decodeProccessThread,this));
    threadDecode = std::move(t);

    return true;
}

void VideoDecode::close()
{
    stopWorkFlag = true;

    // 因为avformat_flush不会刷新AVIOContext (s->pb)。如果有必要,在调用此函数之前调用avio_flush(s->pb)。
    if(formatCtx && formatCtx->pb)
    {
        avio_flush(formatCtx->pb);
    }
    if(formatCtx)
    {
        avformat_flush(formatCtx);   // 清理读取缓冲
    }

    if(threadDecode.joinable())
    {
        threadDecode.join();
    }

    freeDecode();
}

bool VideoDecode::isClose()
{
    return stopWorkFlag;
}

QImage VideoDecode::ConvertRGB24FrameToQImage(unsigned char *data, int width, int height)
{
    // 创建 QImage 并显示
    QImage img(data, width, height, QImage::Format_RGB888);
    return img;
}

void VideoDecode::decodeProccessThread()
{
  std::chrono::high_resolution_clock::time_point tpStart = std::chrono::high_resolution_clock::now();
  int nWaitTimes = 40;
  if(frameRate != 0)
  {
     nWaitTimes = 1000.0/frameRate;
  }

  long long llDecodeTs = 0;
  long long llPlayTs = 0;
  long long llStartPlayTs = 0;
  bool bStartPlayTsSetValueFlag = false;
  bool bProccessFileTail = false;

    while (true)
    {
        if(stopWorkFlag)
        {
            break;
        }

        // 读取下一帧数据
        int readRet = av_read_frame(formatCtx, packet);
        if(readRet < 0)
        {
            if (readRet == AVERROR_EOF)
            {
               int ret = avcodec_send_packet(codecCtx, packet); // 读取完成后向解码器中传如空AVPacket,否则无法读取出最后几帧
               if(ret < 0)
               {
                   av_packet_unref(packet);
                   bProccessFileTail = true;
                   break;
               }
            }
            else
            {
                break;
            }
        }
        else
        {
            if(stopWorkFlag)
            {
                break;
            }

            if(packet->stream_index == videoStreamIndex)     // 如果是图像数据则进行解码
            {
                av_packet_rescale_ts(packet, formatCtx->streams[videoStreamIndex]->time_base, codecCtx->time_base); // 转换至解码器时间基‌
                // 将读取到的原始数据包传入解码器
                int ret = avcodec_send_packet(codecCtx, packet);
                if(ret < 0)
                {
                    qDebug() << "Error sending packet";
                    av_packet_unref(packet);
                    continue;
                }
            }
            else
            {
                //其他流(比如:音频)
                av_packet_unref(packet);
                continue;
            }
        }

        // 接收解码后的帧(这里一次只解码一帧)
       int ret = avcodec_receive_frame(codecCtx, frame);
        if (ret == AVERROR(EAGAIN))
        {
            av_packet_unref(packet);
            continue;
        }
        else if (ret == AVERROR_EOF)
        {
            av_packet_unref(packet);

            //当无法读取到AVPacket并且解码器中也没有数据时表示读取完成
            bProccessFileTail = true;
            break;
        }
        else if (ret < 0)
        {
            qDebug() << "Error during decoding";
            av_packet_unref(packet);
            continue;
        }
        else
        {
            // 这样写是为了兼容软解码或者硬件解码打开失败情况
             AVFrame*  frameTemp = frame;
             if(!frame->data[0])               // 如果是硬件解码就进入
              {
                   // 将解码后的数据从GPU拷贝到CPU
                   if(!dataCopy())
                   {
                        av_frame_unref(frameHW);
                        continue;
                   }

                   frameTemp = frameHW;
             }

            // 处理时间戳的核心逻辑
             int64_t raw_pts = frameTemp->pts;
             int64_t raw_dts = frameTemp->pkt_dts;
             // 处理未定义时间戳的情况
             if (raw_pts == AV_NOPTS_VALUE)
             {
                 // 使用DTS或估算PTS(需要根据帧率等参数)
                 if(raw_dts != AV_NOPTS_VALUE)
                 {
                     raw_pts = raw_dts;
                 }
                 else
                 {
                     raw_pts = 0;
                     raw_dts = 0;
                 }
             }

             // 转换为显示时间戳(秒)
             double display_time = raw_pts * av_q2d(codecCtx->time_base);

             // 转换为全局时间基(例如用于音视频同步)
             AVRational timeBaseTemp{1, AV_TIME_BASE};//AV_TIME_BASE_Q
             llPlayTs = av_rescale_q(raw_pts, codecCtx->time_base, timeBaseTemp);
             llDecodeTs = av_rescale_q(raw_dts, codecCtx->time_base, timeBaseTemp);
             if(!bStartPlayTsSetValueFlag)
             {
                 llStartPlayTs = llPlayTs;
                 bStartPlayTsSetValueFlag = true;
             }

             qDebug("Frame:%4d PTS:%lld display_time:%.2f DTS:%lld llPlayTs:%lld llDecodeTs:%lld packet dts:%lld pts:%lld",
                           codecCtx->frame_number, raw_pts, display_time, raw_dts, llPlayTs, llDecodeTs, packet->dts, packet->pts);


             av_packet_unref(packet);  // 释放数据包,引用计数-1,为0时释放空间

             if(!swsCtx || (frameTemp->width != width || frameTemp->height != height))
             {
                 //重新申请
                 width = frameTemp->width;
                 height = frameTemp->height;
                 if(swsCtx)
                 {
                     sws_freeContext(swsCtx);
                     swsCtx = nullptr;
                 }

                 if(buffer)
                 {
                     av_free(buffer);
                     buffer = nullptr;
                 }
                 int size = av_image_get_buffer_size(AV_PIX_FMT_RGB24, frameTemp->width, frameTemp->height, 1);
                 buffer = (uint8_t *)av_malloc(size + 1000);
                 av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, AV_PIX_FMT_RGB24,
                                      frameTemp->width, frameTemp->height, 1);

                 swsCtx = sws_getCachedContext(swsCtx,
                                                       frameTemp->width,                     // 输入图像的宽度
                                                       frameTemp->height,                    // 输入图像的高度
                                                       (AVPixelFormat)frameTemp->format,     // 输入图像的像素格式
                                                       frameTemp->width,                     // 输出图像的宽度
                                                       frameTemp->height,                    // 输出图像的高度
                                                       AV_PIX_FMT_RGB24,                    // 输出图像的像素格式
                                                       SWS_BILINEAR,                       // 选择缩放算法(只有当输入输出图像大小不同时有效),一般选择SWS_FAST_BILINEAR
                                                       nullptr,                            // 输入图像的滤波器信息, 若不需要传NULL
                                                       nullptr,                            // 输出图像的滤波器信息, 若不需要传NULL
                                                       nullptr);
             }

             //休眠等待
             long long llPlayTsDiff = llPlayTs - llStartPlayTs;
             auto duration = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::high_resolution_clock::now() - tpStart);
             // 计算需要等待的时间(单位:微秒)
             int64_t delay = llPlayTsDiff - duration.count();
             // 同步控制
              if (delay > 0)
              {
                 std::this_thread::sleep_for(std::chrono::microseconds(delay)); // 等待至目标时间‌
              }
              else if (delay < -100000)
              {
                 // 允许100ms误差阈值
                 // 丢弃滞后帧,追赶进度‌
                  av_frame_unref(frame);
                  av_frame_unref(frameHW);
                 continue;
              }

              // 转换颜色空间到 RGB24
              sws_scale(swsCtx, frameTemp->data, frameTemp->linesize, 0, frameTemp->height, rgbFrame->data, rgbFrame->linesize);
              //回调流书籍(方便渲染)
              if(funCallbackByStreamDecode)
              {
                  funCallbackByStreamDecode(StreamType_Video,FormatType_RGB24,llDecodeTs,llPlayTs,frameTemp->width,frameTemp->height,rgbFrame->data, rgbFrame->linesize, userDataByStreamDecode);
              }

              av_frame_unref(frame);
              av_frame_unref(frameHW);
        }
    }

    if(bProccessFileTail && !stopWorkFlag)
    {
        if(funCallbackByFileStatus != nullptr)
        {
            funCallbackByFileStatus(FileStatus_OverFileTail, 0, userDataByFileStatus);
        }
    }
}

void VideoDecode::freeDecode()
{
    // 释放资源
   if (swsCtx)
   {
       sws_freeContext(swsCtx);
       swsCtx = nullptr;
   }

  if (rgbFrame)
  {
      av_frame_free(&rgbFrame);
      rgbFrame = nullptr;
  }

  if (frame)
  {
      av_frame_free(&frame);
      frame = nullptr;
  }

  if(frameHW)
  {
      av_frame_free(&frameHW);
      frameHW = nullptr;
  }

  if (codecCtx)
  {
      avcodec_free_context(&codecCtx);
      codecCtx = nullptr;
  }

  if (formatCtx)
  {
      avformat_close_input(&formatCtx);
      formatCtx = nullptr;
  }

  if(buffer != nullptr)
  {
      av_free(buffer);
      buffer = nullptr;
  }
}

‌3.主窗口调用代码

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QFileDialog>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(this, SIGNAL(sgnShowImage(QImage)), this, SLOT(sltShowImage(QImage)));
    vdVideoDecode.globalInit();
    m_playImages = this->findChildren<PlayImage *>();
}

MainWindow::~MainWindow()
{
    delete ui;
    vdVideoDecode.globalUnInit();
}


void MainWindow::sltShowImage(QImage qimage)
{
    if(vdVideoDecode.isClose())
        return;

    for(int i = 0; i < m_playImages.count(); i++)
    {
        m_playImages.at(i)->updateImage(qimage);
    }
}

void MainWindow::on_pushButtonOpenFile_clicked(bool checked)
{
    QString filename = QFileDialog::getOpenFileName(nullptr, "Open Video File");
   if (!filename.isEmpty())
    {
       //vdVideoDecode.setHWDecoder(true);
       vdVideoDecode.setStreamDecodeCallback([](int nStreamType, int nFormatType, long long llDecodeTs, long long llPlayTs, int width, int height, unsigned char ** pStreamData, int * linesize, void * pUserData){
           MainWindow *pMainWindow = (MainWindow *)pUserData;
           QImage qimage = VideoDecode::ConvertRGB24FrameToQImage(pStreamData[0],width,height);
           emit pMainWindow->sgnShowImage(qimage);
       },this);
       vdVideoDecode.setFileStatusCallback([](int FileStatus, int nErrorCode, void * pUserData){
            qDebug()<<"file is end";
       },this);

       vdVideoDecode.open(filename);
       /*
       if(player.openFile(filename))
       {
          player.show();
       }
       */
    }
}

void MainWindow::on_pushButtonCloseFile_clicked()
{
    vdVideoDecode.close();
}

完整代码下载:https://gitee.com/byxdaz/ffmpeg-qt-player


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

相关文章:

  • 通过SSH隧道与跳板机实现本地端口映射访问服务器文件
  • 【Qt】常用控件
  • SQL注入(SQL Injection)攻击原理与防御措施
  • (c语言)网络编程之UDP认识与并发服务器实现
  • 40岁重启人生学Python,用煎饼摊理解函数,用快递盒认识变量
  • python3使用lxml解析xml时踩坑记录
  • SOFABoot-08-启动加速
  • thymelef
  • 《Python实战进阶》第43集:使用 asyncio 实现异步编程
  • windows下利用Ollama + AnythingLLM + DeepSeek 本地部署私有智能问答知识库
  • Unity/C# 常用XML读写方式详解(LINQ to XML、XmlReader/Writer)
  • ES集群的部署
  • Pytest的夹具
  • 论文阅读笔记——EWA Volume Splatting
  • CityEngine:3D城市建模专家
  • C++进阶——封装红黑树实现map和set
  • Selenium Web UI自动化测试:从入门到实战
  • C#与西门子PLC的六大通信库
  • 使用LangChain开发智能问答系统
  • 最优编码树的双子性