1. 程式人生 > >音訊佇列服務程式設計指南(Audio Queue Services Programming Guide)(二)

音訊佇列服務程式設計指南(Audio Queue Services Programming Guide)(二)

關於音訊佇列(Audio Queues)

本章你將學習到音訊佇列的功能、架構和內部工作原理。我們將向你介紹音訊佇列用來播放或錄製所用的音訊佇列(audio queues)、音訊佇列緩衝區(audio queue buffers)和回撥函式(callback functions),你還可以找到關於音訊佇列狀態和引數的資訊,截至到本章的結尾,你將會獲得有效使用該技術的概念性理解。

什麼是音訊佇列?

    在iOS和Mac OS X中,音訊佇列是一個用來錄製和播放音訊的軟體物件,他用AudioQueueRef這個不透明資料型別來表示,該型別在AudioQueue.h標頭檔案中宣告。

    音訊佇列完成以下工作:

  • 連線音訊硬體

  • 記憶體管理

  • 根據需要為已壓縮的音訊格式引入編碼器

  • 媒體的錄製或播放

    你可以將音訊佇列配合其他Core Audio的介面使用,再加上相對少量的自定義程式碼就可以在你的應用程式中建立一套完整的數字音訊錄製或播放解決方案。

音訊佇列架構

    所有的音訊佇列都含有相同的基礎結構,包含以下幾部分:

  • 一組音訊佇列緩衝區(audio queue buffers),每個音訊佇列緩衝區都是一個儲存音訊資料的臨時倉庫

  • 一個緩衝區佇列(buffer queue),一個包含音訊佇列緩衝區的有序列表

  • 一個你自己編寫的音訊佇列回撥函式(audio queue callback

    )

    它的架構很大程度上依賴於這個音訊佇列是用來錄製還是用來播放的。不同之處在於音訊佇列如何連線到它的輸入和輸入,還有它的回撥函式所扮演的角色。

用來錄製的音訊佇列

圖示 1-1  錄製用音訊佇列A recording audio queue

    錄製用音訊佇列的輸入端一般連線到外部的音訊硬體上,比如說麥克風。在iOS中,音訊來自於由使用者連線的裝置--內建的麥克風或者耳機麥克風,比如說,在Mac OS X預設情況下,音訊來自於由使用者在系統首選項中設定的系統預設音訊輸入裝置。

    錄製用音訊佇列的輸入端利用了你自己寫的回撥函式,當錄製音訊到磁碟上的時候,回撥函式將存有從音訊佇列中接收到的新的音訊資料的緩衝區寫入到音訊檔案中。然而,錄製用音訊佇列也可以用其他方法來使用。你也可以使用其中一種,比如說,在一個實時的分析儀中,在這種情況下,你的回撥函式會直接向你的應用程式提供音訊資料,而不是將它寫入磁碟。

    你將在“記錄用音訊佇列回撥函式”中學到更多關於這個回撥的知識。

    每一個音訊佇列——無論是錄製用還是播放用——都有一個或多個音訊佇列緩衝區。這些緩衝區排列在一個特殊的被稱為緩衝區佇列(buffer queue)的序列中。如圖所示,音訊佇列緩衝區是按照他們被填充的順序編號的——這也是和把他們交付給回撥函式的順序是相同的。你將在“緩衝區佇列和入隊”中學到如何音訊佇列是如何使用它的緩衝區的。

用來播放的音訊佇列

圖示 1-2  播放用音訊佇列A playback audio queue

    在播放用音訊佇列中,回撥函式是在輸入端的,這個回撥函式的職責就是從磁碟(或其他來源)中獲取音訊資料,然後將它交付給音訊佇列。當沒有更多音訊資料需要播放的時候告訴音訊佇列停止,你將在“播放用音訊佇列回撥函式”中學到更多關於這個回撥函式的知識。

    播放用音訊佇列的輸出端一般都是連線到外部的音訊裝置的,比如說揚聲器。在iOS中,音訊通過使用者選擇的裝置播放——比如說,接受者是耳機。在Mac OS X中,預設情況下,音訊會通過使用者在系統首選項中設定的預設音訊輸出裝置中輸出。

