草庐IT

【FFmpeg+Qt开发】解码流程 详细分析+代码示例

猿力猪 2023-04-22 原文

目录

一、FFMPEG 概述

​二、FFMPEG 解码

2.1解码流程

2.2解码示例


一、FFMPEG 概述

  • FFmpeg 是一套可以用来记录、转换,数字音频、视频,并能将其转化为流的开源计算机程序。
  • FFmpeg 采用 LGPL GPL 许可证;它提供了录制、转换以及流化音视频的完整解决案;它还包含了非常先进的音频\视频编解码库 libavcodec,为了保证高可移植性和编解码质量,libavcodec 里很多 code 都是从头开发的。
  • FFmpeg Linux 平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括 WindowsMac OS X 等。
  • FFmpeg最早由 Fabrice Bellard 发起,2004 年至 2015 年间由 Michael Niedermayer 主要负责维护。许多 FFmpeg 的开发人员都来自 MPlayer 项目,而且当前 FFmpeg 也是放在MPlayer 项目组的服务器上。
  • FFmpeg名称来自 MPEG 视频编码标准,前面的"FF" 代表"Fast Forward"

下载链接:Download FFmpeg

具体下载详见:Qt+FFmpeg----windows下环境搭建_猿力猪的博客-CSDN博客_ffmpeg开发环境搭建

​二、FFMPEG 解码

2.1解码流程

🔴解码:将带有封装格式的视频解封装后得到的压缩码流数据(编码数据)经过解码得到像素数据的过程。
例如:解码 H.264 压缩码流数据得到 YUV (或者 RGB )的像素数据。
解码的流程图,如下所示:

🔴解码思路分析:

  1. 注册所有的组件  av_register_all()
  2. 打开视频文件  avformat_open_input()  有可能打开失败
  3. 获取视频信息 视频码流、音频码流、文字码流
  4. 查找流信息  avformat_find_stream_info()
  5. 找到解码器  avcodec_find_decoder()  有可能没找到
  6. 打开解码器  avcodec_open2()
  7. 读取码流中的一帧码流数据  av_read_frame()
  8. 解码读到一帧码流数据 得到一帧的像素数据  YUV RGB
  9. 重复7-8的动作 直到视频所有的帧都处理完
  10. 关闭解码器
  11. 关闭视频文件

🔴解码过程中几个重要的结构体:

  • AVFormatContext

封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装格式相关信息。

  • AVInputFormat//AVOutpufFormat

每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体。

  • AVStream

视频文件中每个视频(音频)流对应一个该结构体。

  • AVCodecContext

编码器上下文结构体,保存了视频(音频)编解码相关信息。

  • AVCodec

每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。

  • AVPacket

存储一帧压缩编码数据。

  • AVFrame

存储一帧解码后像素(采样)数据。

2.2解码示例

🟢解码类的定义

//ffmpeg使用c语言实现的,引入用c写的代码就要用extern
extern "C"   
{
#include <libavcodec/avcodec.h>   //编码
#include <libavdevice/avdevice.h>  
#include <libavformat/avformat.h>  //封装格式处理
#include <libavutil/error.h>  
#include <libswscale/swscale.h>  //像素处理
#include <libswresample/swresample.h>  //缩放
}
class fdecode
{
public:
    fdecode();
    //注册组件
    void registerFFmpeg();
    //打开视频流
    void openVIdeoStream(QString filename);
    //视频名称
    QString filename;
protected:
private:
};

具体实现如下:

🟢注册所有组件

void fdecode::registerFFmpeg()
{
    //注册所有的组件
    av_register_all();
}

🟢打开视频文件

    AVFormatContext *forContent;//用来保存视频相关信息的结构体
    forContent=avformat_alloc_context();//分配空间

    //打开视频文件
    int res=avformat_open_input(&forContent,filename.toStdString().c_str(),nullptr,nullptr);
    if(res!=0)//判断是否打开视频文件
    {
        qDebug()<<"无法打开视频文件";
        return;
    }

