Audio Queue Services 解讀之 Playing Audio(下)

解讀Play Audio下集,如果你沒看上集,建議先去看看上集.

Audio Queue Services 解讀之 Playing Audio(上)

上集已經準備好音頻隊列的結構體以及回調函數,那么接下來就可以創建音頻隊列并實現播放了!

五、Create a Playback Audio Queue(創建播放的音頻隊列)

下面將演示怎么去創建回放音頻隊列,注意:AudioQueueNewOutput 函數中使用的自定義結構體和回調方法都是在之前寫好了

AudioQueueNewOutput (                                // 1
    &aqData.mDataFormat,                             // 2
    HandleOutputBuffer,                              // 3
    &aqData,                                         // 4
    CFRunLoopGetCurrent (),                          // 5
    kCFRunLoopCommonModes,                           // 6
    0,                                               // 7
    &aqData.mQueue                                   // 8
);
  • 1、通過 AudioQueueNewOutput 函數來創建一個回放音頻隊列
  • 2、需要設置播放的音頻隊列對應的音頻文件數據格式,參考Obtaining a File’s Audio Data Format
  • 3、回放音頻隊列的回調函數,參考Write a Playback Audio Queue Callback
  • 4、音頻隊列對應的自定義結構體,可查看Define a Custom Structure to Manage State
  • 5、當前運行循環,以及將調用音頻隊列回放回調的循環
  • 6、當前可以被調用回調的運行循環模式,通常使用 kCFRunLoopCommonModes
  • 7、保留字段,傳0
  • 8、輸出時,新分配的回放音頻隊列

六、Set Sizes for a Playback Audio Queue(設置播放音頻隊列的大小)

現在,你可以為播放音頻隊列設置一些大小,當你給緩沖隊列分配緩沖區的時候和開始讀取音頻文件之前,你可以使用這些設置的大小

下面代碼將會告訴你怎么設置

  • 1、音頻隊列緩沖區大小
  • 2、每次執行回放音頻隊列回調要讀取的數據包個數
  • 3、用于保存一個緩沖區的音頻數據的分組描述的數組大小

(1)、Setting Buffer Size and Number of Packets to Read(設置緩沖區大小和要讀取的數據包數)

下面代碼段會演示如何使用之前寫的 DeriveBufferSize 函數(see Write a Function to Derive Playback Audio Queue Buffer Size)這里是為每一個音頻緩沖區設置一個大小,單位是字節;以及確認每次調用回放音頻回調要讀取的數據包數

此代碼使用最大包大小的保守估計,Core Audio通過 kAudioFilePropertyPacketSizeUpperBound 屬性提供。 在大多數情況下,最好使用這種方式 - 這是近似但快速的 - 花費時間讀取整個音頻文件以獲得實際的最大包大小

UInt32 maxPacketSize;
UInt32 propertySize = sizeof (maxPacketSize);
AudioFileGetProperty (                               // 1
    aqData.mAudioFile,                               // 2
    kAudioFilePropertyPacketSizeUpperBound,          // 3
    &propertySize,                                   // 4
    &maxPacketSize                                   // 5
);
 
DeriveBufferSize (                                   // 6
    aqData.mDataFormat,                              // 7
    maxPacketSize,                                   // 8
    0.5,                                             // 9
    &aqData.bufferByteSize,                          // 10
    &aqData.mNumPacketsToRead                        // 11
);

下面介紹一下代碼的作用:

  • 1、AudioFileGetProperty 函數,聲明在 AudioFile.h 頭文件中,可以獲取音頻文件中指定屬性的值,這里你可以用來獲取一個你要播放的音頻文件的音頻數據包大小的保守上限
  • 2、音頻文件對象(類型是 AudioFileID)代表你要播放的音頻文件,可查看Opening an Audio File
  • 3、屬性ID,用于在音頻文件中獲取數據包大小的保守上限
  • 4、輸出時,kAudioFilePropertyPacketSizeUpperBound屬性的大小(以字節為單位)
  • 5、輸出是,你要播放的音頻文件數據包大小的保守上限(以字節為單位)
  • 6、DeriveBufferSize 函數,聲明在Write a Function to Derive Playback Audio Queue Buffer Size,可以設置緩沖區大小和每次調用回調要讀取的數據包數
  • 7、你要播放的文件數據格式,可查看 Obtaining a File’s Audio Data Format
  • 8、音頻文件預估最大的數據包大小,上面第5點提到那個
  • 9、每個音頻隊列緩沖區應該保持的音頻的秒數,通常0.5秒是一個很好的選擇
  • 10、輸出時、每個音頻隊列緩沖區的大小(以字節為單位),這個值存放在音頻隊列的自定義結構體中
  • 11、輸出時,每次執行回調要讀取的數據包數,這個值也是存儲在音頻隊列的自定義結構體中

(2)、Allocating Memory for a Packet Descriptions Array(為包描述數組分配內存)

現在為數組分配內存,以保存一個緩沖區的音頻數據的數據包描述。 恒定比特率數據不使用數據包描述,因此下面代碼中CBR情形 - 步驟3非常簡單。

