AudioToolBox 解碼AAC

上一篇文章中,我們針對PCM 數據,通過AudioToolBoxPCM 數據編碼成AAC 數據,并把AAC 數據添加ADTS Header,并把AAC格式的音頻數據寫入文件;

這一章呢,我們主要是用AudioToolBoxAAC數據 解碼成PCM格式,并利用AVFoundation框架把PCM數據 從揚聲器播放處理;

1. 音頻采集

關于音頻采集部分,上篇文章已經介紹過了,是采用 AVFoundation 框架 對AVCaptureSessionSession 進行封裝,添加音頻輸入源,然后添加 output輸出,通過采集音頻設備,最后通過代理方法拿到音頻流PCM 數據;這里不做過多贅述 ,可以參考上篇文章AudioToolBox 編碼AAC 或者直接看源碼:https://github.com/hunter858/OpenGL_Study

2. AAC數據獲取

AAC的原始數據,也是上篇文章介紹過的,通過 AudioEncoder 拿到的編碼后的AAC數據部分,不包含ADTS header部分;因為ADTS Header主要是寫入文件需要的;
通過AudioEncoderaudioEncodeCallback 代理方法拿到編碼后的AAC 數據;

- (void)audioEncodeCallback:(NSData *)aacData;

3. AudioToolBox 創建

關于AudioToolBox的創建 和編碼部分一致,只是解碼部分,把inputoutput的配置 做了一個調換(這么理解);
這里我們創建一個AudioDecoder類封裝AudioToolBox對象,通過AudioConfig音頻配置來初始化 硬件編碼器所需要的一些參數 ;

源碼如下:

- (void)setupEncoder {
   
   //輸出參數pcm
   AudioStreamBasicDescription outputAudioDes = {0};
   outputAudioDes.mSampleRate = (Float64)_config.sampleRate;       //采樣率
   outputAudioDes.mChannelsPerFrame = (UInt32)_config.channelCount; //輸出聲道數
   outputAudioDes.mFormatID = kAudioFormatLinearPCM;                //輸出格式
   outputAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //編碼 12
   outputAudioDes.mFramesPerPacket = 1;                            //每一個packet幀數 ;
   outputAudioDes.mBitsPerChannel = 16;                             //數據幀中每個通道的采樣位數。
   outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame;                              //每一幀大小(采樣位數 / 8 *聲道數)
   outputAudioDes.mBytesPerPacket = outputAudioDes.mBytesPerFrame * outputAudioDes.mFramesPerPacket;                             //每個packet大?。◣笮?* 幀數)
   outputAudioDes.mReserved =  0;                                  //對其方式 0(8字節對齊)
   
   //輸入參數aac
   AudioStreamBasicDescription inputAduioDes = {0};
   inputAduioDes.mSampleRate = (Float64)_config.sampleRate;
   inputAduioDes.mFormatID = kAudioFormatMPEG4AAC;
   inputAduioDes.mFormatFlags = kMPEG4Object_AAC_LC;
   inputAduioDes.mFramesPerPacket = 1024;
   inputAduioDes.mChannelsPerFrame = (UInt32)_config.channelCount;
   
   //填充輸出相關信息
   UInt32 inDesSize = sizeof(inputAduioDes);
   AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inputAduioDes);
   
   //獲取解碼器的描述信息(只能傳入software)
   AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
   /** 創建converter
    參數1:輸入音頻格式描述
    參數2:輸出音頻格式描述
    參數3:class desc的數量
    參數4:class desc
    參數5:創建的解碼器
    */
   OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
   if (status != noErr) {
       NSLog(@"Error!:硬解碼AAC創建失敗, status= %d", (int)status);
       return;
   }
}

4. 解碼AAC

在通過 AudioEncoder 的 代理方法 - (void)audioEncodeCallback:(NSData *)aacData; 拿到編碼后的AAC 數據后,直接把 AAC數據送入解碼器,還原AAC數據至PCM格式;

關于解碼部分的源碼如下:

- (void)decodeAudioAACData:(NSData *)aacData {
  
   if (!_audioConverter) { return; }
   
   dispatch_async(_decoderQueue, ^{
    
       //記錄aac 作為參數參入解碼回調函數
       CCAudioUserData userData = {0};
       userData.channelCount = (UInt32)_config.channelCount;
       userData.data = (char *)[aacData bytes];
       userData.size = (UInt32)aacData.length;
       userData.packetDesc.mDataByteSize = (UInt32)aacData.length;
       userData.packetDesc.mStartOffset = 0;
       userData.packetDesc.mVariableFramesInPacket = 0;
       
       //輸出大小和packet個數
       UInt32 pcmBufferSize = (UInt32)(2048 * _config.channelCount);
       UInt32 pcmDataPacketSize = 1024;
       
       //創建臨時容器pcm
       uint8_t *pcmBuffer = malloc(pcmBufferSize);
       memset(pcmBuffer, 0, pcmBufferSize);
       
       //輸出buffer
       AudioBufferList outAudioBufferList = {0};
       outAudioBufferList.mNumberBuffers = 1;
       outAudioBufferList.mBuffers[0].mNumberChannels = (uint32_t)_config.channelCount;
       outAudioBufferList.mBuffers[0].mDataByteSize = (UInt32)pcmBufferSize;
       outAudioBufferList.mBuffers[0].mData = pcmBuffer;
       
       //輸出描述
       AudioStreamPacketDescription outputPacketDesc = {0};
       
       //配置填充函數,獲取輸出數據
       OSStatus status = AudioConverterFillComplexBuffer(_audioConverter, &AudioDecoderConverterComplexInputDataProc, &userData, &pcmDataPacketSize, &outAudioBufferList, &outputPacketDesc);
       if (status != noErr) {
           NSLog(@"Error: AAC Decoder error, status=%d",(int)status);
           return;
       }
       //如果獲取到數據
       if (outAudioBufferList.mBuffers[0].mDataByteSize > 0) {
           NSData *rawData = [NSData dataWithBytes:outAudioBufferList.mBuffers[0].mData length:outAudioBufferList.mBuffers[0].mDataByteSize];
           dispatch_async(_callbackQueue, ^{
               [_delegate audioDecodeCallback:rawData];
           });
       }
       free(pcmBuffer);
   });
   
}


static OSStatus AudioDecoderConverterComplexInputDataProc(  AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData,  AudioStreamPacketDescription **outDataPacketDescription,  void *inUserData) {
    
    
    CCAudioUserData *audioDecoder = (CCAudioUserData *)(inUserData);
    if (audioDecoder->size <= 0) {
        ioNumberDataPackets = 0;
        return -1;
    }
   
    //填充數據
    *outDataPacketDescription = &audioDecoder->packetDesc;
    (*outDataPacketDescription)[0].mStartOffset = 0;
    (*outDataPacketDescription)[0].mDataByteSize = audioDecoder->size;
    (*outDataPacketDescription)[0].mVariableFramesInPacket = 0;
    
    ioData->mBuffers[0].mData = audioDecoder->data;
    ioData->mBuffers[0].mDataByteSize = audioDecoder->size;
    ioData->mBuffers[0].mNumberChannels = audioDecoder->channelCount;
    
    return noErr;
}

5. PCM 播放

拿到PCM數據后,如何驗證我們解碼是否成功,我們有2種辦法;

  • 第一種方法是把 PCM保存下來使用 ffmpeg 進行播放;
  • 第二種方法是把 PCM 從揚聲器播放出來;
5.1 PCM播放(方法一):

從沙盒拿到PCM 文件后,在終端鍵入如下命令 (記得安裝ffmpeg
例子:ffplay -ar 44100 -ac 1 -f s16le -i /Users/pengchao/Desktop/2022_06_25_20:44:34.pcm

image.png
fplay -ar 44100 -ac 1 -f s16le -i ./201904091310_test.pcm

-ar 表示采樣率

-ac 表示音頻通道數
    單聲道是 1,Android 中為 AudioFormat.CHANNEL_IN_MONO
    雙聲道是 2,Android 中為 AudioFormat.CHANNEL_IN_STEREO

-f 表示 pcm 格式,sample_fmts + le(小端)或者 be(大端)
    sample_fmts可以通過ffplay -sample_fmts來查詢

-i 表示輸入文件,這里就是 pcm 文件
5.2 PCM播放(方法二)

通過 AudioPCMPlayer 類,對PCM數據進行播放,這里AudioPCMPlayer 是一個 基于 AudioQueue的封裝;有興趣的同學可以去看源碼;

總結

源碼地址: 源碼地址 源碼地址: https://github.com/hunter858/OpenGL_Study/AVFoundation/AudiotoolBox-decoder

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,837評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,196評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,688評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,654評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,456評論 6 406
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,955評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,044評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,195評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,725評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,608評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,802評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,318評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,048評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,422評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,673評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,424評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,762評論 2 372

推薦閱讀更多精彩內容