音视频入门基础:AAC专题(6)——FFmpeg源码中解码ADTS格式的AAC的Header的实现

news/2024/9/23 18:15:50/

=================================================================

音视频入门基础:AAC专题系列文章:

音视频入门基础:AAC专题(1)——AAC官方文档下载

音视频入门基础:AAC专题(2)——使用FFmpeg命令生成AAC裸流文件

音视频入门基础:AAC专题(3)——AAC的ADTS格式简介

音视频入门基础:AAC专题(4)——ADTS格式的AAC裸流实例分析

音视频入门基础:AAC专题(5)——FFmpeg源码中,判断某文件是否为AAC裸流文件的实现

音视频入门基础:AAC专题(6)——FFmpeg源码中解码ADTS格式的AAC的Header的实现

音视频入门基础:AAC专题(7)——FFmpeg源码中计算AAC裸流每个packet的size值的实现

音视频入门基础:AAC专题(8)——FFmpeg源码中计算AAC裸流AVStream的time_base的实现

音视频入门基础:AAC专题(9)——FFmpeg源码中计算AAC裸流每个packet的duration和duration_time的实现

音视频入门基础:AAC专题(10)——FFmpeg源码中计算AAC裸流每个packet的pts、dts、pts_time、dts_time的实现

=================================================================

一、引言

通过FFmpeg命令:

./ffmpeg -i XXX.aac

可以获取到ADTS格式的AAC裸流的音频采样频率、声道数、采样位数(注意对于AAC这个采样位数没有意义)、码率等信息:

在vlc中也可以获取到这些信息(vlc底层也使用了FFmpeg进行解码):

所以FFmpeg和vlc是怎样获取到这些信息的呢?除了Bit depth(采样位数)外的信息都是通过解码ADTS格式的AAC的Header(adts_fixed_header + adts_variable_header)获取的。执行FFmpeg命令:./ffmpeg -i XXX.aac时,FFmpeg源码内部会调用adts_aac_probe函数检测该文件是否为ADTS格式的AAC裸流(具体可以参考:《音视频入门基础:AAC专题(5)——FFmpeg源码中,判断某文件是否为AAC裸流文件的实现》)。然后如果检测出该文件为ADTS格式的AAC裸流,会调用ff_adts_header_parse函数解码ADTS格式的AAC的Header。

二、ff_adts_header_parse函数的声明

ff_adts_header_parse函数声明在FFmpeg源码(本文演示用的FFmpeg源码版本为7.0.1)的头文件libavcodec/adts_header.h中:

/*** Parse the ADTS frame header to the end of the variable header, which is* the first 54 bits.* @param[in]  gbc BitContext containing the first 54 bits of the frame.* @param[out] hdr Pointer to struct where header info is written.* @return Returns 0 on success, -1 if there is a sync word mismatch,* -2 if the version element is invalid, -3 if the sample rate* element is invalid, or -4 if the bit rate element is invalid.*/
int ff_adts_header_parse(GetBitContext *gbc, AACADTSHeaderInfo *hdr);

FFmpeg对媒体文件/流进行解复用时,会调用avformat_open_input函数,通过avformat_open_input函数内部的av_probe_input_format3函数来检测该文件是否为ADTS格式的AAC裸流。如果是,FFmpeg源码会继续执行avformat_find_stream_info函数读取部分packet(数据包)以获取码流信息。在avformat_find_stream_info函数内会调用ff_adts_header_parse函数解码ADTS格式的AAC的Header。

所以ff_adts_header_parse函数的作用就是解码ADTS格式的AAC的Header。

形参gbc:既是输入型参数也是输出型参数。指向已经被初始化的GetBitContext对象。关于GetBitContext结构体可以参考:《FFmpeg中位操作相关的源码:GetBitContext结构体,init_get_bits函数、get_bits1函数和get_bits函数分析》。

形参hdr:输出型参数,指向一个AACADTSHeaderInfo对象。AACADTSHeaderInfo结构体声明在libavcodec/adts_header.h中:

typedef struct AACADTSHeaderInfo {uint32_t sample_rate;uint32_t samples;uint32_t bit_rate;uint8_t  crc_absent;uint8_t  object_type;uint8_t  sampling_index;uint8_t  chan_config;uint8_t  num_aac_frames;uint32_t frame_length;
} AACADTSHeaderInfo;

执行ff_adts_header_parse函数后,形参hdr指向的对象的成员变量会得到从AAC Header中解码出来的信息。

