音频AAC解码
在相亲交友源码中,为了提升音频数据的传输效率,我们需要对音频数据进行编码处理,但是编码后的音频文件是无法直接播放的,需要进行解码处理,今天我们就来看一下在相亲交友源码开发中,实现音频AAC解码的相关代码。
在解码时 👉🏻 我们需要封装一个工具类CCAudioDecoder。
解码工具类头文件
#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
@class CCAudioConfig;
/**AAC解码回调代理*/
@protocol CCAudioDecoderDelegate <NSObject>
- (void)audioDecodeCallback:(NSData *)pcmData;
@end
@interface CCAudioDecoder : NSObject
@property (nonatomic, strong) CCAudioConfig *config;
@property (nonatomic, weak) id<CCAudioDecoderDelegate> delegate;
//初始化 传入解码配置
- (instancetype)initWithConfig:(CCAudioConfig *)config;
/**解码aac*/
- (void)decodeAudioAACData: (NSData *)aacData;
@end
.m中的扩展类👇🏻
@interface CCAudioDecoder()
@property (nonatomic, strong) dispatch_queue_t decoderQueue;
@property (nonatomic, strong) dispatch_queue_t callbackQueue;
//对音频转换器对象
@property (nonatomic) AudioConverterRef audioConverter;
//AAC缓存区
@property (nonatomic) char *aacBuffer;
//AAC缓存区大小
@property (nonatomic) UInt32 aacBufferSize;
//音频流包的描述信息
@property (nonatomic) AudioStreamPacketDescription *packetDesc;
@end
初始化
接着看看初始化👇🏻
- (instancetype)initWithConfig:(CCAudioConfig *)config {
self = [super init];
if (self) {
_decoderQueue = dispatch_queue_create("aac hard decoder queue", DISPATCH_QUEUE_SERIAL);
_callbackQueue = dispatch_queue_create("aac hard decoder callback queue", DISPATCH_QUEUE_SERIAL);
_audioConverter = NULL;
_aacBufferSize = 0;
_aacBuffer = NULL;
_config = config;
if (_config == nil) {
_config = [[CCAudioConfig alloc] init];
}
AudioStreamPacketDescription desc = {0};
_packetDesc = &desc;
[self setupEncoder];
}
return self;
}
然后是setupEncoder👇🏻
- (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
OSStatus status = AudioConverterNewSpecific(&inputAduioDes, &outputAudioDes, 1, audioClassDesc, &_audioConverter);
if (status != noErr) {
NSLog(@"Error!:硬解码AAC创建失败, status= %d", (int)status);
return;
}
}
和编码的- (void)setupEncoderWithSampleBuffer: (CMSampleBufferRef)sampleBuffer流程基本上一模一样。区别在于相亲交友源码创建解码器的方法getAudioCalssDescriptionWithType:fromManufacture:👇🏻
- (AudioClassDescription *)getAudioCalssDescriptionWithType:(AudioFormatID)type fromManufacture:(uint32_t)manufacture {
static AudioClassDescription desc;
UInt32 decoderSpecific = type;
//获取满足AAC解码器的总大小
UInt32 size;
OSStatus status = AudioFormatGetPropertyInfo(kAudioFormatProperty_Decoders, sizeof(decoderSpecific), &decoderSpecific, &size);
if (status != noErr) {
NSLog(@"Error!:硬解码AAC get info 失败, status= %d", (int)status);
return nil;
}
//计算aac解码器的个数
unsigned int count = size / sizeof(AudioClassDescription);
//创建一个包含count个解码器的数组
AudioClassDescription description[count];
//将满足aac解码的解码器的信息写入数组
status = AudioFormatGetProperty(kAudioFormatProperty_Encoders, sizeof(decoderSpecific), &decoderSpecific, &size, &description);
if (status != noErr) {
NSLog(@"Error!:硬解码AAC get propery 失败, status= %d", (int)status);
return nil;
}
for (unsigned int i = 0; i < count; i++) {
if (type == description[i].mSubType && manufacture == description[i].mManufacturer) {
desc = description[i];
return &desc;
}
}
有以下几点区别👇🏻
- 输出参数不同:编码是AAC 解码是PCM
- 转换器不同,编码是kAudioFormatProperty_Encoders,解码是kAudioFormatProperty_Decoders
相亲交友源码音频解码前的准备
我们封装了个结构体CCAudioUserData,用于记录aac的信息 作为解码回调函数的参数👇🏻
typedef struct {
char * data;
UInt32 size;
UInt32 channelCount;
AudioStreamPacketDescription packetDesc;
} CCAudioUserData;
解码
接着就是相亲交友源码中的音频解码流程了👇🏻
- (void)decodeAudioAACData:(NSData *)aacData {
if (!_audioConverter) { return; }
dispatch_async(_decoderQueue, ^{
//CCAudioUserData记录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);
});
}
其中解码的回调函数是AudioDecoderConverterComplexInputDataProc。
解码回调
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;
}
在相亲交友源码实现音频解码的过程中你会发现,不需要桥接self对象,因为我们把数据缓存在自定义的结构体CCAudioUserData之中。这点也是和编码的区别。
至此,解码的工具类已封装完毕!