AAC-ADTS 格式分析
AAC?頻格式:Advanced Audio Coding(?級?頻解碼),是?種由MPEG-4標準定義的有損?頻壓縮格式,由Fraunhofer發展,Dolby, Sony和AT&T是主
要的貢獻者。
-
ADIF:Audio Data Interchange Format ?頻數據交換格式。這種格式的特征是可以確定的找到這個?頻數據的開始,不需進?在?頻數據流中間開始
的解碼,即它的解碼必須在明確定義的開始處進?。故這種格式常?在磁盤?件中。 -
ADTS的全稱是Audio Data Transport Stream。是AAC?頻的傳輸流格式。AAC?頻格式在MPEG-2(ISO-13318-7 2003)中有定義。AAC后來
?被采?到MPEG-4標準中。這種格式的特征是它是?個有同步字的?特流,解碼可以在這個流中任何位置開始。它的特征類似于mp3數據流格式。
簡單說,ADTS可以在任意幀解碼,也就是說它每?幀都有頭信息。ADIF只有?個統?的頭,所以必須得到所有的數據后解碼且這兩種的header的格式也是不同的,?前?般編碼后的和抽取出的都是ADTS格式的?頻流。兩者具體的組織結構如下所示:
- AAC的ADIF格式?下圖:
- AAC的ADTS的?般格式?下圖:
- 有的時候當你編碼AAC裸流的時候,會遇到寫出來的AAC?件并不能在PC和?機上播放,很?的可能就是AAC?件的每?幀?缺少了ADTS頭信息?件的包裝拼接。
- 只需要加?頭?件ADTS即可。?個AAC原始數據塊?度是可變的,對原始幀加上ADTS頭進?ADTS的封裝,就形成了ADTS幀。
- AAC?頻?件的每?幀由ADTS Header和AAC Audio Data組成。結構體如下:
每?幀的ADTS的頭?件都包含了?頻的采樣率,聲道,幀?度等信息,這樣解碼器才能解析讀取。?般情況下ADTS的頭信息都是7個字節,分為2部分:
- adts_fixed_header();
- adts_variable_header()
其?為固定頭信息,緊接著是可變頭信息。固定頭信息中的數據每?幀都相同,?可變頭信息則在幀與幀之間可變。
固定頭信息
- syncword :同步頭 總是0xFFF, all bits must be 1,代表著?個ADTS幀的開始
- ID:MPEG標識符,0標識MPEG-4,1標識MPEG-2
- Layer:always: ‘00’
- protection_absent:表示是否誤碼校驗。Warning, set to 1 if there is noCRC and 0 if there is CRC
- profile:表示使?哪個級別的AAC,如01 Low Complexity(LC)— AACLC。有些芯?只?持AAC LC 。
sampling_frequency_index:表示使?的采樣率下標,通過這個下標在SamplingFrequencies[]
數組中查找得知采樣率的值。
在MPEG-2 AAC中定義了3種:
- profile的值等于 Audio Object Type的值減1
- profile = MPEG-4 Audio Object Type - 1
- channel_configuration: 表示聲道數,?如2表示?體聲雙聲道
聲道數的定義如下
- 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
接下來看下adts_variable_header();
可變頭信息
-
frame_length : ?個ADTS幀的?度包括ADTS頭和AAC原始流.
-
frame length, this value must include 7 or 9 bytes of header length:aac_frame_length = (protection_absent == 1 ? 7 : 9) + size(AACFrame)
-
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數據塊。
下?是ADTS的AAC?件部分:
- ?字節開始算
第?幀的幀頭7個字節為:0xFF 0xF1 0x4C 0x40 0x20 0xFF 0xFC
實現流程
準備文件,準備音頻格式在MPEG-2
支持的3
種AAC格式的mp4
和flv
,這里不使用ts
是因為它的aac
流自帶ADTS
頭部信息。
這三種都支持
Main Profile
LC
SSR
將文件放入build
路徑下,通過main
參數傳遞進來
創建一個輸出文件,以二進制寫的方式打開,用于寫入轉換后的ADTS
文件
char *in_filename = NULL;char *aac_filename = NULL;FILE *aac_fd = NULL;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;}
文件解封裝
將文件解封裝,無論是mp4
還是flv
,找出對應的音頻流,讀取音頻流數據
AVFormatContext *ifmt_ctx = NULL;// 打開輸入文件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
類型的profile
,因為我們只支持三種:
printf("audio profile:%d, FF_PROFILE_AAC_LOW:%d\n",ifmt_ctx->streams[audio_index]->codecpar->profile,FF_PROFILE_AAC_LOW);
如果音頻格式對應支持的AAC
,那么我們就可以進行循環讀取音頻包數據
- 讀取音頻包數據,獲得對應的
profile
、sample_rate
以及channel
- 傳入包的數據大小
ADTS
一般是7
字節,因此用一個7
字節的char
數組接收(一個char
占1字節)- 寫入頭部后,將頭部信息寫入文件
- 寫入數據包信息
- 釋放數據包內存
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);
ADTS
格式詳解
準備采樣率表
- 這個是固定的,與協議對應
const int sampling_frequencies[] = {96000, // 0x088200, // 0x164000, // 0x248000, // 0x344100, // 0x432000, // 0x524000, // 0x622050, // 0x716000, // 0x812000, // 0x911025, // 0xa8000 // 0xb// 0xc d e f是保留的
};
- 我們這里直接使用
48000
采樣率即可
int sampling_frequency_index = 3; // 默認使用48000hz
寫入固定頭信息
- 同步頭(
12bit
),始終為0xfff
p_adts_header[0] = 0xff; //syncword:0xfff 高8bits
p_adts_header[1] = 0xf0; //syncword:0xfff 低4bits
- 版本號(
1bit
),如果使用的是MPEG-2
為0,MPEG-4
為1
p_adts_header[1] |= (0 << 3); //MPEG Version:0 for MPEG-4,1 for MPEG-2 1bit
layer
(2bit
),永遠是0
p_adts_header[1] |= (0 << 1); //Layer:0
- 校驗位(
protection_absent
),0表示有校驗,1表示無校驗(1bit
)
p_adts_header[1] |= 1; //protection absent:1 1bit
profile
(2bit
),ffmpeg
參考的是MPEG-2
,因此這里使用它的枚舉值即可
p_adts_header[2] = (profile)<<6; //profile:profile 2bits
- 采樣率索引,需要轉為
16
進制(2bit
)
p_adts_header[2] |= (sampling_frequency_index & 0x0f)<< 2; //sampling frequency index:sampling_frequency_index 4bits
private_bit
,固定為0
(1bit
)
p_adts_header[2] |= (0 << 1); //private bit:0 1bit
- 聲道布局,需要轉換為
16
進制(3bit
)
p_adts_header[2] |= (channels & 0x04)>>2; //channel configuration:channels 高1bit
p_adts_header[3] = (channels & 0x03)<<6; //channel configuration:channels 低2bits
original_copy
,固定為0
(1bit
)
p_adts_header[3] |= (0 << 5); //original:0 1bit
home
,固定為0
(1bit
)
p_adts_header[3] |= (0 << 4); //home:0 1bit
寫入可變頭
copyright_identification_bit
,固定為0(1bit
)
p_adts_header[3] |= (0 << 3); //copyright id bit:0 1bit
copyright_identify_start
,固定為0(1bit
)
p_adts_header[3] |= (0 << 2); //copyright id start:0 1bit
aac_frame_length
,aac
數據幀的長度(13bit
),通過下面的方式獲取:
- 如果
protection_absent
校驗位為1
,那么aac_frame_length = 7 + sizeof(aac_frame)
- 如果
protection_absent
校驗位為0
,那么aac_frame_length = 9 + sizeof(aac_frame)
前面設置了校驗位為1
,因此:
int adtsLen = data_length + 7;
p_adts_header[3] |= ((adtsLen & 0x1800) >> 11); //frame length:value 高2bits
p_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3); //frame length:value 中間8bits
p_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5); //frame length:value 低3bits
adts_buffer_fullness
(11bit
),設置為0x7ff
表示為可變碼流
p_adts_header[5] |= 0x1f; //buffer fullness:0x7ff 高5bits
p_adts_header[6] = 0xfc; //buffer fullness:0x7ff 低6bits
number_of_raw_data_blocks_in_frame
(2bit
),意義如下:
- 表示ADTS幀中有
number_of_raw_data_blocks_in_frame
+ 1個AAC原始幀。 - 因此我們設置
number_of_raw_data_blocks_in_frame = 0
表示說ADTS幀中有?個AAC數據塊。
p_adts_header[6] |= 0 << 2; //buffer fullness:0x7ff 低6bits
寫入的函數如下所示:
#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 + ADTS_HEADER_LEN;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// p_adts_header[6] |= 0 << 2; // number_of_raw_data_blocks_in_frame:// 表示ADTS幀中有number_of_raw_data_blocks_in_frame + 1個AAC原始幀。return 0;
}
釋放內存
最后還是要釋放內存,關閉文件
// 關閉輸入文件
if(ifmt_ctx)
{avformat_close_input(&ifmt_ctx);
}
if(aac_fd)
{fclose(aac_fd);
}
HE-AAC
需要調整
#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;int adtsLen = data_length + 7; // 修正宏定義問題// 查找采樣率索引for (int i = 0; i < sizeof(sampling_frequencies)/sizeof(int); i++) {if (sampling_frequencies[i] == samplerate) {sampling_frequency_index = i;break;}}// 設置ADTS頭各字段p_adts_header[0] = 0xFF;p_adts_header[1] = 0xF0;p_adts_header[1] |= 0x01; // protection_absent// Profile設置為傳入值(需外部處理HE-AAC情況)p_adts_header[2] = (profile & 0x03) << 6;p_adts_header[2] |= (sampling_frequency_index & 0x0F) << 2;p_adts_header[2] |= (channels >> 3) & 0x01; // 通道高1位p_adts_header[3] = (channels & 0x07) << 5; // 通道低3位p_adts_header[3] |= (adtsLen >> 11) & 0x03;p_adts_header[4] = (adtsLen >> 3) & 0xFF;p_adts_header[5] = (adtsLen & 0x07) << 5;p_adts_header[5] |= 0x1F;p_adts_header[6] = 0xFC;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,1,ifmt_ctx->streams[audio_index]->codecpar->sample_rate /2 ,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;
}
profile字段錯誤
HE-AAC(AAC LC + SBR)的Profile值在ADTS頭中應設為1
(對應AAC LC的Object Type減1),而非直接使用HE-AAC的Profile值(FF_PROFILE_AAC_HE為5)。直接使用導致高位溢出,字段無效。
采樣率索引未調整
HE-AAC使用SBR技術時,實際采樣率為ADTS頭中采樣率的兩倍。例如,48kHz音頻在ADTS頭中應使用24kHz的索引(索引6),但代碼未進行此調整。
更多資料:https://github.com/0voice