返回值:解码成功返回包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节。解码失败返回一个负数。

三、ff_adts_header_parse函数的定义

ff_adts_header_parse函数定义在libavcodec/adts_header.c中:

int ff_adts_header_parse(GetBitContext *gbc, AACADTSHeaderInfo *hdr)
{int size, rdb, ch, sr;int aot, crc_abs;memset(hdr, 0, sizeof(*hdr));if (get_bits(gbc, 12) != 0xfff)return AAC_AC3_PARSE_ERROR_SYNC;skip_bits1(gbc);             /* id */skip_bits(gbc, 2);           /* layer */crc_abs = get_bits1(gbc);    /* protection_absent */aot     = get_bits(gbc, 2);  /* profile_objecttype */sr      = get_bits(gbc, 4);  /* sample_frequency_index */if (!ff_mpeg4audio_sample_rates[sr])return AAC_AC3_PARSE_ERROR_SAMPLE_RATE;skip_bits1(gbc);             /* private_bit */ch = get_bits(gbc, 3);       /* channel_configuration */skip_bits1(gbc);             /* original/copy */skip_bits1(gbc);             /* home *//* adts_variable_header */skip_bits1(gbc);             /* copyright_identification_bit */skip_bits1(gbc);             /* copyright_identification_start */size = get_bits(gbc, 13);    /* aac_frame_length */if (size < AV_AAC_ADTS_HEADER_SIZE)return AAC_AC3_PARSE_ERROR_FRAME_SIZE;skip_bits(gbc, 11);          /* adts_buffer_fullness */rdb = get_bits(gbc, 2);      /* number_of_raw_data_blocks_in_frame */hdr->object_type    = aot + 1;hdr->chan_config    = ch;hdr->crc_absent     = crc_abs;hdr->num_aac_frames = rdb + 1;hdr->sampling_index = sr;hdr->sample_rate    = ff_mpeg4audio_sample_rates[sr];hdr->samples        = (rdb + 1) * 1024;hdr->bit_rate       = size * 8 * hdr->sample_rate / hdr->samples;hdr->frame_length   = size;return size;
}

四、ff_adts_header_parse函数的内部实现分析

ff_adts_header_parse函数中,首先通过memset让形参hdr指向的对象的成员变量清0:

memset(hdr, 0, sizeof(*hdr));

从《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》可以知道,syncword为嵌入在ADTS流中的一种编码,用于标识ADTS帧的起始位,其占12位,每个位都必须被设置为1,也就是0b111111111111。所以通过下面的if语句来判断是否读取到了syncword,如果没有读取到,返回宏定义AAC_AC3_PARSE_ERROR_SYNC(-0x3030c0a)。关于get_bits函数的用法可以参考:《FFmpeg中位操作相关的源码:GetBitContext结构体,init_get_bits函数、get_bits1函数和get_bits函数分析》:

    if (get_bits(gbc, 12) != 0xfff)return AAC_AC3_PARSE_ERROR_SYNC;

如果读取到了syncword,继续往下执行,跳过adts_fixed_header的ID和layer属性(因为跳过了ID属性,所以FFmpeg根本不会区分到底是MPEG-2还是MPEG-4的AAC),关于skip_bits1和skip_bits函数的用法可以参考:《FFmpeg源码:skip_bits、skip_bits1、show_bits函数分析》:

    skip_bits1(gbc);             /* id */skip_bits(gbc, 2);           /* layer */

读取adts_fixed_header的protection_absent、profile_ObjectType、samplingFrequencyIndex属性:

    crc_abs = get_bits1(gbc);    /* protection_absent */aot     = get_bits(gbc, 2);  /* profile_objecttype */sr      = get_bits(gbc, 4);  /* sample_frequency_index */

全局数组ff_mpeg4audio_sample_rates定义在libavcodec/mpeg4audio_sample_rates.h中:

const int ff_mpeg4audio_sample_rates[16] = {96000, 88200, 64000, 48000, 44100, 32000,24000, 22050, 16000, 12000, 11025, 8000, 7350
};

通过samplingFrequencyIndex属性得到音频采样频率,单位为Hz:

    if (!ff_mpeg4audio_sample_rates[sr])return AAC_AC3_PARSE_ERROR_SAMPLE_RATE;

跳过private_bit属性。读取channel_configuration属性,也就是音频声道数:

    skip_bits1(gbc);             /* private_bit */ch = get_bits(gbc, 3);       /* channel_configuration */

