AudioToolBox 解码AAC

2022-06-23  本文已影响0人  pengxiaochao

上一篇文章中,我们针对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种办法;

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

上一篇下一篇

猜你喜欢

热点阅读