ios 實時音頻AAC格式轉碼---分解LFLiveKit


概念
/*
AAC - Advanced Audio Coding - 高級音頻編碼,基於 MPEG-2 的音頻編碼技術 2000年后,MPEG-4標准發布,為了區別於MPEG-2 AAC 特別加入了SBR技術和PS技術,稱之 MPEG-4 AAC (kAudioFormatMPEG4AAC) 特點1: 壓縮率提升,以更小的文件獲得更高的音質 特點2: 支持多通道 特點3: 更高的解析度,最高支持96khz的采樣率 特點4: 更高的解碼效率,解碼占用資源更少 AAC音頻文件的每一幀由ADTS Header和AAC Audio Data組成。 AAC 的音頻格式ADTS、ADIF ADIF: 音頻數據交換格式化,可以確定的找到音頻數據的開始處,即解碼相關屬性參數必須明確定義在文件開始處 ADTS: 音頻數據傳輸流,他是一個有同步字的比特流,可以在音頻流中任何位置開始,結構是 header&body,header&body... 一般頭信息有7(or 9)個字節,分為兩部分adts_fixed_header()-28bits 、 adts_variable_header()-28bits protection_absent=1 7字節 =0 9字節 */

 

#import "LFHardwareAudioEncoder.h"

typedef struct DelegateStruct {
    unsigned int  encoder;
    
} DelegateType;


@interface LFHardwareAudioEncoder (){
    AudioConverterRef m_converter; /* 音頻格式轉換工具 */
    char *leftBuf;                 /* char 指針--->pcm格式音頻數據內存地址*/
    char *aacBuf;                  /* char 指針--->aac格式音頻數據內存地址*/
    NSInteger leftLength;          /* 內存數據長度 */
    FILE *fp;                      /* 文件指針(用於打開文件進行操作) 詳細參考本博客中pcm轉mp3(方案一)*/
    BOOL enabledWriteVideoFile;    /* 是否本地保存轉換后音頻格式的文件 */
}
@property (nonatomic, strong) LFLiveAudioConfiguration *configuration;
@property (nonatomic, weak) id<LFAudioEncodingDelegate> aacDeleage;
@property (nonatomic, assign) DelegateType delegateType;

 

 /*
         extern void *malloc(unsigned int num_bytes);
         
         malloc - memory allocation - 動態內存分配,
         用於申請一塊連續且指定大小的內存區域,以void*類型返回系統分配的內存地址,一般和free函數配對使用。
         void * 標識未確定類型的指針,C、C++中,此void*類型可以通過類型強制轉換成其他類型指針。
         
         申請長度為 1024*2*self.numberOfChannels 字節的內存空間
         */
        if (!leftBuf) {
            leftBuf = malloc(_configuration.bufferLength);
        }
        
        if (!aacBuf) {
            aacBuf = malloc(_configuration.bufferLength);
        }

 

- (void)dealloc {
    
    /*
     void     free(void *);
     釋放通過malloc(或calloc、realloc)函數申請的內存空間
     */
    if (aacBuf) free(aacBuf);
    if (leftBuf) free(leftBuf);
}

 

