幾種播放音頻文件的方式(七) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)之錄制音頻(四)

版本記錄

版本號(hào) 時(shí)間
V1.0 2017.12.28

前言

ios系統(tǒng)中有很多方式可以播放音頻文件黔寇,這里我們就詳細(xì)的說(shuō)明下播放音樂(lè)文件的原理和實(shí)例。感興趣的可以看我寫的上面幾篇斩萌。
1. 幾種播放音頻文件的方式(一) —— 播放本地音樂(lè)
2. 幾種播放音頻文件的方式(二) —— 音效播放
3. 幾種播放音頻文件的方式(三) —— 網(wǎng)絡(luò)音樂(lè)播放
4. 幾種播放音頻文件的方式(四) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)(一)
5. 幾種播放音頻文件的方式(五) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)簡(jiǎn)介(二)
6. 幾種播放音頻文件的方式(六) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)之關(guān)于音頻隊(duì)列(三)

Recording Audio - 錄制音頻

使用音頻隊(duì)列服務(wù)進(jìn)行錄制時(shí)缝裤,存儲(chǔ)目標(biāo)可以是任何事物 —— 磁盤上的文件,網(wǎng)絡(luò)連接颊郎,內(nèi)存中的對(duì)象等等憋飞。 本章介紹最常見(jiàn)的情況:基本錄制到磁盤文件。

注意:本章描述了一個(gè)基于ANSI-C的錄制實(shí)現(xiàn)姆吭,并使用了 Mac OS X Core Audio SDK中的C ++類榛做。 對(duì)于基于Objective-C的示例,請(qǐng)參閱iOS Dev CenterSpeakHere示例代碼内狸。

要將錄制功能添加到應(yīng)用程序中检眯,通常需要執(zhí)行以下步驟:

  • 定義一個(gè)自定義結(jié)構(gòu)來(lái)管理狀態(tài),格式和路徑信息昆淡。
  • 編寫一個(gè)音頻隊(duì)列callback回調(diào)函數(shù)來(lái)執(zhí)行實(shí)際錄制锰瘸。
  • 可以編寫代碼來(lái)確定音頻隊(duì)列緩沖區(qū)的大小。 如果您將以使用cookie的格式進(jìn)行錄制昂灵,編寫代碼與magic cookies一起配合避凝。
  • 填寫自定義結(jié)構(gòu)的字段。 這包括指定音頻隊(duì)列發(fā)送到正在記錄的文件的數(shù)據(jù)流眨补,以及該文件的路徑管削。
  • 創(chuàng)建一個(gè)錄制音頻隊(duì)列,并要求它創(chuàng)建一組音頻隊(duì)列緩沖區(qū)撑螺。 還要?jiǎng)?chuàng)建一個(gè)文件來(lái)記錄佩谣。
  • 告訴音頻隊(duì)列開始錄制。
  • 完成后实蓬,告訴音頻隊(duì)列停止并銷毀茸俭。 音頻隊(duì)列銷毀其緩沖區(qū)。

本章的其余部分將詳細(xì)介紹這些步驟安皱。


Define a Custom Structure to Manage State - 定義一個(gè)自定義結(jié)構(gòu)來(lái)管理狀態(tài)

使用音頻隊(duì)列服務(wù)開發(fā)錄音解決方案的第一步是定義一個(gè)自定義結(jié)構(gòu)调鬓。 您將使用此結(jié)構(gòu)來(lái)管理音頻格式和音頻隊(duì)列狀態(tài)信息。 Listing 2-1說(shuō)明了這樣一個(gè)結(jié)構(gòu):