bool isFormatVBR = (                                       // 1
    aqData.mDataFormat.mBytesPerPacket == 0 ||
    aqData.mDataFormat.mFramesPerPacket == 0
);
 
if (isFormatVBR) {                                         // 2
    aqData.mPacketDescs =
      (AudioStreamPacketDescription*) malloc (
        aqData.mNumPacketsToRead * sizeof (AudioStreamPacketDescription)
      );
} else {                                                   // 3
    aqData.mPacketDescs = NULL;
}

下面介紹代碼作用

  • 1、確定音頻文件的數據格式是VBR還是CBR。 在VBR數據中,每個字節的字節數或每幀的幀數是可變的,因此將在音頻隊列的AudioStreamBasicDescription結構中列為0。
  • 2、對于包含VBR數據的音頻文件,為包描述數組分配內存。 基于在每次調用回放回調時要讀取的音頻數據包的數量來計算所需的內存。 請參閱Setting Buffer Size and Number of Packets to Read
  • 3、對于包含CBR數據的音頻文件(例如線性PCM),音頻隊列不使用包描述數組。

七、Set a Magic Cookie for a Playback Audio Queue(設置回放音頻隊列的 Magic Cookie )

一些音頻壓縮的音頻格式,例如 MPEG 4 AAC,利用結構體包含音頻的元數據。這些結構體就是Magic Cookie,當你用 Audio Queue Services 播放這種格式的音頻文件時,你可以從音頻文件中獲取Magic Cookie ,然后在播放之前添加到音頻隊列中

下面代碼教你怎么去從音頻文件中獲取Magic Cookie,并添加到音頻隊列中,下面的代碼需要在開始回放之前執行

UInt32 cookieSize = sizeof (UInt32);                   // 1
bool couldNotGetProperty =                             // 2
    AudioFileGetPropertyInfo (                         // 3
        aqData.mAudioFile,                             // 4
        kAudioFilePropertyMagicCookieData,             // 5
        &cookieSize,                                   // 6
        NULL                                           // 7
    );
 
if (!couldNotGetProperty && cookieSize) {              // 8
    char* magicCookie =
        (char *) malloc (cookieSize);
 
    AudioFileGetProperty (                             // 9
        aqData.mAudioFile,                             // 10
        kAudioFilePropertyMagicCookieData,             // 11
        &cookieSize,                                   // 12
        magicCookie                                    // 13
    );
 
    AudioQueueSetProperty (                            // 14
        aqData.mQueue,                                 // 15
        kAudioQueueProperty_MagicCookie,               // 16
        magicCookie,                                   // 17
        cookieSize                                     // 18
    );
 
    free (magicCookie);                                // 19
}

下面介紹一下代碼的作用:

  • 1、設置magic cookie 的預估大小
  • 2、獲取函數 AudioFileGetPropertyInfo 的返回值,如果成功,返回 NoErr,相當于布爾值 false
  • 3、函數 AudioFileGetPropertyInfo 聲明在 AudioFile.h 頭文件,可以獲取指定屬性值的大小,可以使用它來設置保存屬性值的變量的大小
  • 4、你要播放的音頻文件對象,類型是 AudioFileID
  • 5、屬性ID 表示音頻文件的 Magic Cookie 數據
  • 6、輸入時,是magic cookie 數據的預估大小;輸出時,是真實的大小
  • 7、使用 NULL 表示你不關心這個屬性的讀寫訪問
  • 8、如果音頻文件包含有 magic cookie,那么就為它分配內存進行管理
  • 9、函數 AudioFileGetProperty 聲明在 AudioFile.h 頭文件,可以獲取指定屬性值,在這里是獲取音頻文件的 magic cookie
  • 10、表示你要播放的并且獲取到magic cookie的音頻文件對象,類型是 AudioFileID
  • 11、屬性ID 表示音頻文件的 Magic Cookie 數據
  • 12、輸入時,通過使用函數 AudioFileGetPropertyInfo 獲取 magicCookie 變量的大小;輸出時,真實的magic cookie 大小就是寫入到 magicCookie 變量的字節數
  • 13、輸出時,音頻文件的 magic cookie
  • 14、函數 AudioQueueSetProperty 設置音頻隊列的屬性,在這里,用來匹配要被播放的音頻文件的magic cookie并設置給音頻隊列
  • 15、你要設置magic cookie 的音頻隊列
  • 16、屬性ID 表示音頻文件的 Magic Cookie 數據
  • 17、你要播放的音頻文件的 magic cookie
  • 18、magic cookie 的大小,單位是字節
  • 19、釋放之前被分配內存的magic cookie

八、Allocate and Prime Audio Queue Buffers(分配和填充音頻隊列緩沖區)

現在你可以讓你之前創建的音頻對象去準備一些音頻隊列緩沖區,下面代碼教你怎么做

aqData.mCurrentPacket = 0;                                // 1
 
