iOS 音頻采集整理

音頻采集相關(guān)知識(shí)休里,AduioToolBox 的音頻采集原理,音頻隊(duì)列架構(gòu)琉朽,iOS音頻錄制的過程。

為啥莫名其妙地開始看音頻的東西稚铣?
工作相關(guān)箱叁,梳理相關(guān)模塊需要先了解原理和知識(shí)

1. 音頻采集原理

音頻的采集工作涉及諸多生疏的概念,例如編碼惕医、格式耕漱、緩沖隊(duì)列等抬伺,首先需要將這來生疏的概念梳理清楚螟够。

音頻的采集由三階段工作組成,首先是錄音的硬件設(shè)備沛简,這部分我們不需要關(guān)心齐鲤,系統(tǒng)提供的API幫我們處理了模擬信號(hào)采集到數(shù)字信號(hào)的過程。

1.1 音頻隊(duì)列 AudioQueue

所有的音頻播放和錄制都是通過操作 AudioQueue 來完成的椒楣,通過 AudioQueueRef 這個(gè)數(shù)據(jù)結(jié)構(gòu)來表示给郊,聲明在 AudioQueue.h 中,作用如下:

  1. 連接音頻相關(guān)硬件
  2. 管理內(nèi)存
  3. 為不同的壓縮格式提供編碼器
  4. 錄制
  5. 播放

錄制的音頻隊(duì)列創(chuàng)建使用AudioQueueNewInput函數(shù)捧灰,播放的隊(duì)列創(chuàng)建使用AudioQueueNewOutput函數(shù)淆九。

實(shí)際開發(fā)需要做的是將 AudioQueue 組合其他的 CoreAduio 接口,已實(shí)現(xiàn)符合我們規(guī)范的音頻方案毛俏。

音頻隊(duì)列內(nèi)部包含三個(gè)重要的東西:

  • 多個(gè)(默認(rèn)3個(gè))音頻隊(duì)列緩沖區(qū):audio queue buffers
  • 一個(gè)緩沖隊(duì)列:buffer queue
  • 一個(gè)用戶自己實(shí)現(xiàn)的回調(diào)函數(shù):audio queue callback function

下圖引用自Audio Queue Services Programming Guide炭庙,展示了這三部分的工作原理

音頻隊(duì)列架構(gòu)

需要注意的是:
AudioQueue 向前的輸入設(shè)備,默認(rèn)使用的是用戶連接的設(shè)備煌寇,可 iOS 默認(rèn)是連接的耳機(jī)焕蹄、內(nèi)置麥克風(fēng)等。
AudioQueue 向后的 CallbackFunction 之后的行為完全是用戶自定義的阀溶,是上傳還是發(fā)送還是存儲(chǔ)到 disk 中用戶可以自己實(shí)現(xiàn)腻脏,這種場(chǎng)景非常多鸦泳,例如錄音完直接發(fā)送、錄音 app 保存到磁盤永品、噪音實(shí)時(shí)監(jiān)測(cè)不需要任何數(shù)據(jù)保存等...

1.2 音頻隊(duì)列緩沖區(qū) AudioQueueBuffer

音頻隊(duì)列緩沖區(qū)的數(shù)據(jù)結(jié)構(gòu)做鹰,聲明于:AudioQueue.h,如下

typedef struct AudioQueueBuffer {
    const UInt32   mAudioDataBytesCapacity;
    void *const    mAudioData; // 指向緩沖區(qū)
    UInt32         mAudioDataByteSize;
    void           *mUserData;
} AudioQueueBuffer;
typedef AudioQueueBuffer *AudioQueueBufferRef;

這個(gè)結(jié)構(gòu)中的 mAudioData 指針指向的就是真正的緩沖區(qū)數(shù)據(jù)鼎姐。

1.3 緩沖區(qū)隊(duì)列 BufferQueue

緩沖區(qū)隊(duì)列就像任務(wù)隊(duì)列一樣钾麸,它是一個(gè)緩沖區(qū)的有序列表,這里我們需要知道炕桨,隊(duì)列在實(shí)際錄制中的作用饭尝。

一個(gè)音頻隊(duì)列可以使用任意數(shù)量的緩沖區(qū),但是一般來說我們使用3個(gè)就可以献宫,實(shí)際的錄制過程是這樣的:

音頻錄制的過程

上圖中步驟:

  1. 錄音設(shè)備開始用捕獲的數(shù)據(jù)填充緩沖區(qū)芋肠。
  2. 第一個(gè)緩沖區(qū)數(shù)據(jù)填滿之后,音頻隊(duì)列調(diào)用了 CallBack 將其交出遵蚜,然后將新的數(shù)據(jù)向下一個(gè)緩沖區(qū)進(jìn)行填充。
  3. 交出的緩沖區(qū)被CallBack函數(shù)處理(不一定是寫入磁盤奈惑,而已程序自定義邏輯)
  4. Callback 函數(shù)交回緩沖區(qū)將其重新利用吭净。
  5. 重復(fù)第二步。
  6. 重復(fù)第三步肴甸。

1.4 音頻隊(duì)列回調(diào)函數(shù) Callback function