- (void)encodeAudioData:(nullable NSData*)audioData timeStamp:(uint64_t)timeStamp {
    if (![self createAudioConvert]) {
        return;
    }
    
    /* memcpy: C 和 C++ 常用的內存拷貝函數
     void *memcpy(void *dest, const void *src, size_t n);
     從源src指向的內存地址的起始位置開始拷貝n個字節到到dest指向的內存地址的起始位置處,返回指向dest內存地址的指針
     */
    
  
    /*
     預設條件:
     self.configuration.bufferLength = 100 字節
     全局變量初始化 leftLength=0
     char類型數據占用 1 個字節的內存
     
     《《《《《《《《《《 第一次收到數據 audioData.length = 40字節數據  》》》》》》》》》》
     leftLength + audioData.length = 0+40=40 < 100 所以走else邏輯
     1. 從 接收的pcm數據(audioData.bytes)的起始位置 拷貝 40 字節數據到以第0字節為開始的leftBuf內存地址(leftBuf+leftLength=0)
     2. 累積  leftLength = leftLength + audioData.length = 0 + 40 = 40

     
     《《《《《《《《《《 第二次收到數據 audioData.length = 55字節數據  》》》》》》》》》》
     leftLength + audioData.length = 40 + 55=95 < 100 所以走else邏輯
     1. 從 接收的pcm數據(audioData.bytes)的起始位置 拷貝 55 字節數據到以第40字節開始的leftBuf內存地址(0+40=40)
     2. 累積  leftLength = leftLength + audioData.length = 40 + 55 = 95
     

     《《《《《《《《《《 第三次收到數據 audioData.length = 120字節數據  》》》》》》》》》》
     audioData.length = 120
     leftLength + audioData.length = 95 + 120=215 > 100 所以走if邏輯

     1. 計算當前總字節數 totalSize = leftLength + audioData.length = 95 + 120 = 215
     2. 計算 循環發送編碼數據次數 encodeCount = totalSize/self.configuration.bufferLength = 215 / 100 = 2
     3. 聲明一個totalBuf指向 申請一塊 totalSize 字節的內存空間地址的指針,指針不會發生偏移,一直指向開始位置
     4. 聲明 p是一個變量指針(支持算數運算)記錄發送的位置,指針會發生偏移
     5. 將 totalBuf 指向的內存空間清空(用於重新存放數據)
     6. 從 leftBuf 內存地址的0開始位置拷貝 leftLength = 95 字節數據到以第0字節開始的totalBuf內存地址中
     7. 從 pcm數據(audioData.bytes)的起始位置 拷貝 120 字節數據到以第95字節開始的totalBuf內存地址中(totalBuf+leftLength=0+95=95)
     8. 開始循環編碼 (循環 encodeCount = 2 次)
     8-1. 從totalBuf起始位置0,發送 self.configuration.bufferLength = 100 字節數據進行編碼
     8-2. 從totalBuf起始位置100,發送 self.configuration.bufferLength = 100 字節數據進行編碼,
     8-3. 循環結束
     9. 計算剩余字節數 leftLength = totalSize%self.configuration.bufferLength = 215%100 = 15 字節
     10. 清空leftBuf
     11. 從 totalBuf 中 以第200(0+(215-15))字節開始拷貝剩余的15字節到以第0字節開始的leftBuf內存地址中,繼續累積
     12. 釋放(系統回收) 申請的 totalBuf 的內存空間
     
     
     《《《《《《《《《《 第四次收到數據 audioData.length = 30字節數據  》》》》》》》》》》
     leftLength + audioData.length = 15+30=45 < 100 所以走else邏輯
     1. 從 接收的pcm數據(audioData.bytes)的起始位置 拷貝 30 字節數據到以第15字節開始的leftBuf內存地址(leftBuf+leftLength=15)
     2. 累積  leftLength = leftLength + audioData.length = 15 + 30 = 45

     
     《《《《《《《《《《 第 N 次收到數據 audioData.length = X字節數據  》》》》》》》》》》

     */
    
    
    /* 參考:https://www.jianshu.com/p/4dd2009b0902 對下面代碼的邏輯解釋*/
    if(leftLength + audioData.length >= self.configuration.bufferLength){
        ///<  發送
        NSInteger totalSize = leftLength + audioData.length;
        NSInteger encodeCount = totalSize/self.configuration.bufferLength;
       
        char *totalBuf = malloc(totalSize);
        char *p = totalBuf;

        memset(totalBuf, 0, (int)totalSize);
        memcpy(totalBuf, leftBuf, leftLength);
        memcpy(totalBuf + leftLength, audioData.bytes, audioData.length);
        
        for(NSInteger index = 0;index < encodeCount;index++){
            [self encodeBuffer:p  timeStamp:timeStamp];
            p += self.configuration.bufferLength;
        }
        
        leftLength = totalSize%self.configuration.bufferLength;
        memset(leftBuf, 0, self.configuration.bufferLength);
        memcpy(leftBuf, totalBuf + (totalSize -leftLength), leftLength);
        
        // 釋放申請的內存空間
        free(totalBuf);
        
    }else{
        ///< 積累
        /*
         memcpy(leftBuf, audioData.bytes, audioData.length);
         如果按照上面的寫法會導致把上一次copy的data給覆蓋,就無法實現疊加效果。
         用一個全局變量 leftLength 保存上一次copy的data的長度,下一次在此基礎上疊加,
         這樣能夠實現指針偏移的目的(指針偏移到上一次data的末尾處),但是指針指向也發生了變化。
         */
        memcpy(leftBuf+leftLength, audioData.bytes, audioData.length);
        leftLength = leftLength + audioData.length;
    }
}

 

