基于 FFmpeg 的视频解码基本步骤全流程:从组件初始化到帧图输出

写本篇文章,是因为本人初学FFMPEG,便于日后复习使用

decodevideo.h
#ifndef DECODEVIDEO_H
#define DECODEVIDEO_H

#include <QThread>
#include <QImage>
#include <QDebug>

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavdevice/avdevice.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libpostproc/postprocess.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
}

class DecodeVideo : public QThread
{
public:
    DecodeVideo();

    // QThread interface
    void decoding();

private:
    QImage img;

protected:
    void run();
};

#endif // DECODEVIDEO_H
decodevideo.cpp
#include "decodevideo.h"

DecodeVideo::DecodeVideo()
{

}

//解码
void DecodeVideo::decoding()
{
    //1、注册组件
    av_register_all();

    //2、打开视频文件
    AVFormatContext *formatContext;
    formatContext=avformat_alloc_context();
    int res=avformat_open_input(&formatContext,"../video/Warcraft3_End.avi",nullptr,nullptr);
    if(res!=0)
    {
        qDebug()<<"open video failed";
    }
    else
    {
        qDebug()<<"open video success";
    }

    //3、获取视频信息
    res=avformat_find_stream_info(formatContext,nullptr);
    if(res<0)
    {
        qDebug()<<"avformat_find_stream_info failed";
    }
    else
    {
        qDebug()<<"avformat_find_stream_info success";
    }

    //4、判断是否有视频流
    int video_input=-1;
    for(int i=0;i<formatContext->nb_streams;i++)
    {
        if(formatContext->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
        {
            //判断为视频
            //记录流的下标
            video_input=i;
            break;
        }
    }
    if(video_input==-1)
    {
        //没有流
        qDebug()<<"video not found";
    }
    else
    {
        //有流
        qDebug()<<"总帧数"<<formatContext->streams[video_input]->nb_frames;//视频流总帧数
        qDebug()<<"video_input"<<video_input;
    }

    //5、查找解码器
    AVCodecContext *codeContext=formatContext->streams[video_input]->codec;

    //通过文件信息中文件编码id找到解码器
    AVCodec *decoder=avcodec_find_decoder(codeContext->codec_id);
    if(decoder==nullptr)
    {
        qDebug()<<"avcodec_find_decoder failed";
    }
    else
    {
        qDebug()<<"avcodec_find_decoder success";
    }

    //6、打开解码器
    res=avcodec_open2(codeContext,decoder,nullptr);
    if(res!=0)
    {
        qDebug()<<"avcodec_open2 failed";
    }
    else
    {
        qDebug()<<"avcodec_open2 success";
    }

    /******************************AVPacket准备工作开始***************************************/
    //7、读取一帧压缩数据
    AVPacket *pkt;
    pkt=(AVPacket*)malloc(sizeof(AVPacket));

    //视频大小
    int size=codeContext->width*codeContext->height;

    res=av_new_packet(pkt,size);
    if(res!=0)
    {
        qDebug()<<"av_new_packet failed";
    }
    else
    {
        qDebug()<<"av_new_packet success";
    }
    /******************************AVPacket准备工作完成***************************************/

    /******************************AVFrame RGB准备工作***************************************/
    AVFrame *pictureRGB;
    AVFrame *picture;
    picture=av_frame_alloc();
    pictureRGB=av_frame_alloc();

    //解码画面信息必须要按原图进行设置
    pictureRGB->width=codeContext->width;
    pictureRGB->height=codeContext->height;
    pictureRGB->format=codeContext->pix_fmt;

    //设置缓冲区
    int imgByteRGB=avpicture_get_size(AV_PIX_FMT_RGB32,codeContext->width,codeContext->height);
    uint8_t *bufferRGB=(uint8_t*)av_malloc(imgByteRGB*sizeof (uint8_t));

    //填充缓冲区
    avpicture_fill((AVPicture*)pictureRGB,bufferRGB,AV_PIX_FMT_RGB32,codeContext->width,codeContext->height);

    //制定一个图像规则
    SwsContext *SwsContextRGB=sws_getContext(codeContext->width,codeContext->height,codeContext->pix_fmt,
                                             codeContext->width,codeContext->height,
                                             AV_PIX_FMT_RGB32,SWS_BICUBIC,
                                             nullptr,nullptr,nullptr);
    /*****************************AVFrame RGB准备工作完成**************************************/

    //读取一帧解码一帧
    int x=0;
    while(av_read_frame(formatContext,pkt)==0)
    {
        if(pkt->stream_index==video_input)
        {
            //压缩数据为图像压缩数据
            int get_picture_ptr=-1;
            avcodec_decode_video2(codeContext,picture,&get_picture_ptr,pkt);
            if(get_picture_ptr!=0)
            {
                //删除无效数据,转RGB存储方式
                sws_scale(SwsContextRGB,picture->data,picture->linesize,0,picture->height,
                          pictureRGB->data,pictureRGB->linesize);
                this->img=QImage((uchar*)bufferRGB,codeContext->width,codeContext->height,
                                 QImage::Format_RGB32);
                //保存
                this->img.save(QString("../fileout/%1.jpg").arg(x));
                x++;
            }
        }

        av_packet_unref(pkt);
    }
    //释放解码器
    avcodec_close(codeContext);

    //释放关闭文件
    avformat_close_input(&formatContext);
}

void DecodeVideo::run()
{
    decoding();
}
main.cpp
#include <QApplication>
#include <QDebug>

#include "decodevideo.h"

extern "C"{
#include "libavcodec/avcodec.h"
}

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    DecodeVideo *dv=new DecodeVideo();
    dv->start();

    return a.exec();
}