大部分的編程工作都是在回調(diào)函數(shù)上寂殉,因?yàn)檫@里是我們編寫的錄制組件接收數(shù)據(jù)的地方,也是調(diào)用最頻繁的地方原在。

根據(jù)緩沖區(qū)的設(shè)置友扰,根據(jù)容量,會(huì)計(jì)算出調(diào)用回調(diào)函數(shù)的間隔時(shí)間庶柿,一般在半秒到幾秒之間村怪。

另外就是向上圖的過程4中記錄的那樣,我們需要將目前返回的這個(gè)緩沖區(qū)在函數(shù)結(jié)束的時(shí)候加入到緩沖區(qū)的末尾浮庐,方法是通過調(diào)用函數(shù) AudioQueueEnqueueBuffer 實(shí)現(xiàn)將緩沖區(qū)加入到緩沖區(qū)的末尾甚负。

錄制的 CallBack 和播放的 CallBack 結(jié)構(gòu)上不同,錄制音頻的 CallBack 如下:

AudioQueueInputCallback (
    void                               *inUserData,
    AudioQueueRef                      inAQ,
    AudioQueueBufferRef                inBuffer,
    const AudioTimeStamp               *inStartTime,
    UInt32                             inNumberPacketDescriptions,
    const AudioStreamPacketDescription *inPacketDescs
);

各字段解釋說明如下:

  • inUserData:通常是一個(gè)穿件用來保存音頻隊(duì)列和它的緩沖區(qū)狀態(tài)信息的自定義結(jié)構(gòu)审残、音頻文件(AudioFileID類型)代表寫入的文件梭域、以及音頻格式的信息
  • inAQ 調(diào)用回調(diào)函數(shù)的音頻隊(duì)列
  • inBuffer 進(jìn)入回調(diào)函數(shù)的剛剛被填滿的緩沖,根據(jù)inUserData中指定的格式格式化搅轿。
  • inStartTime 采樣的參考時(shí)間病涨,正常的錄制不會(huì)使用這個(gè)參數(shù)
  • inNumberPacketDescriptions 是inPacketDescs下一個(gè)參數(shù)中描述符的數(shù)量。加入在錄制一個(gè)VBR可變比特率音頻(variable bitrate)璧坟,音頻隊(duì)列會(huì)提供這個(gè)參數(shù)給回調(diào)函數(shù)既穆,這個(gè)參數(shù)里的值可以讓程序傳遞給AudioFileWritePackets函數(shù)赎懦。CBR(常量比特率)的錄制不使用包描述符,會(huì)將inPacketDescs設(shè)置為NULL
  • inPacketDescs 一組對(duì)應(yīng)于緩沖區(qū)采樣信息的包描述符循衰。

播放音頻時(shí)的回調(diào)函數(shù):

AudioQueueOutputCallback (
    void                  *inUserData,
    AudioQueueRef         inAQ,
    AudioQueueBufferRef   inBuffer
);

1.5 編碼和音頻數(shù)據(jù)格式

采集和播放必然涉及編碼解碼組件铲敛,這個(gè)處理是在回調(diào)函數(shù)之前的,所以回調(diào)函數(shù)不需要處理編碼解碼的過程会钝。

AudioQueue 的 AudioStreamBasicDescription 中有一個(gè)域用來描述音頻數(shù)據(jù)格式伐蒋。當(dāng)你在 MFormatID 域中指定了格式之后,音頻隊(duì)列就會(huì)使用相應(yīng)的解碼器迁酸,然后指定相應(yīng)的采樣率和聲道數(shù)先鱼。

下圖是音頻錄制中進(jìn)行音頻轉(zhuǎn)換的過程

音頻轉(zhuǎn)換
  1. 程序告訴音頻隊(duì)列開始錄制,并指定數(shù)據(jù)格式
  2. 音頻隊(duì)列獲取新的音頻數(shù)據(jù)奸鬓,并根據(jù)指定的格式使用編解碼器對(duì)其進(jìn)行轉(zhuǎn)換焙畔。然后,音頻隊(duì)列將調(diào)用該回調(diào)串远,并將其交給包含適當(dāng)格式的音頻數(shù)據(jù)的緩沖區(qū)
  3. 回調(diào)將格式化的音頻數(shù)據(jù)寫入磁盤宏多。這樣回調(diào)不需要了解數(shù)據(jù)格式

2. 實(shí)現(xiàn)步驟

總覽:

  1. 定義一個(gè)自定義結(jié)構(gòu),包括狀態(tài)澡罚、格式伸但、緩沖區(qū)大小、保存路勁
  2. 編寫回調(diào)函數(shù)
  3. 可選:指定每個(gè)緩沖區(qū)的大小
  4. 填充第一步的自定義結(jié)構(gòu)
  5. 創(chuàng)建AudioQueue以及緩沖區(qū)留搔,以及寫入的文件
  6. 通知AudioQueue開始錄制
  7. 錄制完畢的時(shí)候通知停止錄制更胖,然后釋放它,以及釋放緩沖區(qū)

2.1 自定義結(jié)構(gòu)