跳过original_copy、home、copyright_identification_bit、copyright_identification_start属性:

    skip_bits1(gbc);             /* original/copy */skip_bits1(gbc);             /* home *//* adts_variable_header */skip_bits1(gbc);             /* copyright_identification_bit */skip_bits1(gbc);             /* copyright_identification_start */

读取aac_frame_length属性,即包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节。宏定义AV_AAC_ADTS_HEADER_SIZE的值为7,判断aac_frame_length属性的值是否小于7。ADTS Header至少占7个字节(当存在CRC校验时,ADTS Header占9字节;不存在CRC校验时,ADTS Header占7字节),所以如果aac_frame_length属性的值小于7,表示ADTS Header格式不正确,返回宏定义AAC_AC3_PARSE_ERROR_FRAME_SIZE(-0x4030c0a):

    size = get_bits(gbc, 13);    /* aac_frame_length */if (size < AV_AAC_ADTS_HEADER_SIZE)return AAC_AC3_PARSE_ERROR_FRAME_SIZE;

跳过adts_buffer_fullness属性,读取number_of_raw_data_blocks_in_frame属性:

    skip_bits(gbc, 11);          /* adts_buffer_fullness */rdb = get_bits(gbc, 2);      /* number_of_raw_data_blocks_in_frame */

将profile_ObjectType属性的值加1赋值给hdr->object_type。所以MPEG版本为MPEG-4时,如果hdr->object_type为1,表示AAC的规格为AAC Main;hdr->object_type为2,表示规格为AAC LC;hdr->object_type为3,表示规格为AAC SSR;hdr->object_type为4,表示规格为AAC LTP:

hdr->object_type    = aot + 1;

将音频声道数赋值给hdr->chan_config:

hdr->chan_config    = ch;

将protection_absent属性的值赋值给hdr->crc_absent。所以hdr->crc_absent为0时,表示CRC校验存在,当hdr->crc_absent为1时,CRC校验不存在:

hdr->crc_absent     = crc_abs;

将number_of_raw_data_blocks_in_frame属性的值加1赋值给hdr->num_aac_frames。所以该ADTS音频帧中有hdr->num_aac_frames个原始数据块:

hdr->num_aac_frames = rdb + 1;

将samplingFrequencyIndex属性的值赋值给hdr->sampling_index。将音频采样频率(单位为Hz)赋值给hdr->sample_rate:

    hdr->sampling_index = sr;hdr->sample_rate    = ff_mpeg4audio_sample_rates[sr];

将该ADTS音频帧中原始数据块的个数乘以1024,得到的结果赋值给hdr->samples。FFmpeg源码内部强制默认AAC(AAC Main、AAC LC、AAC SSR、AAC LTP)的samples是1024。hdr->samples为该ADTS音频帧中采样的次数:

hdr->samples        = (rdb + 1) * 1024;

通过公式得到该ADTS音频帧的码率,单位为bits/s,赋值给hdr->bit_rate:

hdr->bit_rate       = size * 8 * hdr->sample_rate / hdr->samples;

将整个ADTS音频帧的长度(包含ADTS Header、错误校验和AAC原始数据块的整个ADTS音频帧的长度,单位为字节)赋值给hdr->frame_length:

hdr->frame_length   = size;

五、FFmpeg源码对AAC裸流解封装过程中,对Bit depth的处理

从《音视频入门基础:AAC专题(3)——AAC的ADTS格式简介》中可以知道,Bit depth只对PCM数字信号有意义。非PCM格式,如AAC这种有损压缩格式,Bit depth是没有意义的。

FFmpeg源码对AAC裸流解封装过程中会调用avformat_find_stream_info函数,而avformat_find_stream_info函数中会调用aac_decode_init函数对AAC解码器进行初始化。在aac_decode_init函数内部会强制让AAC的采样格式赋值为AV_SAMPLE_FMT_FLTP。所以不管是什么规格的AAC,其采样格式都会被强制赋值为AV_SAMPLE_FMT_FLTP:

static av_cold int aac_decode_init(AVCodecContext *avctx)
{
//...avctx->sample_fmt = AV_SAMPLE_FMT_FLTP;
//...
}

所以使用FFmpeg命令:ffmpeg -i XXX.aac获取AAC裸流信息时,显示的这个fltp没有任何意义:

六、FFmpeg源码对AAC裸流解封装过程中,对samples的处理

