本文主要是对AAC音频编码数据格式的解析,以从AAC码流中分析得到它的基本单元ADTS frame,并且可以简单解析ADTS frame首部的字段。只解析格式,但是不对AAC编码具体描述。(音频编码没有视频编码重要,因为音频数据没有视频数据那么大,AAC编码压缩率在10倍)
可以代码以下问题阅读:

  • 1、ADTS和AAC分别是什么?他们之间有什么联系
  • 2、编码号的AAC数据为什么无法直接播放?
  • 3、怎么解析一个AAC数据,获得它的采样率、通道、数据大小,怎么区分开一个个的ADTS帧?

一、ADTS

  • AAC频格式: Advanced Audio Coding(⾼级⾳频解码),是⼀种由MPEG-4 标准定义的有损频压缩格式。一般flv、MP4、直播等都是用的AAC
  • ADIF: Audio Data Interchange Format频数据交换格式。这种格式的特征是可以确定的找到这个频数据的开始,不需进在频数据流中间开始 的解码,即它的解码必须在明确定义的开始处进。故这种格式常在磁盘件中。(很少用到,不是重点)
  • ADTS的全称是Audio Data Transport Stream 是AAC频的传输流格
    式,即封装格式。AAC频格式在MPEG-2(ISO-13318-72003)中有定义。AAC后来被采到MPEG-4标准中。这种格式的特征是它是个有同步字的特 流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。

简单说,ADTS可以在任意帧解码,也就是说它每⼀帧都有头信息。 ADIF只有一个统一的头,所以必须得到所有的数据后解码。

且这两种的header的格式也是不同的,目前一般编码后的和抽取出的都是 ADTS格式的音频流。两者具体的组织结构如下所示:

  • AAC的ADIF格下图:

  • AAC的ADTS一般格见下图:

    空白处表示前后帧

  • 机上播放,很大的可能就是AAC文件的每一帧里缺少了ADTS头信息文件的包装拼接。 只需要加入头件ADTS即可。一个AAC原始数据块长度是可变的,对原始帧加上ADTS头进行ADTS的封装,就形成了ADTS帧.

  • 在使用ffmpeg对音频进行转码,发现从编码器里面出来的音频数据,存成的aac文件并不能用播放器(VLC)播放,goldwave也不行,原因:缺少包头信息,即ADTS头
    AAC音频文件的每一帧由ADTS Header和AAC Audio Data组成。结构体如 下:

二、ADTS的内容及结构

ADTS 头中相对有用的信息 采样率声道数帧长度。想想也是,我要是解码器的话,你给我一堆得AAC音频ES流我也解不出来。每一个带ADTS头信息的AAC流会清晰的告送解码器他需要的这些信息。

一般情况下ADTS的头信息都是7个字节,分为2部分:

  • adts_fixed_header();
  • adts_variable_header();

1、adts_fixed_header

  • 成员定义

  • syncword : 同步头 总是0xFFF, all bits must be 1,代表着一个ADTS帧的开始

  • ID: MPEG Version: 0 for MPEG-4, 1 for MPEG-2、

  • Layer: always: ‘00’

  • protection_absent: 表示是否误码校验。Warning, set to 1 if there is no CRC and 0 if there is CRC

  • profile: 表示使用哪个级别的AAC,有些芯片只支持AAC LC 。在MPEG-2 AAC中定义了3种:

  • sampling_frequency_index: 表示使用的采样率下标,通过这个下标在 Sampling Frequencies[ ]数组中查找得知采样率的值。

  • channel_configuration: 表示声道数
    0: Defined in AOT Specifc Config
    1: 1 channel: front-center
    2: 2 channels: front-left, front-right
    3: 3 channels: front-center, front-left, front-right
    4: 4 channels: front-center, front-left, front-right, back-center
    5: 5 channels: front-center, front-left, front-right, back-left, back-right
    6: 6 channels: front-center, front-left, front-right, back-left, back-right, LFE-channel
    7: 8 channels: front-center, front-left, front-right, side-left, side-right, back-left, back-right, LFE-channel
    8-15: Reserved