static const int kNumberBuffers = 3;                            // 1
struct AQRecorderState {
    AudioStreamBasicDescription  mDataFormat;                   // 2
    AudioQueueRef                mQueue;                        // 3
    AudioQueueBufferRef          mBuffers[kNumberBuffers];      // 4
    AudioFileID                  mAudioFile;                    // 5
    UInt32                       bufferByteSize;                // 6
    SInt64                       mCurrentPacket;                // 7
    bool                         mIsRunning;                    // 8
};

使用這個(gè)結(jié)構(gòu)來管理音頻格式和音頻隊(duì)列狀態(tài)信息隔显。

  1. 設(shè)置使用的音頻隊(duì)列緩沖區(qū)的數(shù)量却妨,一般是3
  2. 一個(gè)AudioStreamBasicDescription結(jié)構(gòu)(CoreAudioTypes.h),標(biāo)識(shí)寫入磁盤的音頻數(shù)據(jù)的格式括眠,音頻隊(duì)列也會(huì)使用它來指定mQueue域彪标,mDataFormat域是由app業(yè)務(wù)測(cè)來初始化的。
  3. 創(chuàng)建的音頻隊(duì)列AudioQueueRef
  4. 音頻隊(duì)列所管理的音頻隊(duì)列緩沖區(qū)的指針數(shù)組
  5. 程序錄制音頻時(shí)寫入的文件的音頻文件對(duì)象
  6. 每個(gè)音頻隊(duì)列緩沖區(qū)的字節(jié)大小掷豺,它的值在隨后的例子中的DeriveBufferSize函數(shù)中計(jì)算出來捐下,它在音頻隊(duì)列創(chuàng)建之后,開始錄制音頻之前計(jì)算出來
  7. 從當(dāng)前音頻隊(duì)列緩沖區(qū)寫入文件的第一個(gè)包(packet)的索引
  8. 布爾值萌业,用來指示音頻隊(duì)列是否在運(yùn)行中

2.2 編寫回調(diào)函數(shù)

接下來要編寫回調(diào)函數(shù)坷襟,這個(gè)回調(diào)函數(shù)做兩件事情:

  1. 接受剛剛填充好的緩沖區(qū),取數(shù)據(jù)
  2. 將這個(gè)緩沖區(qū)返回緩沖隊(duì)列重新利用

2.2.1 回調(diào)函數(shù)聲明

在AudioQueue.h頭文件中聲明的AudioQueueInputCallback生年。

錄制用音頻隊(duì)列回調(diào)函數(shù)聲明:

static void HandleInputBuffer (
    void                                *aqData,             // 1
    AudioQueueRef                       inAQ,                // 2
    AudioQueueBufferRef                 inBuffer,            // 3
    const AudioTimeStamp                *inStartTime,        // 4
    UInt32                              inNumPackets,        // 5
    const AudioStreamPacketDescription  *inPacketDesc        // 6
)
  1. 一般來說婴程,aqData是一個(gè)自定義的數(shù)據(jù)結(jié)構(gòu),他包含了音頻隊(duì)列的狀態(tài)信息抱婉,就像“Define a Custom Structure to Manage State.”中的一樣档叔。
  2. 擁有這個(gè)回調(diào)函數(shù)的音頻隊(duì)列
  3. 包含錄制數(shù)據(jù)的音頻隊(duì)列緩沖區(qū)
  4. 音頻隊(duì)列緩沖區(qū)中第一個(gè)采樣的的時(shí)間(對(duì)于簡(jiǎn)單的錄制桌粉,這個(gè)是不需要的)
  5. inPacketDesc域中packet descriptions的數(shù)量,如果是0衙四,表明這是個(gè)CBR數(shù)據(jù)
  6. 對(duì)于壓縮數(shù)據(jù)格式如果需要packet descriptions铃肯,這個(gè)packet descriptions是由編碼器產(chǎn)生的

2.2.2 緩沖區(qū)數(shù)據(jù)寫入磁盤

這個(gè)回調(diào)函數(shù)使用AudioFile.h頭文件中聲明的AudioFileWritePackets函,

AudioFileWritePackets (                     // 1
    pAqData->mAudioFile,                    // 2
    false,                                  // 3
    inBuffer->mAudioDataByteSize,           // 4
    inPacketDesc,                           // 5
    pAqData->mCurrentPacket,                // 6
    &inNumPackets,                          // 7
    inBuffer->mAudioData                    // 8
);
  1. AudioFileWritePackets 將緩沖區(qū)的內(nèi)容寫入音頻數(shù)據(jù)文件
  2. pAqData表示音頻文件對(duì)象(類型為:AudioFileID)传蹈,pAqData變量是指向自定義結(jié)構(gòu)的指針
  3. false表示在寫入時(shí)不處理任何緩存
  4. 正在寫入的音頻數(shù)據(jù)的字節(jié)數(shù)押逼。該inBuffer變量表示音頻隊(duì)列傳遞給回調(diào)的音頻隊(duì)列緩沖區(qū)
  5. 音頻數(shù)據(jù)包描述的副本。值NULL表示不需要數(shù)據(jù)包描述(例如惦界,對(duì)于CBR音頻數(shù)據(jù))
  6. 要寫入的第一個(gè)數(shù)據(jù)包的數(shù)據(jù)包索引
  7. 輸入時(shí)挑格,要寫入的數(shù)據(jù)包數(shù)。輸出時(shí)沾歪,實(shí)際寫入的數(shù)據(jù)包數(shù)
  8. 將新的音頻數(shù)據(jù)寫入音頻文件

