CarPlay Wireless 使用fdk

您所在的位置:网站首页 aac解码协议 CarPlay Wireless 使用fdk

CarPlay Wireless 使用fdk

2023-12-19 04:11| 来源: 网络整理| 查看: 265

CarPlay在Wireless的模式下,音频数据传输不再采用Wired模式下的LPCM格式,而是压缩编码格式。多媒体音频(Main High Audio)采用Raw AAC-LC编码格式,其他类型音频(Main Audio & Alt Audio)可选Raw AAC-ELD或OPUS编码格式。而且由于CarPlay运行时会同时出现输出多路音频流的情况,所以需要支持多解码器实例同时工作。

CarPlay over USB uses LPCM for audio. CarPlay over wireless uses raw AAC-LC for high latency audio (Main High Audio) and either OPUS or raw AAC-ELD for low latency audio (Main Audio except “media” and Alt Audio). Accessories supporting CarPlay over wireless must support multiple decode instances and concurrent decode/encode instances.

因此,考虑采用软件解码的方式来支持CarPlay Wireless的音频解码功能。常见的AAC软解码库有faad2和fdk_aac。 faad2库仅能支持包含AAC-LC的少数profile解码,而fdk_aac库是ffmpeg项目中推荐采用的高精度AAC编解码库,可以支持的profile更多,所以打算使用fdk_aac库来实现AAC-LC和AAC-ELD解码。

FAAD2的主页 GitHub上fdk-aac的项目主页 fdk-aac的介绍界面 fdk_aac源码包下载

fdk-aac解码库的API使用,可以参考其自带的文档./fdk-aac-2.0.0/documentation/aacDecoder.pdf 大致的调用流程就是:

aacDecoder_Open(); aacDecoder_ConfigRaw(); loop{ aacDecoder_Fill(); aacDecoder_DecodeFrame(); } aacDecoder_Close();

这里记录一些自己解码过程中遇到的2个主要问题:

1. 获取音频配置信息AudioSpecificConfig

AudioSpecificConfig 定义于文档 ISO/IEC 14496-3中。如下: 在这里插入图片描述 AudioSpecificConfig包含流的配置信息,对于解码处理和解析Raw数据流是必须的。(其实就类似于咱们解码H.264视频流时需要提供的SPS和PPS信息一样)

从aacDecoder.pdf中文档可以看到相关说明,在调用解码API aacDecoder_DecodeFrame()之前需要进行配置: 在这里插入图片描述

这里说没有外带ASC或者SMC数据的话,可以不必使用aacDecoder_ConfigRaw()进行设置,aacDecoder_DecodeFrame()执行中会自己配置。但其实如果是Raw Data的话,是必须要设置的,否则解码不成功。这个看函数说明可以知道,如下: 在这里插入图片描述

看说明的话,我目前处理的AAC Raw Data需要提供的ASC格式的信息。但在CarPlay Wireless中手机端似乎无法直接获取到AAC Raw Data对应的信息数据块。网上找了一些ASC的格式相关的说明,如下: 在这里插入图片描述 标准文档里有整体的语法定义,但是不完整,一些新的Audio Object Type(数值超过5个bits的)没有加入进来。AAC-LC需要对应的GASpecificConfig也没在这里定义。

audioObjectType,samplingFrequencyIndex,channelConfiguration这3个字段的完整的配置可以参考这里: Audio_Specific_Config 后续的部分字段可以参考这里: Understanding_AAC 有个可以参考的GASpecificConfig信息设置: Microsoft aac-decoder

我没有在网上找到ASC完整的定义,所以自己构建ASC数据的话,可能还是有一些问题。(AAC-LC的ASC相对比较简单,GASpecificConfig中3个bits填充0即可,测试可以正常解码)

另外,有个曲线救国的办法来解决ASC的问题,先使用包含fdk_aac编解码库的ffmpeg工具转码一个指定AAC格式的文件,然后来用ffmpeg工具把音频信息中的extradata给dump出来。从ffmpeg源码中调用fdk_aac的地方可以得知,AVCodecContext结构中的extradata就是aacDecoder_ConfigRaw()需要的数据,如下:

static av_cold int fdk_aac_decode_init(AVCodecContext* avctx) { FDKAACDecContext* s = avctx->priv_data; AAC_DECODER_ERROR err; s->handle = aacDecoder_Open(avctx->extradata_size ? TT_MP4_RAW : TT_MP4_ADTS, 1); if(!s->handle) { av_log(avctx, AV_LOG_ERROR, "Error opening decoder\n"); return AVERROR_UNKNOWN; } if(avctx->extradata_size) { if((err = aacDecoder_ConfigRaw(s->handle, &avctx->extradata, &avctx->extradata_size)) != AAC_DEC_OK) { av_log(avctx, AV_LOG_ERROR, "Unable to set extradata\n"); return AVERROR_INVALIDDATA; } } ... }

