FFmpeg的视频解码详解

FFmpeg的视频解码详解,第1张

第一步:组册组件

av_register_all()

例如:编码器、解码器等等…

第二步:打开封装格式->打开文件

例如:mp4、mov、wmv文件等等

avformat_open_input();

第三步:查找视频

如果是视频解码,那么查找视频流,如果是音频解码,那么就查找音频流

avformat_find_stream_info();

第四步:查找视频解码器

1、查找视频流索引位置

2、根据视频流索引,获取解码器上下文

3、根据解码器上下文,获得解码器ID,然后查找解码器

第五步:打开解码器

avcodec_open2();

第六步:读取视频压缩数据->循环读取

没读取一帧数据,立马解码一帧数据

第七步:视频解码->播放视频->得到视频像素数据

第八步:关闭解码器->解码完成

//第一步:组册组件

    av_register_all();

    //第二步:打开封装格式->打开文件

    //参数一:封装格式上下文

    //作用:保存整个视频信息(解码器、编码器等等)

    //信息:码率、帧率等

    AVFormatContext avformat_context = avformat_alloc_context();

    //参数二:视频路径

    const char url = [jinFilePath UTF8String]

    //参数三:指定输入的格式

    //参数四:设置默认参数

    int avformat_open_input_result = avformat_open_input(&avformat_context, url,NULL, NULL);

    if (avformat_open_input_result !=0){

        NSLog("打开文件失败");

        //不同的平台替换不同平台log日志

        return;

    }

    //第三步:查找视频流->拿到视频信息

    //参数一:封装格式上下文

    //参数二:指定默认配置

    int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context,NULL);

    if (avformat_find_stream_info_result <0){

        NSLog(" 查找失败");

        return;

    }

    //第四步:查找视频解码器

    //1、查找视频流索引位置

    int 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;

        }

    }

    //2、根据视频流索引,获取解码器上下文

    AVCodecContext avcodec_context = avformat_context->streams[av_stream_index]->codec;

    //3、根据解码器上下文,获得解码器ID,然后查找解码器

    AVCodec avcodec = avcodec_find_decoder(avcodec_context->codec_id);

    //第五步:打开解码器

    int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec,NULL);

    if (avcodec_open2_result !=0){

        NSLog("打开解码器失败");

        return;

    }

    //第六步:读取视频压缩数据->循环读取

    //1、分析av_read_frame参数

    //参数一:封装格式上下文

    //参数二:一帧压缩数据 = 一张

    //av_read_frame()

    //结构体大小计算:字节对齐原则

    AVPacket packet = (AVPacket)av_malloc(sizeof(AVPacket));

    //32 解码一帧视频压缩数据->进行解码(作用:用于解码 *** 作)

    //开辟一块内存空间

    AVFrame avframe_in = av_frame_alloc();

    int decode_result =0;

    //4、注意:在这里我们不能够保证解码出来的一帧视频像素数据格式是yuv格式

    //参数一:源文件->原始视频像素数据格式宽

    //参数二:源文件->原始视频像素数据格式高

    //参数三:源文件->原始视频像素数据格式类型

    //参数四:目标文件->目标视频像素数据格式宽

    //参数五:目标文件->目标视频像素数据格式高

    //参数六:目标文件->目标视频像素数据格式类型

    SwsContext swscontext = sws_getContext(avcodec_context->width,

                   avcodec_context->height,

                   avcodec_context->pix_fmt,

                   avcodec_context->width,

                   avcodec_context->height,

                   AV_PIX_FMT_YUV420P,

                   SWS_BICUBIC,

                   NULL,

                   NULL,

                   NULL);

    //创建一个yuv420视频像素数据格式缓冲区(一帧数据)

    AVFrame avframe_yuv420p = av_frame_alloc();

    //给缓冲区设置类型->yuv420类型

    //得到YUV420P缓冲区大小

    //参数一:视频像素数据格式类型->YUV420P格式

    //参数二:一帧视频像素数据宽 = 视频宽

    //参数三:一帧视频像素数据高 = 视频高

    //参数四:字节对齐方式->默认是1

    int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,

                             avcodec_context->width,

                             avcodec_context->height,

                             1);

    //开辟一块内存空间

    uint8_t out_buffer = (uint8_t )av_malloc(buffer_size);

    //向avframe_yuv420p->填充数据

    //参数一:目标->填充数据(avframe_yuv420p)

    //参数二:目标->每一行大小

    //参数三:原始数据

    //参数四:目标->格式类型

    //参数五:宽

    //参数六:高

    //参数七:字节对齐方式

    av_image_fill_arrays(avframe_yuv420p->data,

                         avframe_yuv420p->linesize,

                         out_buffer,

                         AV_PIX_FMT_YUV420P,

                         avcodec_context->width,

                         avcodec_context->height,

                         1);

    int y_size, u_size, v_size;

    //52 将yuv420p数据写入yuv文件中

    //打开写入文件

    const char outfile = [joutFilePath UTF8String];

    FILE file_yuv420p = fopen(outfile,"wb+");

    if (file_yuv420p ==NULL){

       NSLog("输出文件打开失败");

        return;

    }

    int current_index =0;

    while (av_read_frame(avformat_context, packet) >=0){

        //>=:读取到了

        //<0:读取错误或者读取完毕

        //2、是否是我们的视频流

        if (packet->stream_index == av_stream_index){

            //第七步:解码

            //学习一下C基础,结构体

            //3、解码一帧压缩数据->得到视频像素数据->yuv格式

            //采用新的API

            //31 发送一帧视频压缩数据

            avcodec_send_packet(avcodec_context, packet);

            //32 解码一帧视频压缩数据->进行解码(作用:用于解码 *** 作)

            decode_result = avcodec_receive_frame(avcodec_context, avframe_in);

            if (decode_result ==0){

                //解码成功

                //4、注意:在这里我们不能够保证解码出来的一帧视频像素数据格式是yuv格式

                //视频像素数据格式很多种类型: yuv420P、yuv422p、yuv444p等等

                //保证:我的解码后的视频像素数据格式统一为yuv420P->通用的格式

                //进行类型转换: 将解码出来的视频像素点数据格式->统一转类型为yuv420P

                //sws_scale作用:进行类型转换的

                //参数一:视频像素数据格式上下文

                //参数二:原来的视频像素数据格式->输入数据

                //参数三:原来的视频像素数据格式->输入画面每一行大小

                //参数四:原来的视频像素数据格式->输入画面每一行开始位置(填写:0->表示从原点开始读取)

                //参数五:原来的视频像素数据格式->输入数据行数

                //参数六:转换类型后视频像素数据格式->输出数据

                //参数七:转换类型后视频像素数据格式->输出画面每一行大小

                sws_scale(swscontext,

                          (const uint8_t const )avframe_in->data,

                          avframe_in->linesize,

                          0,

                          avcodec_context->height,

                          avframe_yuv420p->data,

                          avframe_yuv420p->linesize);

                //方式一:直接显示视频上面去

                //方式二:写入yuv文件格式

                //5、将yuv420p数据写入yuv文件中

                //51 计算YUV大小

                //分析一下原理

                //Y表示:亮度

                //UV表示:色度

                //有规律

                //YUV420P格式规范一:Y结构表示一个像素(一个像素对应一个Y)

                //YUV420P格式规范二:4个像素点对应一个(U和V: 4Y = U = V)

                y_size = avcodec_context->width avcodec_context->height;

                u_size = y_size /4;

                v_size = y_size /4;

                //52 写入yuv文件

                //首先->Y数据

                fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);

                //其次->U数据

                fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);

                //再其次->V数据

                fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);

                current_index++;

                NSLog("当前解码第%d帧", current_index);

            }

        }

    }

    //第八步:释放内存资源,关闭解码器

    av_packet_free(&packet);

    fclose(file_yuv420p);

    av_frame_free(&avframe_in);

    av_frame_free(&avframe_yuv420p);

    free(out_buffer);

    avcodec_close(avcodec_context);

    avformat_free_context(avformat_context);

