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

版本記錄

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

前言

ios系統(tǒng)中有很多方式可以播放音頻文件,這里我們就詳細(xì)的說(shuō)明下播放音樂(lè)文件的原理和實(shí)例。感興趣的可以看我寫(xiě)的上面幾篇。
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)可以是任何事物 —— 磁盤(pán)上的文件,網(wǎng)絡(luò)連接,內(nèi)存中的對(duì)象等等。 本章介紹最常見(jiàn)的情況:基本錄制到磁盤(pán)文件。

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

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

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

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


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

使用音頻隊(duì)列服務(wù)開(kāi)發(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. 表示要寫(xiě)入磁盤(pán)的音頻數(shù)據(jù)格式的AudioStreamBasicDescription結(jié)構(gòu)(來(lái)自CoreAudioTypes.h)。該格式被mQueue字段中指定的音頻隊(duì)列使用。
      mDataFormat字段最初由程序中的代碼填充,如 Set Up an Audio Format for Recording中所述。最好通過(guò)查詢(xún)音頻隊(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ū)寫(xiě)入的第一個(gè)數(shù)據(jù)包的數(shù)據(jù)包索引。
    1. 指示音頻隊(duì)列是否正在運(yùn)行的布爾值。

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

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

  • 將新填充的音頻隊(duì)列緩沖區(qū)的內(nèi)容寫(xiě)入到正在錄制的音頻文件中
  • 將音頻隊(duì)列緩沖區(qū)(其內(nèi)容剛剛寫(xiě)入磁盤(pán))排入緩沖區(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ū)寫(xiě)入磁盤(pán)

錄音的音頻隊(duì)列回調(diào)的第一項(xiàng)任務(wù)是將音頻隊(duì)列緩沖區(qū)寫(xiě)入磁盤(pán)。 這個(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)容寫(xiě)入音頻數(shù)據(jù)文件。
    1. 音頻文件對(duì)象(類(lèi)型AudioFileID),表示要寫(xiě)入的音頻文件。 pAqData變量是一個(gè)指向Listing 2-1中描述的數(shù)據(jù)結(jié)構(gòu)的指針。
    1. 使用false值表示函數(shù)在寫(xiě)入時(shí)不應(yīng)該緩存數(shù)據(jù)。
    1. 正在寫(xiě)入的音頻數(shù)據(jù)的字節(jié)數(shù)。 inBuffer變量表示由音頻隊(duì)列交給回調(diào)的音頻隊(duì)列緩沖區(qū)。
    1. 音頻數(shù)據(jù)的數(shù)據(jù)包描述數(shù)組。 值為NULL表示不需要數(shù)據(jù)包描述(例如用于CBR音頻數(shù)據(jù))。
    1. 要寫(xiě)入的第一個(gè)數(shù)據(jù)包的數(shù)據(jù)包索引。
    1. 在輸入時(shí),要寫(xiě)入的數(shù)據(jù)包的數(shù)量。 輸出時(shí),實(shí)際寫(xiě)入的數(shù)據(jù)包數(shù)量。
    1. 將新的音頻數(shù)據(jù)寫(xiě)入音頻文件。

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

現(xiàn)在來(lái)自音頻隊(duì)列緩沖區(qū)的音頻數(shù)據(jù)已被寫(xiě)入音頻文件,回調(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)容寫(xiě)入音頻數(shù)據(jù)文件。有關(guān)詳細(xì)說(shuō)明,請(qǐng)參閱Writing an Audio Queue Buffer to Disk。
    1. 如果成功寫(xiě)入音頻數(shù)據(jù),則增加音頻數(shù)據(jù)文件的包索引以準(zhǔn)備好寫(xiě)入下一個(gè)緩沖器的音頻數(shù)據(jù)。
    1. 如果音頻隊(duì)列已經(jīng)停止,則返回。
    1. 將剛剛寫(xiě)入內(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 - 編寫(xiě)一個(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ù),查詢(xún)音頻隊(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)被稱(chēng)為Magic Cookie。 當(dāng)您使用音頻隊(duì)列服務(wù)錄制這種格式時(shí),您必須從音頻隊(duì)列中獲取Magic Cookie并將其添加到音頻文件,然后再開(kāi)始錄制。

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. 您正在錄制寫(xiě)入的音頻文件。
    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ò)查詢(xún)音頻隊(duì)列的kAudioQueueProperty_MagicCookie屬性獲取magic cooki。
    1. 為正在錄制寫(xiě)入的音頻文件設(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ù)格式類(lèi)型(如線性PCM,AAC等)
  • 采樣率(如44.1 kHz)
  • 音頻通道數(shù)量(如2,立體聲)
  • 比特深度(如16比特)
  • 每個(gè)數(shù)據(jù)包的幀數(shù)(線性PCM,例如,每個(gè)數(shù)據(jù)包使用一個(gè)幀)
  • 音頻文件類(lèi)型(如CAF,AIFF等)
  • 文件類(lèi)型所需的音頻數(shù)據(jù)格式的詳細(xì)信息

Listing 2-8說(shuō)明了如何為每個(gè)屬性設(shè)置一個(gè)固定的選項(xiàng)來(lái)設(shè)置錄制的音頻格式。 在產(chǎn)品代碼中,通常允許用戶(hù)指定音頻格式的一些或全部方面。 無(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ù)格式類(lèi)型和文件類(lèi)型為您提供了音頻格式。
    1. 將音頻數(shù)據(jù)格式類(lèi)型定義為線性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. 將文件類(lèi)型定義為AIFF。請(qǐng)參閱AudioFile.h頭文件中的音頻文件類(lèi)型枚舉以獲取可用文件類(lèi)型的完整列表。您可以指定已安裝編解碼器的任何文件類(lèi)型,如Using Codecs and Audio Data Formats中所述。
    1. 設(shè)置指定文件類(lèi)型所需的格式標(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)用程序的用戶(hù)界面線程等待用戶(hù)輸入停止錄制時(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ù)期的屬性值大小,以便在查詢(xún)有關(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ì)從用戶(hù)獲取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. 新文件的文件類(lèi)型。在本章的示例代碼中,這是以前通過(guò)kAudioFileAIFFType文件類(lèi)型常量設(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ì)象(類(lèi)型AudioFileID)。

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

在準(zhǔn)備錄制時(shí)使用的一組音頻隊(duì)列緩沖區(qū)之前,請(qǐng)使用您之前編寫(xiě)的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)在您可以詢(xú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,開(kāi)始在音頻文件的開(kāi)頭進(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ì)列開(kāi)始。
    1. 使用NULL來(lái)指示音頻隊(duì)列應(yīng)該立即開(kāi)始記錄。
    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)銷(xiāo)毀音頻隊(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ù)銷(xiāo)毀音頻隊(duì)列及其所有資源,包括其緩沖區(qū)。
    1. 您想要銷(xiāo)毀的音頻隊(duì)列。
    1. 使用true來(lái)同步銷(xiāo)毀音頻隊(duì)列(即立即)。
    1. 關(guān)閉用于錄制的音頻文件。 AudioFileClose函數(shù)在AudioFile.h頭文件中聲明。

后記

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,595評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,560評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,035評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,814評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,224評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,444評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,988評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,804評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,998評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評(píng)論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,237評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,665評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,927評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,706評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,993評(píng)論 2 374

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