// Listing 2-1  A custom structure for a recording audio queue

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)中的字段的描述:

    1. 設(shè)置要使用的音頻隊(duì)列緩沖區(qū)的數(shù)量酌伊。
    1. 表示要寫入磁盤的音頻數(shù)據(jù)格式的AudioStreamBasicDescription結(jié)構(gòu)(來(lái)自CoreAudioTypes.h)腾窝。該格式被mQueue字段中指定的音頻隊(duì)列使用缀踪。
      mDataFormat字段最初由程序中的代碼填充,如 Set Up an Audio Format for Recording中所述虹脯。最好通過(guò)查詢音頻隊(duì)列的kAudioQueueProperty_StreamDescription屬性來(lái)更新此字段的值驴娃,如Getting the Full Audio Format from an Audio Queue中所述。在Mac OS X v10.5上循集,改用kAudioConverterCurrentInputStreamDescription屬性唇敞。有關(guān)AudioStreamBasicDescription結(jié)構(gòu)的詳細(xì)信息,請(qǐng)參閱Core Audio Data Types Reference咒彤。
    1. 由您的應(yīng)用程序創(chuàng)建錄制音頻隊(duì)列疆柔。
    1. 一個(gè)數(shù)組,指向由音頻隊(duì)列管理的音頻隊(duì)列緩沖區(qū)的指針镶柱。
    1. 音頻文件對(duì)象旷档,表示您的程序?qū)⒁纛l數(shù)據(jù)記錄到其中的文件。
      每個(gè)音頻隊(duì)列緩沖區(qū)的大行稹(以字節(jié)為單位)鞋屈。在創(chuàng)建音頻隊(duì)列之后并在啟動(dòng)之前,在DeriveBufferSize函數(shù)的這些示例中計(jì)算此值故觅。請(qǐng)參閱Write a Function to Derive Recording Audio Queue Buffer Size厂庇。
    1. 要從當(dāng)前音頻隊(duì)列緩沖區(qū)寫入的第一個(gè)數(shù)據(jù)包的數(shù)據(jù)包索引。
    1. 指示音頻隊(duì)列是否正在運(yùn)行的布爾值逻卖。

Write a Recording Audio Queue Callback - 寫一個(gè)錄制音頻隊(duì)列回調(diào)函數(shù)

接下來(lái)宋列,寫一個(gè)錄音音頻隊(duì)列回調(diào)函數(shù)。 這個(gè)回調(diào)做了兩件事:

  • 將新填充的音頻隊(duì)列緩沖區(qū)的內(nèi)容寫入到正在錄制的音頻文件中
  • 將音頻隊(duì)列緩沖區(qū)(其內(nèi)容剛剛寫入磁盤)排入緩沖區(qū)隊(duì)列

本節(jié)展示一個(gè)示例回調(diào)聲明评也,然后分別描述這兩個(gè)任務(wù)炼杖,最后展示一個(gè)完整的錄制回調(diào)。 有關(guān)錄制音頻隊(duì)列回調(diào)角色的說(shuō)明盗迟,請(qǐng)參閱Figure 1-3坤邪。

1. The Recording Audio Queue Callback Declaration - 錄制音頻隊(duì)列回調(diào)函數(shù)聲明

Listing 2-2顯示了一個(gè)記錄音頻隊(duì)列回調(diào)函數(shù)的示例聲明,聲明為AudioQueue.h頭文件中的AudioQueueInputCallback:

// Listing 2-2  The recording audio queue callback declaration

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是包含音頻隊(duì)列的狀態(tài)數(shù)據(jù)的自定義結(jié)構(gòu)艇纺,如 Define a Custom Structure to Manage State所述。
    1. 擁有此回調(diào)的音頻隊(duì)列邮弹。
    1. 包含要錄制的傳入音頻數(shù)據(jù)的音頻隊(duì)列緩沖區(qū)黔衡。
    1. 音頻隊(duì)列緩沖區(qū)中第一個(gè)采樣的采樣時(shí)間(簡(jiǎn)單記錄不需要)。
    1. inPacketDesc參數(shù)中的數(shù)據(jù)包描述數(shù)量腌乡。 值為0表示CBR數(shù)據(jù)盟劫。
    1. 對(duì)于需要數(shù)據(jù)包描述的壓縮音頻數(shù)據(jù)格式税弃,編碼器為緩沖區(qū)中數(shù)據(jù)包生成的數(shù)據(jù)包描述料按。

2. Writing an Audio Queue Buffer to Disk - 將音頻隊(duì)列緩沖區(qū)寫入磁盤

錄音的音頻隊(duì)列回調(diào)的第一項(xiàng)任務(wù)是將音頻隊(duì)列緩沖區(qū)寫入磁盤。 這個(gè)緩沖區(qū)是回調(diào)的音頻隊(duì)列剛剛完成填充來(lái)自輸入設(shè)備的新的音頻數(shù)據(jù)的那個(gè)緩沖區(qū)顷啼。 回調(diào)使用AudioFile.h頭文件中的AudioFileWritePackets函數(shù)做修,如Listing 2-3所示媒怯。

// Listing 2-3  Writing an audio queue buffer to disk

AudioFileWritePackets (                     // 1
    pAqData->mAudioFile,                    // 2
    false,                                  // 3
    inBuffer->mAudioDataByteSize,           // 4
    inPacketDesc,                           // 5
    pAqData->mCurrentPacket,                // 6
    &inNumPackets,                          // 7
    inBuffer->mAudioData                    // 8
);

