iOS下 AAC 音頻編碼

編碼結構圖

前言

iOS下Apple爲咱們提供了很是方便的音頻編解碼工具AudioToolbox。該工具中包含了常見的編解碼庫,如AAC、iLBC、OPUS等。今天咱們就介紹一下如何使用 AudioToolbox 進行AAC音頻的編碼工做。數組

AAC編碼的基本流程

在 iOS 中進行AAC編碼的流程比較簡單,按如下幾步便可完成。bash

  • 設置AAC編器的輸入、輸出格式。
  • 建立AAC編碼器。
  • 轉碼。
  • 獲得AAC編碼數據後,增長ADTS頭。該頭用於區分每一個AAC數據幀。

下面咱們詳細介紹每一步。ide

設置轉碼格式

在建立編碼器以前,咱們首先要設置好編碼器的輸入數據格式和輸出數據格式。好比輸入數據是單聲道仍是雙聲道,數據是什麼格式的,採樣率是多少等。一樣的,輸出參數是AAC,仍是OPUS? 每一個傳輸包的大小等。只有這樣,AudioToolbox才清楚他要建立一個什麼樣的編解碼器。函數

固然,這與建立編碼器的函數也有關。該函數的前兩個輸入參數就是音頻輸入格式和輸出格式。函數原型以下:工具

AudioConverterNewSpecific( 
    inSourceFormat: AudioStreamBasicDescription, //輸入參數
    inDestinationFormat: AudioStreamBasicDescription, //輸出參數
    inNumberClassDescriptions: UInt32, //音頻描述符數量
    inClassDescriptions: AudioClassDescription, //音頻描述符數組
    outAudioConverter: AudioConverterRef //編碼器
    ) -> OSStatus
複製代碼

因此,基於以上兩個緣由,在建立編碼器以前必定要先將輸入、輸出格式設置好。ui

下面咱們來看一下設置輸入、輸出格式的代碼。this

AudioStreamBasicDescription inAudioStreamBasicDescription =
 *CMAudioFormatDescriptionGetStreamBasicDescription((CMAudioFormatDescriptionRef)
 CMSampleBufferGetFormatDescription(sampleBuffer));
 
複製代碼

上面這段代碼就是輸入格式的設置。這裏用到了一個小技巧,設置編碼器的輸入格式是經過傳入的第一個音頻數據包來得到的。由於,在iOS中每一個音視頻的輸入數據中都包含了必要的參數。而iOS也爲咱們提供了提取這些數據的方法,很是方便。編碼

下面的代碼是對編碼器輸出格式的設置。 註釋已經寫的很是詳細了。spa

// 先將輸出描述符清0
AudioStreamBasicDescription outAudioStreamBasicDescription = {0}; 

// 設置採樣率,有 32K, 44.1K,48K
outAudioStreamBasicDescription.mSampleRate = 44100; 

// 音頻格式能夠設置爲 :
// kAudioFormatMPEG4AAC_HE 
// kAudioFormatMPEG4AAC_HE_V2
// kAudioFormatMPEG4AAC
outAudioStreamBasicDescription.mFormatID = kAudioFormatMPEG4AAC; 

// 指明格式的細節. 設置爲 0 說明沒有子格式。
// 若是 mFormatID 設置爲 kAudioFormatMPEG4AAC_HE 該值應該爲0
outAudioStreamBasicDescription.mFormatFlags = kMPEG4Object_AAC_LC;

// 每一個音頻包的字節數. 
// 該字段設置爲 0, 代表包裏的字節數是變化的。
// 對於使用可變包大小的格式,請使用AudioStreamPacketDescription結構指定每一個數據包的大小。 
outAudioStreamBasicDescription.mBytesPerPacket = 0;

// 每一個音頻包幀的數量. 對於未壓縮的數據設置爲 1. 
// 動態碼率格式,這個值是一個較大的固定數字,好比說AAC的1024。
// 若是是動態幀數(好比Ogg格式)設置爲0。
outAudioStreamBasicDescription.mFramesPerPacket = 1024; 