2、adts_variable_header

  • 成员定义:
  • frame_length : 一个ADTS帧的长度包括ADTS头和AAC原始流.
    • protection_absent=0时, header length=9bytes
    • protection_absent=1时, header length=7bytes
  • adts_buffer_fullness: 0x7FF 说明是码率可变的码流
  • number_of_raw_data_blocks_in_frame: 表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。 所以说number_of_raw_data_blocks_in_frame == 0 表示说ADTS帧中有⼀个 AAC数据块。

三、将AAC打包成ADTS格式

如果是通过嵌入式高清解码芯片做产品的话,一般情况的解码工作都是由硬件来完成的。所以大部分的工作是把AAC原始流打包成ADTS的格式,然后丢给硬件就行了。

通过对ADTS格式的了解,很容易就能把AAC打包成ADTS。我们只需得到封装格式里面关于音频采样率、声道数、元数据长度、aac格式类型等信息。然后在每个AAC原始流前面加上个ADTS头就OK了。

贴上ffmpeg中添加ADTS头的代码,就可以很清晰的了解ADTS头的结构:

int ff_adts_write_frame_header(ADTSContext *ctx,  uint8_t *buf, int size, int pce_size)
{  PutBitContext pb;  init_put_bits(&pb, buf, ADTS_HEADER_SIZE);  /* adts_fixed_header */  put_bits(&pb, 12, 0xfff);   /* syncword */  put_bits(&pb, 1, 0);        /* ID */  put_bits(&pb, 2, 0);        /* layer */  put_bits(&pb, 1, 1);        /* protection_absent */  put_bits(&pb, 2, ctx->objecttype); /* profile_objecttype */  put_bits(&pb, 4, ctx->sample_rate_index);  put_bits(&pb, 1, 0);        /* private_bit */  put_bits(&pb, 3, ctx->channel_conf); /* channel_configuration */  put_bits(&pb, 1, 0);        /* original_copy */  put_bits(&pb, 1, 0);        /* home */  /* adts_variable_header */  put_bits(&pb, 1, 0);        /* copyright_identification_bit */  put_bits(&pb, 1, 0);        /* copyright_identification_start */  put_bits(&pb, 13, ADTS_HEADER_SIZE + size + pce_size); /* aac_frame_length */  put_bits(&pb, 11, 0x7ff);   /* adts_buffer_fullness */  put_bits(&pb, 2, 0);        /* number_of_raw_data_blocks_in_frame */  flush_put_bits(&pb);  return 0;
}

四、AAC音频码流解析

每个ADTS frame之间通过syncword(同步字)进行分隔。同步字为0xFFF(二进制“111111111111”)。AAC码流解析的步骤就是首先从码流中搜索0x0FFF,分离出ADTS frame;然后再分析ADTS frame的首部各个字段

