FFmpeg笔记(五)-- 编解码函数详解

1.libavformat

AVFormatContext可以进行格式的封装与解封装,它的数据部分由底层提供,底层使用了AVIOContext,这个AVIOContext实际上就是为普通的I/O增加了一层Buffer缓冲区,再往 底层就是URLContext,也就是到达了协议层。如图:


libavformat

相关函数及结构体:

av_register_all()

编译FFmpeg的时候,做了一个configure的配置,configure的配置会生成两个文件:config.mk与config.h。 config.mk实际上就是makefile文件需要包含进去的子模块,会作用在编译阶段,帮助开发者编译出正确的库;而config.h是作用在运行阶段, 这一阶段将确定需要注册哪些容器以及编解码格式到FFmpeg框架中。 所以该函数的内部实现会先调用avcodec_register_all来注册所有config.h 里面开放的编解码器,然后会注册所有的Muxer和Demuxer(也就是封 装格式),最后注册所有的Protocol(即协议层的东西)。

avformat_open_input()

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

avformat_find_stream_info()

该方法的作用就是把所有Stream的MetaData信息填充好。方法内部会先查找对应的解码器,然后打开对应的解码器,紧接着会利用Demuxer中的 read_packet函数读取一段数据进行解码,当然解码的数据越多,分析出的流信息就会越准确,如果是本地资源,那么很快就可以得到非常准确的信息了,但是对于网络资源来说,则会比较慢,因此该函数有几个参数可以控制读取数据的长度,一个是probe size,一个是max_analyze_duration,还有一个是fps_probe_size,这三个参数共同控制解码数据的长度,当然,如果配置这几个参数的值越小,那么这个函数执行的时间就会越快,但是会导致AVStream结构体里面一些信息(视频的宽、高、fps、编码类型等)不准确。

av_find_best_stream()

获取各种流的索引。

av_read_frame()

使用该方法读取出来的数据是AVPacket,在FFmpeg的早期版本中 开放给开发者的函数其实就是av_read_packet,但是需要开发者自己来处理AVPacket中的数据不能被解码器完全处理完的情况,即需要把未处理完的压缩数据缓存起来的问题。所以到了新版本的FFmpeg中,其提供了该函数,用于处理此状况。该函数的实现首先会委托到Demuxer的 read_packet方法中去,当然read_packet通过解复用层和协议层的处理之后,会将数据返回到这里,在该函数中进行数据缓冲处理。为了保证每次读取完整的一帧,读取到的数据长度可能不一样。

2.libavcodec

对于开发者来说,这一层我们能接触到的最顶层的结构体就是 AVCodecContext,该结构体包含的就是与实际的编解码有关的部分。首先,AVCodecContext是包含在一个AVStream里面的,即描述了这路流的编码格式是什么,其中存放了具体的编码格式信息,根据Codec的信息可以打开编码器或者解码器,然后利用该编码器或者解码器进行 AVPacket与AVFrame之间的转换(实际上就是解码或者编码的过程), 这是FFmpeg中最重要的一部分。结构如图:


libavformat

相关函数及结构体:

avcodec_find_decoder()

根据上下文找到解码器。

avcodec_open2()

打开解码器。

av_frame_alloc()

AVFrame对象必须调用av_frame_alloc()在堆上分配。

avcodec_decode_audio4()
参数1:avctx编解码器上下文
参数2:frame用于存储解码音频样本的AVFrame
参数3:got_frame_ptr如果没有帧可以解码则为零,否则为非零
参数4:avpkt包含输入缓冲区的输入AVPacket
参数5:如果在解码期间发生错误,则返回否定错误代码,否则返回从输入AVPacket消耗的字节数。
avcodec_send_packet():发送解码未处理数据
avcodec_receive_frame():接收解码后的数据
avcodec_send_frame:发送编码未处理数据
avcodec_receive_packet():接收编码后的数据

一个packet会被解码出一个frame,不过也存在一个packet被解码出多个frame或者多个packet才能解码出一个frame的情况,甚至也有些解码器在输入以及输出端上可能会有延迟。了解更多看这里

返回状态:

send 0        :send_packet返回值为0,正常状态,意味着输入的packet被解码器正常接收。
send EAGAIN   :send_packet返回值为EAGAIN,输入的packet未被接收,需要输出一个或多个的frame后才能重新输入当前packet。
send EOF      :send_packet返回值为EOF,当send_packet输入为NULL时才会触发该状态,用于通知解码器输入packet已结束。
send EINVAL   :没有打开×××,或者这是一个编码器,或者要求刷新
receive 0     :receive_frame返回值为0,正常状态,意味着已经输出一帧。
receive EAGAIN:receive_frame返回值为EAGAIN,未能输出frame,需要输入更多的packet才能输出当前frame。
receive EOF   :receive_frame返回值为EOF,当处于send EOF状态后,调用一次或者多次receive_frame后就能得到该状态,表示所有的帧已经被输出。
简略流程:av_read_frame读取一帧数据,avcodec_send_packet发送数据,解码后avcodec_receive_frame接收一个完整帧,但因为一些特殊原因avcodec_send_packet发送的不是一个完成帧,此时avcodec_receive_frame会返回

3.libavutil

av_image_get_buffer_size()

给缓冲区设置类型类型,得到YUV420P缓冲区大小。

参数一:视频像素数据格式类型->YUV420P格式
参数二:一帧视频像素数据宽 = 视频宽
参数三:一帧视频像素数据高 = 视频高
参数四:字节对齐方式->默认是1
int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                           videoCodecCtx->width,
                                           videoCodecCtx->height,
                                           1);
av_image_fill_arrays()

缓存区填充数据。

参数一:目标->填充数据(avframe_yuv420p)
参数二:目标->每一行大小
参数三:原始数据
参数四:目标->格式类型
参数五:宽
参数六:高
参数七:字节对齐方式
av_image_fill_arrays(avframe_yuv420p->data,
                     avframe_yuv420p->linesize,
                     out_buffer,
                     AV_PIX_FMT_YUV420P,
                     videoCodecCtx->width,
                     videoCodecCtx->height,
                     1);

4.libswresample

音频重采样,就是改变音频的采样率、sample format、声道数等参数,使之按照我们期望的参数输出。

SwrContext

音频格式转换上下文。

swr_alloc()

初始化上下文结构体。

swr_alloc_set_opts()

设置重采样参数。

swr_init()

在swr_alloc_set_opts之后调用,将采样参数设置到上下文。

参数1:重采样上下文
参数2:输出的layout, 如:5.1声道…
参数3:输出的样本格式。Float, S16, S24
参数4:输出的样本率。可以不变。
参数5:输入的layout。
参数6:输入的样本格式。
参数7:输入的样本率。
参数8,参数9,日志,不用管,可直接传0 */
av_samples_get_buffer_size()

使用av_sample_get_buffer_size来计算音频占用的字节数。

参数1:linesize calculated linesize, may be NULL
参数2:nb_channels   声道
参数3:nb_samples    单个通道中的样本数
参数4:sample_fmt    采样格式
参数5:align         对齐缓冲区大小对齐(0 =默认,1 =无对齐)
swr_convert()

转码。使用avcodec_decode_audio4函数解码音频得到的数据类型为float 4bit,而播放器播放的格式一般为S16(signed 16bit),这就需要对解码得到的数据进行转换。

参数1:音频重采样的上下文
参数2:输出的指针。传递的输出的数组
参数3:输出的样本数量,不是字节数。单通道的样本数量。
参数4:输入的数组,AVFrame解码出来的DATA
参数5:输入的单通道的样本数量。

5.libswscale

SwsContext

视频格式转换上下文,编解码中可能需要对视频格式重新设置,就需要用到SwsContext。

sws_getContext()

设置视频转换上下文。

参数一:源文件->原始视频像素数据格式宽
参数二:源文件->原始视频像素数据格式高
参数三:源文件->原始视频像素数据格式类型
参数四:目标文件->目标视频像素数据格式宽
参数五:目标文件->目标视频像素数据格式高
参数六:目标文件->目标视频像素数据格式类型
sws_getContext(videoCodecCtx->width,
               videoCodecCtx->height,
               videoCodecCtx->pix_fmt,
               videoCodecCtx->width,
               videoCodecCtx->height,
               AV_PIX_FMT_YUV420P,
               SWS_FAST_BILINEAR,
               NULL,
               NULL,
               NULL);
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);

你可能感兴趣的:(FFmpeg笔记(五)-- 编解码函数详解)