[关闭]
@ltlovezh 2020-08-03T13:57:43.000000Z 字数 10746 阅读 120

音视频元数据

AAC AVC HEVC


AAC

AAC元数据一般包含Profile、SampleRate和Channel,这些信息在封装容器中有两种存储方式:

AudioSpecificConfig

AudioSpecificConfig一般存储在封装容器的独立模块,作为AAC全局元数据,例如:Flv的第一个音频Tag包含AudioSpecificConfig;Mp4的ESDS Box也包含AudioSpecificConfig,其格式如下所示:

  1. // 如果5bit等于31的话,就再读取6bit+32就是Audio Object Type
  2. 5 bits: object type
  3. if (object type == 31)
  4. 6 bits + 32: object type
  5. // 4bit是采样率数组的索引,若等于15,则再读取24bit就直接是采样率
  6. 4 bits: frequency index
  7. if (frequency index == 15)
  8. 24 bits: frequency
  9. // 读取4bit是通道数数组的索引
  10. 4 bits: channel configuration
  11. var bits: AOT Specific Config

audio object type是profile的索引,frequency index是采样率的索引,channel configuration是声道数的索引,具体取值可以参考:MPEG-4 Audio

最简单的AudioSpecificConfig只要两个字节,例如:Profile: LC AAC,Sample Rate: 44100,Channel Count: 2,参照索引表,audio object type为2,frequency index为4,channel configuration为2,那么最终的AudioSpecificConfig就是0x1210,10进制就是18, 16。

ADTS

ADTS是固定字节数的AAC头部(protection_absent为1,占7字节;protection_absent为0,占9字节),里面同样包含了profile索引、采样率索引和声道数索引以及一个ADTS帧的长度,表示ADTS头和ES AAC的总长度。ADTS头部存储在每个AAC音频帧前面。mpegts中的AAC流就是这种格式,每个Bit的含义可以参考:ADTS
或者AAC ADTS格式分析因为ADTS的前12 Bit固定为1,所以可利用这一点快速判断AAC是否包含ADTS头部
image_1e40u3rg71kgv2o9rdcsau1jnt9.png-10.9kB

不管是AudioSpecificConfig,还是ADTS,都包含了audio object type、sample rate index(frequency index)和channel configuration。

从FLV和Mp4种demux出的AAC,直接保存在本地是无法播放的(ES AAC),因为缺少7字节的ADTS头部。需要在ES AAC帧前添加7字节的ADTS头部,才能正常解码播放。生成7字节的ADTS头部,可以参考libavformat/adtsenc.c中的adts_write_frame_header函数。

MediaCodec编码输出的也是ES AAC,若想保存成文件直接播放,也需要添加ADTS头部。

当通过FFmpeg Mux时,首先需要把AudioSpecificConfig存储在AVStream->codecpar->extradata,然后通过avformat_write_header写入到封装容器中,例如:Mp4的ESDS BOX,FLv的第一个音频TAG。可参考libavformat/movenc.c中的mov_write_esds_tag函数:track->vos_data就是AVStream->codecpar->extradata数据。

通过写入一个假的asc,验证了extradata会被直接写入到esds box。

AudioSpecificConfig与ADTS互相转换

两种AAC元数据格式之间的转换,需要依赖AAC元数据作为中介。整体转换行为:ADTS Buffer <-> AAC元数据 <-> AudioSpecificConfig。

根据AudioSpecificConfig生成ADTS

libavformat/adtsenc.c是ADTS AAC的AVOutputFormat实现,当mux成AAC时,负责添加额外的ADTS头部,这样才能保证生成的裸AAC文件可以解码播放。

adtsenc.c的主要逻辑是解析AVStream->codecpar->extradata表示的AudioSpecificConfig结构,获得AAC Profile、采样率和声道数,然后基于这些AAC元数据,为每个音频帧添加ADTS头部。
adts_decode_extradata函数解析AudioSpecificConfig(extradata),得到AAC元数据;adts_write_frame_header函数根据AAC元数据,生成ADTS Buffer。

adts AVOutputFormat: AudioSpecificConfig -> AAC元数据 -> ADTS

根据ADTS生成AudioSpecificConfig