不做任何预处理合并,容易导致问题

两个10s的(不同分辨率、帧率)合并之后,出现时长39s,严重超长

且出现问题:第一个视频的10s结束之后画面不动,隔了nsecond出现下一个10s的视频。

最终分析问题是,在用CLI的时候,应该注意和Cpp的API一样注意timebase,fps等配置

最终解决:

参考stackoverflow: >

FFmpeg名称中的mpeg来自视频编码标准MPEG,而前缀FF是Fast Forward的首字母缩写。

目录

默认的编译会生成 4 个可执行文件和 8 个静态库。可执行文件包括用于 转码 、 推流 、Dump媒体文件的 ffmpeg 、用于播放媒体文件的 ffplay 、 用于获取媒体文件信息的 ffprobe ,以及作为简单流媒体服务器的 ffserver 。

8个静态库其实就是FFmpeg的8个模块,具体包括如下内容。

比如AAC编码,常见的有两种封装格式

AAC 的 bit stream filter 常常应用在 编码 的过程中。

与音频的AAC编码格式相对应的是视频中的 H264编码 ,它也有两种封装格式

FFmpeg中也提供了对应的 bit stream filter ,称 H264_mp4toannexb ,可以将MP4封装格式的H264数据包转换为annexb封装格式的H264数据 (其实就是裸的H264的数据)包。