🟢获取视频文件信息

    //打开视频文件成功,获取文件信息
    res = avformat_find_stream_info(forContent,nullptr);//查看有没有相关视频流信息
    if(res<0)
    {
        qDebug()<<"没有流媒体信息"<<endl;
        return;
    }

    //一个视频流有多股码流,存在forContentext中streams数组中
    int videoType=-1;
    //nb_streams代表封装格式里面的结构体信息有几个,正常两个:音频信息、视频信息
    for(int i=0;i<forContent->nb_streams;i++) //i小于流的个数
    {
        if(forContent->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)//视频流
        {
            videoType=i;//标识类型
            break;
        }
    }
    //判断是否有视频流信息
    if(videoType==-1)
    {
        qDebug()<<"没有视频流相关信息"<<endl;
        return;
    }

🟢根据编解码上下文中的编码ID查找对应解码器

    //编解码器对应的上下文对象结构体:保存解码器信息以及图形的宽高、像素信息
    AVCodecContext *codec=forContent->streams[videoType]->codec;
    //查找对应的视频流解码器
    AVCodec *decoder = avcodec_find_decoder(codec->codec_id);
    if(decoder==nullptr)//判断是否找到解码器
    {
        qDebug()<<"没有对应的解码器"<<endl;
        return;
    }

🟢打开解码器

    //找到了解码器,打开解码器
    res = avcodec_open2(codec,decoder,nullptr);
    if(res!=0)
    {
        qDebug()<<"解码器打开失败"<<endl;
        return;
    }

🟢读取准备 获取YUV和RGB像素数据

    //为准备读取帧数据做准备--AVPacket 用来存储一帧一帧的压缩数据(h264)
    AVPacket *pkt=nullptr;
    //设置缓冲区,开空间
    pkt=(AVPacket *) malloc(sizeof(AVPacket));
    int size=codec->width*codec->height;//计算一张图片数据大小
    av_new_packet(pkt,size);

    /* pictureRGB 保存解码后的RGB像素数据
        * pictureYUV 保存解码后的YUV像素数据
        * picture    保存未处理的像素数据
        */
    AVFrame *pictureRGB,*pictureYUV,*picture=nullptr;

    //内存分配
    pictureRGB=av_frame_alloc();
    pictureYUV=av_frame_alloc();
    picture=av_frame_alloc();

    //大小以及格式设置RGB
    pictureRGB->width=codec->width;//宽度
    pictureRGB->height=codec->height;//高度
    pictureRGB->format=codec->pix_fmt;//格式设置

    //大小以及格式设置YUV
    pictureYUV->width=codec->width;//宽度
    pictureYUV->height=codec->height;//高度
    pictureYUV->format=codec->pix_fmt;//格式设置

    //一帧码流数据解码后得到YUV RGB像素数据有多大
    int numByte_RGB=avpicture_get_size(AV_PIX_FMT_RGB32,codec->width,codec->height);
    int numByte_YUV=avpicture_get_size(AV_PIX_FMT_YUV420P,codec->width,codec->height);

    //开的空间用来保存YUV RGB像素数据大小
    uint8_t *buffer_RGB=(uint8_t *)av_malloc(numByte_RGB*sizeof(uint8_t));
    uint8_t *buffer_YUV=(uint8_t *)av_malloc(numByte_YUV*sizeof(uint8_t));

    //像素数据的填充
    avpicture_fill((AVPicture *)pictureRGB,buffer_RGB,AV_PIX_FMT_RGB32,codec->width,codec->height);
    avpicture_fill((AVPicture *)pictureYUV,buffer_YUV,AV_PIX_FMT_YUV420P,codec->width,codec->height);

    //转换规则
    SwsContext *sws_RGB=nullptr;//保存转换规则的结构体
    SwsContext *sws_YUV=nullptr;//保存转换规则的结构体

    //转换规则的设置              AV_PIX_FMT_YUV420P   AV_PIX_FMT_RGB32
    sws_RGB=sws_getContext(codec->width,codec->height,codec->pix_fmt,
                           codec->width,codec->height,AV_PIX_FMT_RGB32,  //目标格式
                           SWS_BICUBIC,nullptr,nullptr,nullptr);         //转换规则
    sws_YUV=sws_getContext(codec->width,codec->height,codec->pix_fmt,
                           codec->width,codec->height,AV_PIX_FMT_YUV420P,//目标格式
                           SWS_BICUBIC,nullptr,nullptr,nullptr);         //转换规则
    
    //保存h.264压缩码流数据的文件
    FILE *fp=fopen("fileout/alenH264.h264","wb+");//文件名字可以自行定义
    //保存yuv像素数据的文件
    FILE *fp_yuv=fopen("fileout/alenyuv.yuv","wb+");//文件名字可以自行定义

