FFmpeg 4.x 从入门到精通(一)—— QT 中如何用 FFmpeg 实现软件解码

FFmpeg 4.x 从入门到精通(一)—— QT 中如何用 FFmpeg 实现软件解码背景因为在2021年给自己定了目标和计划,学习ffmpeg,所以这篇文章是实现计划的第一步。ffmpeg众所周知,就不展开介绍了,下面给出FFmpeg4.2windowsx64lib库和头文件的下载地址(粉丝免积分下载):https://download.csdn.net/download/u012534831/14045436本文也是属于博主的入门学习总结与分享,因此我们先从ffmpeg的软解码开始,从解码到绘制,一起体验下亲自动手的快乐。本文的语言环境基于C++,界面部分是QT。

大家好,又见面了,我是你们的朋友全栈君。

背景

因为在2021年给自己定了目标和计划,学习ffmpeg,所以这篇文章是实现计划的第一步。

ffmpeg 众所周知,就不展开介绍了,下面给出 FFmpeg 4.2 windows x64 lib库和头文件的下载地址(粉丝免积分下载):

https://download.csdn.net/download/u012534831/14045436

本文也是属于博主的入门学习总结与分享,因此我们先从ffmpeg的软解码开始,从解码到绘制,一起体验下亲自动手的快乐。本文的语言环境基于C++,界面部分是 QT。

流程分析

在开始看代码之前,我们必须先了解下ffmpeg软解的常规流程:
在这里插入图片描述

在以前的教程中我们经常见到av_regeister_all,这是旧版ffmpeg的用法,必须在开始进行初始化,新版的ffmpeg4.0之后已经不需要了,详见github: av_register_all() has been deprecated in ffmpeg 4.0

1、avformat_open_input

为 AVFormatContext 分配空间,打开输入的视频数据并且探测视频的格式,这个函数里面包含了复杂的格式解析与探测算法,可解析的内容包括:视频流、音频流、视频流参数、音频流参数、视频帧索引等。用雷神的话说就是 可以算作FFmpeg的“灵魂”

2、avformat_find_stream_info

获取多媒体流的信息,包括码流、帧率、时长等信息。但是有些早期格式或者裸流数据它的索引并没有放到头当中,因此需要在后面进行探测。注意一个视频文件中可能会同时包括视频文件、音频文件、字幕文件等多个媒体流。

3、av_find_best_stream

当视频被解封装出来后,需要分开处理音频和视频,需要找到对应的音频流和视频流,获取音视频对应的stream_index。

4、avcodec_find_decoder(enum AVCodecID id)

“Find a registered decoder with a matching codec ID.”
上一步找到的AVStream中的成员变量 codecpar->codec_id 就是这儿的参数 ID,codecpar类型为AVCodecParameters。网上的很多资料为 AVCodecContext->codec_id,这个用法在FFMPEG3.4及以上版本已经被弃用了,官方推荐使用codecpar。

5、avcodec_alloc_context3

创建AVCodecContext并分配空间。

6、avcodec_parameters_to_context

该函数用于将流里面的参数,也就是AVStream里面的参数直接复制到AVCodecContext的上下文当中,执行真正的内容拷贝。avcodec_parameters_to_context()是新的API,替换了旧版本的avcodec_copy_context()。

7、avcodec_open2

用给定的 AVCodec 去初始化 AVCodecContext。

到这儿,解码器的初始化工作已经完成。下面就可以开始真正的解码操作了。

8、av_read_frame

读取码流中的音频若干帧或者视频一帧,av_read_frame()函数是新型ffmpeg的用法,对 av_read_packet 进行了封装,旧用法之所以被抛弃,就是因为以前获取的数据可能不是完整的,而av_read_frame()保证了视频数据一帧的完整性,使读出的数据总是完整的一帧。

8、avcodec_send_packet

发送数据到后台解码队列。