// 每一個幀的字節數。對於壓縮數據,設置爲 0.
outAudioStreamBasicDescription.mBytesPerFrame = 0; 

// 音頻聲道數
outAudioStreamBasicDescription.mChannelsPerFrame = 1;

// 壓縮數據,該值設置爲0.
outAudioStreamBasicDescription.mBitsPerChannel = 0;

// 用於字節對齊,必須是0.
outAudioStreamBasicDescription.mReserved = 0; 

複製代碼

下一步,咱們來建立編碼器。指針

建立編解碼器

建立編碼器除了上面說的要設置輸入輸出數據格式外,還要告訴 AudioToolbox 是建立編碼器仍是建立解碼器;是建立 AAC 的,仍是建立OPUS的;是硬編碼仍是軟編碼。

iOS爲咱們提供了 AudioClassDescription 來描述這些信息。它包括下面三個字段:

struct AudioClassDescription {
    OSType  mType; 
    OSType  mSubType;
    OSType  mManufacturer;
};

複製代碼
  • mType: 指明提編碼器仍是解碼器。kAudioDecoderComponentType/kAudioEncoderComponentType。
  • mSubType: 指明是 AAC, iLBC 仍是 OPUS等。
  • mManufacturer: 指明是軟編仍是硬編碼。

瞭解了上面的信息後,咱們再來看下面的代碼就很好理解了。

  • 首先經過 AudioFormatGetPropertyInfo 獲取音頻屬性信息。在這裏就是得到全部與 格式ID一致的描術信息的個數。格式ID在這裏就是 kMPEG4Object_AAC_LC
  • 而後,使用 AudioFormatGetProperty 獲取音頻格式屬性值,在這裏就是獲得全部的音頻描述符。
  • 找到與用戶指定一致的描述符。
  • 最後調用 AudioConverterNewSpecific 建立轉碼器。
...

AudioClassDescription audioClassDescription;
memset(&audioClassDescription, 0, sizeof(audioClassDescription));
UInt32 size;

//根據編碼格式,獲取描述符個數。
NSAssert(AudioFormatGetPropertyInfo(kAudioFormatProperty_Encoders, 
            sizeof(outAudioStreamBasicDescription.mFormatID),
            &outAudioStreamBasicDescription.mFormatID, 
            &size) == noErr, nil);
            
uint32_t count = size / sizeof(AudioClassDescription);

//取出全部的描述符
AudioClassDescription descriptions[count];
NSAssert(AudioFormatGetProperty(kAudioFormatProperty_Encoders,
            sizeof(outAudioStreamBasicDescription.mFormatID), 
            &outAudioStreamBasicDescription.mFormatID, 
            &size,
            descriptions) == noErr, nil);
            
//找出與輸出格式一致的軟編描述符
for (uint32_t i = 0; i < count; i++) {

    if ((outAudioStreamBasicDescription.mFormatID == descriptions[i].mSubType) && 
        (kAppleSoftwareAudioCodecManufacturer == descriptions[i].mManufacturer)) {

        memcpy(&audioClassDescription, &descriptions[i], sizeof(audioClassDescription));

    }
}

//建立軟編碼器
NSAssert(audioClassDescription.mSubType == outAudioStreamBasicDescription.mFormatID &&
            audioClassDescription.mManufacturer == kAppleSoftwareAudioCodecManufacturer, nil);

AudioConverterRef audioConverter;
memset(&audioConverter, 0, sizeof(audioConverter));
NSAssert(AudioConverterNewSpecific(&inAudioStreamBasicDescription,
            &outAudioStreamBasicDescription,
            1, 
            &audioClassDescription,
            &audioConverter) == 0, nil);
...

複製代碼

建立好編碼器後,還要修改一下編碼器的碼率。若是要正確的編碼,編碼碼率參數是必須設置的。代碼以下:

...

UInt32 outputBitrate = 64000;
UInt32 propSize = sizeof(outputBitrate);
    
if(result == noErr) {
    result = AudioConverterSetProperty(audioConverter,
                 kAudioConverterEncodeBitRate, 
                 propSize, 
                 &outputBitrate);
}
...