libavcodec/aac_adtstoasc_bsf.c是一个bsf filter,可以根据AVPacket中的ADTS头部,生成AudioSpecificConfig,并且保存在AVBSFContext->par_out->extradata中,同时把AVPacket中的ADTS头部删掉。使用者需要把AVBSFContext->par_out作为新的AVCodecParameters,去初始化AVCodecContext(decode)或者更新AVStream->codecpar(mux)。

aac_adtstoasc_filter函数首先通过avpriv_aac_parse_header解析ADTS Buffer,得到AAC元数据,然后根据AAC元数据,生成AudioSpecificConfig,保存在AVBSFContext->par_out->extradata,作为对外的输出。

当把ADTS AAC封装为Mp4或者FLV时,需要使用aac_adtstoasc提取AudioSpecificConfig,删除ADTS头部,因为Mp4和FLV容器都是存储ES AAC,同时通过独立模块存储AudioSpecificConfig。

aac_adtstoasc bsf filter: ADTS Buffer -> AAC元数据 -> AudioSpecificConfig


当通过FFMpeg Demux时,若封装容器包含了AudioSpecificConfig(FLV和MP4),那么avformat_open_input打开文件后,AVStream->codecpar->extradata就是AudioSpecificConfig了;若封装容器包含的是ADTS AAC,那么AVStream->codecpar->extradata一直是空的。

AAC元数据在封装容器中

MOV、MP4(libavformat/movenc.c)和FLV(libavformat/flvenc.c)封装容器,有独立的模块存储AudioSpecificConfig,比如:Mp4的ESDS Box中包含了AudioSpecificConfig,Flv第一个音频Tag也包含了AudioSpecificConfig。

TS容器(libavformat/mpegtsenc.c)没有单独存储AudioSpecificConfig,而是在每个音频帧前添加了ADTS头部,这样可以保证每个TS文件可以独立播放。

裸的AAC容器(libavformat/adtsenc.c),也是在每个音频帧前包含ADTS,这样可以保证裸AAC文件可以直接解码播放。

当需要在不同封装容器之间转封装时,就涉及到AudioSpecificConfig与ADTS的互相转换了。例如:从Mp4容器拆分出aac时,就会通过libavformat/adtsenc.c生成ADTS AAC;从TS转封装为Mp4时,需要通过aac_adtstoasc bsf filter提取AudioSpecificConfig,并删除AVPacket的ADTS头部。

AAC与MediaCodec

MediaCodec编码AAC

MediaCodec编码AAC时,会分别输出AudioSpecificConfig和ES AAC,所以存储为AAC文件时,需要添加ADTS头部,才能解码播放。

获取AudioSpecificConfig有两种方式,测试下来两种方式获取的数据是一致的:

  1. dequeueOutputBuffer拿到的outputBufferIndex等于MediaCodec.INFO_OUTPUT_FORMAT_CHANGED时,先拿到新的MediaFormat,然后通过MediaFormat.getByteBuffer("csd-0")获取AudioSpecificConfig。
  2. dequeueOutputBuffer拿到的outputBufferIndex大于等于0时,若MediaCodec.BufferInfo.flags包含MediaCodec.BUFFER_FLAG_CODEC_CONFIG标志位,那么输出的ByteBuffer就是AudioSpecificConfig。

MediaCodec编码的ES AAC,通过FFmpeg Mux时,需要先把AudioSpecificConfig写入AVStream->codecpar->extradata,然后才能调用avformat_write_header写入容器头信息。当封装容器是Mp4时,AudioSpecificConfig会写入ESDS BOX,ES AAC直接写入Mdat Sample;当容器是TS时,libavformat/mpegtsenc.c会根据AudioSpecificConfig,转换为ADTS AAC(adts AVOutputFormat),然后写入到TS容器。

MediaCodec解码AAC

MediaCodec解码AAC时,AudioSpecificConfig有三种输入方式:

  1. 首先以csd-0为key,把AudioSpecificConfig以csd-0设置给MediaFormat,然后通过MediaFormat配置解码器:MediaCodec.configure(MediaFormat),后续只要输入ES AAC就可以了。
  2. 在MediaCodec.start()之后,输入任何ES AAC裸流之前,首先通过ByteBuffer存储AudioSpecificConfig,结合BUFFER_FLAG_CODEC_CONFIGFlag,发送给解码器,后续只要输入ES AAC就可以了。
  3. 在每个ES AAC帧前添加ADTS头部,生成ADTS AAC,直接送入解码器。

