音频解码过程
?如下图所示 
FFmpeg流程

关键函数
- avcodec_find_decoder:根据指定的AVCodecID找注册解码器。
- av_parser_init:初始化AVCodecParserContext。
- avcodec_alloc_context3:为AVCodecContext分配内存。
- avcodec_open打开解码器。
- av_parser_parse2:分析获得个Packet。
- avcodec_send_packet:将AVPacket压缩数据给解码器。
- avcodec_receive_frame:获取解码后AVFrame数据。
- av_get_bytes_per_sample: 获取每个sample中间字节数。
关键数据结构
关键数据结构说明:
- AVCodecParser:用于解析输?的数据流并把它分成?帧?帧的压缩编码数据。比较形象 的说法就是把长长的?段连续的数据“切割”成?段段的数据。 比如AAC aac_parser
ffmpeg-4.2.1\libavcodec\aac_parser.c
AVCodecParser ff_aac_parser = { .codec_ids = { AV_CODEC_ID_AAC }, .priv_data_size = sizeof(AACAC3ParseContext), .parser_init = aac_parse_init, .parser_parse = ff_aac_ac3_parse, .parser_close = ff_parse_close,}; 从AVCodecParser我们可以看到不同编码类型的结构实例化parser是和CODE_ID进入绑定。所以 也可以解释
parser = av_parser_init(codec->id);
可以通过CODE_ID找到相应的码流 parser。
avcodec编解码API介绍
avcodec_send_packet、avcodec_receive_frame的API是FFmpeg3版加。为了正确 使它们,有必要阅读它们FFmpeg(请点击链接)。
以下内容摘译自文档描述 FFmpeg在编码和解码中提供了两组函数:
- 解码: avcodec_send_packet()、avcodec_receive_frame()。
- 解码: avcodec_send_frame()、avcodec_receive_packet()。
API设计和编解码过程非常合适。
建议流程如下:
- 像以前一样设置和打开AVCodecContext。
- 有效数据:
- 解码:调?avcodec_send_packet()将原始压缩数据输入解码器AVPacket对象。
- 编码:调整 avcodec_send_frame()将包含解压数据的数据传输到编码器AVFrame对象。推荐两种情况。AVPacket和AVFrame都使用refcounted(引用计数)模式,否则 libavcodec可能要复制丢失的数据。
- 在一个循环体内接收codec输出,即周期性地调avcodec_receive_*()来接收codec 输出数据:
- 解码:调用avcodec_receive_frame(),如果成功,将返回包含未压缩数据的 AVFrame。
- 编码:调用avcodec_receive_packet(),如果成功,将返回包含压缩数据的 AVPacket。 反复调用avcodec_receive_packet()直到返回 AVERROR(EAGAIN)或其他错误。返回 AVERROR(EAGAIN)错误表示codec需要新的输出来输出更多的数据。对于每个输出, packet或frame,codec?一般输出?frame或packet,但也有可能输出0个或多个。
- 流处理结束时需要流处理flush(冲刷) codec。因为codec可能有多个内部缓冲frame或 packet,出于性能或其他必要的情况(如考虑)B帧)。处理过程如下:
- 调?avcodec_send_*()传入的AVFrame或AVPacket指针设置为NULL。这将进? draining mode(排水模式)。
- 反复地调?avcodec_receive_*()直到返回AVERROR_EOF,该方法在draining mode 时不会返回AVERROR(EAGAIN)除非你没有进入,否则错误draining mode。
- 当重新开启codec需要先调? avcodec_flush_buffers()来重置codec。
说明:
- 在编码或解码开始时,codec可能收到多个输家frame或packet在内部输出数据之前buffer填满。这种情况可以通过上述使用过程来处理。
- 理论上,只有输出数据没有完全接收?avcodec_send_()发生时可能会发生AVERROR(EAGAIN)错误。您可以依靠这种机制来实现不同于上述建议流程的处理 型,例如,每个循环都被调用avcodec_send_(),出现AVERROR(EAGAIN)再次调用错误avcodec_receive_*()。
- 并非全部codec都遵循严格可预测的数据处理流程,只能保证
- 在同⼀个AVCodecContext上混合使⽤新旧API是不允许的,这将导致未定义的行为。
avcodec_send_packet
- 函数: int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
- 作用: ⽀持将裸流数据包送给解码器
- 警告:
- 输⼊的avpkt-data缓冲区必须⼤于AV_INPUT_PADDING_SIZE,因为优化的字节流读取 器必须⼀次读取32或者64⽐特的数据
- 不能跟之前的API(例如avcodec_decode_video2)混⽤,否则会返回不可预知的错误
- 备注: 在将包发送给解码器的时候,AVCodecContext必须已经通过avcodec_open2打开
- 参数:
- avctx:解码上下⽂
- avpkt:输入AVPakcet.通常情况下,输⼊数据是⼀个单⼀的视频帧或者⼏个完整的音频帧。调用者保留包的原有属性,解码器不会修改包的内容。解码器可能创建对包的引用。 如果包没有引用计数将拷⻉⼀份。跟以往的API不⼀样,输⼊的包的数据将被完全地消耗, 如果包含有多个帧,要求多次调⽤avcodec_recvive_frame,直到 avcodec_recvive_frame返回VERROR(EAGAIN)或AVERROR_EOF。输入参数可以为 NULL,或者AVPacket的data域设置为NULL或者size域设置为0,表示将刷新所有的包, 意味着数据流已经结束了。第⼀次发送刷新会总会成功,第⼆次发送刷新包是没有必要的,并且返回AVERROR_EOF,如果×××缓存了⼀些帧,返回⼀个刷新包,将会返回所有的 解码包
- 返回值:
- 0: 表示成功
- AVERROR(EAGAIN):当前状态不接受输⼊,用户必须先使⽤avcodec_receive_frame() 读 取数据帧;
- AVERROR_EOF:解码器已刷新,不能再向其发送新包;
- AVERROR(EINVAL):没有打开解码器,或者这是⼀个编码器,或者要求刷新;
- AVERRO(ENOMEN):⽆法将数据包添加到内部队列。
avcodec_receive_frame
- 函数: int avcodec_receive_frame ( AVCodecContext * avctx, AVFrame * frame )
- 作⽤: 从解码器返回已解码的输出数据。
- 参数:
- avctx: 编解码器上下⽂
- frame: 获取使⽤reference-counted机制的audio或者video帧(取决于解码器类型)。请注意,在执⾏其他操作之前,函数内部将始终先调⽤av_frame_unref(frame)。
- 返回值:
- 0: 成功,返回⼀个帧
- AVERROR(EAGAIN): 该状态下没有帧输出,需要使⽤avcodec_send_packet发送新的 packet到解码器
- AVERROR_EOF: 解码器已经被完全刷新,不再有输出帧
- AVERROR(EINVAL): 编解码器没打开 其他<0的值: 具体查看对应的错误码
代码
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <libavutil/frame.h>#include <libavutil/mem.h>#include <libavcodec/avcodec.h>#define AUDIO_INBUF_SIZE 20480#define AUDIO_REFILL_THRESH 4096static char err_buf[128] = {
0};static char* av_get_err(int errnum){
av_strerror(errnum, err_buf, 128); return err_buf;}static void print_sample_format(const AVFrame *frame){
printf("ar-samplerate: %uHz\n", frame->sample_rate); printf("ac-channel: %u\n", frame->channels); printf("f-format: %u\n", frame->format);// 格式需要注意,实际存储到本地文件时已经改成交错模式}static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame, FILE *outfile){
拓展阅读
MP3文件结构解析(超详细)