複製代碼

須要注意,AAC並非隨便的碼率均可以支持。好比,若是PCM採樣率是44100KHz,那麼碼率能夠設置64000bps,若是是16K,能夠設置爲32000bps。

設置好碼率後,能夠經過 AudioConverterGetProperty 方法查詢一下是否已經設置成功。代碼以下:

UInt32 value = 0;  
size = sizeof(value);  
AudioConverterGetProperty(audioConverter,
            kAudioConverterPropertyMaximumOutputPacketSize,
            &size, 
            &value);
複製代碼

下面咱們來看下如何進行轉碼。

轉碼

iOS 使用 AudioConverterFillComplexBuffer 方法進行轉碼。它的參數以下:

AudioConverterFillComplexBuffer(
            inAudioConverter: AudioConverterRef, 
            inInputDataProc: AudioConverterComplexInputDataProc, 
            inInputDataProcUserData: UnsafeMutablePointer, 
            ioOutputDataPacketSize: UnsafeMutablePointer<UInt32>, 
            outOutputData: UnsafeMutablePointer<AudioBufferList>, 
            outPacketDescription: AudioStreamPacketDescription
            ) -> OSStatus
複製代碼
  • inAudioConverter : 轉碼器
  • inInputDataProc : 回調函數。用於將PCM數據餵給編碼器。
  • inInputDataProcUserData : 用戶自定義數據指針。
  • ioOutputDataPacketSize : 輸出數據包大小。
  • outOutputData : 輸出數據 AudioBufferList 指針。
  • outPacketDescription : 輸出包描述符。

下面是轉碼的具體代碼:

  • 首先,建立一個 AudioBufferList,並將輸入數據存到 AudioBufferList裏。
  • 其次,設置輸出。
  • 而後,調用 AudioConverterFillComplexBuffer 方法,該方法又會調用 inInputDataProc 回調函數,將輸入數據拷貝到編碼器中。
  • 最後,轉碼。將轉碼後的數據輸出到指定的輸出變量中。
//設置輸入
AudioBufferList inAaudioBufferList;
CMBlockBufferRef blockBuffer;
CMSampleBufferGetAudioBufferListWithRetainedBlockBuffer(sampleBuffer, NULL, &inAaudioBufferList, sizeof(inAaudioBufferList), NULL, NULL, 0, &blockBuffer);
NSAssert(inAaudioBufferList.mNumberBuffers == 1, nil);

//設置輸出
uint32_t bufferSize = inAaudioBufferList.mBuffers[0].mDataByteSize;
uint8_t *buffer = (uint8_t *)malloc(bufferSize);
memset(buffer, 0, bufferSize);
AudioBufferList outAudioBufferList;
outAudioBufferList.mNumberBuffers = 1;
outAudioBufferList.mBuffers[0].mNumberChannels = inAaudioBufferList.mBuffers[0].mNumberChannels;
outAudioBufferList.mBuffers[0].mDataByteSize = bufferSize;
outAudioBufferList.mBuffers[0].mData = buffer;

UInt32 ioOutputDataPacketSize = 1;

//轉碼
NSAssert(
    AudioConverterFillComplexBuffer(audioConverter, 
                    inInputDataProc, 
                    &inAaudioBufferList, 
                    &ioOutputDataPacketSize, 
                    &outAudioBufferList, NULL) == 0, 
nil);

//將輸出數據變成 NSData 數據
NSData *data = [NSData 
                dataWithBytes:outAudioBufferList.mBuffers[0].mData 
                length:outAudioBufferList.mBuffers[0].mDataByteSize];

free(buffer);
CFRelease(blockBuffer);

複製代碼

下面咱們看一下 inInputDataProc 這個回調函數的具體實現。其中 inUserData 就是在 AudioConverterFillComplexBuffer 方法中傳入的第三個參數,也就是輸入數據。

inInputDataProc 回調函數的做用就是將輸入數據拷貝到 ioData 中。ioData 就是編碼器編碼時用到的真正輸入緩衝區。