🟢一帧一帧的读取压缩数据 保存码流数据和YUV、RGB像素数据

    int count=0;//存图片
    //一帧一帧的读取压缩数据
    while(av_read_frame(forContent,pkt)==0)//读到数据
    {
        if(pkt->stream_index == videoType)//判断一帧码流数据是不是需要得到的视频流
        {
            fwrite(pkt->data,pkt->size,1,fp);//写入文件中
            int got_picture_ptr=-1;
            //解码得到YUV
            res = avcodec_decode_video2(codec,picture,&got_picture_ptr,pkt);
            if(res < 0)
            {
                qDebug()<<"解码错误"<<endl;
                return;
            }
            //压缩码流数据,解码后的像素数据,判断有没有数据可以解码,对谁进行解码
            if(got_picture_ptr!=0)//解码操作
            {
                //把解码得到的坏数据剔除
                sws_scale(sws_RGB,picture->data,picture->linesize,0,picture->height,
                          pictureRGB->data,pictureRGB->linesize);//RGB
                sws_scale(sws_YUV,picture->data,picture->linesize,0,picture->height,
                          pictureYUV->data,pictureYUV->linesize);//YUV

                //输出的YUV文件
                //AVFrame像素帧写入文件
                fwrite(pictureYUV->data[0],size,1,fp_yuv);  //y数据
                fwrite(pictureYUV->data[1],size/4,1,fp_yuv);//u数据
                fwrite(pictureYUV->data[2],size/4,1,fp_yuv);//v数据

                //设置每25帧输出一张图片
                count++;
                if(count%25==0)
                {
                    QImage image((uchar *)pictureRGB->data[0],pictureRGB->width,pictureRGB->height,QImage::Format_RGB32);//像素数据
                    QString imageName = QString("image/test%1.jpg").arg(count);
                    image.save(imageName);//保存图片函数
                }
            }
        }
        //释放包以及AVFrame资源
        av_packet_unref(pkt);
        av_frame_unref(picture);
    }
    qDebug()<<"保存码流数据成功"<<endl;

🟢资源释放

    //关闭h.264
    fclose(fp);
    //关闭YUV文件
    fclose(fp_yuv);
    //释放AVFrame
    av_frame_free(&picture);
    //关闭解码器
    avcodec_close(codec);
    //释放视频信息结构体
    avformat_free_context(forContent);

🟢测试主函数代码,如下所示:

int main(int argc, char *argv[])
{
    //QApplication a(argc, argv);
    //Widget w;
    //w.show();
    fdecode *decode = new fdecode;
    decode->registerFFmpeg();
    decode->openVIdeoStream("filein/alen.avi");
    //return a.exec();
}

保存的H.264、yuv文件以及图片数据,如下所示:

🔵H.264、yuv文件

🔵图片数据(每25帧截图一张)

🔵 利用以下应用程序可以播放我们得到的H.264YUV文件

 

🔵来播放博主喜欢的日漫吧!

效果如下:

 

🚀​FFMPEG技术---环境配置,详见:

FFmpeg+Qt开发(一):Windows下 环境搭建 详细步骤_猿力猪的博客-CSDN博客_ffmpeg库

🚀​FFMPEG技术---编码流程,详见:

【FFmpeg+Qt开发】编码流程 普通视频编码+示例详解 一学就会_猿力猪的博客-CSDN博客

🚀​FFMPEG技术---转码流程,详见:

【FFmpeg+Qt开发】转码流程 H.264 转(mov、mp4、avi、flv)等视频格式 示例详解_猿力猪的博客-CSDN博客

✍ 本文主要介绍了FFmpeg技术中的解码部分,如有疑问,欢迎各位评论区学习交流!     

✍  觉得博主写的不错的,麻烦!😀点赞!😀评论!😀收藏!支持一下哈!蟹蟹你们! 

有关【FFmpeg+Qt开发】解码流程 详细分析+代码示例的更多相关文章

  1. ruby - 如何在 buildr 项目中使用 Ruby 代码? - 2

    如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby​​

  2. ruby-on-rails - Rails 源代码 : initialize hash in a weird way? - 2

    在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has

  3. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  4. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  5. ruby-on-rails - 浏览 Ruby 源代码 - 2

    我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru

  6. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  7. ruby - 模块嵌套代码风格偏好 - 2

    我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的

  8. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  9. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  10. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

随机推荐