It can be NULL (or an AVPacket with data set to NULL and
size set to 0); in this case, it is considered a flush
packet, which signals the end of the stream. Sending the
first flush packet will return success. Subsequent ones are
unnecessary and will return AVERROR_EOF. If the decoder
still has frames buffered, it will return them after sending

源码中关于发送一包空数据的解释:
由于ffmpeg内部会缓存帧,在av_read_frame读不到数据的时候,需要通过packet.data = NULL;packet.size = 0;给ffmpeg发送一包空数据,即再avcodec_send_packet一次,将ffmpeg里面缓存的帧全部刷出来,解决最后几帧没有解码出来的问题。

9、avcodec_receive_frame

从解码器读取帧数据,这个函数执行完后,就已经能拿到我们的帧数据了,它被存储在 AVFrame 中。
此处需要注意的是:
一般而言,一次avcodec_send_packet()对应一次avcodec_receive_frame(),但是也会有一次对应多次的情况。这个得看具体的流,并常见于音频流,会存在一个AVPacket对应多个AVFrame的情况。因此可以看我上面的流程图有两个while循环。

代码示例

//头文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include<thread>
extern "C"
{
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavutil/pixfmt.h"
    #include "libswscale/swscale.h"
    #include "libavdevice/avdevice.h"
    #include <libavutil/pixdesc.h>
    #include <libavutil/hwcontext.h>
    #include <libavutil/opt.h>
    #include <libavutil/avassert.h>
    #include <libavutil/imgutils.h>
}

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    void init();
    void play();
private:
    std::thread m_decodecThread;
    Ui::MainWindow *ui;
    AVFormatContext *pAVFormatCtx;
    AVCodecContext *pAVCodecCtx;
    SwsContext *pSwsCtx;
    uint8_t *pRgbBuffer;
    AVPacket packet;
    AVFrame *pAVFrame = NULL;
    AVFrame *pAVFrameRGB;
    int iVideoIndex = -1;
    QImage m_image;
    bool isFinish  =false;
    void decodec();
    signals:
    void signalDraw();
public slots:
    void slotDraw();
protected:
    void paintEvent(QPaintEvent *event) override;
};

#endif // MAINWINDOW_H

//CPP文件

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
#include <QPainter>
#include<thread>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    connect(this,&MainWindow::signalDraw,this,&MainWindow::slotDraw);
}