2.2.3 緩沖區(qū)入隊(duì)

使用完緩沖區(qū)的數(shù)據(jù)之后漂彤,使得緩沖區(qū)重新入隊(duì)

AudioQueueEnqueueBuffer (                    // 1
    pAqData->mQueue,                         // 2
    inBuffer,                                // 3
    0,                                       // 4
    NULL                                     // 5
);
  1. 該AudioQueueEnqueueBuffer函數(shù)將音頻體重閾值添加到音頻長(zhǎng)度的閾值。
  2. 將指定的音頻隊(duì)列緩沖區(qū)添加到的音頻隊(duì)列
  3. buffer
  4. 音頻為音頻數(shù)據(jù)的數(shù)據(jù)中的數(shù)據(jù)包描述數(shù)灾搏。設(shè)置為挫望,0因?yàn)榇藚?shù)未使用記錄
  5. 數(shù)據(jù)包描述摘要,描述音頻編碼附件的數(shù)據(jù)狂窑。設(shè)置為士骤,NULL因?yàn)榇藚?shù)未使用記錄

2.2.4 完整的 CallBack

static void HandleInputBuffer (
    void                                 *aqData,
    AudioQueueRef                        inAQ,
    AudioQueueBufferRef                  inBuffer,
    const AudioTimeStamp                 *inStartTime,
    UInt32                               inNumPackets,
    const AudioStreamPacketDescription   *inPacketDesc
) {
    AQRecorderState *pAqData = (AQRecorderState *) aqData;               // 1
 
    if (inNumPackets == 0 &&                                             // 2
          pAqData->mDataFormat.mBytesPerPacket != 0)
       inNumPackets =
           inBuffer->mAudioDataByteSize / pAqData->mDataFormat.mBytesPerPacket;
 
    if (AudioFileWritePackets (                                          // 3
            pAqData->mAudioFile,
            false,
            inBuffer->mAudioDataByteSize,
            inPacketDesc,
            pAqData->mCurrentPacket,
            &inNumPackets,
            inBuffer->mAudioData
        ) == noErr) {
            pAqData->mCurrentPacket += inNumPackets;                     // 4
    }
   if (pAqData->mIsRunning == 0)                                         // 5
      return;
 
    AudioQueueEnqueueBuffer (                                            // 6
        pAqData->mQueue,
        inBuffer,
        0,
        NULL
    );
}
  1. 自定義結(jié)構(gòu)的實(shí)例化
  2. 數(shù)據(jù)包的數(shù)量
  3. 緩沖區(qū)的內(nèi)容寫入文件
  4. 如果成功寫入數(shù)據(jù),增加音頻數(shù)據(jù)文件的數(shù)據(jù)包index值蕾域,準(zhǔn)備寫入下一個(gè)
  5. 如果已經(jīng)停止就返回
  6. 把緩沖區(qū)入隊(duì)

2.3 計(jì)算音頻緩沖區(qū)大小

音頻隊(duì)列服務(wù)要求應(yīng)用程序?yàn)槟褂玫囊纛l隊(duì)列緩沖區(qū)指定大小。下面的函數(shù)展示了一種方法到旦。
它衍生出一個(gè)足夠大的緩沖區(qū)來保存給定的音頻數(shù)據(jù)旨巷。

這里的計(jì)算考慮了要錄制的音頻數(shù)據(jù)格式。格式包括可能影響緩沖區(qū)大小的所有因素添忘,例如音頻通道的數(shù)量采呐。

void DeriveBufferSize (
    AudioQueueRef                audioQueue,                  // 1
    AudioStreamBasicDescription  &ASBDescription,             // 2
    Float64                      seconds,                     // 3
    UInt32                       *outBufferSize               // 4
) {
    static const int maxBufferSize = 0x50000;                 // 5
 
    int maxPacketSize = ASBDescription.mBytesPerPacket;       // 6
    if (maxPacketSize == 0) {                                 // 7
        UInt32 maxVBRPacketSize = sizeof(maxPacketSize);
        AudioQueueGetProperty (
                audioQueue,
                kAudioQueueProperty_MaximumOutputPacketSize,
                // 在 Mac OS X v10.5 應(yīng)該使用:
                //   kAudioConverterPropertyMaximumOutputPacketSize
                &maxPacketSize,
                &maxVBRPacketSize
        );
    }
 
    Float64 numBytesForTime =
        ASBDescription.mSampleRate * maxPacketSize * seconds; // 8
    *outBufferSize =
    UInt32 (numBytesForTime < maxBufferSize ?
        numBytesForTime : maxBufferSize);                     // 9
}
  1. 需要操作的這個(gè)音頻隊(duì)列
  2. 音頻隊(duì)列的AudioStreamBasicDescription結(jié)構(gòu)
  3. 為每個(gè)音頻隊(duì)列緩沖區(qū)指定的大小,以音頻的秒數(shù)為單位
  4. 輸出時(shí)搁骑,每個(gè)音頻隊(duì)列緩沖區(qū)的大小斧吐,以字節(jié)為單位
  5. 音頻隊(duì)列緩沖區(qū)大小的上限,以字節(jié)為單位仲器。在本例中煤率,上限設(shè)置為320 KB。這相當(dāng)于大約5秒鐘的立體聲乏冀、24位音頻蝶糯,采樣率為96 kHz
  6. 對(duì)于CBR音頻數(shù)據(jù),從AudioStreamBasicDescription結(jié)構(gòu)中獲攘韭佟(常量)數(shù)據(jù)包大小昼捍。使用此值作為最大數(shù)據(jù)包大小识虚。該賦值的副作用是確定要記錄的音頻數(shù)據(jù)是CBR還是VBR。如果是VBR妒茬,則音頻隊(duì)列的AudioStreamBasicDescription結(jié)構(gòu)將每個(gè)數(shù)據(jù)包的字節(jié)值列為0
  7. 對(duì)于VBR音頻數(shù)據(jù)担锤,查詢音頻隊(duì)列以獲得估計(jì)的最大數(shù)據(jù)包大小
  8. 派生緩沖區(qū)大小(以字節(jié)為單位)
  9. 如果需要乍钻,將緩沖區(qū)大小限制為先前設(shè)置的上限