1、原代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int getADTSframe(unsigned char* buffer, int buf_size, unsigned char* data ,int* data_size){int size = 0;if(!buffer || !data || !data_size ){return -1;}while(1){if(buf_size  < 7 ){return -1;}//Sync wordsif((buffer[0] == 0xff) && ((buffer[1] & 0xf0) == 0xf0) ){size |= ((buffer[3] & 0x03) <<11);     //high 2 bitsize |= buffer[4]<<3;                //middle 8 bitsize |= ((buffer[5] & 0xe0)>>5);        //low 3bitbreak;}--buf_size;++buffer;}if(buf_size < size){return 1;}memcpy(data, buffer, size);*data_size = size;return 0;
}int simplest_aac_parser(char *url)
{int data_size = 0;int size = 0;int cnt=0;int offset=0;//FILE *myout=fopen("output_log.txt","wb+");FILE *myout=stdout;unsigned char *aacframe=(unsigned char *)malloc(1024*5);unsigned char *aacbuffer=(unsigned char *)malloc(1024*1024);FILE *ifile = fopen(url, "rb");if(!ifile){printf("Open file error");return -1;}printf("-----+- ADTS Frame Table -+------+\n");printf(" NUM | Profile | Frequency| Size |\n");printf("-----+---------+----------+------+\n");while(!feof(ifile)){data_size = fread(aacbuffer+offset, 1, 1024*1024-offset, ifile);unsigned char* input_data = aacbuffer;while(1){/* size是adts头+aac数据的总长度 */int ret=getADTSframe(input_data, data_size, aacframe, &size);if(ret==-1){break;}else if(ret==1){memcpy(aacbuffer,input_data,data_size);offset=data_size;break;}char profile_str[10]={0};char frequence_str[10]={0};/* s数据存入内存是一个字节一个自己的,所以读取来取的是高位 */unsigned char profile=aacframe[2]&0xC0;profile=profile>>6;switch(profile){case 0: sprintf(profile_str,"Main");break;case 1: sprintf(profile_str,"LC");break;case 2: sprintf(profile_str,"SSR");break;default:sprintf(profile_str,"unknown");break;}unsigned char sampling_frequency_index=aacframe[2]&0x3C;sampling_frequency_index=sampling_frequency_index>>2;switch(sampling_frequency_index){case 0: sprintf(frequence_str,"96000Hz");break;case 1: sprintf(frequence_str,"88200Hz");break;case 2: sprintf(frequence_str,"64000Hz");break;case 3: sprintf(frequence_str,"48000Hz");break;case 4: sprintf(frequence_str,"44100Hz");break;case 5: sprintf(frequence_str,"32000Hz");break;case 6: sprintf(frequence_str,"24000Hz");break;case 7: sprintf(frequence_str,"22050Hz");break;case 8: sprintf(frequence_str,"16000Hz");break;case 9: sprintf(frequence_str,"12000Hz");break;case 10: sprintf(frequence_str,"11025Hz");break;case 11: sprintf(frequence_str,"8000Hz");break;default:sprintf(frequence_str,"unknown");break;}fprintf(myout,"%5d| %8s|  %8s| %5d|\n",cnt,profile_str ,frequence_str,size);data_size -= size;input_data += size;cnt++;}   }fclose(ifile);free(aacbuffer);free(aacframe);return 0;
}

2、结果

五、AAC封装头输出ADTS流

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>#define ADTS_HEADER_LEN  7;const int sampling_frequencies[] = {96000,  // 0x088200,  // 0x164000,  // 0x248000,  // 0x344100,  // 0x432000,  // 0x524000,  // 0x622050,  // 0x716000,  // 0x812000,  // 0x911025,  // 0xa8000   // 0xb// 0xc d e f是保留的
};int adts_header(char * const p_adts_header, const int data_length,const int profile, const int samplerate,const int channels)
{int sampling_frequency_index = 3; // 默认使用48000hzint adtsLen = data_length + 7;int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]);int i = 0;for(i = 0; i < frequencies_size; i++){if(sampling_frequencies[i] == samplerate){sampling_frequency_index = i;break;}}if(i >= frequencies_size){printf("unsupport samplerate:%d\n", samplerate);return -1;}p_adts_header[0] = 0xff;         //syncword:0xfff                          高8bitsp_adts_header[1] = 0xf0;         //syncword:0xfff                          低4bitsp_adts_header[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bitp_adts_header[1] |= (0 << 1);    //Layer:0                                 2bitsp_adts_header[1] |= 1;           //protection absent:1                     1bitp_adts_header[2] = (profile)<<6;            //profile:profile               2bitsp_adts_header[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index  4bitsp_adts_header[2] |= (0 << 1);             //private bit:0                   1bitp_adts_header[2] |= (channels & 0x04)>>2; //channel configuration:channels  高1bitp_adts_header[3] = (channels & 0x03)<<6; //channel configuration:channels 低2bitsp_adts_header[3] |= (0 << 5);               //original:0                1bitp_adts_header[3] |= (0 << 4);               //home:0                    1bitp_adts_header[3] |= (0 << 3);               //copyright id bit:0        1bitp_adts_header[3] |= (0 << 2);               //copyright id start:0      1bitp_adts_header[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bitsp_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bitsp_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bitsp_adts_header[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bitsp_adts_header[6] = 0xfc;      //‭11111100‬       //buffer fullness:0x7ff 低6bits// number_of_raw_data_blocks_in_frame://    表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。return 0;
}int main(int argc, char *argv[])
{int ret = -1;char errors[1024];char *in_filename = NULL;char *aac_filename = NULL;FILE *aac_fd = NULL;int audio_index = -1;int len = 0;AVFormatContext *ifmt_ctx = NULL;AVPacket pkt;// 设置打印级别av_log_set_level(AV_LOG_DEBUG);if(argc < 3){av_log(NULL, AV_LOG_DEBUG, "the count of parameters should be more than three!\n");return -1;}in_filename = argv[1];      // 输入文件aac_filename = argv[2];     // 输出文件if(in_filename == NULL || aac_filename == NULL){av_log(NULL, AV_LOG_DEBUG, "src or dts file is null, plz check them!\n");return -1;}aac_fd = fopen(aac_filename, "wb");if (!aac_fd){av_log(NULL, AV_LOG_DEBUG, "Could not open destination file %s\n", aac_filename);return -1;}// 打开输入文件if((ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL)) < 0){av_strerror(ret, errors, 1024);av_log(NULL, AV_LOG_DEBUG, "Could not open source file: %s, %d(%s)\n",in_filename,ret,errors);return -1;}// 获取解码器信息if((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0){av_strerror(ret, errors, 1024);av_log(NULL, AV_LOG_DEBUG, "failed to find stream information: %s, %d(%s)\n",in_filename,ret,errors);return -1;}// dump媒体信息av_dump_format(ifmt_ctx, 0, in_filename, 0);// 初始化packetav_init_packet(&pkt);// 查找audio对应的steam indexaudio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);if(audio_index < 0){av_log(NULL, AV_LOG_DEBUG, "Could not find %s stream in input file %s\n",av_get_media_type_string(AVMEDIA_TYPE_AUDIO),in_filename);return AVERROR(EINVAL);}// 打印AAC级别printf("audio profile:%d, FF_PROFILE_AAC_LOW:%d\n",ifmt_ctx->streams[audio_index]->codecpar->profile,FF_PROFILE_AAC_LOW);if(ifmt_ctx->streams[audio_index]->codecpar->codec_id != AV_CODEC_ID_AAC){printf("the media file no contain AAC stream, it's codec_id is %d\n",ifmt_ctx->streams[audio_index]->codecpar->codec_id);goto failed;}// 读取媒体文件,并把aac数据帧写入到本地文件while(av_read_frame(ifmt_ctx, &pkt) >=0 ){if(pkt.stream_index == audio_index){char adts_header_buf[7] = {0};adts_header(adts_header_buf, pkt.size,ifmt_ctx->streams[audio_index]->codecpar->profile,ifmt_ctx->streams[audio_index]->codecpar->sample_rate,ifmt_ctx->streams[audio_index]->codecpar->channels);fwrite(adts_header_buf, 1, 7, aac_fd);  // 写adts header , ts流不适用,ts流分离出来的packet带了adts headerlen = fwrite( pkt.data, 1, pkt.size, aac_fd);   // 写adts dataif(len != pkt.size){av_log(NULL, AV_LOG_DEBUG, "warning, length of writed data isn't equal pkt.size(%d, %d)\n",len,pkt.size);}}av_packet_unref(&pkt);}failed:// 关闭输入文件if(ifmt_ctx){avformat_close_input(&ifmt_ctx);}if(aac_fd){fclose(aac_fd);}return 0;
}