视频

H264的关键信息是SPS和PPS,H265的关键信息是VPS、SPS和PPS。因为H264和H265存在AVCC和Annexb两种格式,所有VPS、SPS和PPS也存在两种存储形式。

H264 AVCDecoderConfigurationRecord

  1. aligned(8) class AVCDecoderConfigurationRecord {
  2. unsigned int(8) configurationVersion = 1;
  3. unsigned int(8) AVCProfileIndication;
  4. unsigned int(8) profile_compatibility;
  5. unsigned int(8) AVCLevelIndication;
  6. bit(6) reserved = 111111b;
  7. // lengthSizeMinusOne + 1表示Nalu Length Size,即一个Nalu长度用几个字节表示,一般是4字节
  8. unsigned int(2) lengthSizeMinusOne;
  9. bit(3) reserved = 111b;
  10. // sps的个数
  11. unsigned int(5) numOfSequenceParameterSets;
  12. for (i=0; i< numOfSequenceParameterSets; i++) {
  13. // 两个字节表示一个sps的长度
  14. unsigned int(16) sequenceParameterSetLength ;
  15. // sps的内容
  16. bit(8*sequenceParameterSetLength) sequenceParameterSetNALUnit;
  17. }
  18. // pps的个数
  19. unsigned int(8) numOfPictureParameterSets;
  20. for (i=0; i< numOfPictureParameterSets; i++) {
  21. // 两个字节表示一个pps的长度
  22. unsigned int(16) pictureParameterSetLength;
  23. // pps的内容
  24. bit(8*pictureParameterSetLength) pictureParameterSetNALUnit;
  25. }
  26. }

即AVStream->codecpar->extradata的内容,主要包含了H264的SPS和PPS数据,解码时会copy到AVCodecContext中,是解码必不可少的关键数据。

