版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2017.12.28 |
前言
ios系統中有很多方式可以播放音頻文件,這里我們就詳細的說明下播放音樂文件的原理和實例。感興趣的可以看我寫的上面幾篇。
1. 幾種播放音頻文件的方式(一) —— 播放本地音樂
2. 幾種播放音頻文件的方式(二) —— 音效播放
3. 幾種播放音頻文件的方式(三) —— 網絡音樂播放
4. 幾種播放音頻文件的方式(四) —— 音頻隊列服務(Audio Queue Services)(一)
5. 幾種播放音頻文件的方式(五) —— 音頻隊列服務(Audio Queue Services)簡介(二)
6. 幾種播放音頻文件的方式(六) —— 音頻隊列服務(Audio Queue Services)之關于音頻隊列(三)
7. 幾種播放音頻文件的方式(七) —— 音頻隊列服務(Audio Queue Services)之錄制音頻(四)
Playing Audio - 播放音頻
使用音頻隊列服務播放音頻時,音源可以是任何東西 - 磁盤上文件,基于軟件的音頻合成器,內存中的對象等等。本章介紹最常見的情況:播放磁盤上的文件。
注意:本章介紹了一個基于ANSI-C的播放實現,并且使用了
Mac OS X Core Audio SDK
中的C ++類。對于基于Objective-C的示例,請參閱iOS Dev Center的SpeakHere
示例代碼。
要為您的應用程序添加播放功能,通常需要執行以下步驟:
- 定義一個自定義結構來管理狀態,格式和路徑信息。
- 編寫音頻隊列回調函數來執行實際播放。
- 編寫代碼來確定音頻隊列緩沖區的大小。
- 打開音頻文件進行播放并確定其音頻數據格式。
- 創建一個播放音頻隊列并將其配置為播放。
- 分配和入隊音頻隊列緩沖區。告訴音頻隊列開始播放。完成后,回放回調告知音頻隊列停止。
- 銷毀音頻隊列,釋放資源。
本章的其余部分將詳細介紹這些步驟。
Define a Custom Structure to Manage State - 定義一個自定義結構管理狀態
首先,定義一個用來管理音頻格式和音頻隊列狀態信息的自定義結構。 Listing 3-1
說明了這樣一個結構:
// Listing 3-1 A custom structure for a playback audio queue
static const int kNumberBuffers = 3; // 1
struct AQPlayerState {
AudioStreamBasicDescription mDataFormat; // 2
AudioQueueRef mQueue; // 3
AudioQueueBufferRef mBuffers[kNumberBuffers]; // 4
AudioFileID mAudioFile; // 5
UInt32 bufferByteSize; // 6
SInt64 mCurrentPacket; // 7
UInt32 mNumPacketsToRead; // 8
AudioStreamPacketDescription *mPacketDescs; // 9
bool mIsRunning; // 10
};
此結構中的大多數字段與用于記錄的自定義結構中的字段相同(或接近),如Define a Custom Structure to Manage State中的“錄制音頻”章節中所述。例如,mDataFormat
字段在此用于保存正在播放的文件的格式。錄制時,類似字段保存正在寫入磁盤的文件的格式。
以下是這個結構中的字段的描述:
- 設置要使用的音頻隊列緩沖區的數量。如Audio Queue Buffers中所述,“三”通常是一個很好的數字。
- 表示正在播放的文件的音頻數據格式的
AudioStreamBasicDescription
結構(來自CoreAudioTypes.h
)。該格式被mQueue
字段中指定的音頻隊列使用。通過查詢音頻文件的kAudioFilePropertyDataFormat
屬性來填充mDataFormat
字段,如Obtaining a File’s Audio Data Format中所述。有關AudioStreamBasicDescription
結構的詳細信息,請參閱Core Audio Data Types Reference。
- 表示正在播放的文件的音頻數據格式的
- 由應用程序創建的播放音頻隊列。
- 一個數組,指向由音頻隊列管理的音頻隊列緩沖區的指針。
- 音頻文件對象,表示您的程序播放的音頻文件。
- 每個音頻隊列緩沖區的大小(以字節為單位)。在創建音頻隊列之后并在啟動之前,在
DeriveBufferSize
函數的這些示例中計算此值。請參閱 Write a Function to Derive Playback Audio Queue Buffer Size。
- 每個音頻隊列緩沖區的大小(以字節為單位)。在創建音頻隊列之后并在啟動之前,在
- 從音頻文件播放下一個數據包的數據包索引。
- 每次調用音頻隊列的回放回調時讀取的數據包數量。與
bufferByteSize
字段類似,在創建音頻隊列之后并在啟動之前,在DeriveBufferSize
函數的這些示例中計算此值。
- 每次調用音頻隊列的回放回調時讀取的數據包數量。與
- 對于
VBR
音頻數據,正在播放的文件的數據包描述數組。對于CBR
數據,該字段的值為NULL。
- 對于
- 指示音頻隊列是否正在運行的布爾值。
Write a Playback Audio Queue Callback - 編寫播放音頻隊列回調
接下來,寫一個回放音頻隊列回調函數。 這個回調有三個主要的事情:
- 從音頻文件讀取指定數量的數據并將其放入音頻隊列緩沖區
- 將音頻隊列緩沖區排入緩沖隊列
- 當沒有更多的數據要從音頻文件讀取,告訴音頻隊列停止
本部分顯示了一個示例回調聲明,分別描述了這些任務,并最終呈現整個回放回調。 有關回放回調角色的說明,請參閱圖Figure 1-4。
1. The Playback Audio Queue Callback Declaration - 播放音頻隊列回調函數聲明
Listing 3-2
顯示了一個播放音頻隊列回調函數的示例聲明,聲明為AudioQueue.h
頭文件中的AudioQueueOutputCallback
// Listing 3-2 The playback audio queue callback declaration
static void HandleOutputBuffer (
void *aqData, // 1
AudioQueueRef inAQ, // 2
AudioQueueBufferRef inBuffer // 3
)
以下是這段代碼的工作原理:
- 通常,
aqData
是包含音頻隊列狀態信息的自定義結構,如Define a Custom Structure to Manage State所述。
- 通常,
- 擁有此回調的音頻隊列。
- 一個音頻隊列緩沖區,回調將通過讀取音頻文件來填充數據。
2. Reading From a File into an Audio Queue Buffer - 從文件中讀取數據到音頻隊列緩沖區
回放音頻隊列回調的第一個動作是從音頻文件讀取數據并將其放入音頻隊列緩沖區。Listing 3-3
顯示了如何做到這一點。
// Listing 3-3 Reading from an audio file into an audio queue buffer
AudioFileReadPackets ( // 1
pAqData->mAudioFile, // 2
false, // 3
&numBytesReadFromFile, // 4
pAqData->mPacketDescs, // 5
pAqData->mCurrentPacket, // 6
&numPackets, // 7
inBuffer->mAudioData // 8
);
以下是這段代碼的工作原理:
- 在
AudioFile.h
頭文件中聲明的AudioFileReadPackets
函數從音頻文件讀取數據并將其放入緩沖區。
- 在
- 要從中讀取的音頻文件。
- 使用值為false來指示函數在讀取時不應該緩存數據。
- 輸出時,從音頻文件中讀取的音頻數據的字節數。
- 在輸出上,從音頻文件中讀取數據的數據包描述數組。 對于CBR數據,此參數的輸入值為NULL。
- 從音頻文件中讀取的第一個數據包數據包索引。
- 輸入時,從音頻文件中讀取的數據包數量。 輸出時,實際讀取的數據包數量。
- 輸出時,填充的音頻隊列緩沖區包含從音頻文件中讀取的數據。
3. Enqueuing an Audio Queue Buffer - 聲頻隊列緩沖入隊
現在,數據已經從音頻文件中讀取并放入音頻隊列緩沖區,回調將緩沖區排入隊列中,如Listing 3-4
所示。 一旦進入緩沖區隊列,緩沖區中的音頻數據就可供音頻隊列發送到輸出設備。
// Listing 3-4 Enqueuing an audio queue buffer after reading from disk
AudioQueueEnqueueBuffer ( // 1
pAqData->mQueue, // 2
inBuffer, // 3
(pAqData->mPacketDescs ? numPackets : 0), // 4
pAqData->mPacketDescs // 5
);
以下是這段代碼的工作原理:
-
AudioQueueEnqueueBuffer
函數將音頻隊列緩沖區添加到緩沖區隊列中。
-
- 擁有緩沖隊列
buffer queue
的音頻隊列audio queue
。
- 擁有緩沖隊列
- 要排隊的音頻隊列緩沖區
- 音頻隊列緩沖區數據中表示的數據包數量。 對于不使用數據包描述的CBR數據,使用0。
- 對于使用數據包描述的壓縮音頻數據格式,緩沖區中數據包的數據包描述。
4. Stopping an Audio Queue - 停止音頻隊列
你回調的最后一件事是檢查是否沒有更多的數據從你正在播放的音頻文件中讀取。 一旦發現文件結束,你的回調告訴播放音頻隊列停止。 Listing 3-5
說明了這一點。
// Listing 3-5 Stopping an audio queue
if (numPackets == 0) { // 1
AudioQueueStop ( // 2
pAqData->mQueue, // 3
false // 4
);
pAqData->mIsRunning = false; // 5
}
以下是這段代碼的工作原理:
- 檢查
AudioFileReadPackets
函數讀取的數據包的數量是否為0。
- 檢查
-
AudioQueueStop
函數停止音頻隊列。
-
- 要停止的音頻隊列。
- 當所有排隊的緩沖區都被播放時,異步停止音頻隊列。 請參閱Audio Queue Control and State。
- 在自定義結構中設置一個標志來指示播放完成。
5. A Full Playback Audio Queue Callback - 播放音頻隊列回調函數完整版
Listing 3-6
顯示了完整播放音頻隊列回調的基本版本。 與本文檔中的其他代碼示例一樣,此列表不包括錯誤處理。
// Listing 3-6 A playback audio queue callback function
static void HandleOutputBuffer (
void *aqData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer
) {
AQPlayerState *pAqData = (AQPlayerState *) aqData; // 1
if (pAqData->mIsRunning == 0) return; // 2
UInt32 numBytesReadFromFile; // 3
UInt32 numPackets = pAqData->mNumPacketsToRead; // 4
AudioFileReadPackets (
pAqData->mAudioFile,
false,
&numBytesReadFromFile,
pAqData->mPacketDescs,
pAqData->mCurrentPacket,
&numPackets,
inBuffer->mAudioData
);
if (numPackets > 0) { // 5
inBuffer->mAudioDataByteSize = numBytesReadFromFile; // 6
AudioQueueEnqueueBuffer (
pAqData->mQueue,
inBuffer,
(pAqData->mPacketDescs ? numPackets : 0),
pAqData->mPacketDescs
);
pAqData->mCurrentPacket += numPackets; // 7
} else {
AudioQueueStop (
pAqData->mQueue,
false
);
pAqData->mIsRunning = false;
}
}
以下是這段代碼的工作原理:
- 實例化時提供給音頻隊列的自定義數據,包括表示要播放的文件的音頻文件對象(類型
AudioFileID
)以及各種狀態數據。 請參閱 Define a Custom Structure to Manage State。
- 實例化時提供給音頻隊列的自定義數據,包括表示要播放的文件的音頻文件對象(類型
- 如果音頻隊列停止,立即返回。
- 保存正在播放的文件中讀取的音頻數據字節數的變量。
- 使用要從正在播放的文件中讀取的數據包數初始化
numPackets
變量。
- 使用要從正在播放的文件中讀取的數據包數初始化
- 測試是否從文件中檢索到某些音頻數據。 如果是,排隊新填充的緩沖區。 如果不是,則停止音頻隊列。
- 告訴音頻隊列緩沖區結構讀取數據的字節數。
- 根據讀取的數據包數量遞增數據包索引。
Write a Function to Derive Playback Audio Queue Buffer Size - 編寫函數獲取播放音頻隊列緩沖區的大小
音頻隊列服務期望您的應用程序指定您使用的音頻隊列緩沖區的大小。 Listing 3-7
顯示了一種方法。 它產生足夠大的緩沖區大小來保存給定持續時間的音頻數據。
在創建一個回放音頻隊列之后,您將在您的應用程序中調用此DeriveBufferSize
函數,作為要求音頻隊列分配緩沖區的先決條件。 請參閱Set Sizes for a Playback Audio Queue。
與你在Write a Function to Derive Recording Audio Queue Buffer Size類似的函數相比,這里的代碼做了兩個額外的事情, 對于回放同樣如此:
- 每次您的回調調用
AudioFileReadPackets
函數時,要讀取數據包的數量
- 每次您的回調調用
- 設置緩沖區大小的下限,以避免過度頻繁的磁盤訪問
這里的計算考慮了您從磁盤讀取的音頻數據格式。 格式包括可能影響緩沖區大小的所有因素,例如音頻通道的數量。
// Listing 3-7 Deriving a playback audio queue buffer size
void DeriveBufferSize (
AudioStreamBasicDescription &ASBDesc, // 1
UInt32 maxPacketSize, // 2
Float64 seconds, // 3
UInt32 *outBufferSize, // 4
UInt32 *outNumPacketsToRead // 5
) {
static const int maxBufferSize = 0x50000; // 6
static const int minBufferSize = 0x4000; // 7
if (ASBDesc.mFramesPerPacket != 0) { // 8
Float64 numPacketsForTime =
ASBDesc.mSampleRate / ASBDesc.mFramesPerPacket * seconds;
*outBufferSize = numPacketsForTime * maxPacketSize;
} else { // 9
*outBufferSize =
maxBufferSize > maxPacketSize ?
maxBufferSize : maxPacketSize;
}
if ( // 10
*outBufferSize > maxBufferSize &&
*outBufferSize > maxPacketSize
)
*outBufferSize = maxBufferSize;
else { // 11
if (*outBufferSize < minBufferSize)
*outBufferSize = minBufferSize;
}
*outNumPacketsToRead = *outBufferSize / maxPacketSize; // 12
}
以下是這段代碼的工作原理:
- 音頻隊列的
AudioStreamBasicDescription
結構。
- 音頻隊列的
- 您正在播放的音頻文件中數據的估計最大數據包大小。您可以通過調用
AudioFileGetProperty
函數(在AudioFile.h
頭文件中聲明)使用屬性IDkAudioFilePropertyPacketSizeUpperBound
來確定此值。請參閱Set Sizes for a Playback Audio Queue。
- 您正在播放的音頻文件中數據的估計最大數據包大小。您可以通過調用
- 按照音頻的秒數指定每個音頻隊列緩沖區的大小。
- 輸出時,每個音頻隊列緩沖區的大小(以字節為單位)。
- 輸出時,每次調用回放音頻隊列回調時從文件中讀取的音頻數據包的數量。
- 音頻隊列緩沖區大小的上限(以字節為單位)。在這個例子中,上限設置為320 KB。這對應于大約五秒鐘的立體聲,采樣率為96kHz的24位音頻。
- 音頻隊列緩沖區大小的下限,以字節為單位。在這個例子中,下限設置為16 KB。
- 對于定義每個數據包固定數量幀的音頻數據格式,獲取音頻隊列緩沖區大小。
- 對于沒有為每個數據包定義固定數量幀的音頻數據格式,根據最大數據包大小和您設置的上限獲取合理的音頻隊列緩沖區大小。
- 如果導出的緩沖區大小高于您設置的上限,則根據估計的最大數據包大小調整邊界考慮。
- 如果導出的緩沖區大小低于您設置的下限,則將其調整到界限。
- 計算每次調用回調時從音頻文件中讀取的數據包數量。
Open an Audio File for Playback - 打開用于播放的音頻文件
現在您打開一個音頻文件進行播放,使用以下三個步驟:
- 獲取表示要播放的音頻文件的
CFURL
對象。
- 獲取表示要播放的音頻文件的
- 打開文件。
- 獲取文件的音頻數據格式
1. Obtaining a CFURL Object for an Audio File - 從音頻文件中獲取CFURL對象
Listing 3-8
演示了如何獲取要播放的音頻文件的CFURL對象。 在下一步中使用CFURL對象,打開文件。
// Listing 3-8 Obtaining a CFURL object for an audio file
CFURLRef audioFileURL =
CFURLCreateFromFileSystemRepresentation ( // 1
NULL, // 2
(const UInt8 *) filePath, // 3
strlen (filePath), // 4
false // 5
);
以下是這段代碼的工作原理:
- 在
CFURL.h
頭文件中聲明的CFURLCreateFromFileSystemRepresentation
函數創建一個代表要播放文件的CFURL對象。
- 在
- 使用
NULL
(或kCFAllocatorDefault
)來使用當前的默認內存分配器。
- 使用
- 您要轉換為CFURL對象的文件系統路徑。 在生產代碼中,通常會從用戶獲取
filePath
的值。
- 您要轉換為CFURL對象的文件系統路徑。 在生產代碼中,通常會從用戶獲取
- 文件系統路徑中的字節數。
- 值為
false
表示filePath
表示文件,而不是文件夾directory
。
- 值為
2. Opening an Audio File - 打開音頻文件
Listing 3-9
演示了如何打開一個音頻文件進行播放。
// Listing 3-9 Opening an audio file for playback
AQPlayerState aqData; // 1
OSStatus result =
AudioFileOpenURL ( // 2
audioFileURL, // 3
fsRdPerm, // 4
0, // 5
&aqData.mAudioFile // 6
);
CFRelease (audioFileURL); // 7
以下是這段代碼的工作原理:
- 創建
AQPlayerState
自定義結構的實例(請參閱Define a Custom Structure to Manage State)。 當您打開音頻文件進行播放時,您可以使用此實例作為放置表示音頻文件的音頻文件對象(AudioFileID
類型)的位置。
- 創建
- 在
AudioFile.h
頭文件中聲明的AudioFileOpenURL
函數打開你想要播放的文件。
- 在
- 對要播放的文件的引用。
- 您想要與您正在播放的文件一起使用的文件權限。 可用權限在文件管理器的
File Access Permission Constants
枚舉中定義。 在這個例子中你要求讀取文件的權限。
- 您想要與您正在播放的文件一起使用的文件權限。 可用權限在文件管理器的
- 一個可選的文件類型提示。 此處的值為0表示該示例不使用此功能。
- 在輸出時,對音頻文件的引用被放置在自定義結構的
mAudioFile
字段中。
- 在輸出時,對音頻文件的引用被放置在自定義結構的
- 釋放在步驟1中創建的
CFURL
對象。
- 釋放在步驟1中創建的
3. Obtaining a File’s Audio Data Format - 獲取文件音頻數據格式
Listing 3-10
顯示了如何獲取文件的音頻數據格式
Listing 3-10 Obtaining a file’s audio data format
UInt32 dataFormatSize = sizeof (aqData.mDataFormat); // 1
AudioFileGetProperty ( // 2
aqData.mAudioFile, // 3
kAudioFilePropertyDataFormat, // 4
&dataFormatSize, // 5
&aqData.mDataFormat // 6
);
以下是這段代碼的工作原理:
- 獲取預期的屬性值大小,用于查詢音頻文件的音頻數據格式。
- 在
AudioFile.h
頭文件中聲明的AudioFileGetProperty
函數獲取音頻文件中指定屬性的值。
- 在
- 音頻文件對象(類型為
AudioFileID
),代表要獲取其音頻數據格式的文件。
- 音頻文件對象(類型為
- 用于獲取音頻文件的數據格式的值的屬性ID。
- 輸入時,描述音頻文件數據格式的
AudioStreamBasicDescription
結構的預期大小。 在輸出上,實際的大小。 您的回放應用程序不需要使用此值。
- 輸入時,描述音頻文件數據格式的
- 在輸出時,以音頻文件的形式從
AudioStreamBasicDescription
結構中獲得完整的音頻數據格式。 該行將文件的音頻數據格式存儲到音頻隊列的自定義結構中,以將其應用于音頻隊列。
- 在輸出時,以音頻文件的形式從
Create a Playback Audio Queue - 創建播放音頻隊列
Listing 3-11
顯示了如何創建一個回放音頻隊列。 請注意,AudioQueueNewOutput
函數使用前面步驟中配置的自定義結構和回調,以及要播放的文件的音頻數據格式。
// Listing 3-11 Creating a playback audio queue
AudioQueueNewOutput ( // 1
&aqData.mDataFormat, // 2
HandleOutputBuffer, // 3
&aqData, // 4
CFRunLoopGetCurrent (), // 5
kCFRunLoopCommonModes, // 6
0, // 7
&aqData.mQueue // 8
);
以下是這段代碼的工作原理:
-
AudioQueueNewOutput
函數創建一個新的播放音頻隊列。
-
- 音頻隊列正在設置播放的文件的音頻數據格式。 請參閱 Obtaining a File’s Audio Data Format。
- 與回放音頻隊列一起使用的回調函數。 請參閱Write a Playback Audio Queue Callback。
- 播放音頻隊列的自定義數據結構。 請參閱Define a Custom Structure to Manage State。
- 當前運行循環,以及將調用音頻隊列回放回調的循環。
- 可以調用回調的運行循環模式。 通常,在這里使用
kCFRunLoopCommonModes
常量。
- 可以調用回調的運行循環模式。 通常,在這里使用
- 保留。 必須為0。
- 輸出時,新分配的播放音頻隊列。
Set Sizes for a Playback Audio Queue - 設置回放音頻隊列的大小
接下來,您為播放音頻隊列設置一些大小。 在為音頻隊列分配緩沖區之前以及在開始讀取音頻文件之前,請使用這些大小。
本節中的代碼清單顯示如何設置:
- 音頻隊列緩沖區大小
- 每次調用回放音頻隊列回調時要讀取的數據包數量
- 數組大小,用于保存一個緩沖區的音頻數據的數據包描述
1. Setting Buffer Size and Number of Packets to Read - 設置緩存大小和要讀取的包的數量
Listing 3-12
演示了如何使用之前編寫的DeriveBufferSize
函數(請參閱 Write a Function to Derive Playback Audio Queue Buffer Size)。 此處的目標是為每個音頻隊列緩沖區設置一個以字節為單位的大小,并確定每次調用回放音頻隊列回調時要讀取的數據包數量。
此代碼使用保守估計的最大數據包大小,Core Audio
通過kAudioFilePropertyPacketSizeUpperBound
屬性提供。 在大多數情況下,與比花費時間讀取整個音頻文件來獲得實際的最大數據包大小相比,最好使用這種技術 - 這是近似而快速的 。
// Listing 3-12 Setting playback audio queue buffer size and number of packets to read
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
);
以下是這段代碼的工作原理:
- 在
AudioFile.h
頭文件中聲明的AudioFileGetProperty
函數獲取音頻文件的指定屬性的值。在這里,你用它來獲得一個保守的上限,以字節為單位,你想播放的文件中的音頻數據包的大小。
- 在
- 表示要播放的文件的音頻文件對象(類型為
AudioFileID
)。請參閱打Opening an Audio File。
- 表示要播放的文件的音頻文件對象(類型為
- 用于獲取音頻文件中數據包大小保守上限的屬性ID。
- 在輸出上,
kAudioFilePropertyPacketSizeUpperBound
屬性的大小(以字節為單位)。
- 在輸出上,
- 在輸出上,對于要播放的文件,數據包大小的保守上限(以字節為單位)。
- 在Write a Function to Derive Playback Audio Queue Buffer Size中描述的
DeriveBufferSize
函數設置每次調用回放音頻隊列回調時要讀取的緩沖區大小和數據包的數量。
- 在Write a Function to Derive Playback Audio Queue Buffer Size中描述的
- 您要播放的文件的音頻數據格式。請參閱Obtaining a File’s Audio Data Format。
- 音頻文件中估計的最大數據包大小,來自此列表的第5行。
- 每個音頻隊列緩沖區應該容納的音頻的秒數。半秒鐘,如這里設置,通常是一個不錯的選擇。
- 輸出時,每個音頻隊列緩沖區的大小(以字節為單位)。該值放置在音頻隊列的自定義結構中。
- 輸出時,每次調用播放音頻隊列回調時要讀取的數據包數量。該值也放置在音頻隊列的自定義結構中。
2. Allocating Memory for a Packet Descriptions Array - 為包的描述數組分配內存
現在,您為數組分配內存,以保存一個緩沖區的音頻數據的數據包描述。 恒定比特率數據不使用數據包描述,因此Listing 3-13
中的CBR情況步驟3非常簡單。
// Listing 3-13 Allocating memory for a packet descriptions array
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;
}
以下是這段代碼的工作原理:
- 確定音頻文件的數據格式是VBR還是CBR。 在VBR數據中,每個字節數據包或幀每個數據包值中的一個或兩個是可變的,因此在音頻隊列的
AudioStreamBasicDescription
結構中將被列為0。
- 確定音頻文件的數據格式是VBR還是CBR。 在VBR數據中,每個字節數據包或幀每個數據包值中的一個或兩個是可變的,因此在音頻隊列的
- 對于包含VBR數據的音頻文件,為數據包描述數組分配內存。 根據每次調用回放回調時要讀取的音頻數據包的數量來計算所需的內存。 請參閱Setting Buffer Size and Number of Packets to Read。
- 對于包含CBR數據(如線性PCM)的音頻文件,音頻隊列不使用數據包描述數組。
Set a Magic Cookie for a Playback Audio Queue - 為播放音頻隊列設置Magic Cookie
某些壓縮音頻格式(如MPEG 4 AAC
)利用結構來包含音頻元數據。 這些結構被稱為Magic Cookie
。 當您使用音頻隊列服務以這種格式播放文件時,您將從音頻文件中獲取Magic Cookie,并在開始播放之前將其添加到音頻隊列中。
Listing 3-14
展示了如何從一個文件中獲得一個Magic Cookie并將其應用到一個音頻隊列中。 開始播放之前,您的代碼會調用此函數。
// Listing 3-14 Setting a magic cookie for a playback audio queue
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
}
以下是這段代碼的工作原理:
- 為magic cookie數據設置估計大小。
- 捕獲
AudioFileGetPropertyInfo
函數的結果。如果成功,則此函數返回NoErr
的值,相當于布爾值false。
- 捕獲
- 在
AudioFile.h
頭文件中聲明的AudioFileGetPropertyInfo
函數獲取指定屬性值的大小。您可以使用它來設置保存屬性值的變量的大小。
- 在
- 音頻文件對象(類型為
AudioFileID
),表示要播放的音頻文件。
- 音頻文件對象(類型為
- 屬性ID代表音頻文件的magic cookie數據。
- 在輸入時,magic cookie數據的估計大小。在輸出上,實際的大小。
- 使用
NULL
來表示您不關心屬性的讀/寫訪問權限。
- 使用
- 如果音頻文件包含一個magic cookie,分配內存來持有它。
- 在
AudioFile.h
頭文件中聲明的AudioFileGetProperty
函數獲取指定屬性的值。在這種情況下,它會獲取音頻文件的magic cookie。
- 在
- 音頻文件對象(類型為
AudioFileID
),表示您要播放的音頻文件,以及您獲取的magic cookie。
- 音頻文件對象(類型為
- 表示音頻文件magic cookie數據的屬性ID。
- 在輸入上,使用
AudioFileGetPropertyInfo
函數獲得的magicCookie
變量的大小。在輸出上,根據寫入到magicCookie
變量的字節數來計算magic cookie的實際大小。
- 在輸入上,使用
- 輸出時,音頻文件的magic cookie。
-
AudioQueueSetProperty
函數在音頻隊列中設置一個屬性。在這種情況下,它為音頻隊列設置一個magic cookie,匹配要播放的音頻文件中的magic cookie。
-
- 您要為其設置magic cookie的音頻隊列。
- 屬性ID代表音頻隊列的magic cookie。
- 您要播放的音頻文件中的magic cookie。
- magic cookie的大小,以字節為單位。
- 釋放為magic cookie分配的內存。
Allocate and Prime Audio Queue Buffers - 分配和填充音頻隊列緩沖區
現在您可以詢問您創建的音頻隊列(在Create a Playback Audio Queue中)以準備一組音頻隊列緩沖區。 Listing 3-15
演示了如何做到這一點。
// Listing 3-15 Allocating and priming audio queue buffers for playback
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
);
}
以下是這段代碼的工作原理:
- 將數據包索引設置為0,以便當音頻隊列回調開始填充緩沖區時(步驟7),它將從音頻文件的開始處開始。
- 分配和填充一組音頻隊列緩沖區。 (您可以在Define a Custom Structure to Manage State。將此編號
kNumberBuffers
設置為3)。
- 分配和填充一組音頻隊列緩沖區。 (您可以在Define a Custom Structure to Manage State。將此編號
-
AudioQueueAllocateBuffer
函數通過為其分配內存來創建音頻隊列緩沖區。
-
- 正在分配音頻隊列緩沖區的音頻隊列。
- 新的音頻隊列緩沖區的大小(以字節為單位)。
- 在輸出上,將新的音頻隊列緩沖區添加到自定義結構中的
mBuffers
數組中。
- 在輸出上,將新的音頻隊列緩沖區添加到自定義結構中的
-
HandleOutputBuffer
函數是你寫的回放音頻隊列回調函數。 請參閱 Write a Playback Audio Queue Callback。
-
- 音頻隊列的自定義結構。
- 您正在調用的回調的音頻隊列。
- 您傳遞給音頻隊列回調的音頻隊列緩沖區。
Set an Audio Queue’s Playback Gain - 設置音頻隊列的播放增益
在您告訴音頻隊列開始播放之前,您需要通過音頻隊列參數機制設置其增益。 Listing 3-16
顯示了如何做到這一點。 有關參數機制的更多信息,請參閱Audio Queue Parameters。
// Listing 3-16 Setting an audio queue’s playback gain
Float32 gain = 1.0; // 1
// Optionally, allow user to override gain setting here
AudioQueueSetParameter ( // 2
aqData.mQueue, // 3
kAudioQueueParam_Volume, // 4
gain // 5
);
以下是這段代碼的工作原理:
- 在0(用于靜音)和1(用于單位增益)之間設置與音頻隊列一起使用的增益。
-
AudioQueueSetParameter
函數設置音頻隊列的參數值。
-
- 您正在設置參數的音頻隊列。
- 您正在設置的參數的ID。
kAudioQueueParam_Volume
常量讓您設置音頻隊列的增益。
- 您正在設置的參數的ID。
- 您正在應用到音頻隊列的增益設置。
Start and Run an Audio Queue - 開始和運行音頻隊列
所有前面的代碼都說明了播放文件的過程。 這包括啟動音頻隊列并在文件播放時保持運行循環,如Listing 3-17
所示
// Listing 3-17 Starting and running 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
);
以下是這段代碼的工作原理:
- 在自定義結構中設置一個標志來指示音頻隊列正在運行。
-
AudioQueueStart
函數在其自己的線程上啟動音頻隊列。
-
- 音頻隊列開始。
- 使用
NULL
來指示音頻隊列應該立即開始播放。
- 使用
- 定期輪詢自定義結構的
mIsRunning
字段,以檢查音頻隊列是否已停止。
- 定期輪詢自定義結構的
-
CFRunLoopRunInMode
函數運行包含音頻隊列線程的運行循環。
-
- 使用運行循環的默認模式。
- 將運行循環的運行時間設置為0.25秒。
- 使用false來指示運行循環應該持續指定的全部時間。
- 音頻隊列停止后,再運行一次運行循環,以確保當前正在播放的音頻隊列緩沖區有時間完成。
Clean Up After Playing - 播放完后的清理
當您完成播放文件時,請釋放音頻隊列,關閉音頻文件并釋放剩余的資源。 Listing 3-18
說明了這些步驟。
// Listing 3-18 Cleaning up after playing an audio file
AudioQueueDispose ( // 1
aqData.mQueue, // 2
true // 3
);
AudioFileClose (aqData.mAudioFile); // 4
free (aqData.mPacketDescs); // 5
以下是這段代碼的工作原理:
-
AudioQueueDispose
函數銷毀音頻隊列及其所有資源,包括其緩沖區。
-
- 您想要處理的音頻隊列。
- 使用true來同步銷毀音頻隊列。
- 關閉播放的音頻文件。
AudioFileClose
函數在AudioFile.h
頭文件中聲明。
- 關閉播放的音頻文件。
- 釋放用來保存數據包描述的內存。
后記
未完,待續~~~