H264 的 bit stream filter 常常应用于视频解码过程中。

ffmpeg 是进行媒体文件转码的命令行工具

ffprobe 是用于查看媒体 文件头信息的工具

ffplay 则是用于播放媒体文件的工具

1首先用ffprobe查看一个音频的文件

2输出格式信息format_name、时间长度duration、文件 大小size、比特率bit_rate、流的数目nb_streams等。

3以JSON格式的形式输出具体每一个流 最详细 的信息

4显示帧信息的命令如下:

5查看包信息的命令如下:

ffplay是以FFmpeg框架为基础,外加渲染音视频 的库libSDL来构建的媒体文件播放器。

业界内开源的 ijkPlayer 其实就是基于 ffplay 进行改造的播放器,当然其做了硬件解码以及很多兼容性的工作。

在 ffplay中音画同步的实现方式其实有三种。分别是

并且在 ffplay 中默认的对齐方式也是以 音频 为基准进行对齐的。

首先要声明的是,播放器接收到的视频帧或者音频帧,内部都会有 时间戳(PTS时钟) 来标识它实际应该在什么时刻进行展示。

实际的对齐策略如下:比较视频当前的播放时间和音频当前的播放时间

关键就在于音视频时间的比较以及延迟的计算,当然在比较的过程中会设 置一个 阈值(Threshold) ,若超过预设的阈值就应该做调整(丢帧渲染 或者重复渲染),这就是整个对齐策略。

ffmpeg 就是强大的媒体文件转换工具。它可以转换任何格式的媒体文件,并且还可以用自己的 AudioFilter 以及 VideoFilter 进行处理和编辑。

接下来介绍一个解码的实例,该实例实现的功能非常单一,就是把一个视频文件解码成单独的音频PCM文件和视频YUV文件。

AVFormatContext是API层直接接触到的结构体,它会进行格式的封 装与解封装。

该结构体包含的就是与实际的 编解码 有关的部分。

331 av_register_all

所以该函数的内部实现会先调用 avcodec_register_all 来注册所有configh里面开放的编解码器,然后会注册所有的 Muxer 和 Demuxer (也就是封装格式),最后注册所有的 Protocol (即协议层的东西)。

332 av_find_codec

这里面其实包含了两部分的内容:一部分是寻找 解码器 ,一部分是寻找 编码器 。

333 avcodec_open2

该函数是打开编解码器(Codec)的函数,无论是编码过程还是解码过程,都会用到该函数。

avformat_open_input

根据所提供的文件路径判断文件的格 式,其实就是通过这一步来决定使用的到底是哪一个 Demuxer 。

avformat_find_stream_info

该方法的作用就是把所有 Stream 的 MetaData 信息填充好。

av_read_frame