以下是這段代碼的工作原理:

    1. AudioFile.h頭文件中聲明的AudioFileWritePackets函數(shù)將緩沖區(qū)的內(nèi)容寫入音頻數(shù)據(jù)文件赠制。
    1. 音頻文件對(duì)象(類型AudioFileID)赶袄,表示要寫入的音頻文件。 pAqData變量是一個(gè)指向Listing 2-1中描述的數(shù)據(jù)結(jié)構(gòu)的指針猴娩。
    1. 使用false值表示函數(shù)在寫入時(shí)不應(yīng)該緩存數(shù)據(jù)阴幌。
    1. 正在寫入的音頻數(shù)據(jù)的字節(jié)數(shù)。 inBuffer變量表示由音頻隊(duì)列交給回調(diào)的音頻隊(duì)列緩沖區(qū)胀溺。
    1. 音頻數(shù)據(jù)的數(shù)據(jù)包描述數(shù)組裂七。 值為NULL表示不需要數(shù)據(jù)包描述(例如用于CBR音頻數(shù)據(jù))皆看。
    1. 要寫入的第一個(gè)數(shù)據(jù)包的數(shù)據(jù)包索引仓坞。
    1. 在輸入時(shí),要寫入的數(shù)據(jù)包的數(shù)量腰吟。 輸出時(shí)无埃,實(shí)際寫入的數(shù)據(jù)包數(shù)量。
    1. 將新的音頻數(shù)據(jù)寫入音頻文件毛雇。

3. Enqueuing an Audio Queue Buffer - 將一個(gè)音頻隊(duì)列緩沖入隊(duì)

現(xiàn)在來(lái)自音頻隊(duì)列緩沖區(qū)的音頻數(shù)據(jù)已被寫入音頻文件嫉称,回調(diào)將緩沖區(qū)排入隊(duì)列,如Listing 2-4所示灵疮。 一旦回到緩沖區(qū)隊(duì)列中织阅,緩沖區(qū)就準(zhǔn)備好接受更多的輸入音頻數(shù)據(jù)。

// Listing 2-4  Enqueuing an audio queue buffer after writing to disk

AudioQueueEnqueueBuffer (                    // 1
    pAqData->mQueue,                         // 2
    inBuffer,                                // 3
    0,                                       // 4
    NULL                                     // 5
);

以下是這段代碼的工作原理:

    1. AudioQueueEnqueueBuffer函數(shù)將音頻隊(duì)列緩沖區(qū)添加到音頻隊(duì)列的緩沖區(qū)隊(duì)列中震捣。
    1. 將音頻隊(duì)列添加到指定的音頻隊(duì)列緩沖區(qū)荔棉。 pAqData變量是一個(gè)指向Listing 2-1中描述的數(shù)據(jù)結(jié)構(gòu)的指針。
    1. 要排隊(duì)的音頻隊(duì)列緩沖區(qū)蒿赢。
    1. 音頻隊(duì)列緩沖區(qū)數(shù)據(jù)中的數(shù)據(jù)包描述數(shù)量润樱。 設(shè)置為0,因?yàn)榇藚?shù)未用于記錄羡棵。
    1. 描述音頻隊(duì)列緩沖區(qū)數(shù)據(jù)的數(shù)據(jù)包描述數(shù)組壹若。 設(shè)置為NULL,因?yàn)榇藚?shù)未用于記錄皂冰。

4. A Full Recording Audio Queue Callback - 一個(gè)完整的錄制音頻隊(duì)列回調(diào)

Listing 2-5顯示了完整記錄音頻隊(duì)列回調(diào)的基本版本店展。 與本文檔中的其他代碼示例一樣,此列表不包括錯(cuò)誤處理秃流。

// Listing 2-5  A recording audio queue callback function

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. 實(shí)例化時(shí)提供給音頻隊(duì)列對(duì)象的自定義結(jié)構(gòu)赂蕴,包括表示要錄制的音頻文件的音頻文件對(duì)象以及各種狀態(tài)數(shù)據(jù)。請(qǐng)參閱Define a Custom Structure to Manage State剔应。
    1. 如果音頻隊(duì)列緩沖區(qū)包含CBR數(shù)據(jù)睡腿,則計(jì)算緩沖區(qū)中的數(shù)據(jù)包數(shù)量语御。此數(shù)字等于緩沖區(qū)中的數(shù)據(jù)總字節(jié)數(shù)除以每個(gè)數(shù)據(jù)包的(恒定)字節(jié)數(shù)。對(duì)于VBR數(shù)據(jù)席怪,音頻隊(duì)列在調(diào)用回調(diào)時(shí)提供緩沖區(qū)中的數(shù)據(jù)包數(shù)量应闯。
    1. 將緩沖區(qū)的內(nèi)容寫入音頻數(shù)據(jù)文件。有關(guān)詳細(xì)說(shuō)明挂捻,請(qǐng)參閱Writing an Audio Queue Buffer to Disk碉纺。
    1. 如果成功寫入音頻數(shù)據(jù),則增加音頻數(shù)據(jù)文件的包索引以準(zhǔn)備好寫入下一個(gè)緩沖器的音頻數(shù)據(jù)刻撒。
    1. 如果音頻隊(duì)列已經(jīng)停止骨田,則返回。
    1. 將剛剛寫入內(nèi)容的音頻隊(duì)列緩沖區(qū)重新入隊(duì)声怔。有關(guān)詳細(xì)說(shuō)明态贤,請(qǐng)參閱Enqueuing an Audio Queue Buffer