注意:DedcodeVideo继承于QThread是因为不能将延时操作放在正常代码顺序中,不然资源过大就会未响应

代码功能概述

这段代码的主要功能是读取一个视频文件,对视频流进行解码,将解码后的视频帧转换为 RGB 格式,并将每一帧保存为 PNG 图像文件。

步骤笔记

1. 组件注册
  • 操作:调用 av_register_all() 注册所有的 FFmpeg 组件,这是使用 FFmpeg 库的基础准备工作。
  • 记忆点:初始化 FFmpeg,就像启动一个工具箱,先把所有工具准备好。
2. 打开视频文件
  • 操作
    • 分配 AVFormatContext 结构体,用于存储视频文件的格式信息。
    • 调用 avformat_open_input 打开指定的视频文件。
  • 记忆点:先准备一个 “文件信息盒子”(AVFormatContext),然后用它去打开视频文件。
3. 获取视频信息
  • 操作:调用 avformat_find_stream_info 获取视频文件的流信息。
  • 记忆点:打开文件后,查看里面的详细信息,了解视频的构成。
4. 判断是否有视频流
  • 操作
    • 遍历 AVFormatContext 中的所有流,找到视频流的下标。
    • 如果没有找到视频流,输出错误信息;否则,输出视频流的总帧数和下标。
  • 记忆点:在众多流中找到视频流,就像在一堆物品中找出视频这个 “宝贝”。
5. 查找解码器
  • 操作
    • 获取视频流的 AVCodecContext,用于存储编解码器的上下文信息。
    • 根据 AVCodecContext 中的 codec_id 查找对应的解码器。
  • 记忆点:根据视频的编码规则,找到能解开它的 “钥匙”(解码器)。
6. 打开解码器
  • 操作:调用 avcodec_open2 打开找到的解码器。
  • 记忆点:用找到的 “钥匙” 打开视频编码的 “锁”。
7. 读取一帧压缩数据
  • 操作
    • 分配 AVPacket 结构体,用于存储压缩的视频数据。
    • 调用 av_new_packet 为 AVPacket 分配内存。
  • 记忆点:准备一个 “包裹”(AVPacket)来装一帧压缩的视频数据。
8. 准备 AVFrame 和 RGB 转换
  • 操作
    • 分配 AVFrame 结构体,分别用于存储解码后的视频帧和 RGB 格式的视频帧。
    • 设置 AVFrame 的参数,如宽度、高度和像素格式。
    • 分配 RGB 缓冲区,并填充到 AVFrame 中。
    • 创建 SwsContext 用于图像格式转换。
  • 记忆点:准备两个 “相框”(AVFrame),一个装解码后的原始图像,一个装转换后的 RGB 图像,同时准备好转换工具(SwsContext)。
9. 循环读取和解码视频帧
  • 操作
    • 调用 av_read_frame 循环读取视频文件中的每一帧压缩数据。
    • 如果读取的是视频流的数据,调用 avcodec_decode_video2 进行解码。
    • 如果解码成功,调用 sws_scale 将解码后的帧转换为 RGB 格式。
    • 将 RGB 数据保存为 PNG 图像文件。
    • 释放 AVPacket 中的数据。
  • 记忆点:一帧一帧地读取视频数据,解码后转换为 RGB 格式,然后保存为图片,最后清空 “包裹”(AVPacket)。
10. 释放资源
  • 操作
    • 关闭解码器。
    • 关闭视频文件。
  • 记忆点:使用完工具后,记得关闭和解开连接,收拾好 “工具”。

代码示例总结

1. 注册组件:av_register_all()

2. 打开视频文件:avformat_alloc_context() + avformat_open_input()

3. 获取视频信息:avformat_find_stream_info()

4. 判断是否有视频流:遍历查找视频流下标

5. 查找解码器:avcodec_find_decoder()

6. 打开解码器:avcodec_open2()

7. 读取一帧压缩数据:av_new_packet()

8. 准备 AVFrame 和 RGB 转换:av_frame_alloc() + 缓冲区设置 + sws_getContext()

9. 循环读取和解码视频帧:av_read_frame() + avcodec_decode_video2() + sws_scale() + 保存图片

10. 释放资源:avcodec_close() + avformat_close_input()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值