for (int i = 0; i < kNumberBuffers; ++i) {                // 2
    AudioQueueAllocateBuffer (                            // 3
        aqData.mQueue,                                    // 4
        aqData.bufferByteSize,                            // 5
        &aqData.mBuffers[i]                               // 6
    );
 
    HandleOutputBuffer (                                  // 7
        &aqData,                                          // 8
        aqData.mQueue,                                    // 9
        aqData.mBuffers[i]                                // 10
    );
}

下面介紹代碼的作用:

  • 1、將包索引設置為0,以便在音頻隊列回調開始填充緩沖區時(步驟7),它從音頻文件的開頭開始
  • 2、分配和填充音頻隊列緩沖區(你在 Define a Custom Structure to Manage State 設置了 kNumberBuffers這個值為 3
  • 3、函數 AudioQueueAllocateBuffer 通過分配內存來創建音頻隊列緩沖區
  • 4、負責分配音頻隊列緩沖區的音頻隊列
  • 5、新的音頻隊列緩沖區的大小,單位是字節
  • 6、輸出時,添加新的音頻隊列緩沖區到自定義結構體的 mBuffers 數組中
  • 7、函數 HandleOutputBuffer 是你之前寫的回放音頻隊列回調,可查看 Write a Playback Audio Queue Callback
  • 8、音頻隊列的自定義結構體
  • 9、調用回調的音頻隊列
  • 10、傳遞到音頻隊列回調的音頻隊列緩沖區

九、Set an Audio Queue’s Playback Gain(設置音頻隊列回放增益)

在告訴音頻隊列播放之前,你可以通過音頻隊列參數機制設置其增益,下面代碼教你怎么設置,更多設置可以查看 Audio Queue Parameters

Float32 gain = 1.0;                                       // 1
    // Optionally, allow user to override gain setting here
AudioQueueSetParameter (                                  // 2
    aqData.mQueue,                                        // 3
    kAudioQueueParam_Volume,                              // 4
    gain                                                  // 5
);

下面介紹一下代碼的作用:

  • 1、設置音頻隊列的增益,在0-1之間
  • 2、通過函數 AudioQueueSetParameter 設置音頻隊列的參數值
  • 3、設置參數得音頻隊列
  • 4、設置參數的ID,kAudioQueueParam_Volume常數可讓你設置音頻隊列的增益
  • 5、應用于音頻隊列的增益設置

十、Start and Run an Audio Queue(開啟并運行音頻隊列)

所有上述代碼已經播放文件的過程。 這包括在播放文件時啟動音頻隊列并維護運行循環,看下面代碼

aqData.mIsRunning = true;                          // 1
 
AudioQueueStart (                                  // 2
    aqData.mQueue,                                 // 3
    NULL                                           // 4
);
 
do {                                               // 5
    CFRunLoopRunInMode (                           // 6
        kCFRunLoopDefaultMode,                     // 7
        0.25,                                      // 8
        false                                      // 9
    );
} while (aqData.mIsRunning);
 
CFRunLoopRunInMode (                               // 10
    kCFRunLoopDefaultMode,
    1,
    false
);

介紹代碼作用:

  • 1、設置自定義結構體的標識,指示音頻隊列正在運行
  • 2、使用函數 AudioQueueStart 開啟音頻隊列,在自己的線程中
  • 3、要開啟的音頻隊列
  • 4、使用 NULL 表示音頻隊列需要馬上開啟播放
  • 5、定期輪詢自定義結構的mIsRunning字段,以檢查音頻隊列是否已停止
  • 6、CFRunLoopRunInMode 函數運行包含音頻隊列的線程的運行循環
  • 7、使用默認的運行循環模式
  • 8、設置運行循環的運行時間為0.25秒
  • 9、使用 false 表示運行循環應該繼續指定整個時間
  • 10、在音頻隊列停止后,再運行循環一會兒,以確保當前播放的音頻隊列緩沖區有時間完成

十一、Clean Up After Playing(播放完畢后清除)

當你的音頻文件播放完畢,應該要銷毀這個音頻隊列,關閉音頻文件,同時要釋放所有資源,下面代碼處理了這些步驟

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

下面介紹一下代碼的作用:

  • 1、函數 AudioQueueDispose 銷毀音頻隊列和它的所有資源,包括它的緩沖區
  • 2、要銷毀的音頻隊列
  • 3、使用true可以同步銷毀音頻隊列
  • 4、關閉播放完畢的音頻文件,函數 AudioFileClose 聲明在 AudioFile.h 頭文件中
  • 5、釋放用于保存數據包描述的內存

十二、總結:

  • 1、實現上面的步驟,就可以播放本地的音頻文件了,后續工作就是優化封裝了
  • 2、因為上面步驟只能實現播放本地音頻文件,至于網絡音頻文件播放就沒提,后續 出的Demo 會實現播放網絡音頻文件,敬請期待
  • 3、playing audio 這篇的翻譯算是完成了,歡迎大家關注我,喜歡就給個like,如果有問題,請留言喔,謝謝
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容