Write a Function to Derive Recording Audio Queue Buffer Size - 編寫一個(gè)函數(shù)來(lái)獲取記錄音頻隊(duì)列緩沖區(qū)大小

音頻隊(duì)列服務(wù)期望您的應(yīng)用程序指定您使用的音頻隊(duì)列緩沖區(qū)的大小醋火。 Listing 2-6顯示了一個(gè)這樣做的方法悠汽。 它產(chǎn)生足夠大的緩沖區(qū)大小來(lái)保存給定持續(xù)時(shí)間的音頻數(shù)據(jù)。

這里的計(jì)算考慮了您正在記錄的音頻數(shù)據(jù)格式芥驳。 格式包括可能影響緩沖區(qū)大小的所有因素柿冲,例如音頻通道的數(shù)量。

// Listing 2-6  Deriving a recording audio queue buffer size

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,
                // in Mac OS X v10.5, instead use
                //   kAudioConverterPropertyMaximumOutputPacketSize
                &maxPacketSize,
                &maxVBRPacketSize
        );
    }
 
    Float64 numBytesForTime =
        ASBDescription.mSampleRate * maxPacketSize * seconds; // 8
    *outBufferSize =
    UInt32 (numBytesForTime < maxBufferSize ?
        numBytesForTime : maxBufferSize);                     // 9
}

以下是這段代碼的工作原理:

    1. 音頻隊(duì)列擁有您要指定大小的緩沖區(qū)兆旬。
    1. 音頻隊(duì)列的AudioStreamBasicDescription結(jié)構(gòu)假抄。
    1. 按照音頻的秒數(shù)指定每個(gè)音頻隊(duì)列緩沖區(qū)的大小。
    1. 輸出時(shí)丽猬,按字節(jié)計(jì)算每個(gè)音頻隊(duì)列緩沖區(qū)的大小宿饱。
    1. 音頻隊(duì)列緩沖區(qū)大小的上限(以字節(jié)為單位)。在這個(gè)例子中宝鼓,上限設(shè)置為320 KB刑棵。這對(duì)應(yīng)于大約五秒鐘的立體聲,采樣率為96kHz的24位音頻愚铡。
    1. 對(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é)數(shù)值列為0片橡。
    1. 對(duì)于VBR音頻數(shù)據(jù),查詢音頻隊(duì)列以獲取估計(jì)的最大數(shù)據(jù)包大小淮野。
    1. 以字節(jié)為單位獲取緩沖區(qū)大小捧书。
    1. 將緩沖區(qū)大写蹬荨(如果需要)限制到先前設(shè)置的上限。

Set a Magic Cookie for an Audio File - 為聲音文件設(shè)置一個(gè)Magic Cookie

某些壓縮音頻格式(如MPEG 4 AAC)利用包含音頻元數(shù)據(jù)的結(jié)構(gòu)经瓷。 這些結(jié)構(gòu)被稱為Magic Cookie爆哑。 當(dāng)您使用音頻隊(duì)列服務(wù)錄制這種格式時(shí),您必須從音頻隊(duì)列中獲取Magic Cookie并將其添加到音頻文件舆吮,然后再開始錄制揭朝。

Listing 2-7顯示了如何從音頻隊(duì)列中獲取Magic Cookie并將其應(yīng)用于音頻文件。 在錄制之前色冀,您的代碼會(huì)調(diào)用這樣的函數(shù)潭袱,然后在錄制之后再次調(diào)用一些函數(shù) —— 一些編解碼器在錄制停止時(shí)更新Magic Cookie數(shù)據(jù)。

