相親交友原始碼中,音訊AAC解碼的實現程式碼

雲豹科技程式設計師發表於2021-11-15

音訊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之中。這點也是和編碼的區別。 至此,解碼的工具類已封裝完畢!
本文轉載自網路,轉載僅為分享乾貨知識,如有侵權歡迎聯絡雲豹科技進行刪除處理


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69996194/viewspace-2842260/,如需轉載,請註明出處,否則將追究法律責任。

相關文章