- (void)encodeBuffer:(char*)buf timeStamp:(uint64_t)timeStamp{
    
    /*
     設置輸入緩沖
     */
    AudioBuffer inBuffer;
    inBuffer.mNumberChannels = 1;
    inBuffer.mData = buf;
    inBuffer.mDataByteSize = (UInt32)self.configuration.bufferLength;
    
    AudioBufferList inBufferList;
    inBufferList.mNumberBuffers = 1;
    inBufferList.mBuffers[0] = inBuffer;
    
    /*
     設置輸出緩沖
     */
    AudioBufferList outBufferList;
    outBufferList.mNumberBuffers = 1;
    outBufferList.mBuffers[0].mNumberChannels = inBuffer.mNumberChannels;
    outBufferList.mBuffers[0].mDataByteSize = inBuffer.mDataByteSize;
    outBufferList.mBuffers[0].mData = aacBuf;
    UInt32 outputDataPacketSize = 1;
    
    /*
     音頻格式轉換(實現所有音頻格式之間的轉換,不限於AAC),返回AAC的原始音頻數據流,然后需要添加ADTS頭數據
     而 AudioConverterConvertComplexBuffer 把音頻數據從線性PCM轉換成其他格式,而轉換的格式必須具有相同的采樣率、通道等參數。
     
     param1. 編碼器
     param2. 回調函數 編碼過程中,會要求這個函數來填充輸入數據(把原始PCM數據輸入給編碼器)
     param3. 輸入緩沖數據的地址《指針類型》
     param4. 輸出的包大小《指針類型》
     param5. 輸出的緩沖數據的地址《指針類型》
     param6. 輸出數據的描述 
     */
    if (AudioConverterFillComplexBuffer(m_converter,
                                        inputDataProc,
                                        &inBufferList,
                                        &outputDataPacketSize,
                                        &outBufferList,
                                        NULL) != noErr) {
        return;
    }
    
    LFAudioFrame *audioFrame = [LFAudioFrame new];
    audioFrame.timestamp = timeStamp;
    audioFrame.data = [NSData dataWithBytes:aacBuf length:outBufferList.mBuffers[0].mDataByteSize];
    
    /*
     添加ADTS頭信息 參考https://blog.csdn.net/jay100500/article/details/52955232
     
     self.asc[0] = 0x10 | ((sampleRateIndex>>1) & 0x7);
     self.asc[1] = ((sampleRateIndex & 0x1)<<7) | ((self.numberOfChannels & 0xF) << 3);
     */
    char exeData[2];
    exeData[0] = _configuration.asc[0];
    exeData[1] = _configuration.asc[1];
    audioFrame.audioInfo = [NSData dataWithBytes:exeData length:2];
    
    if (_delegateType.encoder == 1) {
        [self.aacDeleage audioEncoder:self audioFrame:audioFrame];
    }
    
    /*
    if (self.aacDeleage && [self.aacDeleage respondsToSelector:@selector(audioEncoder:audioFrame:)]) {
        [self.aacDeleage audioEncoder:self audioFrame:audioFrame];
    }
     */

    
    /*
     AAC文件寫入沙盒
     */
    if (self->enabledWriteVideoFile) {
        NSData *adts = [self adtsData:_configuration.numberOfChannels rawDataLength:audioFrame.data.length];
        fwrite(adts.bytes, 1, adts.length, self->fp);
        fwrite(audioFrame.data.bytes, 1, audioFrame.data.length, self->fp);
    }
    
}

 

/*
 inUserData 就是輸入給編碼器的 pcm 數據(就是AudioConverterFillComplexBuffer中 &inBufferList)
 把輸入的pcm數據copy到ioData中,ioData就是編碼器工作時用到的輸入緩沖數據的地址
 */
OSStatus inputDataProc(AudioConverterRef               inAudioConverter,
                       UInt32          *               ioNumberDataPackets,
                       AudioBufferList *               ioData,
                       AudioStreamPacketDescription * __nullable * __nullable outDataPacketDescription,
                       void * __nullable               inUserData) {
    AudioBufferList bufferList = *(AudioBufferList *)inUserData;
    ioData->mBuffers[0].mNumberChannels = 1;
    ioData->mBuffers[0].mData = bufferList.mBuffers[0].mData;
    ioData->mBuffers[0].mDataByteSize = bufferList.mBuffers[0].mDataByteSize;
    return noErr;
}

 

