草庐IT

【Qt+FFmpeg】 - FFmpeg解码详细流程

音视频开发老舅 2023-04-08 原文

目录

一:视频解码流程

二:FFMPEG解码流程

三:FFmpeg解码函数

四:FFmpeg解码的数据结构

五:FFmpeg数据结构简介

六:FFmpeg数据结构分析

七:像素数据转换

八:FFMPEG解码

九:FFMPEG解码-视频播放

一:视频解码流程

1.1 纯净的视频解码流程

压缩编码数据->像素数据。例如解码H.264,就是“H.264码流->YUV”。

1.2 一般的视频解码流程

视频码流一般存储在一定的封装格式(例如MP4、AVI等)中。

封装格式中通常还包含音频码流等内容。

对于封装格式中的视频,需要先从封装格式中提取中视频码流,然后再进行解码。

例如解码MKV格式的视频文件,就是“MKV->H.264码流->YUV”。

二:FFmpeg解码流程

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

三:FFmpeg解码函数  

av_register_all():注册所有组件。
avformat_open_input():打开输入视频文件。
avformat_find_stream_info():获取视频文件信息。
avcodec_find_decoder():查找解码器。
avcodec_open2():打开解码器。
av_read_frame():从输入文件读取一帧压缩数据。
avcodec_decode_video2():解码一帧压缩数据。
avcodec_close():关闭解码器。
avformat_close_input():关闭输入视频文件。

四:FFmpeg解码的数据结构  

五:FFmpeg数据结构简介 

AVFormatContext

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

AVInputFormat

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

AVStream[2]

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

AVCodecContext

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

AVCodec

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

AVPacket

存储一帧压缩编码数据。

AVFrame

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

六:FFmpeg数据结构分析

 

 

 

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

七:像素数据转换

解码后YUV格式的视频像素数据保存在AVFrame的data[0]、data[1]、data[2]中。

但是这些像素值并不是连续存储的,每行有效像素之后存储了一些无效像素 。

以亮度 Y 数据为例 , data[0] 中一共包含了linesize[0]* height个数据。

但是出于优化等方面的考虑,linesize[0]实际上并不等于宽度width,而是一个比宽度大一些的值。

因此需要使用sws_scale()进行转换。转换后去除了无效数据,width和linesize[0]取值相等。

 

 八:FFmpeg解码

实现步骤

1.注册所有组件

av_register_all();

 2.打开视频输入文件 

    //文件路径设置                        程序运行当前路径-exe所存在的路径
    QString filename = QCoreApplication::applicationDirPath();
    qDebug()<<"获取程序运行目录 "<<filename;
    //文件名称中文乱码-建议使用英文
    QString cinputFilePath = "test.avi";  //本地视频文件放入程序运行目录
    //指针开空间
    avformat_context = avformat_alloc_context();
    //参数一:封装格式上下文->AVFormatContext->包含了视频信息(视频格式、大小等等...)
    //参数二:打开文件(入口文件)->url
    qDebug()<<"打开"<<videoname<<"视频文件进行播放";
    //打开文件把文件详细信息传入avformat_context
    //形参代表什么 返回值什么含义                                       标准C++的String类型转C的char*类型
    int avformat_open_result = avformat_open_input(&avformat_context,videoname.toStdString().c_str(),NULL,NULL);
    if (avformat_open_result != 0)
    {
        //获取异常信息--打开视频文件失败--具体失败原因
        char* error_info = new char[32];
        av_strerror(avformat_open_result, error_info, 1024);
        qDebug()<<QString("异常信息 %1").arg(error_info);
    };

3.查找视频流信息

    //参数一:封装格式上下文->AVFormatContext
    //参数二:配置
    //返回值:0>=返回OK,否则失败                  查找流信息
    int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
    if (avformat_find_stream_info_result < 0){
        //获取失败--没有找到流信息
        char* error_info = new char[32];
        av_strerror(avformat_find_stream_info_result, error_info, 1024);
        qDebug()<<QString("异常信息 %1").arg(error_info);
    }