音訊佇列緩衝區

    音訊佇列緩衝區(audio queue buffer)是一個型別的資料結構,聲明於AudioQueue.h標頭檔案。

  1. <span style="font-size:12px;">typedefstruct AudioQueueBuffer {  
  2.     const UInt32   mAudioDataBytesCapacity;  
  3.     <strong>void *const    mAudioData;</strong>  
  4.     UInt32         mAudioDataByteSize;  
  5.     void           *mUserData;  
  6. } AudioQueueBuffer;  
  7. typedef AudioQueueBuffer *AudioQueueBufferRef;</span>  
    上述程式碼中高亮的mAudioData域,指向了緩衝區本身:一個用來當作暫時存放錄製或播放音訊資料的容器的記憶體,其他域中的資料用來輔助音訊佇列管理這個緩衝區。

    音訊佇列可以使用任意數量的緩衝區。你的應用程式制定它的數量。一般情況下這個數字是3。這樣就可以讓給一個忙於將資料寫入磁碟,同時另一個在填充新的音訊資料,第三個緩衝區在需要做磁碟I/O延遲補償的時候可用。圖示1-3演示了這個過程。

    音訊佇列負責對它的緩衝區進行記憶體管理

    這提高了你新增到應用程式中的錄製和播放功能的魯棒性。同時它也幫助你優化了資源的使用。

    關於AudioQueueBuffer資料結構的完整描述,請參照音訊佇列服務參考()

緩衝區佇列和入隊

    傳遞給音訊佇列的緩衝區佇列,就如它的名字一樣,就是事實上的音訊佇列服務(Audio Queue Services),你見過緩衝區佇列——一個緩衝區的有序列表——在“音訊佇列架構”中你學到了音訊佇列物件如何配合回撥函式在錄製或播放的過程中管理緩衝區佇列。特別的,你將學習到入隊(enqueuing),緩衝區佇列對音訊佇列緩衝區的附加操作。無論你正在實現錄製或者播放,入隊都是你在回撥函式中需要執行的任務。

錄製過程

    當進行錄製的時候,一個音訊佇列緩衝區被填充了從列入例如麥克風的輸入裝置種獲取的音訊資料。緩衝區佇列中的其他緩衝區將在當前緩衝區的後面依次排隊等待被填充音訊資料。

    音訊佇列將按照緩衝區填充的順序把已填充過音訊資料的緩衝區交付給你的回撥函式。圖示1-3演示了當使用音訊佇列的時候這個錄製過程是如何工作的。

圖示 1-3  錄製過程The recording process

    在圖示1-3中的第一步,錄製開始,音訊佇列用獲取到的資料填充緩衝區。

    第二步,第一個緩衝區填充完畢,音訊佇列呼叫回撥函式來處理這個被填充滿的緩衝區(緩衝區一)。回撥函式(第三步)將緩衝區的內容寫到音訊檔案中。同時,音訊佇列將另一個緩衝區(緩衝區二)填充新獲取到的資料。

    在第四步,回撥函式將剛剛寫入磁碟的緩衝區(緩衝區一)入隊,使它重新重新回到被填充的佇列。音訊佇列再一次呼叫回撥函式(第五步),處理下一個填充完畢的緩衝區(緩衝區二)。回撥函式(第六步)將這個緩衝區的內容寫入到音訊檔案。這種穩定狀態會一直持續到使用者停止錄製。

播放過程

    當進行播放的時候,音訊佇列緩衝區將被傳送到像揚聲器這樣的輸出裝置。緩衝區佇列中其他的緩衝區講按順序排在當前緩衝區後面等待播放。

    音訊佇列將已經播放過的音訊資料按照他們播放的順序交付給你的回撥函式,回撥函式將新的音訊資料讀取到一個緩衝區中,然後將它入隊。圖示1-4演示了當使用音訊佇列時播放是如何工作的

圖示 1-4  播放過程Illustration of the playback process when using an audio queue

    圖示1-4中的第一步,應用程式啟動播放用音訊佇列,應用程式對每一個音訊佇列緩衝區呼叫回撥函式,填充這些緩衝區並且將它們加入緩衝區佇列。啟動操作會確保播放可以立即執行當你的應用程式呼叫函式之後。

    在第三步,音訊佇列將第一個緩衝區(緩衝區一)交付給輸出。

    當第一個緩衝區被播放完畢之後,播放用音訊佇列就進入了一個穩定的迴圈狀態。音訊佇列開始播放下一個緩衝區(第四步,緩衝區二)然後呼叫回撥函式(第五步),處理剛剛播放完的那個緩衝區(緩衝區一)。這個回撥函式(第六步)從音訊檔案中讀取資料填充緩衝區然後將他們入隊用於播放。

控制播放過程

    音訊佇列緩衝區總是按照他們入隊的順序進行播放,然而,在播放過程中,音訊佇列服務為你提供了函式來進行一些控制,這個函式有以下功能:

  • 設定緩衝區的精確播放時間,這可以讓你支援同步

  • 截斷音訊佇列緩衝區開頭或結尾的幀(frame),這可以讓你移除開頭或結尾的靜音

  • 在緩衝區的粒度上設定播放增益

    關於更多播放增益的知識,請看"音訊佇列引數"(),如果要對函式的完整描述,請參照“音訊佇列服務參考”()。