2.4 特殊格式元數(shù)據(jù)的處理

一些壓縮的音頻格式肛循,需要用包含音頻元數(shù)據(jù)的結(jié)構(gòu),稱之為 Magic Cookies团赁,稱之為饃干育拨。如果需要錄制這種格式的音頻文件,必須先處理好 MC 數(shù)據(jù)結(jié)構(gòu)欢摄,先從音頻隊(duì)列中獲取MC熬丧,然后將其添加到音頻文件中,再進(jìn)行錄制的操作怀挠。

下面這個(gè)函數(shù)就是從音頻中途獲取 MC 的信息析蝴,并將其替換為音頻文件,代碼需要在錄制之前就調(diào)用此類函數(shù)绿淋,然后錄制之后再調(diào)用闷畸,某些編碼解碼器會(huì)在錄制停止的時(shí)候更新MC數(shù)據(jù)。

OSStatus SetMagicCookieForFile (
    AudioQueueRef inQueue,                                      // 1
    AudioFileID   inFile                                        // 2
) {
    OSStatus result = noErr;                                    // 3
    UInt32 cookieSize;                                          // 4
 
    if (
            AudioQueueGetPropertySize (                         // 5
                inQueue,
                kAudioQueueProperty_MagicCookie,
                &cookieSize
            ) == noErr
    ) {
        char* magicCookie =
            (char *) malloc (cookieSize);                       // 6
        if (
                AudioQueueGetProperty (                         // 7
                    inQueue,
                    kAudioQueueProperty_MagicCookie,
                    magicCookie,
                    &cookieSize
                ) == noErr
        )
            result =    AudioFileSetProperty (                  // 8
                            inFile,
                            kAudioFilePropertyMagicCookieData,
                            cookieSize,
                            magicCookie
                        );
        free (magicCookie);                                     // 9
    }
    return result;                                              // 10
}
  1. 正在用于錄制的音頻隊(duì)列吞滞。
  2. 你正在錄制的音頻文件佑菩。
  3. 指示此函數(shù)成功或失敗的結(jié)果變量。
  4. 一個(gè)變量來保存神奇的cookie數(shù)據(jù)大小裁赠。
  5. 從音頻隊(duì)列獲取magic cookie的數(shù)據(jù)大小殿漠,并將其存儲(chǔ)在cookieSize變量中。
  6. 分配一個(gè)字節(jié)數(shù)組來保存魔法cookie信息佩捞。
  7. 通過查詢音頻隊(duì)列的kaudioqueproperty\u MagicCookie屬性獲取magic cookie绞幌。
  8. 設(shè)置要錄制到的音頻文件的魔法cookie。AudioFileSetProperty函數(shù)在AudioFile.h頭文件中聲明一忱。
  9. 釋放臨時(shí)cookie變量的內(nèi)存莲蜘。
  10. 返回此函數(shù)的成功或失敗。

2.5 設(shè)置音頻格式進(jìn)行錄制

如何為音頻體制設(shè)置音頻數(shù)據(jù)格式帘营。音頻格式使用此格式記錄到文件票渠。

要設(shè)置音頻數(shù)據(jù)格式,需要指定:

  • 音頻數(shù)據(jù)格式類型(例如線性PCM芬迄,AAC等)
  • 采樣率(例如44.1 kHz)
  • 音頻通道數(shù)(例如2庄新,用于立體聲)
  • 位深度(例如16位)
  • 每個(gè)包的幀數(shù)(例如,線性PCM每包使用一幀)
  • 音頻文件類型(例如,CAF择诈,AIFF等)
  • 文件類型所需的音頻數(shù)據(jù)格式的詳細(xì)信息

下面這段代碼說明了如何設(shè)置錄音的音頻格式械蹋,為每個(gè)屬性使用固定的選項(xiàng)。
在產(chǎn)品代碼中羞芍,通常允許用戶指定音頻格式的某些或所有方面哗戈。
無論哪種方法,目標(biāo)都是填充 AQRecorderState 自定義結(jié)構(gòu)的 mDataFormat 字段荷科。

AQRecorderState aqData;                                       // 1
 