【流媒体开发】【数据与封装格式】20、AAC码流格式与解析相关推荐

  1. 2019.11.12-最新大华摄像机SDK开发,预览实时视频并指定码流格式保存到文件中(可观看)

    大华摄像机SDK开发,预览实时视频并指定码流格式保存到文件中 由于本人最近在开发大华摄像机,特此分享一些经验给到各位开发朋友,本次实例是关于大华摄像机的实时预览视频码流保存到文件中的Demo,本人还开 ...

  2. 【H264/AVC 句法和语义详解】(二):h264码流格式与NALU详解一

    上一篇中,我们站在句法元素(或称语法元素)的角度,介绍了H.264的句法和语义,和句法元素的分层结构.在这篇中,我们更进一步,从比特的角度出发,来探索h264码流的组成.通过这篇的学习,我们会初步具备 ...

  3. NDK学习笔记:RtmpPusher之利用rtmpdump推h264/aac码流

    NDK学习笔记:RtmpPusher之利用rtmpdump推h264/aac码流 本篇将是 RtmpPusher 的最后一篇.在之前的3篇文章里,我们已经把原生的视频YUV格式编码成h264,把音频的 ...

  4. H264视频码流格式浅析

    原文地址 https://blog.csdn.net/h514434485/article/details/52064945 针对H264码流格式说明,网上已经有很多介绍了,最近也在看这个,这里根据自 ...

  5. 【雷神源码解析】无基础看懂AAC码流解析,看不懂你打我

    一 前言 最近在尝试学习一些视频相关的知识,随便一搜才知道原来国内有雷神这么一个真正神级的人物存在,尤其是在这里(传送门)看到他的感言更是对他膜拜不已,雷神这种无私奉献的精神应当被我辈发扬光大.那写这 ...

  6. 码流格式: Annex-B, AVCC(H.264)与HVCC(H.265), extradata详解

    1.前言 介绍H.264结构的文章铺天盖地,无责任翻译.无责任转载以及部分经验之谈(目前搜索最靠前的一篇实际是对stackoverflow上答案的翻译..链接后面给出了),所以缺的不是资料,是叙述准确 ...

  7. 音乐格式M4A(AAC)转mp3格式转换器

    M4A是一种音频格式,它通常使用AAC(Advanced Audio Coding)压缩算法进行编码.如果你需要将M4A文件转换为MP3文件格式,你可以使用以下两种方法: 方法一:使用记灵在线工具 使 ...

  8. RTP协议封装H264/H265/AAC

    <RTSP实时音视频传输介绍> 目录 一.前言 二.RTP基本格式介绍 1.RTP 固定头 2.RTP 扩展头 3.RTP 载荷 三.RTP封装H264 1.封装包类型 四.RTP封装H2 ...

  9. 海康sdk捕获码流数据通过JavaCV推成rtmp流的实现思路(PS流转封装RTMP)

    海康sdk捕获码流数据通过JavaCV推成rtmp流的实现思路(PS流转封装RTMP) 问题分析 转码推rtmp PS流转封装 码云(Gitee)主页:https://gitee.com/banmaj ...

  10. 音视频方案,音视频扩展内容- 音视频数据解析,码流分析及质量评价(笔记)3

    -- 音视频编解码流程如下图: -- 关于音频各种参数: freq:音频数据的采样率.常用的有48000,44100等. format:音频数据的格式.举例几种格式: AUDIO_U16SYS:Uns ...

