上一篇文章中,我们针对PCM
数据,通过AudioToolBox
将PCM 数据
编码成AAC 数据
,并把AAC 数据
添加ADTS Header
,并把AAC
格式的音频数据写入文件;
这一章呢,我们主要是用AudioToolBox
把AAC数据
解码成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
主要是写入文件需要的;
通过AudioEncoder
的audioEncodeCallback
代理方法拿到编码后的AAC 数据;
- (void)audioEncodeCallback:(NSData *)aacData;
3. AudioToolBox 创建
关于AudioToolBox
的创建 和编码部分一致,只是解码部分,把input
和output
的配置 做了一个调换(这么理解);
这里我们创建一个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
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