aqData.mDataFormat.mFormatID         = kAudioFormatLinearPCM; // 2
aqData.mDataFormat.mSampleRate       = 44100.0;               // 3
aqData.mDataFormat.mChannelsPerFrame = 2;                     // 4
aqData.mDataFormat.mBitsPerChannel   = 16;                    // 5
aqData.mDataFormat.mBytesPerPacket   =                        // 6
   aqData.mDataFormat.mBytesPerFrame =
      aqData.mDataFormat.mChannelsPerFrame * sizeof (SInt16);
aqData.mDataFormat.mFramesPerPacket  = 1;                     // 7
 
AudioFileTypeID fileType             = kAudioFileAIFFType;    // 8
aqData.mDataFormat.mFormatFlags =                             // 9
    kLinearPCMFormatFlagIsBigEndian
    | kLinearPCMFormatFlagIsSignedInteger
    | kLinearPCMFormatFlagIsPacked;
  1. 創(chuàng)建AQRecorderState自定義結(jié)構(gòu)的實(shí)例唯咬。結(jié)構(gòu)的mDataFormat字段包含AudioStreamBasicDescription結(jié)構(gòu)。mDataFormat字段中設(shè)置的值提供音頻隊(duì)列的音頻格式的初始定義畏浆,該音頻隊(duì)列也是您錄制到的文件的音頻格式胆胰。在清單2-10中,您獲得了一個(gè)更完整的音頻格式規(guī)范刻获,核心音頻根據(jù)格式類型和文件類型提供給您
  2. 將音頻數(shù)據(jù)格式類型定義為線性PCM蜀涨。有關(guān)可用數(shù)據(jù)格式的完整列表,請(qǐng)參見核心音頻數(shù)據(jù)類型參考
  3. 將采樣率定義為44.1 kHz
  4. 將通道數(shù)定義為2
  5. 將每個(gè)通道的位深度定義為16
  6. 將每個(gè)數(shù)據(jù)包的字節(jié)數(shù)和每幀的字節(jié)數(shù)定義為4(即蝎毡,每個(gè)采樣2個(gè)通道乘以2個(gè)字節(jié))
  7. 將每個(gè)數(shù)據(jù)包的幀數(shù)定義為1
  8. 將文件類型定義為AIFF厚柳。有關(guān)可用文件類型的完整列表,請(qǐng)參見AudioFile.h頭文件中的音頻文件類型枚舉沐兵”鹂澹可以指定已安裝編解碼器的任何文件類型,如使用編解碼器和音頻數(shù)據(jù)格式中所述
  9. 設(shè)置指定文件類型所需的格式標(biāo)志

2.6 創(chuàng)建音頻隊(duì)列

2.6.1 創(chuàng)建音頻隊(duì)列

創(chuàng)建好了 Callback 和 音頻格式 之后扎谎,就可以創(chuàng)建用于錄制的音頻隊(duì)列了碳想,創(chuàng)建的時(shí)候回使用到前面步驟配置好的回調(diào)、自定義結(jié)構(gòu)和音頻數(shù)據(jù)格式毁靶。

AudioQueueNewInput (                              // 1
    &aqData.mDataFormat,                          // 2
    HandleInputBuffer,                            // 3
    &aqData,                                      // 4
    NULL,                                         // 5
    kCFRunLoopCommonModes,                        // 6
    0,                                            // 7
    &aqData.mQueue                                // 8
);
  1. AudioQueueNewInput 函數(shù)創(chuàng)建一個(gè)新的錄制音頻隊(duì)列
  2. 音頻數(shù)據(jù)格式
  3. 回調(diào)函數(shù)
  4. 自定義數(shù)據(jù)結(jié)構(gòu)
  5. 調(diào)用回調(diào)的運(yùn)行循環(huán)胧奔。使用 NULL 指定默認(rèn)行為,其中回調(diào)將在音頻隊(duì)列內(nèi)部的線程上調(diào)用老充。這是一個(gè)典型的用法,它允許音頻隊(duì)列在應(yīng)用程序的用戶界面線程等待用戶輸入停止錄制時(shí)進(jìn)行錄制
  6. 可以調(diào)用回調(diào)的運(yùn)行循環(huán)模式螟左。通常使用 kCFRunLoopCommonModes 常量
  7. 保留啡浊。必須為0
  8. 輸出時(shí),新分配的錄制音頻隊(duì)列

2.6.2 從音頻隊(duì)列中獲取完整的音頻格式

音頻隊(duì)列中可能比 AudioStreamBasicDescription 結(jié)構(gòu)更完整胶背,尤其是對(duì)于壓縮格式巷嚣。要獲得完整的格式描述,調(diào)用 AudioQueueGetProperty 函數(shù)钳吟。創(chuàng)建要錄制到的音頻文件時(shí)使用完整的音頻格式廷粒。

UInt32 dataFormatSize = sizeof (aqData.mDataFormat);       // 1
 