我在进行解码功能验证前,在CarPlay Wireless代码接收RTP数据的位置分别dump出了 AAC-LC/44KHZ/STEREO 和 AAC-ELD/16KHZ/MONO 的数据到文件。比如想获取 AAC-ELD/16KHZ/MONO 这个格式的ASC可以这样:

lzy@~/GitHub/ffmpeg$ ./ffmpeg -i 1.mp3 -acodec libfdk_aac -ar 16000 -ac 1 -profile:a aac_eld aac_eld_16k_ch1.m4a lzy@~/GitHub/ffmpeg$ ./ffprobe -show_data -show_streams aac_eld_16k_ch1.m4a ##省略了部分输出 [STREAM] index=0 codec_name=aac codec_long_name=AAC (Advanced Audio Coding) profile=ELD codec_type=audio codec_time_base=1/16000 codec_tag_string=mp4a nb_frames=9014 extradata= 00000000: f8f0 2000 .. . [/STREAM]

可以看到extradata数据为: 0xF8 0xF0 0x20 0x00。至此,AudioSpecificConfig的问题算是解决了。

2. 解码函数调用不成功问题

正确设置ASC之后,按照文档说明,循环读取dump出来的AAC文件内容进行解码,但是aacDecoder_DecodeFrame()会直接返回AAC_DEC_UNKNOWN错误。因为aacDecoder_ConfigRaw()设置完ASC之后,使用aacDecoder_GetStreamInfo()获取信息可以看到参数解析的都是正确的。最后还是看文档发现问题所在: 在这里插入图片描述 文档里有说明,如果Raw Data格式一次只能装载一帧数据去解码。因为我在使用fdk-aac解码之前,使用FAAD2的API已经成功完成了对dump出来的AAC-LC数据的解码。而在使用FAAD2时,数据都是整块丢进解码器的,所以这里使用fdk-aac时,也想当然了。(使用开源库还是需要仔细阅读以下API文档,很重要!) 怎么划分每一帧AAC数据呢,对于AAC-lC的Raw Data格式,有个工具可以比较直观的看出来(不支持AAC-LED),可以用雷神的这个AAC解析器: 视音频编解码学习工程:AAC格式分析器 我自己dump出来AAC-LC数据解析后是这样,可以看到每一帧的Size: 在这里插入图片描述 当然这个工具只能用于辅助分析。经过比较Size数值可以发现,这里的每一帧大小和CarPlay Wireless模式下,每次音频送入解码器之前的Size是一致的。(CarPlay的RTP payload数据还需要经过一次Decrypt才是Raw ACC Data) 这样的话,其实只需要按照Decrypt之后的数据块大小就可以使用fdk-aac解码了。为了最小化我的解码功能测试,我又按照frame块大小重新dump了数据进行测试(每个frame一个单独的文件)。 解码的整理流程如下:

int main() { AAC_DECODER_ERROR err = AAC_DEC_OK; HANDLE_AACDECODER decoder = aacDecoder_Open(TT_MP4_RAW, 1); assert(decoder); // 设置ASC信息 if (g_decode_aac_profile == AOT_ER_AAC_ELD) { UCHAR conf[] = {0xF8, 0xF0, 0x20, 0x00}; //AAL-ELD 16000kHz MONO UCHAR* conf_array[1] = { conf }; UINT length = 4; err = aacDecoder_ConfigRaw(decoder, conf_array, &length); assert(!err); } else if (g_decode_aac_profile == AOT_AAC_LC) { UCHAR conf[] = {0x12, 0x10}; //AAL-LC 44100kHz STEREO UCHAR* conf_array[1] = { conf }; UINT length = 2; err = aacDecoder_ConfigRaw(decoder, conf_array, &length); assert(!err); } // 获取信息 CStreamInfo* info = aacDecoder_GetStreamInfo(decoder); assert(info); // 构建AAC Raw Data Buffer List prepare_aac_raw_buf_list(); // pcm输出buffer的大小可以参考CStreamInfo中frameSize的定义 // typedef struct { // ... // INT frameSize; /*!< The frame size of the decoded PCM audio signal. \n // Typically this is: \n // 1024 or 960 for AAC-LC \n // 2048 or 1920 for HE-AAC (v2) \n // 512 or 480 for AAC-LD and AAC-ELD \n // 768, 1024, 2048 or 4096 for USAC */ //... //}CStreamInfo int max_frame_size; if (g_decode_aac_profile == AOT_ER_AAC_ELD) { max_frame_size = 512; } else if (g_decode_aac_profile == AOT_AAC_LC) { max_frame_size = 1024; } int pcm_buffer_size = max_frame_size * MAX_CHANNELS; INT_PCM* pcm_buffer = (INT_PCM*)malloc(sizeof(INT_PCM) * pcm_buffer_size); assert(pcm_buffer); FILE* output_fp = fopen(PCM_OUTPUT_FILE_NAME, "wb"); assert(output_fp); // 开始解码循环 int cur_raw_buf_index = 0; UINT flags = 0; UINT bytes_valid; do { if (cur_raw_buf_index >= raw_buf_total_cout) { printf("decode end\n"); break; } // 加载本次需要解码的数据 bytes_valid = g_raw_buf_size_list[cur_raw_buf_index]; err = aacDecoder_Fill(decoder, &(g_raw_buf_list[cur_raw_buf_index]), &(g_raw_buf_size_list[cur_raw_buf_index]), &bytes_valid); assert(err == AAC_DEC_OK); // 解码 err = aacDecoder_DecodeFrame(decoder, pcm_buffer, pcm_buffer_size / sizeof(INT_PCM), flags); // 因为这里我们解码的是Raw Data,每次送入一帧后就可以解码完成返回AAC_DEC_OK // 对于非Raw Data 的情况需要针对返回值进行处理,如出错处理,数据不够的处理 assert(err == AAC_DEC_OK); // 通过获取信息,计算实际输出的pcm数据大小 CStreamInfo* info = aacDecoder_GetStreamInfo(decoder); assert(info); int output_pcm_bytes = info->frameSize * info->numChannels * 2; // pcm 数据写入文件 if (output_fp) { size_t ws = fwrite(pcm_buffer, output_pcm_bytes, 1, output_fp); assert(ws > 0); } cur_raw_buf_index++; } while (1); // 释放资源 if (output_fp) { fclose(output_fp); } if (pcm_buffer) { free(pcm_buffer); } release_aac_raw_buf_list(); aacDecoder_Close(decoder); return 0; }

完整代码和测试用的ACC Raw Data放在这里了: https://github.com/lzy831/demo/tree/master/fdk_aac_decode_raw

2019-3-22 补充: 随着CarPlay Wireless的开发,遇到了一个新问题。 ACC-ELD数据每次解码出来的framesize是512或者480,这个从下面framesize的注释可以看出来:

/** * \brief This structure gives information about the currently decoded audio * data. All fields are read-only. */ typedef struct { ... INT frameSize; /*!< The frame size of the decoded PCM audio signal. \n Typically this is: \n 1024 or 960 for AAC-LC \n 2048 or 1920 for HE-AAC (v2) \n 512 or 480 for AAC-LD and AAC-ELD \n 768, 1024, 2048 or 4096 for USAC */ ... INT aacSamplesPerFrame; /*!< Samples per frame for the AAC core (from ASC) divided by a (ELD) downscale factor if present. \n Typically this is (with a downscale factor of 1): \n 1024 or 960 for AAC-LC \n 512 or 480 for AAC-LD and AAC-ELD */ ... } CStreamInfo;

而CarPlay Wireless中手机端发过来的ACC-ELD数据默认是固定按照480来处理的。(RTP包的timestamp值,解码数据存放的缓存都是根据这个值来设置的),所以我需要设置解码器来输出匹配的数据量。 上面代码中aacSamplesPerFrame的注释中可以看出,framesize是从ASC中获取的。但是我实在是找不到ASC对应的文档了。只能从代码里看:

static TRANSPORTDEC_ERROR EldSpecificConfig_Parse(CSAudioSpecificConfig *asc, HANDLE_FDK_BITSTREAM hBs, CSTpCallBacks *cb) { ... FDKmemclear(esc, sizeof(CSEldSpecificConfig)); esc->m_frameLengthFlag = FDKreadBits(hBs, 1); if (esc->m_frameLengthFlag) { asc->m_samplesPerFrame = 480; } else { asc->m_samplesPerFrame = 512; } ... }

可以看出来AAC-ELD对应的配置是EldSpecificConfig(AAC-LC对应的是GaSpecificConfig),而samplesPerFrame的值是由EldSpecificConfig中的samplesPerFrame这个bit决定的。然后我查了一下代码,这个bit就是紧跟着channelConfiguration这个域后面的一个bit。 进行正确的设置后,问题解决。

另外发现的一个小问题是在aacDecoder_ConfigRaw配置成功ASC后,CStreamInfo.frameSize的值并不会立即更新为正确的framesize,需要在第一次aacDecoder_DecodeFrame解码之后才会更新。所以提前分配输出缓存的话,可以在aacDecoder_ConfigRaw之后参考CStreamInfo.aacSamplesPerFrame的值来进行分配。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3