samples为一帧音频数据中采样的次数。FFmpeg源码对AAC裸流解封装过程中会调用avformat_find_stream_info函数,而avformat_find_stream_info函数中会调用parse_adts_frame_header函数解码AAC Header。parse_adts_frame_header函数内部会强制让ac->oc[1].m4ac.frame_length_short被赋值为0:

static int parse_adts_frame_header(AACDecContext *ac, GetBitContext *gb)
{
//...ac->oc[1].m4ac.frame_length_short = 0;
//...
}

由于ac->oc[1].m4ac.frame_length_short为0,所以aac_decode_frame_int函数中,局部变量samples的值一定为1024。可以看到不管是什么规格的AAC(AAC Main、AAC LC、AAC SSR、AAC LTP),FFmpeg源码内部会强制默认其samples是1024。所以FFmpeg不支持samples为960的AAC,只支持samples是1024的AAC:

static int aac_decode_frame_int(AVCodecContext *avctx, AVFrame *frame,int *got_frame_ptr, GetBitContext *gb,const AVPacket *avpkt)
{//...samples = ac->oc[1].m4ac.frame_length_short ? 960 : 1024;//...
}


http://www.ppmy.cn/news/1529439.html

相关文章

Lingo求解器基本语法

Lingo是一款用于线性规划和整数规划的数学建模和求解软件&#xff0c;被广泛应用于运筹学、生产优化、供应链管理等领域。今天与大家一起来熟悉一下它的基本语法 Lingo基本语法 1、定义目标函数为MIN&#xff0c;MAX. 2、以一个分号“&#xff1b;”结尾。除SETS,ENDSETS,D…

数据结构——“二叉搜索树”

二叉搜索树是一个很重要的数据结构&#xff0c;它的特殊结构可以在很短的时间复杂度找到我们想要的数据。最坏情况下的时间复杂度是O(n)&#xff0c;最好是O(logn)。接下来看一看它的接口函数的实现。 为了使用方便&#xff0c;这里采用模版的方式&#xff1a; 一、节点 temp…

【监控】【Nginx】使用 Docker 部署 Prometheus + Grafana 监控 Nginx

在现代应用程序中&#xff0c;监控是确保服务高可用性和性能的关键。本文将详细介绍如何使用 Docker 部署 Prometheus 和 Grafana&#xff0c;以监控 Nginx。我们将分步骤讲解每个环节&#xff0c;以确保你能够顺利完成整个过程。 准备工作 在开始之前&#xff0c;请确保你的…

Redis详细解析

Redis 什么是Redis?关系型与非关系型数据库Redis可以做什么Redis入门安装在Windows系统上安装在Linux系统上安装 Redis在Linux系统上启动运行如何设置redis-server后台运行与关闭如何设置redis客户端登录时需要验证密码**设置允许远程连接redis服务**Redis数据类型Redis常用命…

帝可得项目总结

业务需求 在区域列表查询中&#xff0c;需要显示每个区域的点位数 (1)同步存储:在区域表中有点位数的字段&#xff08;冗余字段6.&#xff09;&#xff0c;当点位发生变化时&#xff0c;同步区域表中的点位数。 优点:由于是单表查询操作&#xff0c;查询列表效率最高。 缺点…

RHEL7(RedHat红帽)软件安装教程

目录 1、下载RHEL7镜像 2、安装RedHat7 注&#xff1a;如果以下教程不想看&#xff0c;可以远程控制安装V:OYH-Cx330 【风险告知】 本人及本篇博文不为任何人及任何行为的任何风险承担责任&#xff0c;图解仅供参考&#xff0c;请悉知&#xff01;本次安装图解是在一个全新的演…

【人工智能学习之卷积神经网络发展简述】

【人工智能学习之卷积神经网络发展简述】 早期探索&#xff08;1960s-1980s&#xff09;初步发展&#xff08;1990s-2000s&#xff09;快速增长&#xff08;2010s&#xff09;当前进展&#xff08;2010s末-2020s&#xff09;未来趋势总结 卷积神经网络&#xff08;Convolutiona…

[论文笔记]MRRNET

这是一篇河大的论文 感觉跟SANET很像 摘要 摘要&#xff1a;随着物联网&#xff08;IoT&#xff09;的大规模部署&#xff0c;道路场景中实时感知和环境理解的需求变得越来越迫切。 同时&#xff0c;语义分割作为像素级场景解析得到了广泛的研究。 然而&#xff0c;资源有限…