音訊佇列回撥函式

    一般來說,使用音訊佇列服務的大部分程式設計任務都在程式設計音訊佇列回撥函式上。

    在錄製或播放過程中,音訊佇列將反覆的呼叫它所擁有的音訊佇列回撥函式。呼叫的時間間隔取決於音訊佇列緩衝區的容量,並且一般來一說這個時間在半秒或者幾秒。

    無論對於錄製或者播放,音訊佇列回撥的一個職責就是返回一個緩衝區佇列的音訊佇列緩衝區。回撥函式使用函式將一個緩衝區加入到緩衝區佇列的末尾。對於播放來說,你也可以使用函式來獲得更多的控制,就像“控制播放過程”中描述的一樣。

錄製用音訊佇列回撥函式

    本節介紹了一般情況下——將音訊錄製到磁碟上,這種情況的回撥函式。這裡是這個錄製用回撥函式的原型,就和AudioQueue.h標頭檔案中宣告的一樣:

  1. AudioQueueInputCallback (  
  2.     void                               *inUserData,  
  3.     AudioQueueRef                      inAQ,  
  4.     AudioQueueBufferRef                inBuffer,  
  5.     const AudioTimeStamp               *inStartTime,  
  6.     UInt32                             inNumberPacketDescriptions,  
  7.     const AudioStreamPacketDescription *inPacketDescs  
  8. );  
    錄製用音訊佇列,在呼叫回撥函式的時候,提供了回撥函式將下一組音訊資料寫入到檔案的一切資訊。
  • inUserData,通常是一個你建立用來儲存音訊佇列和它的緩衝區狀態資訊的自定義結構,一個音訊檔案物件 (AudioFileID型別)代表你正在寫入的檔案,還有這個檔案的音訊格式資訊。

  • inAQ 是呼叫回撥函式的音訊佇列

  • inBuffer 是一個被音訊佇列填充新的音訊資料的音訊佇列緩衝區,它包含了回撥函式寫入檔案所需要的新資料。. 資料已經根據你在自己指定的自定義結構(由inUserData引數傳入)中指定的格式格式化。關於此點的更多資訊,請參照“使用編碼器和音訊資料格式”

  • inStartTime 是緩衝區中的一取樣的參考時間,對於基本的錄製,你的毀掉函式不會使用這個引數

  • inNumberPacketDescriptions 是inPacketDescs引數中包描述符(packet descriptions)的數量,如果你正在錄製一個VBR(可變位元率(variable bitrate))格式, 音訊佇列將會提供這個引數給你的回撥函式,這個引數可以讓你傳遞給函式. CBR (常量位元率(constant bitrate)) 格式不使用包描述符。對於CBR錄製,音訊佇列會設定這個引數並且將inPacketDescs這個引數設定為NULL

  • inPacketDescs 是一組對應於緩衝區中取樣的包描述符,音訊佇列提供了這個引數的值,如果音訊檔案是VBR格式的,你的回撥函式可以將這個值傳遞給函式(聲明於AudioFile.h標頭檔案中)

播放用音訊佇列回撥函式

    本節介紹了一般情況下——從磁碟檔案播放音訊,這種情況的回撥函式。 這裡是這個播放用回撥函式的原型,就和AudioQueue.h標頭檔案中宣告的一樣:

  1. AudioQueueOutputCallback (  
  2.     void                  *inUserData,  
  3.     AudioQueueRef         inAQ,  
  4.     AudioQueueBufferRef   inBuffer  
  5. );  

    播放用音訊佇列,在呼叫回撥函式的時候,提供了回撥函式將下一組音訊資料進行讀取進行播放的資訊。

  • inUserData域,一般來說是一個你建立的包含音訊佇列和它的緩衝區的的狀態資訊的自定義結構,一個音訊檔案物件 (AudioFileID型別) 代表了你要寫入的檔案和檔案的音訊資料格式資訊。

    在播放音訊佇列的情況下,回撥函式會在這個結構中用一個域保持對當前包的索引

  • inAQ域是呼叫這個回撥函式的音訊佇列

  • inBuffer域是一個音訊佇列緩衝區,是一個有音訊佇列變成可用狀態的音訊佇列緩衝區,你的回撥函式將把它填充上下一組要進行播放的音訊資料。

    如果你的應用程式在播放VBR資料,回撥函式需要得到正在播放的音訊資料的包資料,它通過呼叫函式來完成這個任務,這個函式聲明於AudioFile.h標頭檔案,回撥函式隨後將包資訊放到自定義的資料結構中以使得它對播放用音訊佇列可用。