4.查找解码器 

    //第一点:获取当前解码器是属于什么类型解码器->找到了视频流
    //音频解码器、视频解码器、字幕解码器等等...
    //获取视频解码器流引用
    av_stream_index = -1;
    //循环遍历流信息
    for (int i = 0; i < avformat_context->nb_streams; ++i) {
        //循环遍历每一流
        //视频流、音频流、字幕流等等...                            查找视频流
        if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
            //找到了
            av_stream_index = i;
            break;
        }
    }
    if (av_stream_index == -1)
    {
        qDebug()<<QString("没有找到视频流");
    }
    //第二点:根据视频流->查找到视频解码器上下文->视频压缩数据
    //编解码器上下文---是否有合适的编解码器
    avcodec_context = avformat_context->streams[av_stream_index]->codec;
 
    //第三点:根据解码器上下文->获取解码器ID
    avcodec = avcodec_find_decoder(avcodec_context->codec_id);
    if (avcodec == NULL)
    {
        qDebug()<<QString("没有找到视频解码器");
    }

5.打开解码器 

    int avcodec_open2_result = avcodec_open2(avcodec_context,avcodec,NULL);
    if (avcodec_open2_result != 0)
    {
        char* error_info = new char[32];
        av_strerror(avformat_find_stream_info_result, error_info, 1024);
        qDebug()<<QString("异常信息 %1").arg(error_info);
    }
 
    qDebug()<<"视频详细信息输出";
    //此函数自动打印输入或输出的详细信息--退出时候才会有信息显示
    av_dump_format(avformat_context, 0, cinputFilePath.toStdString().c_str(), 0);
    qDebug()<<"----------------解码准备工作完成-----------------";

6.循环解码 

    //读取帧数据换成到哪里->缓存到packet里面
    av_packet = (AVPacket*)av_malloc(sizeof(AVPacket));
 
    //输入->环境一帧数据->缓冲区->类似于一张图
    pFramein = av_frame_alloc();
    //输出->帧数据->数据格式->RGB
    pFrameRGB = av_frame_alloc();
    //只有指定了AVFrame的像素格式、画面大小才能真正分配内存
    //缓冲区分配内存
    pOutbuffer = (uint8_t *)av_malloc(avpicture_get_size(
                                          AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height));
    //初始化缓冲区 类似于内存的memset-开辟完清理操作
    avpicture_fill((AVPicture *)pFrameRGB, pOutbuffer,
                   AV_PIX_FMT_RGB32, avcodec_context->width, avcodec_context->height);
 
    //解码的状态类型(0:表示解码完毕,非0:表示正在解码)
    //    int current_frame_index = 0;
 
    //用于转码(缩放)的参数,转之前的宽高,转之后的宽高,格式等
    //准备一个视频像素数据格式上下文
    //参数一:输入帧数据宽
    //参数二:输入帧数据高
    //参数三:输入帧数据格式
    //参数四:输出帧数据宽
    //参数五:输出帧数据高
    //参数六:输出帧数据格式->AV_PIX_FMT_RGB32
    //参数七:视频像素数据格式转换算法类型
    //参数八:字节对齐类型(C/C++里面)->提高读取效率
    SwsContext* pSwsContext = sws_getContext(avcodec_context->width,
                                             avcodec_context->height,
                                             avcodec_context->pix_fmt,
                                             avcodec_context->width,
                                             avcodec_context->height,
                                             AV_PIX_FMT_RGB32,
                                             SWS_BICUBIC,NULL,NULL,NULL);
    int ret;//解码结果--处理出来的图片
 
    //解码的状态类型(0:表示解码完毕,非0:表示正在解码)--在循环体外进行操作
    int current_frame_index = 0;
 
    //线程标志位
    while (m_stop == false)
    {
        //>=0:说明有数据,继续读取   <0:说明读取完毕,结束
        //从视频文件上下文中读取包--- 有数据就一直读取
        //判断--有数据的话才会一直读取
        if (av_read_frame(avformat_context,av_packet) >= 0)
        {
            //解码什么类型流(视频流、音频流、字幕流等等...)
            if (av_packet->stream_index == av_stream_index)
            {
                //解码一帧视频数据
                avcodec_send_packet(avcodec_context, av_packet);
 
                //接收一帧数据->解码一帧      处理出来的图片存储到pFramein
                ret = avcodec_receive_frame(avcodec_context,pFramein);
                //处理出来的图片是否可行
                if (ret == 0)//解码成功
                {
                    //图片的转换的相关操作  输入pFramein 输出pFrameRGB
                    sws_scale(pSwsContext, (const unsigned char* const*)pFramein->data, pFramein->linesize, 0, avcodec_context->height,
                              pFrameRGB->data,  pFrameRGB->linesize);
 
                    QImage  *tmpImg  = new QImage((uchar *)pOutbuffer, avcodec_context->width,
                                                  avcodec_context->height,QImage::Format_RGB32);
 
                    QImage image=tmpImg->copy();
 
                    qDebug()<<"接收图片信号"<<image;
                    //解码得到的
                    //一部分做显示  发送信号(图片)--emit
                    emit sigGetOneFrame(image);
                    //一部分做编码
                    //循环 编码一帧数据
                    pvideoCode->codeingOneFrame(pFramein);
 
                    //遍历每一帧的信息进行打印
                    current_frame_index++;
                    //发送信号-emit 解码信息放在窗口中进行显示
                    emit SendOneData(current_frame_index);
                    //延时操作  1秒显示25帧--1000/25=40
                    QThread::msleep(40);
                    //获取的视频信息
                    qDebug()<<QString("当前遍历第 %1 帧").arg(current_frame_index);
                }
            }
        }
 
        av_free_packet(av_packet);

7.关闭所有解码组件 

    av_packet_free(&av_packet);
    //关闭流
    avcodec_close(avcodec_context);
    avformat_free_context(avformat_context);

九:FFMPEG解码-视频播放

线程简介

•线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

线程状态

线程启动和停止 

•线程启动调用start()函数。

•RUN函数执行完表示线程退出。

QThread 使用测试

如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论! 

本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg webRTC rtmp hls rtsp ffplay srs↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

有关【Qt+FFmpeg】 - FFmpeg解码详细流程的更多相关文章

  1. Qt Designer的简单使用 - 2

    在前面两节的例子中,主界面窗口的尺寸和标签控件显示的矩形区域等,都是用C++代码编写的。窗口和控件的尺寸都是预估的,控件如果多起来,那就不好估计每个控件合适的位置和大小了。用C++代码编写图形界面的问题就是不直观,因此Qt项目开发了专门的可视化图形界面编辑器——QtDesigner(Qt设计师)。通过QtDesigner就可以很方便地创建图形界面文件*.ui,然后将ui文件应用到源代码里面,做到“所见即所得”,大大方便了图形界面的设计。本节就演示一下QtDesigner的简单使用,学习拖拽控件和设置控件属性,并将ui文件应用到Qt程序代码里。使用QtDesigner设计界面在开始菜单中找到「Q

  2. 在VMware16虚拟机安装Ubuntu详细教程 - 2

    在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主

  3. python ffmpeg 使用 pyav 转换 一组图像 到 视频 - 2

    2022/8/4更新支持加入水印水印必须包含透明图像,并且水印图像大小要等于原图像的大小pythonconvert_image_to_video.py-f30-mwatermark.pngim_dirout.mkv2022/6/21更新让命令行参数更加易用新的命令行使用方法pythonconvert_image_to_video.py-f30im_dirout.mkvFFMPEG命令行转换一组JPG图像到视频时,是将这组图像视为MJPG流。我需要转换一组PNG图像到视频,FFMPEG就不认了。pyav内置了ffmpeg库,不需要系统带有ffmpeg工具因此我使用ffmpeg的python包装p

  4. ruby - 无法在 Ruby 中将 ffmpeg 作为子进程运行 - 2

    我正在尝试使用以下代码通过将ffmpeg实用程序作为子进程运行并获取其输出并解析它来确定视频分辨率:IO.popen'ffmpeg-i'+path_to_filedo|ffmpegIO|#myparsegoeshereend...但是ffmpeg输出仍然连接到标准输出并且ffmepgIO.readlines是空的。ffmpeg实用程序是否需要一些特殊处理?或者还有其他方法可以获得ffmpeg输出吗?我在WinXP和FedoraLinux下测试了这段代码-结果是一样的。 最佳答案 要跟进mouviciel的评论,您需要使用类似pope

  5. 100个python算法超详细讲解:画直线 - 2

    1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva

  6. 关于Qt程序打包后运行库依赖的常见问题分析及解决方法 - 2

    目录一.大致如下常见问题:(1)找不到程序所依赖的Qt库version`Qt_5'notfound(requiredby(2)CouldnotLoadtheQtplatformplugin"xcb"in""eventhoughitwasfound(3)打包到在不同的linux系统下,或者打包到高版本的相同系统下,运行程序时,直接提示段错误即segmentationfault,或者Illegalinstruction(coredumped)非法指令(4)ldd应用程序或者库,查看运行所依赖的库时,直接报段错误二.问题逐个分析,得出解决方法:(1)找不到程序所依赖的Qt库version`Qt_5'

  7. H2数据库配置及相关使用方式一站式介绍(极为详细并整理官方文档) - 2

    目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式

  8. 华为ensp详细安装包、安装教程及所遇问题 - 2

    目录一、安装包链接二、安装详细步骤1.安装Wireshark和WinPcap2.安装OracleVMVirtualBox3.安装ensp三、安装后注册四、启动路由器出现40错误怎么解决一、安装包链接二、安装详细步骤链接:https://pan.baidu.com/s/1QbUUYMOMIV2oeIKHWP1SpA?pwd=xftx提取码:xftx1.安装Wireshark和WinPcap找到Wireshark安装包所在文件夹,双击它,按照以下步骤安装。2.安装OracleVMVirtualBox找到OracleVMVirtualBox安装包所在文件夹,双击它,按照以下步骤安装。注:可自定义安装

  9. Linux操作系统CentOS7安装Nginx[详细版] - 2

    Nginx安装1.官网下载Nginx2.使用XShell和Xftp将压缩包上传到Linux虚拟机中3.解压文件nginx-1.20.2.tar.gz4.配置nginx5.启动nginx6.拓展(修改端口和常用命令)(一)修改nginx端口(二)常用命令1.官网下载Nginxhttp://nginx.org/en/download.html这里我下载的是1.20.2版本,大家按需下载对应稳定版即可2.使用XShell和Xftp将压缩包上传到Linux虚拟机中没有XShell可以参考《Linux操作系统CentOS7连接XShell》3.解压文件nginx-1.20.2.tar.gz1)检查是否存

  10. 等保工作流程和明细 - 2

    一、系统定级信息系统运营使用单位按照等级保护管理办法和定级指南,自主确定信息系统的安全保护等级。有上级主管部门的,应当经上级主管部门审批。跨省或全国统一联网运行的信息系统可以由其主管部门统一确定安全保护等级。定级需要根据信息系统的实际情况合理定级。二、系统备案第二级以上信息系统定级单位到所在地设区的市级以上公安机关办理备案手续。省级单位到省公安厅网安总队备案,各地市单位一般直接到市级网安支队备案,也有部分地市区县单位的定级备案资料是先交到区县公安网监大队的,具体根据各地市要求来。信息系统运营、使用单位或者其主管部门应当在信息系统安全保护等级确定后30日内,到公安机关办理备案手续。三、初次测评信

随机推荐