最新文章

  1. Windows 查看所有进程命令tasklist
  2. .net core 24
  3. java io 文件路径_【IO流】java中文件路径(相对路径、绝对路径)相关类及方法...
  4. 全局变量定义的时候左侧加了static_c语言中static 用法
  5. python numpy安装步骤-NumPy 安装
  6. VB获取窗体的位置和大小
  7. API接口设计:token、timestamp、sign使用
  8. 教您用公式编辑器打恒不等于符号
  9. ndis拨号软件 linux,[4G模块]华为ME909S-821 NDIS拨号指令流程
  10. 计算机中sumif函数的使用方法,WPS表格的sumif函数用法与模糊条件使用方法
  11. see into/see off/seek to等动词词组
  12. 第12课:如何理解技术管理者(上)
  13. 游戏创业团队的技术选型之Flash AIR
  14. 云南一脸通行业解决方案、钉钉智慧食堂、智慧餐厅、智慧工地解决方案
  15. 使用python快速搭建接口自动化测试脚本实战总结
  16. C++中cout的含义????
  17. 知识产权(二)——检索流程和要素表达
  18. 二元函数的连续、可偏导、可微、偏导数连续之间的关系
  19. 向量 - 模 余弦值 和 方向角的计算
  20. 【转】暴风影音播放MKV高清电影一快进就卡死的问题

热门文章

  1. Angular cdk 学习之 drag-drop
  2. SFP光模块的多模和单模区别
  3. 【你是如何应对杠精行为?】如此精彩语录,看完不笑算我输
  4. Audience Targeting
  5. 【教程】用微信创建生日提醒
  6. 算术练习题(java)
  7. 300ETF期权和50ETF期权的区别
  8. Nim理论初探——编程之美1.12
  9. 利普希茨连续(Lipschitz continuity)和利普希茨常数(Lipschitz constant)
  10. LaTeX数学表达式