使用編碼和音訊資料格式

    音訊佇列服務根據在不同的音訊格式之間轉換的時候會根據需要使用編碼器(音訊資料編碼/解碼元件)。你的錄製或播放程式可以使用任意已經安裝過相應編碼器的格式,不需要寫自定義的程式碼來處理各種各樣的音訊格式。特別的,你的回撥函式不需要知道資料格式。

    現在來講解一下這是如何工作的,每一個音訊佇列在AudioStreamBasicDescription結構中都有一個域代表了音訊資料格式。當你在mFormatID域中指定了它的格式的時候——音訊佇列會使用相應的解碼器。然後你指定取樣率和聲道數,這些就是所有你需要做的。你將會在“錄製音訊”和“播放音訊”中看到如何設定音訊資料格式的示例。

    錄製用音訊佇列按照圖示1-5中的流程使用已安裝的編碼器。

圖示 1-5  在錄製音訊的時候進行音訊格式轉換Using a code when recording with an audio queue




    在圖示1-5中的第一步,你的應用程式告訴音訊佇列開始錄製,同時也告訴它所要使用的音訊格式。在第二部,音訊佇列獲取新的音訊資料,並且根據你指定的格式使用相應的編碼器轉換音訊資料。然後音訊佇列呼叫回撥函式,將適當的格式化過的音訊資料放進緩衝區中。第三步,回撥函式將格式化後的音訊資料寫入磁碟。再次,你的回撥函式不需要了解資料格式。

    播放用音訊佇列按照圖示1-6的流程使用已安裝的編碼器。

圖示 1-6  在播放過程中進行音訊格式轉換Using a codec when playing a file with an audio queue

    在圖示1-6的第一步中,你的應用程式告訴音訊佇列開始播放,同時也告訴了它將要播放放的音訊檔案的資料格式。在第二步,音訊佇列呼叫回撥函式來從音訊檔案中讀取音訊資料。回撥函式按照它的原始格式將音訊資料交付給音訊佇列。在第三步,音訊佇列使用對應的解碼器將音訊交付給目標輸出。

          音訊佇列可以使用任意已安裝的編碼器,無論是Mac OS X原生的還是第三方的。你可以通過指定音訊佇列的AudioStreamBasicDescription結構中四位元組的編碼ID來指定將要使用的編碼器。你將會在“錄製音訊”中看到這個欄位的使用示例。

    Mac OS X包含大量的編碼器,他們都在CoreAudioTypes.h標頭檔案中的format IDs列舉值中列出了,並且記錄在核心音訊資料型別參考()中。你可以通過呼叫AudioFormat.h標頭檔案中的介面來查詢當前系統可用的編碼器。在Audio Toolbox框架中。你可以使用Fiendishthngs應用程式來顯示系統的編碼器,相應的示例程式碼在網址.

音訊佇列的控制和狀態

    音訊佇列的生命週期在建立和處理之間。你的應用程式管理它的生命週期——並且控制音訊佇列的狀態——通過使用AudioQueue.h標頭檔案中的六個函式:

  • Prime (). 對於播放, 在呼叫之前呼叫這個函式,用來確定音訊佇列中立刻就有可用的資料來播放。這個函式不在錄製中使用

  • Stop().呼叫這個函式來重置音訊佇列 (參考下面對的描述),然後停止錄製或播放。一個播放用音訊佇列回撥函式當它沒有更多的資料播放的時候會呼叫這個函式

  • Pause().呼叫這個函式可以在不影響緩衝區和不重置音訊佇列的情況下停止錄製或播放。如果需要恢復,呼叫 函式

  • Flush (). 在將最後一個音訊佇列緩衝區入隊之後呼叫,來確保所有快取過的資料,也包括處理的中間資料,得到錄製或播放

  • Reset (). 呼叫這個函式可以立即讓音訊佇列靜音。移除之前排程過的緩衝區,並且重置所有解碼器和DSP狀態

    你可以在同步或非同步模式下使用函式:
  • 同步立刻停止,不考慮之前緩衝的音訊資料

  • 非同步在所有已入隊的緩衝區播放或錄製完畢之後再停止

音訊佇列引數(Audio Queue Parameters)

    音訊佇列有一個稱作引數(parameters)的可調整設定。每個引數都有一個列舉值作為它的鍵,一個浮點數作為它的值。引數一般於播放,不用於錄製。

    在Mac OS X10.5中,只有一個音訊佇列引數就是播放增益。可以通過使用常量來獲取或設定它的值,它的有效範圍在0.0(靜音)到1.0(單位增益)

    你的應用程式可以通過兩種方法來設定音訊佇列引數:

  • 對於每一個音訊佇列,使用函式,這可以讓你直接改變音訊佇列的設定,這個改變是立刻生效的

  • 對於每一個音訊佇列緩衝區,呼叫函式。這可以讓你在將音訊佇列緩衝區入隊的時候設定音訊佇列設定。這種改變只會在播放這個音訊佇列緩衝區的時候生效。

    這兩種情況下,音訊佇列的引數設定會一直保留到你改變它們為止。