H265 HEVCDecoderConfigurationRecord

  1. class HEVCDecoderConfigurationRecord {
  2. unsigned int(8) configurationVersion;
  3. unsigned int(2) general_profile_space;
  4. unsigned int(1) general_tier_flag;
  5. unsigned int(5) general_profile_idc;
  6. unsigned int(32) general_profile_compatibility_flags;
  7. unsigned int(48) general_constraint_indicator_flags;
  8. unsigned int(8) general_level_idc;
  9. bit(4) reserved = 1111b;
  10. unsigned int(12) min_spatial_segmentation_idc;
  11. bit(6) reserved = 111111b;
  12. unsigned int(2) parallelismType;
  13. bit(6) reserved = 111111b;
  14. unsigned int(2) chromaFormat;
  15. bit(5) reserved = 11111b;
  16. unsigned int(3) bitDepthLumaMinus8;
  17. bit(5) reserved = 11111b;
  18. unsigned int(3) bitDepthChromaMinus8;
  19. bit(16) avgFrameRate;
  20. bit(2) constantFrameRate;
  21. bit(3) numTemporalLayers;
  22. bit(1) temporalIdNested;
  23. // lengthSizeMinusOne + 1表示Nalu Length Size,即一个Nalu长度用几个字节表示,一般是4字节
  24. unsigned int(2) lengthSizeMinusOne;
  25. // 数组长度
  26. unsigned int(8) numOfArrays;
  27. for (j=0; j < numOfArrays; j++) {
  28. bit(1) array_completeness;
  29. unsigned int(1) reserved = 0;
  30. // nalu的类型
  31. unsigned int(6) NAL_unit_type;
  32. // 上面👆nalu类型的
  33. unsigned int(16) numNalus;
  34. for (i=0; i< numNalus; i++) {
  35. unsigned int(16) nalUnitLength;
  36. bit(8*nalUnitLength) nalUnit;
  37. }
  38. }

即AVStream->codecpar->extradata的内容,主要包含了H265的SPS、PPS和VPS数据,解码时会copy到AVCodecContext中,是解码必不可少的关键数据。

StartCode分割的VPS、SPS和PPS

H264和H265与MediaCodec

MediaCodec编码H264

MediaCodec编码H264时,会单独输出SPS、PPS以及StartCode分割的NALU裸流。
有两种方式可以获取SPS和PPS,测试下来发现两种方式获取的数据是一致的:

  1. dequeueOutputBuffer拿到的outputBufferIndex等于MediaCodec.INFO_OUTPUT_FORMAT_CHANGED时,先拿到新的MediaFormat。然后通过csd-0获取00 00 00 01 SPS,通过csd-1获取00 00 00 01 PPS
  2. dequeueOutputBuffer拿到的outputBufferIndex大于等于0时,若MediaCodec.BufferInfo.flags包含MediaCodec.BUFFER_FLAG_CODEC_CONFIG标志位,那么输出的ByteBuffer就是00 00 00 01 SPS 00 00 00 01 PPS

这两种方式获取的SPS和PPS是一致的,并且都会携带00 00 00 01分隔符。区别是第一种方式的SPS和PPS是分开存储的,第二种方式是一起存储的。第一种方式拼接在一起就等于第二种方式获取的数据。

因为SPS和PPS是单独输出的,所以MediaCodec输出的I帧前面不会再次携带SPS和PPS信息。

有种说法是当上述两种方式都取不到SPS和PPS时,I帧前面就会携带SPS和PPS,这点还没遇到过,有待确认。

下面是MediaCodec编码H264时,输出的SPS、PPS和I帧前面几字节的数据:

  1. // SPS,起始4字节是分隔符
  2. sps length: 22, content: 0 , 0 , 0 , 1 , 103 , 100 , 0 , 32 , -84 , -76 , 5 , -96 , 89 , -46 , -112 , 80 , 96 , 96 , 109 , 10 , 19 , 80
  3. // PPS,起始4字节是分隔符
  4. pps length: 9, content: 0 , 0 , 0 , 1 , 104 , -18 , 6 , -30 , -64
  5. // I 帧,起始4字节是分隔符
  6. 0, 0, 0, 1, 101, -72, 64, -9, -5, -12, ......

可见,MediaCodec编码输出的H264裸流是以StartCode分割的NALU。

MediaCodec编码的H264,通过FFmpeg Mux时,需要先把SPS和PPS写入AVStream->codecpar->extradata,然后才能调用avformat_write_header写入容器头信息。SPS和PPS可以以两种方式写入AVStream->codecpar->extradata

  1. 00 00 00 01 SPS 00 00 00 01 PPS
  2. AVCDecoderConfigurationRecord格式

当封装容器是Mp4和FLV时,SPS和PPS会被重新组织为AVCDecoderConfigurationRecord格式,写入avcC Box(stsd -> avc1 -> avcC)

extradata可以兼容两种方式,但是写入Mp4和Flv时,会被重新组织成AVCDecoderConfigurationRecord格式。

libavformat/avc.c中的ff_isom_write_avcc函数负责把AVStream->par->extradata中的SPS和PPS组织成AVCDecoderConfigurationRecord格式,并写入avcC Box。

ff_isom_write_avcc的主要逻辑是判断AVStream->par->extradata是什么形式的SPS和PPS,若AVStream->par->extradata是以StartCode分割的SPS和PPS,则首先提取SPS和PPS,然后按照 AVCDecoderConfigurationRecord格式写入;否则,则可以直接写入。

MediaCodec编码H265

MediaCodec编码H265时,会单独输出VPS、SPS和PPS以及StartCode分割的NALU裸流。

H265在H264的基础上新增了VPS信息

有两种方式可以获取VPS、SPS和PPS,测试下来发现两种方式获取的数据是一致的:

  1. dequeueOutputBuffer拿到的outputBufferIndex等于MediaCodec.INFO_OUTPUT_FORMAT_CHANGED时,先拿到新的MediaFormat,然后通过csd-0获取00 00 00 01 VPS 00 00 00 01 SPS 00 00 00 01 PPS
  2. dequeueOutputBuffer拿到的outputBufferIndex大于等于0时,若MediaCodec.BufferInfo.flags包含MediaCodec.BUFFER_FLAG_CODEC_CONFIG标志位,那么输出的ByteBuffer就是00 00 00 01 VPS 00 00 00 01 SPS 00 00 00 01 PPS

因为VPS、SPS和PPS是单独输出的,所以MediaCodec输出的I帧前面不会再次携带SPS和PPS信息。

MediaCodec编码的H265,通过FFmpeg Mux时,需要先把VPS、SPS和PPS写入AVStream->codecpar->extradata,然后才能调用avformat_write_header写入容器头信息。VPS、SPS和PPS可以以两种方式写入AVStream->codecpar->extradata

  1. 00 00 00 01 VPS 00 00 00 01 SPS 00 00 00 01 PPS
  2. HEVCDecoderConfigurationRecord格式

当封装容器是Mp4和FLV时,VPS、SPS和PPS会被重新组织为HEVCDecoderConfigurationRecord格式,写入到hvcC Box(stsd -> hvc1 -> hvcC)。

extradata可以兼容两种方式,但是写入到Mp4和Flv时,都会被重新组织成HEVCDecoderConfigurationRecord(H265)。

libavformat/hevc.c中的ff_isom_write_hvcc函数负责把AVStream->par->extradata中的VPS、SPS和PPS组织成HEVCDecoderConfigurationRecord格式,并写入hvcC Box。

ff_isom_write_hvcc的主要逻辑是判断AVStream->par->extradata是什么形式的VPS、SPS和PPS,若AVStream->par->extradata是以StartCode分割的VPS、SPS和PPS,则首先提取VPS、SPS和PPS,然后按照HEVCDecoderConfigurationRecord格式写入;否则,则可以直接写入。

MediaCodec解码H264

MediaCodec解码H264时,SPS和PPS有三种输入方式:

  1. 首先以csd-0为key,把00 00 00 01 SPS设置给MediaFormat,以csd-1为key,把00 00 00 01 PPS设置给MediaFormat,然后通过MediaFormat配置解码器:MediaCodec.configure(MediaFormat),后续只要输入StartCode分割的NALU裸流就可以了。
  2. 在MediaCodec.start()之后,输入任何NALU裸流之前,首先通过ByteBuffer存储00 00 00 01 SPS 00 00 00 01 PPS,结合BUFFER_FLAG_CODEC_CONFIGFlag,把SPS和PPS送给解码器,后续只要输入StartCode分割的NALU裸流就可以了。
  3. 在每个I帧前面拼接00 00 00 01 SPS 00 00 00 01 PPS,直接送入解码器。

不管哪种输入方式,SPS、PPS和H264裸流都是StartCode分割,不能是AVCC格式的Nalu Size。

MediaCodec解码H265

MediaCodec解码H265时,VPS、SPS和PPS有三种输入方式:

  1. 首先以csd-0为key,把00 00 00 01 VPS 00 00 00 01 SPS 00 00 00 01 PPS设置给MediaFormat,然后通过MediaFormat配置解码器:MediaCodec.configure(MediaFormat),后续只要输入StartCode分割的NALU裸流就可以了。
  2. 在MediaCodec.start()之后,输入任何NALU裸流之前,首先通过ByteBuffer存储00 00 00 01 VPS 00 00 00 01 SPS 00 00 00 01 PPS,结合BUFFER_FLAG_CODEC_CONFIGFlag,把VPS、SPS和PPS送给解码器,后续只要输入StartCode分割的NALU裸流就可以了。
  3. 在每个I帧前面拼接00 00 00 01 VPS 00 00 00 01 SPS 00 00 00 01 PPS,直接送入解码器。

不管哪种输入方式,VPS、SPS、PPS和H265裸流都是StartCode分割,不能是AVCC格式的Nalu Size。

参考文档

  1. Audio Specific Config
  2. RTMP推送AAC ADTS音频流

为什么Mux Mp4时,没有提供AudioSpecificConfig,并且是ES AAC,生成的Mp4也可以正常播放AAC,但是分离出的AAC则无法播放。

这种情况下,Mp4没有ESDS Box,但是mp4a box包含了AAC元数据。

MP4没有包含aac元数据,即没有ESDS Box时,mp4a box也包含了声道数、采样位数和采样率。此时,AAC是可以正确播放。
但是从这种Mp4单独分离出AAC裸流时,因为没有extradata(没有ESDS Box),所以无法生成ADTS头部,即是ES AAC,无法解码播放。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注