OSStatus inInputDataProc(AudioConverterRef inAudioConverter, 
            UInt32 *ioNumberDataPackets, 
            AudioBufferList *ioData, 
            AudioStreamPacketDescription **outDataPacketDescription, 
            void *inUserData)
{
    AudioBufferList audioBufferList = *(AudioBufferList *)inUserData;

    ioData->mBuffers[0].mData = audioBufferList.mBuffers[0].mData;
    ioData->mBuffers[0].mDataByteSize = audioBufferList.mBuffers[0].mDataByteSize;

    return  noErr;
}
複製代碼

至此,AAC編碼部分就已經分析完了。但不少時候咱們須要將 AAC 數據保存成文件。若是咱們直接將一幀一幀的AAC數據直接寫入文件,再從AAC文件中讀取數據交由解碼器解碼,是沒法成功的。緣由很簡單,解碼器搞不清楚文件裏每一個 AAC 幀到底有多大。

解決的辦法是在每一幀前加一個頭。這是一個比較通用的作法。在AAC中加的頭格式咱們稱爲 ADTS頭。

增長ADTS頭

ADTS共7或9個字節。通常狀況下使用 7 字節。它的結構以下:

Structure
AAAAAAAA AAAABCCD EEFFFFGH HHIJKLMM MMMMMMMM MMMOOOOO OOOOOOPP (QQQQQQQQ QQQQQQQQ)

Letter Length (bits) Description

  • A 12 syncword 0xFFF, all bits must be 1
  • B 1 MPEG Version: 0 for MPEG-4, 1 for MPEG-2
  • C 2 Layer: always 0
  • D 1 protection absent, Warning, set to 1 if there is no CRC and 0 if there is CRC
  • E 2 profile, the MPEG-4 Audio Object Type minus 1
  • F 4 MPEG-4 Sampling Frequency Index (15 is forbidden)
  • G 1 private bit, guaranteed never to be used by MPEG, set to 0 when encoding, ignore when decoding
  • H 3 MPEG-4 Channel Configuration (in the case of 0, the channel configuration is sent via an inband PCE)
  • I 1 originality, set to 0 when encoding, ignore when decoding
  • J 1 home, set to 0 when encoding, ignore when decoding
  • K 1 copyrighted id bit, the next bit of a centrally registered copyright identifier, set to 0 when encoding, ignore when decoding
  • L 1 copyright id start, signals that this frame's copyright id bit is the first bit of the copyright id, set to 0 when encoding, ignore when decoding
  • M 13 frame length, this value must include 7 or 9 bytes of header length: FrameLength = (ProtectionAbsent == 1 ? 7 : 9) + size(AACFrame)
  • O 11 Buffer fullness
  • P 2 Number of AAC frames (RDBs) in ADTS frame minus 1, for maximum compatibility always use 1 AAC frame per ADTS frame
  • Q 16 CRC if protection absent is 0

下面是具體代碼。經過上面的描述就很是容易理解了。

- (NSData*) adtsDataForPacketLength:(NSUInteger)packetLength {
    int adtsLength = 7;
    char *packet = malloc(sizeof(char) * adtsLength);
    // Variables Recycled by addADTStoPacket
    int profile = 2;  //AAC LC
    //39=MediaCodecInfo.CodecProfileLevel.AACObjectELD;
    int freqIdx = 4;  //44.1KHz
    int chanCfg = 1;  //MPEG-4 Audio Channel Configuration. 1 Channel front-center
    NSUInteger fullLength = adtsLength + packetLength;
    // 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;
}
複製代碼

小結

本文主要講解了 iOS 下 如何進行 AAC 編碼。它的流程豐常簡單。包括:

  • 設置輸入、輸出格式。
  • 建立AAC編碼器。
  • 轉碼。
  • 增長ADTS頭。

這裏的難點是參數的設置。並且不少參數之間是聯動的,因此設置時要特別當心。

另外,經過本文你能夠了解到,其實在iOS下,其它音頻編碼的流程與AAC編碼的流程都是同樣的,咱們只須要調整不一樣的參數便可。

但願本文能對您有所幫助。並請多多關注。謝謝!

相關文章
相關標籤/搜索