上一篇文章中,我們針對(duì)PCM 數(shù)據(jù),通過AudioToolBox將PCM 數(shù)據(jù)編碼成AAC 數(shù)據(jù),并把AAC 數(shù)據(jù)添加ADTS Header,并把AAC格式的音頻數(shù)據(jù)寫入文件;
這一章呢,我們主要是用AudioToolBox把AAC數(shù)據(jù) 解碼成PCM格式,并利用AVFoundation框架把PCM數(shù)據(jù) 從揚(yáng)聲器播放處理;
1. 音頻采集
關(guān)于音頻采集部分,上篇文章已經(jīng)介紹過了,是采用 AVFoundation 框架 對(duì)AVCaptureSessionSession 進(jìn)行封裝,添加音頻輸入源,然后添加 output輸出,通過采集音頻設(shè)備,最后通過代理方法拿到音頻流PCM 數(shù)據(jù);這里不做過多贅述 ,可以參考上篇文章AudioToolBox 編碼AAC 或者直接看源碼:https://github.com/hunter858/OpenGL_Study
2. AAC數(shù)據(jù)獲取
AAC的原始數(shù)據(jù),也是上篇文章介紹過的,通過 AudioEncoder 拿到的編碼后的AAC數(shù)據(jù)部分,不包含ADTS header部分;因?yàn)?code>ADTS Header主要是寫入文件需要的;
通過AudioEncoder 的audioEncodeCallback 代理方法拿到編碼后的AAC 數(shù)據(jù);
- (void)audioEncodeCallback:(NSData *)aacData;
3. AudioToolBox 創(chuàng)建
關(guān)于AudioToolBox的創(chuàng)建 和編碼部分一致,只是解碼部分,把input 和output的配置 做了一個(gè)調(diào)換(這么理解);
這里我們創(chuàng)建一個(gè)AudioDecoder類封裝AudioToolBox對(duì)象,通過AudioConfig音頻配置來初始化 硬件編碼器所需要的一些參數(shù) ;
源碼如下:
- (void)setupEncoder {
//輸出參數(shù)pcm
AudioStreamBasicDescription outputAudioDes = {0};
outputAudioDes.mSampleRate = (Float64)_config.sampleRate; //采樣率
outputAudioDes.mChannelsPerFrame = (UInt32)_config.channelCount; //輸出聲道數(shù)
outputAudioDes.mFormatID = kAudioFormatLinearPCM; //輸出格式
outputAudioDes.mFormatFlags = (kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked); //編碼 12
outputAudioDes.mFramesPerPacket = 1; //每一個(gè)packet幀數(shù) ;
outputAudioDes.mBitsPerChannel = 16; //數(shù)據(jù)幀中每個(gè)通道的采樣位數(shù)。
outputAudioDes.mBytesPerFrame = outputAudioDes.mBitsPerChannel / 8 *outputAudioDes.mChannelsPerFrame; //每一幀大小(采樣位數(shù) / 8 *聲道數(shù))
outputAudioDes.mBytesPerPacket = outputAudioDes.mBytesPerFrame * outputAudioDes.mFramesPerPacket; //每個(gè)packet大?。◣笮?* 幀數(shù))
outputAudioDes.mReserved = 0; //對(duì)其方式 0(8字節(jié)對(duì)齊)
//輸入?yún)?shù)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;
//填充輸出相關(guān)信息
UInt32 inDesSize = sizeof(inputAduioDes);
AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &inDesSize, &inputAduioDes);
//獲取解碼器的描述信息(只能傳入software)
AudioClassDescription *audioClassDesc = [self getAudioCalssDescriptionWithType:outputAudioDes.mFormatID fromManufacture:kAppleSoftwareAudioCodecManufacturer];
/** 創(chuàng)建converter
參數(shù)1:輸入音頻格式描述
參數(shù)2:輸出音頻格式描述
參數(shù)3:class desc的數(shù)量
參數(shù)4:class desc
參數(shù)5:創(chuàng)建的解碼器
*/
OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
if (status != noErr) {
NSLog(@"Error!:硬解碼AAC創(chuàng)建失敗, status= %d", (int)status);
return;
}
}
4. 解碼AAC
在通過 AudioEncoder 的 代理方法 - (void)audioEncodeCallback:(NSData *)aacData; 拿到編碼后的AAC 數(shù)據(jù)后,直接把 AAC數(shù)據(jù)送入解碼器,還原AAC數(shù)據(jù)至PCM格式;
關(guān)于解碼部分的源碼如下:
- (void)decodeAudioAACData:(NSData *)aacData {
if (!_audioConverter) { return; }
dispatch_async(_decoderQueue, ^{
//記錄aac 作為參數(shù)參入解碼回調(diào)函數(shù)
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個(gè)數(shù)
UInt32 pcmBufferSize = (UInt32)(2048 * _config.channelCount);
UInt32 pcmDataPacketSize = 1024;
//創(chuàng)建臨時(shí)容器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};
//配置填充函數(shù),獲取輸出數(shù)據(jù)
OSStatus status = AudioConverterFillComplexBuffer(_audioConverter, &AudioDecoderConverterComplexInputDataProc, &userData, &pcmDataPacketSize, &outAudioBufferList, &outputPacketDesc);
if (status != noErr) {
NSLog(@"Error: AAC Decoder error, status=%d",(int)status);
return;
}
//如果獲取到數(shù)據(jù)
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;
}
//填充數(shù)據(jù)
*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數(shù)據(jù)后,如何驗(yàn)證我們解碼是否成功,我們有2種辦法;
- 第一種方法是把
PCM保存下來使用ffmpeg進(jìn)行播放; - 第二種方法是把 PCM 從揚(yáng)聲器播放出來;
5.1 PCM播放(方法一):
從沙盒拿到PCM 文件后,在終端鍵入如下命令 (記得安裝ffmpeg)
例子:ffplay -ar 44100 -ac 1 -f s16le -i /Users/pengchao/Desktop/2022_06_25_20:44:34.pcm

fplay -ar 44100 -ac 1 -f s16le -i ./201904091310_test.pcm
-ar 表示采樣率
-ac 表示音頻通道數(shù)
單聲道是 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 類,對(duì)PCM數(shù)據(jù)進(jìn)行播放,這里AudioPCMPlayer 是一個(gè) 基于 AudioQueue的封裝;有興趣的同學(xué)可以去看源碼;
總結(jié)
源碼地址: 源碼地址 源碼地址: https://github.com/hunter858/OpenGL_Study/AVFoundation/AudiotoolBox-decoder