如何在陪玩系统源码开发中实现音频AAC编码?
陪玩系统源码为了方便用户间的交流开发了语音连麦功能,但要想实现用户间语音连麦的实时互动,就要通过各种方式,降低延迟,其中音频编码就是很关键的一步,就目前的陪玩系统源码来说,常用AAC编码标准实现音频数据的编码。音频AAC编码主要使用的是AudioToolBox。,首先我们需要对陪玩系统源码中的音频的编码封装一个工具类。编码工具类头文件 & 初始化音频参数配置类首先,初始化,我们定义一个音频参数配置类,命名为CCAudioConfig👇🏻/**音频配置*/@interfaceCCAudioConfig:NSObject/**码率*/@property(nonatomic,assign)NSIntegerbitrate;//96000)/**声道*/@property(nonatomic,assign)NSIntegerchannelCount;//(1)/**采样率*/@property(nonatomic,assign)NSIntegersampleRate;//(默认44100)/**采样点量化*/@property(nonatomic,assign)NSIntegersampleSize;//(16)+(instancetype)defaultConifg;@end实现部分👇🏻@implementationCCAudioConfig+(instancetype)defaultConifg{return[[CCAudioConfigalloc]init];}-(instancetype)init{self=[superinit];if(self){self.bitrate=96000;self.channelCount=1;self.sampleSize=16;self.sampleRate=44100;}returnself;}@end工具类回调/**AAC编码器代理*/@protocolCCAudioEncoderDelegate<NSObject>-(void)audioEncodeCallback:(NSData*)aacData;@end我们在陪玩系统源码中完成音频编码后,直接输出的是二进制数据NSData。工具类头文件工具类命名为CCAudioEncoder,头文件中应包含初始化方法和编码方法👇🏻/**AAC硬编码器*/@interfaceCCAudioEncoder:NSObject/**编码器配置*/@property(nonatomic,strong)CCAudioConfig*config;@property(nonatomic,weak)id<CCAudioEncoderDelegate>delegate;/**初始化传入编码器配置*/-(instancetype)initWithConfig:(CCAudioConfig*)config;/**编码*/-(void)encodeAudioSamepleBuffer:(CMSampleBufferRef)sampleBuffer;@end初始化流程我们需要定义2个队列 👉🏻 编码队列 + 回调队列,分别异步处理陪玩系统源码中的音频编码和回调结果👇🏻@property(nonatomic,strong)dispatch_queue_tencoderQueue;@property(nonatomic,strong)dispatch_queue_tcallbackQueue;还需要音频编码相关的属性👇🏻//对音频转换器对象@property(nonatomic,unsafe_unretained)AudioConverterRefaudioConverter;//PCM缓存区@property(nonatomic)char*pcmBuffer;//PCM缓存区大小@property(nonatomic)size_tpcmBufferSize;初始化方法中,就是针对以上这些属性的处理👇🏻-(instancetype)initWithConfig:(CCAudioConfig*)config{self=[superinit];if(self){//音频编码队列_encoderQueue=dispatch_queue_create("aachardencoderqueue",DISPATCH_QUEUE_SERIAL);//音频回调队列_callbackQueue=dispatch_queue_create("aachardencodercallbackqueue",DISPATCH_QUEUE_SERIAL);//音频转换器_audioConverter=NULL;_pcmBufferSize=0;_pcmBuffer=NULL;_config=config;if(config==nil){_config=[[CCAudioConfigalloc]init];}}returnself;}编码前的准备在对陪玩系统源码的音频编码之前,我们需要配置音频编码的参数,其中涉及到音频相关的结构体,创建转换器 和 配置属性等函数。1、音频参数结构体音频参数结构体是AudioStreamBasicDescription👇🏻这个结构体提供了对于陪玩系统源码音频文件的描述。音频文件的产生是模拟信号 -> PCM以后的数字信号 -> 压缩、编码以后的音频文件。PCM时采样频率叫做sample rate。每一次采样可以得到若干采样数据,对应多个channel。每一个采样点得到的若干采样数据组合起来,叫做一个frame。若干frame组合起来叫做一个packet。各个成员释义👇🏻mSampleRate,就是采用频率mBitsPerChannel,就是每个采样数据的位数mChannelsPerFrame,可以理解为声道数,也就是一个采样时刻产生几个采样数据mFramesPerPacket,就是每个packet的中frame的个数,等于这个packet中经历了几次采样间隔mBytesPerPacket,每个packet中数据的字节数mBytesPerFrame,每个frame中数据的字节数2、创建converter转换器相关函数是AudioConverterNewSpecific👇🏻各参数释义👇🏻参数1:输入音频格式描述参数2:输出音频格式描述参数3:class desc的数量参数4:class desc参数5:陪玩系统源码创建的转换器3、设置转换器属性相关函数是AudioConverterSetProperty各参数释义👇🏻参数1:转换器参数2:属性的key,可参考枚举AudioConverterPropertyID参数3:属性值value的数据类型的size大小参数4:属性值value的取值地址4、 编码器类型描述编码器类型描述的结构体是AudioClassDescription👇🏻各个成员释义👇🏻mType编码输出的格式,例如AACmSubType编码输出的子格式mManufacturer编码的方式软编码或硬编码编码接着我们来到陪玩系统源码最关键的编码流程,我们对外的编码方法是- (void)encodeAudioSamepleBuffer: (CMSampleBufferRef)sampleBuffer;,调用的地方就是采集类CCSystemCapture的采集回调方法中👇🏻//捕获音视频回调-(void)captureSampleBuffer:(CMSampleBufferRef)sampleBuffertype:(CCSystemCaptureType)type{if(type==CCSystemCaptureTypeAudio){//音频数据//1.直接播放PCM数据NSData*pcmData=[selfconvertAudioSamepleBufferToPcmData:sampleBuffer];[_pcmPlayerplayPCMData:pcmData];//2.AAC编码[_audioEncoderencodeAudioSamepleBuffer:sampleBuffer];}else{[_videoEncoderencodeVideoSampleBuffer:sampleBuffer];}}陪玩系统源码采集的音频,有2种处理方式👇🏻直接播放PCM数据AAC编码我们先看AAC编码👇🏻-(void)encodeAudioSamepleBuffer:(CMSampleBufferRef)sampleBuffer{CFRetain(sampleBuffer);//判断音频转换器是否创建成功.如果未创建成功.则配置音频编码参数且创建转码器if(!_audioConverter){[selfsetupEncoderWithSampleBuffer:sampleBuffer];}//来到音频编码异步队列dispatch_async(_encoderQueue,^{});首先,看看陪玩系统源码配置音频编码参数且创建转码器的流程👇🏻-(void)setupEncoderWithSampleBuffer:(CMSampleBufferRef)sampleBuffer{//获取输入参数AudioStreamBasicDescriptioninputAduioDes=*CMAudioFormatDescriptionGetStreamBasicDescription(CMSampleBufferGetFormatDescription(sampleBuffer));//设置输出参数AudioStreamBasicDescriptionoutputAudioDes={0};outputAudioDes.mSampleRate=(Float64)_config.sampleRate;//采样率outputAudioDes.mFormatID=kAudioFormatMPEG4AAC;//输出格式outputAudioDes.mFormatFlags=kMPEG4Object_AAC_LC;//如果设为0代表无损编码outputAudioDes.mBytesPerPacket=0;//自己确定每个packet大小outputAudioDes.mFramesPerPacket=1024;//每一个packet帧数AAC-1024;outputAudioDes.mBytesPerFrame=0;//每一帧大小outputAudioDes.mChannelsPerFrame=(uint32_t)_config.channelCount;//输出声道数outputAudioDes.mBitsPerChannel=0;//数据帧中每个通道的采样位数。outputAudioDes.mReserved=0;//对其方式0(8字节对齐)//填充输出相关信息UInt32outDesSize=sizeof(outputAudioDes);AudioFormatGetProperty(kAudioFormatProperty_FormatInfo,0,NULL,&outDesSize,&outputAudioDes);//获取编码器的描述信息(只能传入software)AudioClassDescription*audioClassDesc=[selfgetAudioCalssDescriptionWithType:outputAudioDes.mFormatIDfromManufacture:kAppleSoftwareAudioCodecManufacturer];//创建converterOSStatusstatus=AudioConverterNewSpecific(&inputAduioDes,&outputAudioDes,1,audioClassDesc,&_audioConverter);if(status!=noErr){NSLog(@"Error!:硬编码AAC创建失败,status=%d",(int)status);return;}//设置编解码质量/*kAudioConverterQuality_Max=0x7F,kAudioConverterQuality_High=0x60,kAudioConverterQuality_Medium=0x40,kAudioConverterQuality_Low=0x20,kAudioConverterQuality_Min=0*/UInt32temp=kAudioConverterQuality_High;//编解码器的呈现质量AudioConverterSetProperty(_audioConverter,kAudioConverterCodecQuality,sizeof(temp),&temp);//设置比特率uint32_taudioBitrate=(uint32_t)self.config.bitrate;uint32_taudioBitrateSize=sizeof(audioBitrate);status=AudioConverterSetProperty(_audioConverter,kAudioConverterEncodeBitRate,audioBitrateSize,&audioBitrate);if(status!=noErr){NSLog(@"Error!:硬编码AAC设置比特率失败");}}接着我们来到陪玩系统源码编码的异步队列中,需要处理的流程👇🏻获取BlockBuffer中保存的PCM数据CMBlockBufferRefblockBuffer=CMSampleBufferGetDataBuffer(sampleBuffer);CFRetain(blockBuffer);//获取BlockBuffer中音频数据大小以及音频数据地址OSStatusstatus=CMBlockBufferGetDataPointer(blockBuffer,0,NULL,&_pcmBufferSize,&_pcmBuffer);//判断status状态NSError*error=nil;if(status!=kCMBlockBufferNoErr){error=[NSErrorerrorWithDomain:NSOSStatusErrorDomaincode:statususerInfo:nil];NSLog(@"Error:ACCencodegetdatapointerror:%@",error);return;}输出buffer,包装到AudioBufferList中先看看AudioBufferList👇🏻很明显,我们要将上面获取的blockBuffer存储到AudioBufferList的成员AudioBuffer mBuffers[1]之中,这么处理👇🏻//开辟_pcmBuffsize大小的pcm内存空间uint8_t*pcmBuffer=malloc(_pcmBufferSize);//将_pcmBufferSize数据set到pcmBuffer中.memset(pcmBuffer,0,_pcmBufferSize);AudioBufferListoutAudioBufferList={0};outAudioBufferList.mNumberBuffers=1;outAudioBufferList.mBuffers[0].mNumberChannels=(uint32_t)_config.channelCount;outAudioBufferList.mBuffers[0].mDataByteSize=(UInt32)_pcmBufferSize;outAudioBufferList.mBuffers[0].mData=pcmBuffer;配置填充函数,获取输出数据这个填充函数,就是陪玩系统源码编码完成时的回调函数,通过AudioConverterFillComplexBuffer来配置,先看看AudioConverterFillComplexBuffer👇🏻转换由输入回调函数提供的数据,各参数释义如下👇🏻参数1: inAudioConverter 音频转换器参数2: inInputDataProc 回调函数.提供要转换的音频数据的回调函数。当转换器准备好接受新的输入数据时,会重复调用此回调.参数3: inInputDataProcUserData 即self参数4: ioOutputDataPacketSize,输出缓冲区的大小参数5: outOutputData,需要转换的音频数据参数6: outPacketDescription,输出包信息代码如下👇🏻//输出包大小为1UInt32outputDataPacketSize=1;//转换由输入回调函数提供的数据status=AudioConverterFillComplexBuffer(_audioConverter,aacEncodeInputDataProc,(__bridgevoid*_Nullable)(self),&outputDataPacketSize,&outAudioBufferList,NULL);if(status==noErr){//获取数据NSData*rawAAC=[NSDatadataWithBytes:outAudioBufferList.mBuffers[0].mDatalength:outAudioBufferList.mBuffers[0].mDataByteSize];//释放pcmBufferfree(pcmBuffer);//添加ADTS头,想要获取裸流时,请忽略添加ADTS头,写入文件时,必须添加//NSData*adtsHeader=[selfadtsDataForPacketLength:rawAAC.length];//NSMutableData*fullData=[NSMutableDatadataWithCapacity:adtsHeader.length+rawAAC.length];;//[fullDataappendData:adtsHeader];//[fullDataappendData:rawAAC];//将数据传递到回调队列中dispatch_async(_callbackQueue,^{[_delegateaudioEncodeCallback:rawAAC];});}else{error=[NSErrorerrorWithDomain:NSOSStatusErrorDomaincode:statususerInfo:nil];}//释放CFRelease(blockBuffer);CFRelease(sampleBuffer);if(error){NSLog(@"error:AAC编码失败%@",error);}其中,陪玩系统源码编码完成的回调函数指定的是aacEncodeInputDataProc,具体流程请参考3.4 编码回调。接着继续,我们注意到,配置函数成功后,有2种处理的方式👇🏻写入磁盘文件,前提是添加ADTSheader将数据传递到回调队列中,交由调用方去处理解码的流程AAC音频格式说到ADTS头,就必须先讲解下AAC音频格式,分2种👇🏻ADIF:Audio Data Interchange Format音频数据交换格式。这种格式的特征是可以确定的找到这个音频数据的开始,不需进行在音频数据流中间开始的解码,即它的解码必须在明确定义的开始处进行。故这种格式常用在陪玩系统源码磁盘文件中。ADTS:Audio Data Transport Stream音频数据传输流。这种格式的特征是它是一个有同步字的比特流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式。简单说,ADTS可以在任意帧解码,也就是说它每一帧都有头信息。ADIF只有一个统一的头,所以必须得到所有的数据后解码。且这两种的header的格式也是不同的,目前一般陪玩系统源码编码后的和抽取出的都是ADTS格式的音频流👇🏻添加ADTS头这里补充下添加ADTS头的处理流程👇🏻(仅做了解即可)/***AddADTSheaderatthebeginningofeachandeveryAACpacket.*ThisisneededasMediaCodecencodergeneratesapacketofraw*AACdata.**AACADtS头*NotethepacketLenmustcountintheADTSheaderitself.*See:*Also:**/-(NSData*)adtsDataForPacketLength:(NSUInteger)packetLength{intadtsLength=7;char*packet=malloc(sizeof(char)*adtsLength);//VariablesRecycledbyaddADTStoPacketintprofile=2;//AACLC//39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;intfreqIdx=4;//3:48000Hz、4:44.1KHz、8:16000Hz、11:8000HzintchanCfg=1;//MPEG-4AudioChannelConfiguration.1Channelfront-centerNSUIntegerfullLength=adtsLength+packetLength;//fillinADTSdatapacket[0]=(char)0xFF;//11111111=syncwordpacket[1]=(char)0xF9;//11111001=syncwordMPEG-2LayerCRCpacket[2]=(char)(((profile-1)<<6)+(freqIdx<<2)+(chanCfg>>2));packet[3]=(char)(((chanCfg&3)<<6)+(fullLength>>11));packet[4]=(char)((fullLength&0x7FF)>>3);packet[5]=(char)(((fullLength&7)<<5)+0x1F);packet[6]=(char)0xFC;NSData*data=[NSDatadataWithBytesNoCopy:packetlength:adtsLengthfreeWhenDone:YES];returndata;}编码回调最后我们看看陪玩系统源码的编码回调函数aacEncodeInputDataProc的流程 👇🏻staticOSStatusaacEncodeInputDataProc(AudioConverterRefinAudioConverter,UInt32*ioNumberDataPackets,AudioBufferList*ioData,AudioStreamPacketDescription**outDataPacketDescription,void*inUserData){//获取selfCCAudioEncoder*aacEncoder=(__bridgeCCAudioEncoder*)(inUserData);//判断pcmBuffsize大小if(!aacEncoder.pcmBufferSize){*ioNumberDataPackets=0;return-1;}//填充ioData->mBuffers[0].mData=aacEncoder.pcmBuffer;ioData->mBuffers[0].mDataByteSize=(uint32_t)aacEncoder.pcmBufferSize;ioData->mBuffers[0].mNumberChannels=(uint32_t)aacEncoder.config.channelCount;//填充完毕,则清空数据aacEncoder.pcmBufferSize=0;*ioNumberDataPackets=1;returnnoErr;}主要就是将陪玩系统源码解码的数据(缓存在CCAudioEncoder实例中)填充到AudioBufferList中。小结如上图,陪玩系统源码的编码总计三大步:陪玩系统源码配置编码器,开始准备编码;收集到PCM数据,传给编码器;编码完成回调callback,或写入文件。本文转载自网络,转载仅为分享干货知识,如有侵权欢迎联系云豹科技进行删除处理
发表回复