// Listing 2-7  Setting a magic cookie for an audio file

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ì)列锋恬。
    1. 您正在錄制寫入的音頻文件。
    1. 指示此函數(shù)成功或失敗的結(jié)果變量伶氢。
    1. 一個(gè)變量來(lái)保存magic cooki的數(shù)據(jù)大小趟径。
    1. 從音頻隊(duì)列中獲取magic cooki的數(shù)據(jù)大小瘪吏,并將其存儲(chǔ)在cookieSize變量中癣防。
    1. 分配一個(gè)字節(jié)數(shù)組來(lái)保存魔法cookie信息。
    1. 通過(guò)查詢音頻隊(duì)列的kAudioQueueProperty_MagicCookie屬性獲取magic cooki掌眠。
    1. 為正在錄制寫入的音頻文件設(shè)置magic cookie蕾盯。 AudioFileSetProperty函數(shù)在AudioFile.h頭文件中聲明。
    1. 釋放臨時(shí)cookie變量的內(nèi)存蓝丙。
    1. 返回此函數(shù)的成功或失敗级遭。

Set Up an Audio Format for Recording - 設(shè)置錄制的音頻格式

本節(jié)介紹如何為音頻隊(duì)列設(shè)置音頻數(shù)據(jù)格式。 音頻隊(duì)列使用這種格式來(lái)記錄到一個(gè)文件渺尘。

要設(shè)置音頻數(shù)據(jù)格式挫鸽,請(qǐng)指定:

  • 音頻數(shù)據(jù)格式類型(如線性PCM,AAC等)
  • 采樣率(如44.1 kHz)
  • 音頻通道數(shù)量(如2鸥跟,立體聲)
  • 比特深度(如16比特)
  • 每個(gè)數(shù)據(jù)包的幀數(shù)(線性PCM丢郊,例如,每個(gè)數(shù)據(jù)包使用一個(gè)幀)
  • 音頻文件類型(如CAF医咨,AIFF等)
  • 文件類型所需的音頻數(shù)據(jù)格式的詳細(xì)信息

Listing 2-8說(shuō)明了如何為每個(gè)屬性設(shè)置一個(gè)固定的選項(xiàng)來(lái)設(shè)置錄制的音頻格式枫匾。 在產(chǎn)品代碼中,通常允許用戶指定音頻格式的一些或全部方面拟淮。 無(wú)論采取哪種方法干茉,目標(biāo)都是填充 AQRecorderState自定義結(jié)構(gòu)的mDataFormat字段,如 Define a Custom Structure to Manage State中所述很泊。

Listing 2-8  Specifying an audio queue’s audio data format

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)的一個(gè)實(shí)例角虫。結(jié)構(gòu)的mDataFormat字段包含一個(gè)AudioStreamBasicDescription結(jié)構(gòu)沾谓。在mDataFormat字段中設(shè)置的值為音頻隊(duì)列提供了音頻格式的初始定義,這也是您錄制文件的音頻格式戳鹅。在Listing 2-10中搏屑,您可以獲得更完整的音頻格式規(guī)范,Core Audio根據(jù)格式類型和文件類型為您提供了音頻格式粉楚。
    1. 將音頻數(shù)據(jù)格式類型定義為線性PCM辣恋。請(qǐng)參閱Core Audio Data Types Reference以獲取可用數(shù)據(jù)格式的完整列表。
    1. 定義采樣率為44.1 kHz模软。
    1. 將通道數(shù)量定義為2伟骨。
    1. 將每個(gè)通道的位深度定義為16。
    1. 將每個(gè)數(shù)據(jù)包的字節(jié)數(shù)和每個(gè)字節(jié)的字節(jié)數(shù)定義為4(即每個(gè)采樣2個(gè)通道乘以2個(gè)字節(jié))燃异。
    1. 將每個(gè)數(shù)據(jù)包的幀數(shù)定義為1携狭。
    1. 將文件類型定義為AIFF。請(qǐng)參閱AudioFile.h頭文件中的音頻文件類型枚舉以獲取可用文件類型的完整列表回俐。您可以指定已安裝編解碼器的任何文件類型逛腿,如Using Codecs and Audio Data Formats中所述。
    1. 設(shè)置指定文件類型所需的格式標(biāo)志仅颇。

Create a Recording Audio Queue - 創(chuàng)建一個(gè)錄制音頻隊(duì)列

現(xiàn)在单默,通過(guò)設(shè)置錄制回調(diào)和音頻數(shù)據(jù)格式,您可以創(chuàng)建并配置一個(gè)音頻隊(duì)列進(jìn)行錄制忘瓦。

1. Creating a Recording Audio Queue - 創(chuàng)建一個(gè)錄制音頻隊(duì)列

