分析AAC raw data
本文的主要目标是分析说明AAC解码器如何处理RAW AAC数据。通过拆解理解AAC解码器处理raw aac的关键点,通过数据分析和代码阅读,来说明这个细节,某些细微之处尚需深入探索,留待后续更为详尽的阐述。
几种格式介绍
AAC(Advanced Audio Coding
)是一种高效的音频压缩技术,广泛用于音频流媒体和存储。AAC数据可以以几种不同的封装格式存在,每种格式都有其特定的头部信息(Header),用于描述音频流的属性,如采样率、比特率和通道配置等。下面将详细解释AAC中常见的四种头部格式:LOAS, ADIF, ADTS, 和 LATM。
RAWS (Raw AAC Data Stream)
RAWS数据流不包含任何额外的头部信息,它只是连续的AAC帧数据。在这种情况下,解码器必须从外部源获取解码所需的元数据,或者使用特定的同步字(如0xFFF0)来识别和分割音频帧。
LOAS (Lossless Audio Stream)
LOAS是一种无损音频流格式,用于MPEG-4 ALS(Audio Lossless Streaming)标准中。
LOAS头包含了音频流的元数据,允许解码器重建原始音频信号。
LOAS头中包含的信息有助于解码器理解如何正确解码音频数据。然而,由于LOAS关注的是无损音频,它与通常的有损压缩AAC格式的其他变体有所不同。
ADIF (AAC Data Interchange Format)
ADIF格式主要用于文件交换,它不包含头部信息来描述音频流的属性。
ADIF文件的音频参数(如采样率和比特率)需要通过外部手段获取。ADIF文件中,音频数据是连续的,没有分割成帧。
ADIF没有固定的头部信息,音频参数通过外部元数据提供。
ADTS (AAC ADaptive Transport Stream)
ADTS(Audio Data Transport Stream)头部包含了一组固定的参数,这些参数描述了音频流的属性,如采样率、比特率、通道数等。
ADTS将音频数据分割成帧,并在每一帧的开始处插入头部信息,这使得解码器能够在接收到每一帧时立即开始解码,而无需等待整个音频流。
LATM (Low-latency Audio Transport Mechanism)
LATM(Low-overhead MPEG-4 Audio TransportMultiplex 低开销音频传输复用)格式是MPEG-4 AAC制定的一种高效率的码流传输方式,MPEG-2 TS 流也采用LATM。
LATM格式以帧为单位,主要由AudioSpecificConfig(音频特定配置单元)与音频负载组成。
AudioSpecificConfig相当于ADTS header,它包含了音频信息,如采样率,声道数等信息。
这里对LOAS,ADIF,ADTS,LATM不进行展开,这里重点要说的是除了这几种格式之外的aac raw data,就是没有任何header的raw数据,用gstreamer aacparse的SRC pad来看就好理解了,aacparse支持的stream-format
有四种raw,adts,adif,loas:
SRC template: 'src'
Availability: Always
Capabilities:
audio/mpeg
framed: true
mpegversion: { (int)2, (int)4 }
stream-format: { (string)raw, (string)adts, (string)adif, (string)loas }
FFmpeg对AAC的支持
FFmpeg aac demxer
ff_aac_demuxer只支持adts格式:
const AVInputFormat ff_aac_demuxer = {
.name = "aac",
.long_name = NULL_IF_CONFIG_SMALL("raw ADTS AAC (Advanced Audio Coding)"),
.read_probe = adts_aac_probe,
.read_header = adts_aac_read_header,
.read_packet = adts_aac_read_packet,
.flags = AVFMT_GENERIC_INDEX,
.extensions = "aac",
.mime_type = "audio/aac,audio/aacp,audio/x-aac",
.raw_codec_id = AV_CODEC_ID_AAC,
};
FFmpeg aac decoder
从aac_decode_frame的代码看,aac decoder是能支持raw aac解码:
const FFCodec ff_aac_decoder = {
.p.name = "aac",
.p.long_name = NULL_IF_CONFIG_SMALL("AAC (Advanced Audio Coding)"),
.p.type = AVMEDIA_TYPE_AUDIO,
.p.id = AV_CODEC_ID_AAC,
.priv_data_size = sizeof(AACContext),
.init = aac_decode_init,
.close = aac_decode_close,
FF_CODEC_DECODE_CB(aac_decode_frame),
.p.sample_fmts = (const enum AVSampleFormat[]) {
AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE
},
.p.capabilities = AV_CODEC_CAP_CHANNEL_CONF | AV_CODEC_CAP_DR1,
.caps_internal = FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_INIT_CLEANUP,
#if FF_API_OLD_CHANNEL_LAYOUT
.p.channel_layouts = aac_channel_layout,
#endif
.p.ch_layouts = aac_ch_layout,
.flush = flush,
.p.priv_class = &aac_decoder_class,
.p.profiles = NULL_IF_CONFIG_SMALL(ff_aac_profiles),
};
但是实际上通过命令行测试的时候有报错,报错如下,原因是我用的raw aac中包含FF F0
的字节,这个会被当做adts而去做adts parse:
[aac @ 0x556af5d4e370] Format aac detected only with low score of 1, misdetection possible!
[aac @ 0x556af5d4eee0] Assuming an incorrectly encoded 7.1 channel layout instead of a spec-compliant 7.1(wide) layout
, use -strict 1 to decode according to the specification instead.
[aac @ 0x556af5d4eee0] channel element 1.13 is not allocated
[aac @ 0x556af5d4eee0] Error decoding AAC frame header.
[aac @ 0x556af5d4eee0] More than one AAC RDB per ADTS frame is not implemented. Update your FFmpeg version to the newe
st one from Git. If the problem still occurs, it means that your file has a feature which has not been implemented.
[aac @ 0x556af5d4eee0] invalid band type
[aac @ 0x556af5d4eee0] channel element 1.8 is not allocated
[aac @ 0x556af5d4eee0] channel element 2.11 is not allocated
[aac @ 0x556af5d4eee0] Multiple frames in a packet.
[aac @ 0x556af5d4eee0] channel element 3.0 is not allocated
[aac @ 0x556af5d4eee0] channel element 1.6 is not allocated
[aac @ 0x556af5d4eee0] channel element 3.9 is not allocated
[aac @ 0x556af5d4eee0] channel element 2.13 is not allocated
[aac @ 0x556af5d4eee0] channel element 1.4 is not allocated
[aac @ 0x556af5d4eee0] Sample rate index in program config element does not match the sample rate index configured by
the container.
[aac @ 0x556af5d4eee0] channel element 1.10 is not allocated
[aac @ 0x556af5d4eee0] channel element 3.3 is not allocated
关于aac raw data bloack
从ISO_IEC-14996-3文档中找到raw_data_block的说明:
raw_data_block
这部分是google翻译而来
包含 1024 或 960 个样本时间段内的音频数据、相关信息和其他数据的原始数据块。
有七个语法元素,由数据元素 id_syn_ele 标识。
一个 raw_data_block() 中的 audio_channel_element() 必须只有一个采样率。在 raw_data_block() 中,可以出现相同语法元素的多个实例,但必须具有不同的 4 位 element_instance_tag,data_stream_element() 和 fill_element() 除外。
因此,在一个 raw_data_block() 中,任何语法元素的实例可以有 0 到最多 16 个,data_stream_element() 和 fill_element() 除外,这些实例不受此限制。如果出现多个具有相同 element_instance_tag 的 data_stream_element(),则它们是同一数据流的一部分。 fill_element() 没有 element_instance_tag(因为内容不需要后续引用)并且可以出现任意次数。raw_data_block() 的结尾用特殊的 id_syn_ele (TERM) 表示,该 id_syn_ele (TERM) 在 raw_data_block() 中只能出现一次。
single_channel_element()
缩写 SCE。包含单个音频通道编码数据的比特流的语法元素。single_channel_element() 基本上由 individual_channel_stream() 组成。每个原始数据块最多可能有 16 个这样的元素,每个元素都必须具有唯一的 element_instance_tag。
channel_pair_element()
缩写 CPE。包含一对通道数据的比特流有效负载的语法元素。channel_pair_element() 由两个 individual_channel_streams 和附加联合通道编码信息组成。两个通道可以共享公共边信息。 channel_pair_element() 的限制与单通道元素的 element_instance_tag 和出现次数相同。
coupling_channel_element()
缩写 CCE。包含耦合通道音频数据的语法元素。耦合通道表示一个块的多通道强度信息,或用于多语言编程的对话信息。coupling_channel_element() 的数量和实例标签的规则与 single_channel_element() 相同。
lfe_channel_element()
缩写 LFE。包含低采样频率增强通道的语法元素。lfe_channel_element() 的数量和实例标签的规则与single_channel_element() 相同。
program_config_element()
缩写 PCE。包含程序配置数据的语法元素。program_config_element() 的数量和元素实例标签的规则与 single_channel_element() 相同。 PCE 必须位于 raw_data_block() 中的所有其他语法元素之前。
fill_element()
缩写 FIL。包含填充数据的语法元素。填充元素的数量可能任意,它们可以以任何顺序出现在原始数据块中。
data_stream_element()
缩写 DSE。包含数据的语法元素。同样,有 16 个 element_instance_tags。但是,对于任何一个实例标签的 data_stream_element() 的数量没有限制,因为单个数据流可以跨越具有相同实例标签的多个 data_stream_element()。
紧接着后面还有一个解码的举例:
faad2的解码
faad中的header type:
/* header types */
#define RAW 0
#define ADIF 1
#define ADTS 2
#define LATM 3
faad2中,RAW/ADIF/ADTS存在于aac文件中,LATM存在于mp4文件中,可以在文件头中先找到‘ftyp’。
faad2解码的代码:
/** frontend/main.c */
static int faad_main(int argc, char *argv[])
/* frontend/main.c: parse aac header */
static int decodeAACfile(char *aacfile, char *sndfile, char *adts_fn, int to_stdout,
int def_srate, int object_type, int outputFormat, int fileType,
int downMatrix, int infoOnly, int adts_out, int old_format,
float *song_length)
/* libfaad/decoder.c: decode frame */
void* NeAACDecDecode(NeAACDecHandle hpDecoder,
NeAACDecFrameInfo *hInfo,
unsigned char *buffer,
unsigned long buffer_size)
/* libfaad/syntax.c: raw_data_block */
void raw_data_block(NeAACDecStruct *hDecoder, NeAACDecFrameInfo *hInfo,
bitfile *ld, program_config *pce, drc_info *drc)
通过试验,faad2支持raw,adif,adts的解码。
gstreamer aacparse代码
adif header解析
/* Check if an ADIF header is present */
if ((buffer[0] == 'A') && (buffer[1] == 'D') &&
(buffer[2] == 'I') && (buffer[3] == 'F'))
{
hDecoder->adif_header_present = 1;
adts header解析
/* Check if an ADTS header is present */
} else if (faad_showbits(&ld, 12) == 0xfff) {
hDecoder->adts_header_present = 1;
adts.old_format = hDecoder->config.useOldADTSFormat;
adts_frame(&adts, &ld);
hDecoder->sf_index = adts.sf_index;
hDecoder->object_type = adts.profile + 1;
*samplerate = get_sample_rate(hDecoder->sf_index);
*channels = (adts.channel_configuration > 6) ?
2 : adts.channel_configuration;
}
生成aac格式
gstreamer生成aac raw格式
gst-launch-1.0 filesrc location=~/audio/cunzai.aac ! aacparse \
! audio/mpeg,mpegversion=4,stream-format=raw ! filesink location=./cunzai-aacraw.aac
这个命令生成的aac虽然开头缺少adts的0xFFF1,但是文件中间是有0xFFF1的,两个0xFFF1之间的size看着也很大,下面的faad解析log来看是raw格式,所以raw格式为什么也有0xFFF1,需要后续再研究一下。
faad直接解码aac raw
$ ./frontend/faad cunzai-adif.raw
*********** Ahead Software MPEG-4 AAC Decoder V2.10.0 ******************
Build: Aug 13 2024
Copyright 2002-2004: Ahead Software AG
http://www.audiocoding.com
bug tracking: https://sourceforge.net/p/faac/bugs/
Floating point version
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
**************************************************************************
cunzai-adif.raw file info:
RAW
---------------------
| Config: 2 Ch |
---------------------
| Ch | Position |
---------------------
| 00 | Left front |
| 01 | Right front |
---------------------
Decoding cunzai-adif.raw took: 0.58 sec. 0.00x real-time.
faad解析raw aac生成带adts头的aac文件,然后用ffplay播放:
./frontend/faad ~/audio/cunzai-aacraw.aac -a cunzai-adts.aac
ffplay cunzai-adts.aac
aacparse支持三种格式
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
audio/mpeg
mpegversion: { (int)2, (int)4 }
SRC template: 'src'
Availability: Always
Capabilities:
audio/mpeg
framed: true
mpegversion: { (int)2, (int)4 }
stream-format: { (string)raw, (string)adts, (string)adif, (string)loas }
但是不能用stream-format=adif指定adif格式,下面这个命令会报协商失败的错误:
gst-launch-1.0 filesrc location=~/audio/cunzai.aac ! aacparse \
! audio/mpeg,mpegversion=4,stream-format=adif ! filesink location=./cunzai-adif.adif
解析aac文件(带adts头),生成adif格式aac,实际上这个命令运行不了,aacparse不支持adif。
从aacparse的srcpad代码来看,其
output_header_type
只支持DSPAAC_HEADER_NONE
和DSPAAC_HEADER_ADTS
,而DSPAAC_HEADER_NONE就是raw格式:allowed = gst_pad_get_allowed_caps (GST_BASE_PARSE (aacparse)->srcpad); if (allowed && !gst_caps_can_intersect (src_caps, allowed)) { GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad, "Caps can not intersect"); if (aacparse->header_type == DSPAAC_HEADER_ADTS) { GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad, "Input is ADTS, trying raw"); gst_caps_set_simple (src_caps, "stream-format", G_TYPE_STRING, "raw", NULL); if (gst_caps_can_intersect (src_caps, allowed)) { GstBuffer *codec_data_buffer; GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad, "Caps can intersect, we will drop the ADTS layer"); aacparse->output_header_type = DSPAAC_HEADER_NONE; /* The codec_data data is according to AudioSpecificConfig, ISO/IEC 14496-3, 1.6.2.1 */ codec_data_buffer = gst_buffer_new_and_alloc (2); gst_buffer_fill (codec_data_buffer, 0, codec_data, 2); gst_caps_set_simple (src_caps, "codec_data", GST_TYPE_BUFFER, codec_data_buffer, NULL); } } else if (aacparse->header_type == DSPAAC_HEADER_NONE) { GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad, "Input is raw, trying ADTS"); gst_caps_set_simple (src_caps, "stream-format", G_TYPE_STRING, "adts", NULL); if (gst_caps_can_intersect (src_caps, allowed)) { GST_DEBUG_OBJECT (GST_BASE_PARSE (aacparse)->srcpad, "Caps can intersect, we will prepend ADTS headers"); aacparse->output_header_type = DSPAAC_HEADER_ADTS; } }
所以把adif换成raw是可以的:
gst-launch-1.0 filesrc location=~/audio/cunzai.aac ! aacparse \ ! audio/mpeg,mpegversion=4,stream-format=raw ! filesink location=./cunzai-aacraw.aac
faad2解码raw aac
./frontend/faad ~/audio/cunzai-aacraw.aac
修改faad2代码:
$ git diff .
diff --git a/libfaad/syntax.c b/libfaad/syntax.c
index a0ea100..eb953bb 100644
--- a/libfaad/syntax.c
+++ b/libfaad/syntax.c
@@ -624,6 +624,11 @@ void raw_data_block(NeAACDecStruct *hDecoder, NeAACDecFrameInfo *hInfo,
faad_byte_align(ld);
}
+ if (id_syn_ele == ID_END)
+ {
+ printf("id_syn_ele == ID_END bytesconsumed %d\n", ld->buffer_size - ld->bytes_left);
+ }
+
return;
}
通过增加这个打印,可以看到raw_data_block,会在id_syn_ele == ID_END
的时候结束,ID_END意味着当前一帧的结束。从输出的log中grep ID_END也能判断处理的帧数。
gstreamer生成adif aac
因为前面通过gstreamer生成adif格式失败,所以换个思路,通过fdkaacenc生成:
gst-launch-1.0 audiotestsrc ! fdkaacenc \
! audio/mpeg,stream-format=adif ! filesink location=test-aac.adif
这个命令生成ADIF文件成功,文件开头是ADIF,faad可以直接解码:
$ faad test-aac.adif
*********** Ahead Software MPEG-4 AAC Decoder V2.9.1 ******************
Build: 2023-08-22
Copyright 2002-2004: Ahead Software AG
http://www.audiocoding.com
bug tracking: https://sourceforge.net/p/faac/bugs/
Floating point version
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
**************************************************************************
test-aac.adif file info:
ADIF, 393.291 sec, 56 kbps, 44100 Hz
---------------------
| Config: 2 Ch |
---------------------
| Ch | Position |
---------------------
| 00 | Left front |
| 01 | Right front |
---------------------
Decoding test-aac.adif took: 0.35 sec. 1124.36x real-time.
gstreamer avdec_aac解码adif会报错,虽然avdec_aac的sink pad来看也支持adif:
gst-play-1.0 test-aac.adif \
--audiosink="audio/mpeg,stream-format=adif ! aacparse ! avdec_aac ! autoaudiosink"
fdkaacdec可以:
gst-play-1.0 test-aac.adif \
--audiosink="audio/mpeg,stream-format=adif ! aacparse ! fdkaacdec ! autoaudiosink"
因为fdkaacdec的pad如下,支持adif:
Pad Templates:
SINK template: 'sink'
Availability: Always
Capabilities:
audio/mpeg
mpegversion: { (int)2, (int)4 }
stream-format: { (string)adts, (string)adif, (string)raw }
channels: [ 1, 8 ]
faad不行,faad不支持adif输入,但是支持raw:
$ gst-launch-1.0 filesrc location=./test-aac.adif \
! audio/mpeg,stream-format=adif ! aacparse ! faad ! autoaudiosink
Setting pipeline to PAUSED ...
Pipeline is PREROLLING ...
ERROR: from element /GstPipeline:pipeline0/GstAacParse:aacparse0: Internal data stream error.
faad的pad:
SINK template: 'sink'
Availability: Always
Capabilities:
audio/mpeg
mpegversion: 2
audio/mpeg
mpegversion: 4
stream-format: { (string)raw, (string)adts }
附:faad的编译调试
下载代码
git clone https://github.com/knik0/faad2.git
编译
cd faad2
mkdir build
cd build
../configure --disable-shared
make -j8
调试
编译完后在frontend目录下生成了faad,如果没有--disable-shared
选项,frontend/.libs下会生成faad,但是还要依赖libfaad.so
$ file frontend/faad
frontend/faad: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, \
interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b3a7cbde55448d6ba74f4922ec23061d20b29eff, \
for GNU/Linux 3.2.0, with debug_info, not stripped
gdb或者vscode就可以直接启动faad进行调试。