AudioQueueGetProperty (                                    // 2
    aqData.mQueue,                                         // 3
    kAudioQueueProperty_StreamDescription,                 // 4
    // in Mac OS X, instead use
    //    kAudioConverterCurrentInputStreamDescription
    &aqData.mDataFormat,                                   // 5
    &dataFormatSize                                        // 6
);
  1. 獲取在查詢音頻隊(duì)列的音頻數(shù)據(jù)格式時(shí)要使用的預(yù)期屬性值大小
  2. AudioQueueGetProperty 函數(shù)獲取音頻隊(duì)列中指定屬性的值
  3. 從中獲取音頻數(shù)據(jù)格式的音頻隊(duì)列
  4. 用于獲取音頻隊(duì)列數(shù)據(jù)格式值的屬性ID
  5. 輸出時(shí),以 AudioStreamBasicDescription 結(jié)構(gòu)的形式從音頻隊(duì)列中獲取的完整音頻數(shù)據(jù)格式
  6. 輸入時(shí),AudioStreamBasicDescription 結(jié)構(gòu)的預(yù)期大小坝茎。輸出時(shí)涤姊,實(shí)際大小。錄制應(yīng)用程序不需要使用此值

2.7 創(chuàng)建音頻文件

音頻數(shù)據(jù)記錄到這個(gè)文件中嗤放,需要使用自定義結(jié)構(gòu)中的文件格式和文件格式規(guī)范

CFURLRef audioFileURL =
    CFURLCreateFromFileSystemRepresentation (            // 1
        NULL,                                            // 2
        (const UInt8 *) filePath,                        // 3
        strlen (filePath),                               // 4
        false                                            // 5
    );
 
AudioFileCreateWithURL (                                 // 6
    audioFileURL,                                        // 7
    fileType,                                            // 8
    &aqData.mDataFormat,                                 // 9
    kAudioFileFlags_EraseFile,                           // 10
    &aqData.mAudioFile                                   // 11
);
  1. CFURLCreateFromFileSystemRepresentation函數(shù)在CFURL.h頭文件中聲明思喊,它創(chuàng)建一個(gè)CFURL對(duì)象,表示要記錄到的文件次酌。
  2. 使用NULL(或kCFAllocatorDefault)使用當(dāng)前默認(rèn)內(nèi)存分配器恨课。
  3. 要轉(zhuǎn)換為CFURL對(duì)象的文件系統(tǒng)路徑。在生產(chǎn)代碼中岳服,通常會(huì)從用戶處獲取filePath的值剂公。
  4. 文件系統(tǒng)路徑中的字節(jié)數(shù)。
  5. 值false表示filePath表示文件吊宋,而不是目錄纲辽。
  6. AudioFile.h頭文件中的AudioFileCreateWithURL函數(shù)創(chuàng)建新的音頻文件或初始化現(xiàn)有文件。
  7. 創(chuàng)建新音頻文件或在現(xiàn)有文件的情況下初始化的URL贫母。URL是從步驟1中的CFURLCreateFromFileSystemRepresentation派生的文兑。
  8. 新文件的文件類型。在本章的示例代碼中腺劣,這是以前通過kAudioFileAIFFType文件類型常量設(shè)置為AIFF的绿贞。請(qǐng)參見設(shè)置錄音的音頻格式。
  9. 將記錄到文件中的音頻的數(shù)據(jù)格式橘原,指定為AudioStreamBasicDescription結(jié)構(gòu)籍铁。在本章的示例代碼中,還設(shè)置了錄音的音頻格式趾断。
  10. 如果文件已經(jīng)存在拒名,則刪除該文件。
  11. 輸出時(shí)芋酌,表示要錄制到的音頻文件的音頻文件對(duì)象(AudioFileID類型)增显。

2.8 設(shè)置音頻緩沖區(qū)Size

DeriveBufferSize (                               // 1
    aqData.mQueue,                               // 2
    aqData.mDataFormat,                          // 3
    0.5,                                         // 4
    &aqData.bufferByteSize                       // 5
);
  1. 調(diào)用前面聲明的函數(shù),計(jì)算緩沖區(qū)大小
  2. 正在為其設(shè)置緩沖區(qū)大小的音頻隊(duì)列
  3. 正在錄制的文件的音頻數(shù)據(jù)格式脐帝。請(qǐng)參見設(shè)置錄音的音頻格式
  4. 每個(gè)音頻隊(duì)列緩沖區(qū)應(yīng)保留的音頻秒數(shù)同云。這里設(shè)置的半秒通常是一個(gè)不錯(cuò)的選擇
  5. 輸出時(shí),每個(gè)音頻隊(duì)列緩沖區(qū)的大小堵腹,以字節(jié)為單位炸站。此值放置在音頻隊(duì)列的自定義結(jié)構(gòu)中

2.9 準(zhǔn)備好一組音頻緩沖區(qū)

讓音頻隊(duì)列準(zhǔn)備好一組音頻緩沖區(qū)。