Listing 2-9演示了如何創(chuàng)建一個(gè)記錄音頻隊(duì)列搁廓。 請(qǐng)注意,AudioQueueNewInput函數(shù)使用前面步驟中配置的回調(diào)耕皮,自定義結(jié)構(gòu)和音頻數(shù)據(jù)格式

// Listing 2-9  Creating a recording audio queue

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ì)列境蜕。
    1. 用于錄制的音頻數(shù)據(jù)格式。 請(qǐng)參閱Set Up an Audio Format for Recording凌停。
    1. 用于錄音音頻隊(duì)列的回調(diào)函數(shù)粱年。 請(qǐng)參閱Write a Recording Audio Queue Callback
    1. 記錄音頻隊(duì)列的自定義數(shù)據(jù)結(jié)構(gòu)罚拟。 請(qǐng)參閱Define a Custom Structure to Manage State台诗。
    1. 將在其上調(diào)用回調(diào)的運(yùn)行循環(huán)。 使用NULL來(lái)指定默認(rèn)行為舟舒,在該行為中將在音頻隊(duì)列內(nèi)部的線程上調(diào)用回調(diào)拉庶。 這是典型的用法 - 它允許音頻隊(duì)列在您的應(yīng)用程序的用戶界面線程等待用戶輸入停止錄制時(shí)進(jìn)行錄制。
    1. 可以調(diào)用回調(diào)的運(yùn)行循環(huán)模式秃励。 通常氏仗,在這里使用kCFRunLoopCommonModes常量。
    1. 保留。 必須為0皆尔。
    1. 輸出時(shí)呐舔,新分配錄音音頻隊(duì)列。

2. Getting the Full Audio Format from an Audio Queue - 從音頻隊(duì)列中獲取完整的音頻格式

當(dāng)音頻隊(duì)列存在時(shí)(請(qǐng)參閱Creating a Recording Audio Queue)慷蠕,它可能已經(jīng)比您更完整地填充了AudioStreamBasicDescription結(jié)構(gòu)珊拼,特別是對(duì)于壓縮格式。 要獲得完整的格式描述流炕,請(qǐng)調(diào)用AudioQueueGetProperty函數(shù)澎现,如代碼Listing 2-10所示。 創(chuàng)建要錄制的音頻文件時(shí)每辟,請(qǐng)使用完整的音頻格式(請(qǐng)參閱Create an Audio File)剑辫。

Listing 2-10  Getting the audio format from an audio queue

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. 獲取預(yù)期的屬性值大小,以便在查詢有關(guān)其音頻數(shù)據(jù)格式的音頻隊(duì)列時(shí)使用渠欺。
    1. AudioQueueGetProperty函數(shù)獲取音頻隊(duì)列中指定屬性的值妹蔽。
    1. 音頻隊(duì)列從中獲取音頻數(shù)據(jù)格式。
    1. 用于獲取音頻隊(duì)列數(shù)據(jù)格式值的屬性ID挠将。
    1. 在輸出時(shí)胳岂,從音頻隊(duì)列中獲得完整的音頻數(shù)據(jù)格式,以AudioStreamBasicDescription結(jié)構(gòu)的形式舔稀。
    1. 在輸入時(shí)乳丰,AudioStreamBasicDescription結(jié)構(gòu)的預(yù)期大小。 在輸出上镶蹋,實(shí)際的大小成艘。 您的錄制應(yīng)用程序不需要使用此值。

Create an Audio File - 創(chuàng)建一個(gè)音頻文件

通過(guò)創(chuàng)建和配置音頻隊(duì)列贺归,您可以創(chuàng)建將錄制音頻數(shù)據(jù)的音頻文件,如Listing 2-11所示断箫。 音頻文件使用先前存儲(chǔ)在音頻隊(duì)列的自定義結(jié)構(gòu)中的數(shù)據(jù)格式和文件格式規(guī)范拂酣。