使用该方法读取出来的数据是 AVPacket 。

对于 音频流 ,一个 AVPacket 可能包含 多 个 AVFrame ,但是对于 视频流 ,一个 AVPacket 只包含 一 个 AVFrame ,该函数最终只会返回一个 AVPacket 结构体。

avcodec_decode

该方法包含了两部分内容:一部分是 解码视频 ,一部分是 解码音频 , 解码 是会委托给对应的解码器来实施的。

avformat_close_input

该函数负责释放对应的资源。

avformat_alloc_output_context2

该函数内部需要调用方法avformat_alloc_context来分配一个 AVFormatContext 结构体。

avio_open2

编码的阶段了,开发者需要将手动封装好的 AVFrame 结构体,作为 avcodec_encode_video 方法的输入,将其编码成为 AVPacket ,然后调用 av_write_frame 方法输出到媒体文件中。

本文参考 音视频开发进阶指南

项目源码地址 - FFmpegDecoder

ffmpeg基本理解

整体可划分为协议层、容器层、编码层与原始数据层四个层次:

协议层:提供网络协议收发功能,可以接收或推送含封装格式的媒体流。协议层由 libavformat 库及第三方库(如 librtmp)提供支持。

容器层:处理各种封装格式。容器层由 libavformat 库提供支持。

编码层:处理音视频编码及解码。编码层由各种丰富的编解码器(libavcodec 库及第三方编解码库(如 libx264))提供支持。

原始数据层:处理未编码的原始音视频帧。原始数据层由各种丰富的音视频滤镜(libavfilter 库)提供支持

这遍文章目针对对ffmpeg基本结构和变量概念有一定了解后,想进一步理清楚个模块之间是如何关联起来,给出一个

清晰具体的流程。

播放器调用通过几个函数将这个流程串联起来,后续一一展开。

FFMPEG的输入对象AVFormatContext的pb字段指向一个AVIOContext。这是一个带有缓存的读写io上层

说明:

AVIOContext对象是一个带有缓存IO读写层。

AVIOContext的opaque实际指向一个URLContext对象,这个对象封装了协议对象及协议 *** 作对象,其中prot指向具体的协议 *** 作对象,priv_data指向具体的协议对象。

URLProtocol为协议 *** 作对象,针对每种协议,会有一个这样的对象,每个协议 *** 作对象和一个协议对象关联,比如,文件 *** 作对象为ff_file_protocol,它关联的结构体是FileContext

aviobufc函数中 ffio_fdopen()很重要,分配avio资源并建立对象,将AVIOContext和URLContext关联起来。internal->h = h;

ffio_open_whitelist = ffurl_open_whitelist +ffio_fdopen

至此,IO相关部分构造完成啦。

构造FFMPEG的输入对象AVFormatContext的iformat字段指向的对象诸如:

s→iformat 该输入流的Demuxer 存放位置。比如AVInputFormat ff_hls_demuxer

s→priv_data 这个变量很重要:存放对应的AVInputFormat *** 作的上下文信息: 比如hls中的HLSContext

构造好dexuer之后会调用 read_header2() 这个函数开启具体demuxer具体协议解析,hls开始解析:hls_read_header --->parse_playlist→

关于hls协议处理

循环构造AVFormatContext ,AVIOContext变量等。

首先看下 数据结构

然后看下,如何从在hls中 Open the demuxer for each playlist ,此时已经解析完m3u8。继续下面又干什么啦

继续分析hlsc文件获得m3u8解析额ts文件程序做了什么。

其实AVFormatContext s = pls→parent 此时作用,用的黑白名单和option设置参数,这个函数主要是还是构造访问ts文件的AVIOContext对象用的。

下图是hlsc中解析ts流流程如下:

>

以上就是关于FFmpeg的视频解码详解全部的内容,包括:FFmpeg的视频解码详解、[FFMPEG-CLI]合并不同分辨率/帧率视频注意事项-出现合并后时长问题、第三章 FFmpeg的介绍与使用等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/9708896.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-01
下一篇 2023-05-01

发表评论

登录后才能评论

评论列表(0条)

保存