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

版本記錄

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

前言

ios系統(tǒng)中有很多方式可以播放音頻文件,這里我們就詳細(xì)的說明下播放音樂文件的原理和實(shí)例。感興趣的可以看我寫的上面幾篇。
1. 幾種播放音頻文件的方式(一) —— 播放本地音樂
2. 幾種播放音頻文件的方式(二) —— 音效播放
3. 幾種播放音頻文件的方式(三) —— 網(wǎng)絡(luò)音樂播放
4. 幾種播放音頻文件的方式(四) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)(一)
5. 幾種播放音頻文件的方式(五) —— 音頻隊(duì)列服務(wù)(Audio Queue Services)簡介(二)
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ì)象等等。 本章介紹最常見的情況:基本錄制到磁盤文件。

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

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

  • 定義一個(gè)自定義結(jié)構(gòu)來管理狀態(tài),格式和路徑信息。
  • 編寫一個(gè)音頻隊(duì)列callback回調(diào)函數(shù)來執(zhí)行實(shí)際錄制。
  • 可以編寫代碼來確定音頻隊(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è)文件來記錄。
  • 告訴音頻隊(duì)列開始錄制。
  • 完成后,告訴音頻隊(duì)列停止并銷毀。 音頻隊(duì)列銷毀其緩沖區(qū)。

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


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

使用音頻隊(duì)列服務(wù)開發(fā)錄音解決方案的第一步是定義一個(gè)自定義結(jié)構(gòu)。 您將使用此結(jié)構(gòu)來管理音頻格式和音頻隊(duì)列狀態(tài)信息。 Listing 2-1說明了這樣一個(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)(來自CoreAudioTypes.h)。該格式被mQueue字段中指定的音頻隊(duì)列使用。
      mDataFormat字段最初由程序中的代碼填充,如 Set Up an Audio Format for Recording中所述。最好通過查詢音頻隊(duì)列的kAudioQueueProperty_StreamDescription屬性來更新此字段的值,如Getting the Full Audio Format from an Audio Queue中所述。在Mac OS X v10.5上,改用kAudioConverterCurrentInputStreamDescription屬性。有關(guān)AudioStreamBasicDescription結(jié)構(gòu)的詳細(xì)信息,請參閱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ì)算此值。請參閱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ù)

接下來,寫一個(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)角色的說明,請參閱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í)間(簡單記錄不需要)。
    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ì)列剛剛完成填充來自輸入設(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)在來自音頻隊(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ù)。請參閱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ì)說明,請參閱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ì)說明,請參閱Enqueuing an Audio Queue Buffer

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

音頻隊(duì)列服務(wù)期望您的應(yīng)用程序指定您使用的音頻隊(duì)列緩沖區(qū)的大小。 Listing 2-6顯示了一個(gè)這樣做的方法。 它產(chǎn)生足夠大的緩沖區(qū)大小來保存給定持續(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è)變量來保存magic cooki的數(shù)據(jù)大小。
    1. 從音頻隊(duì)列中獲取magic cooki的數(shù)據(jù)大小,并將其存儲(chǔ)在cookieSize變量中。
    1. 分配一個(gè)字節(jié)數(shù)組來保存魔法cookie信息。
    1. 通過查詢音頻隊(duì)列的kAudioQueueProperty_MagicCookie屬性獲取magic cooki。
    1. 為正在錄制寫入的音頻文件設(shè)置magic cookieAudioFileSetProperty函數(shù)在AudioFile.h頭文件中聲明。
    1. 釋放臨時(shí)cookie變量的內(nèi)存。
    1. 返回此函數(shù)的成功或失敗。

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

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

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

  • 音頻數(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說明了如何為每個(gè)屬性設(shè)置一個(gè)固定的選項(xiàng)來設(shè)置錄制的音頻格式。 在產(chǎn)品代碼中,通常允許用戶指定音頻格式的一些或全部方面。 無論采取哪種方法,目標(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。請參閱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。請參閱AudioFile.h頭文件中的音頻文件類型枚舉以獲取可用文件類型的完整列表。您可以指定已安裝編解碼器的任何文件類型,如Using Codecs and Audio Data Formats中所述。
    1. 設(shè)置指定文件類型所需的格式標(biāo)志。

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

現(xiàn)在,通過設(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ì)列。 請注意,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ù)格式。 請參閱Set Up an Audio Format for Recording
    1. 用于錄音音頻隊(duì)列的回調(diào)函數(shù)。 請參閱Write a Recording Audio Queue Callback
    1. 記錄音頻隊(duì)列的自定義數(shù)據(jù)結(jié)構(gòu)。 請參閱Define a Custom Structure to Manage State
    1. 將在其上調(diào)用回調(diào)的運(yùn)行循環(huán)。 使用NULL來指定默認(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í)(請參閱Creating a Recording Audio Queue),它可能已經(jīng)比您更完整地填充了AudioStreamBasicDescription結(jié)構(gòu),特別是對(duì)于壓縮格式。 要獲得完整的格式描述,請調(diào)用AudioQueueGetProperty函數(shù),如代碼Listing 2-10所示。 創(chuàng)建要錄制的音頻文件時(shí),請使用完整的音頻格式(請參閱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è)音頻文件

通過創(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)來使用當(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. 新文件的文件類型。在本章的示例代碼中,這是以前通過kAudioFileAIFFType文件類型常量設(shè)置為AIFF的。請參閱 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ū)之前,請使用您之前編寫的DeriveBufferSize函數(shù)(請參閱 Write a Function to Derive Recording Audio Queue Buffer Size)。 您將此大小分配給正在使用的錄音音頻隊(duì)列。 Listing 2-12說明了這一點(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ù)格式。 請參閱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)在您可以詢問您創(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é)為單位)。 請參閱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)致了非常簡單的記錄過程,如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)志來指示音頻隊(duì)列正在運(yùn)行。 記錄音頻隊(duì)列回調(diào)callback使用此標(biāo)志。
    1. AudioQueueStart函數(shù)在其自己的線程上啟動(dòng)音頻隊(duì)列。
    1. 音頻隊(duì)列開始。
    1. 使用NULL來指示音頻隊(duì)列應(yīng)該立即開始記錄。
    1. AudioQueueStop函數(shù)停止并重置錄音音頻隊(duì)列。
    1. 音頻隊(duì)列停止。
    1. 使用true來使用同步停止。 有關(guān)同步和異步停止的說明,請參閱Audio Queue Control and State
    1. 在自定義結(jié)構(gòu)中設(shè)置一個(gè)標(biāo)志來指示音頻隊(duì)列沒有運(yùn)行。

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

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

// 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來同步銷毀音頻隊(duì)列(即立即)。
    1. 關(guān)閉用于錄制的音頻文件。 AudioFileClose函數(shù)在AudioFile.h頭文件中聲明。

后記

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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