// Listing 2-11  Creating an audio file for recording

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. CFURL.h頭文件中聲明的CFURLCreateFromFileSystemRepresentation函數(shù)創(chuàng)建一個(gè)表示要記錄的文件的CFURL對(duì)象。
    1. 使用NULL(或kCFAllocatorDefault)來(lái)使用當(dāng)前的默認(rèn)內(nèi)存分配器仲义。
    1. 您要轉(zhuǎn)換為CFURL對(duì)象的文件系統(tǒng)路徑婶熬。在生產(chǎn)代碼中,通常會(huì)從用戶獲取filePath的值埃撵。
    1. 文件系統(tǒng)路徑中的字節(jié)數(shù)赵颅。
    1. 值為false表示代表文件的filePath,而不是目錄directory暂刘。
    1. AudioFile.h頭文件中的AudioFileCreateWithURL函數(shù)創(chuàng)建一個(gè)新的音頻文件或初始化現(xiàn)有的文件饺谬。
    1. 用于創(chuàng)建新音頻文件或者在現(xiàn)有文件的情況下進(jìn)行初始化的URL,該URL是從步驟1中的CFURLCreateFromFileSystemRepresentation派生的谣拣。
    1. 新文件的文件類型募寨。在本章的示例代碼中族展,這是以前通過(guò)kAudioFileAIFFType文件類型常量設(shè)置為AIFF的。請(qǐng)參閱 Set Up an Audio Format for Recording拔鹰。
    1. 將被記錄到文件中的音頻的數(shù)據(jù)格式仪缸,指定為AudioStreamBasicDescription結(jié)構(gòu)。在本章的示例代碼中列肢,這也是在 Set Up an Audio Format for Recording中設(shè)置的恰画。
    1. 在文件已經(jīng)存在的情況下擦除文件。
    1. 在輸出上瓷马,表示要錄制的音頻文件的音頻文件對(duì)象(類型AudioFileID)锣尉。

Set an Audio Queue Buffer Size - 設(shè)置音頻隊(duì)列的緩沖大小

在準(zhǔn)備錄制時(shí)使用的一組音頻隊(duì)列緩沖區(qū)之前,請(qǐng)使用您之前編寫的DeriveBufferSize函數(shù)(請(qǐng)參閱 Write a Function to Derive Recording Audio Queue Buffer Size)决采。 您將此大小分配給正在使用的錄音音頻隊(duì)列自沧。 Listing 2-12說(shuō)明了這一點(diǎn)。

// Listing 2-12  Setting an audio queue buffer size

DeriveBufferSize (                               // 1
    aqData.mQueue,                               // 2
    aqData.mDataFormat,                          // 3
    0.5,                                         // 4
    &aqData.bufferByteSize                       // 5
);

以下是這段代碼的工作原理:

    1. Write a Function to Derive Recording Audio Queue Buffer Size中描述的DeriveBufferSize函數(shù)設(shè)置適當(dāng)?shù)囊纛l隊(duì)列緩沖區(qū)大小树瞭。
    1. 您正在設(shè)置緩沖區(qū)大小的音頻隊(duì)列拇厢。
    1. 您正在錄制的文件的音頻數(shù)據(jù)格式。 請(qǐng)參閱Set Up an Audio Format for Recording晒喷。
    1. 每個(gè)音頻隊(duì)列緩沖區(qū)應(yīng)該容納的音頻的秒數(shù)孝偎。 半秒鐘,如這里設(shè)置凉敲,通常是一個(gè)不錯(cuò)的選擇衣盾。
    1. 輸出時(shí),每個(gè)音頻隊(duì)列緩沖區(qū)的大幸ァ(以字節(jié)為單位)势决。 該值放置在音頻隊(duì)列的自定義結(jié)構(gòu)中。

Prepare a Set of Audio Queue Buffers - 準(zhǔn)備一組音頻隊(duì)列緩沖

現(xiàn)在您可以詢問(wèn)您創(chuàng)建的音頻隊(duì)列(在Create a Recording Audio Queue中)以準(zhǔn)備一組音頻隊(duì)列緩沖區(qū)蓝撇。 Listing 2-13演示了如何做到這一點(diǎn)果复。

Listing 2-13  Preparing a set of audio queue buffers

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. 迭代分配和排隊(duì)每個(gè)音頻隊(duì)列緩沖區(qū)。
    1. AudioQueueAllocateBuffer函數(shù)為音頻隊(duì)列分配一個(gè)音頻隊(duì)列緩沖區(qū)渤昌。
    1. 執(zhí)行分配并擁有緩沖區(qū)的音頻隊(duì)列虽抄。
    1. 新分配的音頻隊(duì)列緩沖區(qū)的大小(以字節(jié)為單位)独柑。 請(qǐng)參閱Write a Function to Derive Recording Audio Queue Buffer Size迈窟。
    1. 輸出時(shí),新分配的音頻隊(duì)列緩沖區(qū)忌栅。 指向緩沖區(qū)的指針位于您正在使用音頻隊(duì)列的自定義結(jié)構(gòu)中车酣。
    1. AudioQueueEnqueueBuffer函數(shù)將音頻隊(duì)列緩沖區(qū)添加到緩沖區(qū)隊(duì)列的末尾。
    1. 將緩沖添加到緩沖隊(duì)列的音頻隊(duì)列。
    1. 您排隊(duì)的音頻隊(duì)列緩沖區(qū)骇径。
    1. 入隊(duì)用于記錄的緩沖區(qū)時(shí)躯肌,此參數(shù)未使用。
    1. 入隊(duì)用于記錄的緩沖區(qū)時(shí)破衔,此參數(shù)未使用清女。