for (int i = 0; i < kNumberBuffers; ++i) {           // 1
    AudioQueueAllocateBuffer (                       // 2
        aqData.mQueue,                               // 3
        aqData.bufferByteSize,                       // 4
        &aqData.mBuffers[i]                          // 5
    );
 
    AudioQueueEnqueueBuffer (                        // 6
        aqData.mQueue,                               // 7
        aqData.mBuffers[i],                          // 8
        0,                                           // 9
        NULL                                         // 10
    );
}
  1. 循環(huán)緩沖區(qū)的數(shù)量疚顷,分配并入隊(duì)每個(gè)音頻隊(duì)列緩沖區(qū)
  2. AudioQueueAllocateBuffer函數(shù)要求音頻隊(duì)列分配音頻隊(duì)列緩沖區(qū)
  3. 執(zhí)行分配并擁有緩沖區(qū)的音頻隊(duì)列
  4. 正在分配的新音頻隊(duì)列緩沖區(qū)的大泻狄住(字節(jié))禁偎。請(qǐng)參閱編寫函數(shù)以導(dǎo)出錄音音頻隊(duì)列緩沖區(qū)大小
  5. 輸出時(shí),新分配的音頻隊(duì)列緩沖區(qū)阀坏。指向緩沖區(qū)的指針放置在音頻隊(duì)列使用的自定義結(jié)構(gòu)中
  6. audioqueuenbuffer函數(shù)將音頻隊(duì)列緩沖區(qū)添加到緩沖區(qū)隊(duì)列的末尾
  7. 要向其添加緩沖區(qū)的音頻隊(duì)列
  8. 正在排隊(duì)的音頻隊(duì)列緩沖區(qū)
  9. 將緩沖區(qū)入隊(duì)時(shí)如暖,此參數(shù)未使用
  10. 將緩沖區(qū)入隊(duì)時(shí),此參數(shù)未使用

2.10 錄制

前面都已經(jīng)準(zhǔn)備好全释,錄制的時(shí)候就會(huì)非常簡(jiǎn)單:

aqData.mCurrentPacket = 0;                           // 1
aqData.mIsRunning = true;                            // 2
 
AudioQueueStart (                                    // 3
    aqData.mQueue,                                   // 4
    NULL                                             // 5
);
// Wait, on user interface thread, until user stops the recording
AudioQueueStop (                                     // 6
    aqData.mQueue,                                   // 7
    true                                             // 8
);
 
aqData.mIsRunning = false;                           // 9
  1. 將數(shù)據(jù)包索引初始化為0装处,以便在音頻文件開始時(shí)開始錄制。
  2. 在自定義結(jié)構(gòu)中設(shè)置標(biāo)志浸船,以指示音頻隊(duì)列正在運(yùn)行妄迁。錄制音頻隊(duì)列回調(diào)使用此標(biāo)志。
  3. AudioQueueStart函數(shù)在自己的線程上啟動(dòng)音頻隊(duì)列李命。
  4. 要啟動(dòng)的音頻隊(duì)列登淘。
  5. 使用NULL表示音頻隊(duì)列應(yīng)立即開始錄制。
  6. AudioQueueStop函數(shù)停止并重置錄制音頻隊(duì)列封字。
  7. 要停止的音頻隊(duì)列黔州。
  8. 使用true使用同步停止。有關(guān)同步和異步停止的說明阔籽,請(qǐng)參閱音頻隊(duì)列控制和狀態(tài)流妻。
  9. 在自定義結(jié)構(gòu)中設(shè)置標(biāo)志,以指示音頻隊(duì)列未運(yùn)行笆制。

2.11 善后首位

善后工作:

AudioQueueDispose (                                 // 1
    aqData.mQueue,                                  // 2
    true                                            // 3
);
 
AudioFileClose (aqData.mAudioFile);                 // 4
  1. AudioQueueDispose 處理音頻隊(duì)列和所有相關(guān)的資源绅这,包括所有緩沖區(qū)
  2. 需要處理的音頻隊(duì)列
  3. true 表示 synchronously 立即回收
  4. 管理用于錄制的文件,這個(gè) AudioFileClose 函數(shù)在 AudioFile.h 頭文件中聲明

3. Footnote

Audio Queue Services Programming Guide

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末在辆,一起剝皮案震驚了整個(gè)濱河市证薇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌匆篓,老刑警劉巖浑度,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異鸦概,居然都是意外死亡箩张,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門窗市,熙熙樓的掌柜王于貴愁眉苦臉地迎上來先慷,“玉大人,你說我怎么就攤上這事谨设∈斓啵” “怎么了缎浇?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵扎拣,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng)二蓝,這世上最難降的妖魔是什么誉券? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮刊愚,結(jié)果婚禮上踊跟,老公的妹妹穿的比我還像新娘。我一直安慰自己鸥诽,他們只是感情好商玫,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著牡借,像睡著了一般拳昌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上钠龙,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天炬藤,我揣著相機(jī)與錄音,去河邊找鬼碴里。 笑死沈矿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的咬腋。 我是一名探鬼主播羹膳,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼帝火!你這毒婦竟也來了溜徙?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤犀填,失蹤者是張志新(化名)和其女友劉穎蠢壹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體九巡,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡图贸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冕广。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疏日。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖撒汉,靈堂內(nèi)的尸體忽然破棺而出沟优,到底是詐尸還是另有隱情,我是刑警寧澤睬辐,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布挠阁,位于F島的核電站宾肺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏侵俗。R本人自食惡果不足惜锨用,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望隘谣。 院中可真熱鬧增拥,春花似錦、人聲如沸寻歧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽码泛。三九已至渣玲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弟晚,已是汗流浹背忘衍。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卿城,地道東北人枚钓。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瑟押,于是被迫代替她去往敵國(guó)和親搀捷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容