MainWindow::~MainWindow()
{
    delete ui;
}
void MainWindow::init()
{
  std::string file = "E:/Video/bb.mp4";
 //描述多媒体文件的构成及其基本信息
 if (avformat_open_input(&pAVFormatCtx, file.data(), NULL, NULL) != 0)
     {
         qDebug() <<"open file fail";
         avformat_free_context(pAVFormatCtx);
         return;
     }

 //读取一部分视音频数据并且获得一些相关的信息
 if (avformat_find_stream_info(pAVFormatCtx, NULL) < 0)
 {
     qDebug() <<"vformat find stream fail";
     avformat_close_input(&pAVFormatCtx);
     return;
 }
// 根据解码器枚举类型找到解码器
    AVCodec *pAVCodec;
    int ret = av_find_best_stream(pAVFormatCtx, AVMEDIA_TYPE_VIDEO, -1, -1, &pAVCodec, 0);
    if (ret < 0) {
        qDebug()<< "av_find_best_stream faliture";
        avformat_close_input(&pAVFormatCtx);
        return;
    }
    iVideoIndex = ret;

     pAVCodec = avcodec_find_decoder(pAVFormatCtx->streams[iVideoIndex]->codecpar->codec_id);
     if (pAVCodec == NULL)
     {
         qDebug()<<"not find decoder";
         return;
     }

 qDebug()<<"avcodec_open2 pAVCodec->name:" << QString::fromStdString(pAVCodec->name);


    if(pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.den != 0) {
        float fps_ = pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.num / pAVFormatCtx->streams[iVideoIndex]->avg_frame_rate.den;
           qDebug() <<"fps:" << fps_;
    }
      int64_t video_length_sec_ = pAVFormatCtx->duration/AV_TIME_BASE;
       qDebug() <<"video_length_sec_:" << video_length_sec_;
pAVCodecCtx = avcodec_alloc_context3(pAVCodec);
     if (pAVCodecCtx == NULL)
     {
         qDebug() <<"get pAVCodecCtx fail";
         avformat_close_input(&pAVFormatCtx);
         return;
     }
ret = avcodec_parameters_to_context(pAVCodecCtx,pAVFormatCtx->streams[iVideoIndex]->codecpar);
     if (ret < 0)
     {
         qDebug() <<"avcodec_parameters_to_context fail";
         avformat_close_input(&pAVFormatCtx);
         return;
     }
  if (avcodec_open2(pAVCodecCtx, pAVCodec, NULL) < 0)
     {
         qDebug()<<"avcodec_open2 fail";
         return;
     }
         //为解码帧分配内存
         //AVFrame 存放从AVPacket中解码出来的原始数据
             pAVFrame = av_frame_alloc();
             pAVFrameRGB = av_frame_alloc();
       //用于视频图像的转换,将源数据转换为RGB32的目标数据
         pSwsCtx = sws_getContext(pAVCodecCtx->width, pAVCodecCtx->height, pAVCodecCtx->pix_fmt,
                                              pAVCodecCtx->width, pAVCodecCtx->height, AV_PIX_FMT_RGB32,
                                              SWS_BICUBIC, NULL, NULL, NULL);

        int  m_size = av_image_get_buffer_size(AVPixelFormat(AV_PIX_FMT_RGB32), pAVCodecCtx->width, pAVCodecCtx->height, 1);
      pRgbBuffer = (uint8_t *)(av_malloc(m_size));
         //为已经分配的空间的结构体AVPicture挂上一段用于保存数据的空间
         avpicture_fill((AVPicture *)pAVFrameRGB, pRgbBuffer, AV_PIX_FMT_BGR32, pAVCodecCtx->width, pAVCodecCtx->height);
         //av_image_fill_arrays
         //AVpacket 用来存放解码数据
         av_new_packet(&packet, pAVCodecCtx->width * pAVCodecCtx->height);
}


void MainWindow::play()
{
    m_decodecThread = std::thread([this]()
    {
        decodec();
    });
    m_decodecThread.detach();
}

void MainWindow::decodec()
{
    //读取码流中视频帧
        while (true)
        {
            int ret = av_read_frame(pAVFormatCtx, &packet);
            if(ret != 0)
            {
                qDebug()<<"file end";
                isFinish = !isFinish;
                 return;
            }
            if (packet.stream_index != iVideoIndex)
            {
                av_packet_unref(&packet);
                continue;
            }
           int iGotPic = AVERROR(EAGAIN);
//             //解码一帧视频数据
            iGotPic = avcodec_send_packet(pAVCodecCtx, &packet);
            if(iGotPic!=0){
                qDebug()<<"avcodec_send_packet error";
                      continue;
            }
            iGotPic = avcodec_receive_frame(pAVCodecCtx, pAVFrame);
   if(iGotPic == 0){
                   //转换像素
                   sws_scale(pSwsCtx, (uint8_t const * const *)pAVFrame->data, pAVFrame->linesize, 0, pAVCodecCtx->height, pAVFrameRGB->data, pAVFrameRGB->linesize);

                   //构造QImage
                   QImage img(pRgbBuffer, pAVCodecCtx->width, pAVCodecCtx->height, QImage::Format_RGB32);
                   qDebug()<<"decode img";
                   m_image = img;
                   emit signalDraw();
  }else {
           qDebug()<<"decode error";
           }

            av_packet_unref(&packet);
            std::this_thread::sleep_for(std::chrono::milliseconds(25));
        }
  //资源回收
        av_free(pAVFrame);
        av_free(pAVFrameRGB);
        sws_freeContext(pSwsCtx);
        avcodec_close(pAVCodecCtx);
        avformat_close_input(&pAVFormatCtx);
}

void MainWindow::slotDraw()
{
    update();
}

void MainWindow::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setBrush(Qt::black);
    painter.drawRect(0, 0, this->width(), this->height());

    if (m_image.size().width() <= 0)
        return;

    //比例缩放
    QImage img = m_image.scaled(this->size(),Qt::KeepAspectRatio);
    int x = this->width() - img.width();
    int y = this->height() - img.height();

    x /= 2;
    y /= 2;

    //QPoint(x,y)为中心绘制图像
    painter.drawImage(QPoint(x,y),img);
}

如有兴趣,欢迎加入我的QQ群,QT/Android/音视频 问题在线解答,资源分享。
在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/153390.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • VC++2010注册密钥「建议收藏」

    VC++2010注册密钥「建议收藏」6VPJ7-H3CXH-HBTPT-X4T74-3YVY7

    2022年6月5日
    173
  • sop流程图模板_这是一份标准作业流程SOP详解,附流程图绘制规范,不愁不会画!…「建议收藏」

    sop流程图模板_这是一份标准作业流程SOP详解,附流程图绘制规范,不愁不会画!…「建议收藏」注:资料来源百度、档即用网,品质人生质量开讲平台搜集、整理、编辑,仅供学习交流所用,请勿做其他用途!小编辛苦整理,转载请注明出处。什么是SOP?StandardOperationProcedure所谓SOP,是StandardOperationProcedure三个单词中首字母的大写,即标准作业程序。是以文件的形式描述作业员在生产作业过程中的操作步骤和应遵守的事项;是作业员的作业指导书…

    2022年5月26日
    173
  • Maven配置阿里云仓库下载依赖「建议收藏」

    Maven配置阿里云仓库下载依赖「建议收藏」用过Maven的都知道Maven的方便便捷,但由于某些网络原因,访问国外的Maven仓库不便捷,maven默认使用的是国外的中央仓库,下载jar时有时候会因为网络不好等原因下载不全或失败,好在国内像阿里云、网易、JBoos、开源中国等大厂搭建了国内的maven仓库,阿里云的maven仓库使用的比较多:需要使用的话,要在maven的settings.xml文件里配置mirrors的子节点,添加…

    2022年6月22日
    97
  • python airflow_Airflow 安装

    python airflow_Airflow 安装前段时间部门要求研究 airflow 大概记录一下安装的步骤 airflow 是一个 python 实现的工作流管理平台 官网 http airflow incubator apache org 安装环境和必要软件 ubuntu16 04Python3mys 如果无法安装某些软件 更新一下系统源更改为阿里云源简单步骤 1 先安装 Python3ubunt 04 自带 python

    2025年8月8日
    4
  • Java 认证考试 OCAJP 经验总结

    Java 认证考试 OCAJP 经验总结1. 考证、认证是否有用含金量的话题关于认证考试(无论什么认证)是否有用?这个话题无论是在哪里都有人问。这个问题就好比上大学是否有用吗一样,有的人没上过大学一样年薪百万。认证这种东西需要的时候即有用,不需要的时候就没用。有,并没有什么坏处。说实话个人感觉这证件没什么大用。而自己想考的理由完全是想自我check下,逼自己复习学习基础。如果你是刚毕业的GH或者在校的,手里有些零花钱的可以考虑下,…

    2022年7月8日
    28
  • DenseNet详解[通俗易懂]

    DenseNet详解[通俗易懂]其它机器学习、深度学习算法的全面系统讲解可以阅读《机器学习-原理、算法与应用》,清华大学出版社,雷明著,由SIGAI公众号作者倾力打造。书的购买链接 书的勘误,优化,源代码资源一、概述作为CVPR2017年的BestPaper,DenseNet脱离了加深网络层数(ResNet)和加宽网络结构(Inception)来提升网络性能的定式思维,从特征的角度考虑,通过特征重用和旁路(Byp…

    2022年9月29日
    1

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号