Record Audio - 錄制音頻

所有前面的代碼都導(dǎo)致了非常簡(jiǎn)單的記錄過(guò)程,如Listing 2-14所示晰筛。

// Listing 2-14  Recording audio

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嫡丙,開始在音頻文件的開頭進(jìn)行錄制。
    1. 在自定義結(jié)構(gòu)中設(shè)置一個(gè)標(biāo)志來(lái)指示音頻隊(duì)列正在運(yùn)行读第。 記錄音頻隊(duì)列回調(diào)callback使用此標(biāo)志曙博。
    1. AudioQueueStart函數(shù)在其自己的線程上啟動(dòng)音頻隊(duì)列。
    1. 音頻隊(duì)列開始父泳。
    1. 使用NULL來(lái)指示音頻隊(duì)列應(yīng)該立即開始記錄。
    1. AudioQueueStop函數(shù)停止并重置錄音音頻隊(duì)列吴汪。
    1. 音頻隊(duì)列停止。
    1. 使用true來(lái)使用同步停止。 有關(guān)同步和異步停止的說(shuō)明藕各,請(qǐng)參閱Audio Queue Control and State
    1. 在自定義結(jié)構(gòu)中設(shè)置一個(gè)標(biāo)志來(lái)指示音頻隊(duì)列沒(méi)有運(yùn)行。

Clean Up After Recording - 錄制后的清除工作

當(dāng)您完成錄制時(shí)踢京,請(qǐng)銷毀音頻隊(duì)列并關(guān)閉音頻文件。 Listing 2-15說(shuō)明了這些步驟宦棺。

// Listing 2-15  Cleaning up after recording

AudioQueueDispose (                                 // 1
    aqData.mQueue,                                  // 2
    true                                            // 3
);
 
AudioFileClose (aqData.mAudioFile);                 // 4

以下是這段代碼的工作原理:

    1. AudioQueueDispose函數(shù)銷毀音頻隊(duì)列及其所有資源瓣距,包括其緩沖區(qū)。
    1. 您想要銷毀的音頻隊(duì)列代咸。
    1. 使用true來(lái)同步銷毀音頻隊(duì)列(即立即)蹈丸。
    1. 關(guān)閉用于錄制的音頻文件。 AudioFileClose函數(shù)在AudioFile.h頭文件中聲明。

后記

未完逻杖,待續(xù)~~~

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末奋岁,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子荸百,更是在濱河造成了極大的恐慌闻伶,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件够话,死亡現(xiàn)場(chǎng)離奇詭異蓝翰,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)女嘲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門畜份,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人欣尼,你說(shuō)我怎么就攤上這事爆雹。” “怎么了愕鼓?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵钙态,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我拒啰,道長(zhǎng)驯绎,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任谋旦,我火速辦了婚禮剩失,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘册着。我一直安慰自己拴孤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布甲捏。 她就那樣靜靜地躺著演熟,像睡著了一般。 火紅的嫁衣襯著肌膚如雪司顿。 梳的紋絲不亂的頭發(fā)上芒粹,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音大溜,去河邊找鬼化漆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛钦奋,可吹牛的內(nèi)容都是我干的座云。 我是一名探鬼主播疙赠,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼朦拖!你這毒婦竟也來(lái)了圃阳?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤璧帝,失蹤者是張志新(化名)和其女友劉穎捍岳,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裸弦,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祟同,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了理疙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晕城。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖窖贤,靈堂內(nèi)的尸體忽然破棺而出砖顷,到底是詐尸還是另有隱情,我是刑警寧澤赃梧,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布滤蝠,位于F島的核電站,受9級(jí)特大地震影響授嘀,放射性物質(zhì)發(fā)生泄漏物咳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一蹄皱、第九天 我趴在偏房一處隱蔽的房頂上張望览闰。 院中可真熱鬧,春花似錦巷折、人聲如沸压鉴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)油吭。三九已至,卻和暖如春署拟,著一層夾襖步出監(jiān)牢的瞬間婉宰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工推穷, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芍阎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓缨恒,卻偏偏與公主長(zhǎng)得像谴咸,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骗露,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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