- (BOOL)createAudioConvert { //根據輸入樣本初始化一個編碼轉換器
    if (m_converter != nil) {
        return TRUE;
    }
    
    /*
     描述輸入&輸出的音頻數據
     
     */
    AudioStreamBasicDescription inputFormat = {0};
    inputFormat.mSampleRate = _configuration.audioSampleRate;
    inputFormat.mFormatID = kAudioFormatLinearPCM;
    inputFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked;
    inputFormat.mChannelsPerFrame = (UInt32)_configuration.numberOfChannels;
    inputFormat.mFramesPerPacket = 1;
    inputFormat.mBitsPerChannel = 16;
    inputFormat.mBytesPerFrame = inputFormat.mBitsPerChannel / 8 * inputFormat.mChannelsPerFrame;
    inputFormat.mBytesPerPacket = inputFormat.mBytesPerFrame * inputFormat.mFramesPerPacket;
    
    AudioStreamBasicDescription outputFormat;
    memset(&outputFormat, 0, sizeof(outputFormat));
    outputFormat.mSampleRate = inputFormat.mSampleRate;       // 采樣率保持一致
    outputFormat.mFormatID = kAudioFormatMPEG4AAC;            // AAC編碼 kAudioFormatMPEG4AAC kAudioFormatMPEG4AAC_HE_V2
    outputFormat.mChannelsPerFrame = (UInt32)_configuration.numberOfChannels;;
    outputFormat.mFramesPerPacket = 1024;                     // AAC一幀是1024個字節
    
    const OSType subtype = kAudioFormatMPEG4AAC;
    
    /*
     AudioClassDescription: 用於描述系統中安裝的編解碼工具
     音頻編碼器組件類型
     音頻格式AAC
     軟編碼和硬編碼
     */
    AudioClassDescription requestedCodecs[2] = {
        {
            kAudioEncoderComponentType,
            subtype,
            kAppleSoftwareAudioCodecManufacturer
        },
        {
            kAudioEncoderComponentType,
            subtype,
            kAppleHardwareAudioCodecManufacturer
        }
    };
    
    /*
     用特定的編碼器創建一個音頻轉換工具對象
     param1. 輸入格式
     param2. 輸出格式
     param3. 編碼器描述類個數
     param4. 編碼器描述類
     param5. 編碼器地址
     */
    OSStatus result = AudioConverterNewSpecific(&inputFormat,
                                                &outputFormat,
                                                2,
                                                requestedCodecs,
                                                &m_converter);;
    UInt32 outputBitrate = _configuration.audioBitrate;
    UInt32 propSize = sizeof(outputBitrate);
    
    
    if(result == noErr) {
        /*
         設置編碼器的碼率屬性
         */
        result = AudioConverterSetProperty(m_converter,
                                           kAudioConverterEncodeBitRate,
                                           propSize,
                                           &outputBitrate);
    }
    
    return YES;
}

 

- (NSData *)adtsData:(NSInteger)channel rawDataLength:(NSInteger)rawDataLength {
  
    /* adts頭信息的長度 7 字節 */
    int adtsLength = 7;
    /* 在堆區申請 7 字節的內存空間 */
    char *packet = malloc(sizeof(char) * adtsLength);
    /* AAC LC Variables Recycled by addADTStoPacket */
    int profile = 2;
    /* 獲取采樣率對應的索引(下標)  39=MediaCodecInfo.CodecProfileLevel.AACObjectELD*/
    NSInteger freqIdx = [self sampleRateIndex:self.configuration.audioSampleRate];  //44.1KHz
    /* 獲取通道數*/
    int chanCfg = (int)channel;  //MPEG-4 Audio Channel Configuration. 1 Channel front-center
    /* 獲取 adts頭 + aac原始流 的總長度,即每一個aac數據幀的長度*/
    NSUInteger fullLength = adtsLength + rawDataLength;
    // fill in ADTS data
    packet[0] = (char)0xFF;     // 11111111     = syncword
    packet[1] = (char)0xF9;     // 1111 1 00 1  = syncword MPEG-2 Layer CRC
    packet[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 = [NSData dataWithBytesNoCopy:packet length:adtsLength freeWhenDone:YES];
    return data;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM