解讀Play Audio下集蒸走,如果你沒看上集厉亏,建議先去看看上集.
Audio Queue Services 解讀之 Playing Audio(上)
上集已經(jīng)準(zhǔn)備好音頻隊列的結(jié)構(gòu)體以及回調(diào)函數(shù)跨嘉,那么接下來就可以創(chuàng)建音頻隊列并實現(xiàn)播放了!
五勃黍、Create a Playback Audio Queue(創(chuàng)建播放的音頻隊列)
下面將演示怎么去創(chuàng)建回放音頻隊列踱蛀,注意:AudioQueueNewOutput
函數(shù)中使用的自定義結(jié)構(gòu)體和回調(diào)方法都是在之前寫好了
AudioQueueNewOutput ( // 1
&aqData.mDataFormat, // 2
HandleOutputBuffer, // 3
&aqData, // 4
CFRunLoopGetCurrent (), // 5
kCFRunLoopCommonModes, // 6
0, // 7
&aqData.mQueue // 8
);
- 1、通過
AudioQueueNewOutput
函數(shù)來創(chuàng)建一個回放音頻隊列 - 2馋辈、需要設(shè)置播放的音頻隊列對應(yīng)的音頻文件數(shù)據(jù)格式抚芦,參考Obtaining a File’s Audio Data Format
- 3、回放音頻隊列的回調(diào)函數(shù)迈螟,參考Write a Playback Audio Queue Callback
- 4叉抡、音頻隊列對應(yīng)的自定義結(jié)構(gòu)體,可查看Define a Custom Structure to Manage State
- 5答毫、當(dāng)前運行循環(huán)褥民,以及將調(diào)用音頻隊列回放回調(diào)的循環(huán)
- 6、當(dāng)前可以被調(diào)用回調(diào)的運行循環(huán)模式洗搂,通常使用
kCFRunLoopCommonModes
- 7消返、保留字段,傳0
- 8耘拇、輸出時撵颊,新分配的回放音頻隊列
六、Set Sizes for a Playback Audio Queue(設(shè)置播放音頻隊列的大斜古选)
現(xiàn)在倡勇,你可以為播放音頻隊列設(shè)置一些大小,當(dāng)你給緩沖隊列分配緩沖區(qū)的時候和開始讀取音頻文件之前嘉涌,你可以使用這些設(shè)置的大小
下面代碼將會告訴你怎么設(shè)置
- 1妻熊、音頻隊列緩沖區(qū)大小
- 2、每次執(zhí)行回放音頻隊列回調(diào)要讀取的數(shù)據(jù)包個數(shù)
- 3仑最、用于保存一個緩沖區(qū)的音頻數(shù)據(jù)的分組描述的數(shù)組大小
(1)扔役、Setting Buffer Size and Number of Packets to Read(設(shè)置緩沖區(qū)大小和要讀取的數(shù)據(jù)包數(shù))
下面代碼段會演示如何使用之前寫的 DeriveBufferSize
函數(shù)(see Write a Function to Derive Playback Audio Queue Buffer Size)這里是為每一個音頻緩沖區(qū)設(shè)置一個大小,單位是字節(jié)词身;以及確認(rèn)每次調(diào)用回放音頻回調(diào)要讀取的數(shù)據(jù)包數(shù)
此代碼使用最大包大小的保守估計厅目,Core Audio通過 kAudioFilePropertyPacketSizeUpperBound
屬性提供。 在大多數(shù)情況下法严,最好使用這種方式 - 這是近似但快速的 - 花費時間讀取整個音頻文件以獲得實際的最大包大小
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
函數(shù),聲明在AudioFile.h
頭文件中深啤,可以獲取音頻文件中指定屬性的值拗馒,這里你可以用來獲取一個你要播放的音頻文件的音頻數(shù)據(jù)包大小的保守上限 - 2、音頻文件對象(類型是
AudioFileID
)代表你要播放的音頻文件溯街,可查看Opening an Audio File - 3诱桂、屬性ID洋丐,用于在音頻文件中獲取數(shù)據(jù)包大小的保守上限
- 4、輸出時挥等,kAudioFilePropertyPacketSizeUpperBound屬性的大杏丫(以字節(jié)為單位)
- 5、輸出是肝劲,你要播放的音頻文件數(shù)據(jù)包大小的保守上限(以字節(jié)為單位)
- 6迁客、
DeriveBufferSize
函數(shù),聲明在Write a Function to Derive Playback Audio Queue Buffer Size辞槐,可以設(shè)置緩沖區(qū)大小和每次調(diào)用回調(diào)要讀取的數(shù)據(jù)包數(shù) - 7掷漱、你要播放的文件數(shù)據(jù)格式,可查看 Obtaining a File’s Audio Data Format
- 8榄檬、音頻文件預(yù)估最大的數(shù)據(jù)包大小卜范,上面第5點提到那個
- 9、每個音頻隊列緩沖區(qū)應(yīng)該保持的音頻的秒數(shù)鹿榜,通常0.5秒是一個很好的選擇
- 10海雪、輸出時、每個音頻隊列緩沖區(qū)的大胁盏睢(以字節(jié)為單位)喳魏,這個值存放在音頻隊列的自定義結(jié)構(gòu)體中
- 11、輸出時怀薛,每次執(zhí)行回調(diào)要讀取的數(shù)據(jù)包數(shù),這個值也是存儲在音頻隊列的自定義結(jié)構(gòu)體中
(2)迷郑、Allocating Memory for a Packet Descriptions Array(為包描述數(shù)組分配內(nèi)存)
現(xiàn)在為數(shù)組分配內(nèi)存枝恋,以保存一個緩沖區(qū)的音頻數(shù)據(jù)的數(shù)據(jù)包描述。 恒定比特率數(shù)據(jù)不使用數(shù)據(jù)包描述嗡害,因此下面代碼中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、確定音頻文件的數(shù)據(jù)格式是VBR還是CBR霸妹。 在VBR數(shù)據(jù)中十电,每個字節(jié)的字節(jié)數(shù)或每幀的幀數(shù)是可變的,因此將在音頻隊列的AudioStreamBasicDescription結(jié)構(gòu)中列為0叹螟。
- 2鹃骂、對于包含VBR數(shù)據(jù)的音頻文件,為包描述數(shù)組分配內(nèi)存罢绽。 基于在每次調(diào)用回放回調(diào)時要讀取的音頻數(shù)據(jù)包的數(shù)量來計算所需的內(nèi)存畏线。 請參閱Setting Buffer Size and Number of Packets to Read。
- 3良价、對于包含CBR數(shù)據(jù)的音頻文件(例如線性PCM)寝殴,音頻隊列不使用包描述數(shù)組蒿叠。
七、Set a Magic Cookie for a Playback Audio Queue(設(shè)置回放音頻隊列的 Magic Cookie )
一些音頻壓縮的音頻格式蚣常,例如 MPEG 4 AAC市咽,利用結(jié)構(gòu)體包含音頻的元數(shù)據(jù)。這些結(jié)構(gòu)體就是Magic Cookie抵蚊,當(dāng)你用 Audio Queue Services 播放這種格式的音頻文件時施绎,你可以從音頻文件中獲取Magic Cookie ,然后在播放之前添加到音頻隊列中
下面代碼教你怎么去從音頻文件中獲取Magic Cookie泌射,并添加到音頻隊列中粘姜,下面的代碼需要在開始回放之前執(zhí)行
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、設(shè)置magic cookie 的預(yù)估大小
- 2熔酷、獲取函數(shù)
AudioFileGetPropertyInfo
的返回值孤紧,如果成功,返回NoErr
,相當(dāng)于布爾值false
- 3拒秘、函數(shù)
AudioFileGetPropertyInfo
聲明在AudioFile.h
頭文件号显,可以獲取指定屬性值的大小,可以使用它來設(shè)置保存屬性值的變量的大小 - 4躺酒、你要播放的音頻文件對象押蚤,類型是
AudioFileID
- 5、屬性ID 表示音頻文件的 Magic Cookie 數(shù)據(jù)
- 6羹应、輸入時揽碘,是magic cookie 數(shù)據(jù)的預(yù)估大小园匹;輸出時雳刺,是真實的大小
- 7、使用
NULL
表示你不關(guān)心這個屬性的讀寫訪問 - 8裸违、如果音頻文件包含有 magic cookie掖桦,那么就為它分配內(nèi)存進(jìn)行管理
- 9、函數(shù)
AudioFileGetProperty
聲明在AudioFile.h
頭文件供汛,可以獲取指定屬性值枪汪,在這里是獲取音頻文件的 magic cookie - 10、表示你要播放的并且獲取到magic cookie的音頻文件對象怔昨,類型是
AudioFileID
- 11雀久、屬性ID 表示音頻文件的 Magic Cookie 數(shù)據(jù)
- 12、輸入時朱监,通過使用函數(shù)
AudioFileGetPropertyInfo
獲取magicCookie
變量的大邪斗取;輸出時赫编,真實的magic cookie 大小就是寫入到magicCookie
變量的字節(jié)數(shù) - 13巡蘸、輸出時奋隶,音頻文件的 magic cookie
- 14、函數(shù)
AudioQueueSetProperty
設(shè)置音頻隊列的屬性悦荒,在這里唯欣,用來匹配要被播放的音頻文件的magic cookie并設(shè)置給音頻隊列 - 15、你要設(shè)置magic cookie 的音頻隊列
- 16搬味、屬性ID 表示音頻文件的 Magic Cookie 數(shù)據(jù)
- 17境氢、你要播放的音頻文件的 magic cookie
- 18、magic cookie 的大小碰纬,單位是字節(jié)
- 19萍聊、釋放之前被分配內(nèi)存的magic cookie
八、Allocate and Prime Audio Queue Buffers(分配和填充音頻隊列緩沖區(qū))
現(xiàn)在你可以讓你之前創(chuàng)建的音頻對象去準(zhǔn)備一些音頻隊列緩沖區(qū)悦析,下面代碼教你怎么做
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寿桨、將包索引設(shè)置為0,以便在音頻隊列回調(diào)開始填充緩沖區(qū)時(步驟7)强戴,它從音頻文件的開頭開始
- 2亭螟、分配和填充音頻隊列緩沖區(qū)(你在 Define a Custom Structure to Manage State 設(shè)置了
kNumberBuffers
這個值為3
) - 3、函數(shù)
AudioQueueAllocateBuffer
通過分配內(nèi)存來創(chuàng)建音頻隊列緩沖區(qū) - 4骑歹、負(fù)責(zé)分配音頻隊列緩沖區(qū)的音頻隊列
- 5预烙、新的音頻隊列緩沖區(qū)的大小,單位是字節(jié)
- 6道媚、輸出時扁掸,添加新的音頻隊列緩沖區(qū)到自定義結(jié)構(gòu)體的
mBuffers
數(shù)組中 - 7、函數(shù)
HandleOutputBuffer
是你之前寫的回放音頻隊列回調(diào)最域,可查看 Write a Playback Audio Queue Callback - 8也糊、音頻隊列的自定義結(jié)構(gòu)體
- 9、調(diào)用回調(diào)的音頻隊列
- 10羡宙、傳遞到音頻隊列回調(diào)的音頻隊列緩沖區(qū)
九、Set an Audio Queue’s Playback Gain(設(shè)置音頻隊列回放增益)
在告訴音頻隊列播放之前掐隐,你可以通過音頻隊列參數(shù)機(jī)制設(shè)置其增益狗热,下面代碼教你怎么設(shè)置,更多設(shè)置可以查看 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虑省、設(shè)置音頻隊列的增益匿刮,在0-1之間
- 2、通過函數(shù)
AudioQueueSetParameter
設(shè)置音頻隊列的參數(shù)值 - 3探颈、設(shè)置參數(shù)得音頻隊列
- 4熟丸、設(shè)置參數(shù)的ID,kAudioQueueParam_Volume常數(shù)可讓你設(shè)置音頻隊列的增益
- 5伪节、應(yīng)用于音頻隊列的增益設(shè)置
十光羞、Start and Run an Audio Queue(開啟并運行音頻隊列)
所有上述代碼已經(jīng)播放文件的過程绩鸣。 這包括在播放文件時啟動音頻隊列并維護(hù)運行循環(huán),看下面代碼
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纱兑、設(shè)置自定義結(jié)構(gòu)體的標(biāo)識呀闻,指示音頻隊列正在運行
- 2、使用函數(shù)
AudioQueueStart
開啟音頻隊列潜慎,在自己的線程中 - 3捡多、要開啟的音頻隊列
- 4、使用
NULL
表示音頻隊列需要馬上開啟播放 - 5铐炫、定期輪詢自定義結(jié)構(gòu)的mIsRunning字段垒手,以檢查音頻隊列是否已停止
- 6、
CFRunLoopRunInMode
函數(shù)運行包含音頻隊列的線程的運行循環(huán) - 7倒信、使用默認(rèn)的運行循環(huán)模式
- 8科贬、設(shè)置運行循環(huán)的運行時間為0.25秒
- 9、使用
false
表示運行循環(huán)應(yīng)該繼續(xù)指定整個時間 - 10堤结、在音頻隊列停止后唆迁,再運行循環(huán)一會兒,以確保當(dāng)前播放的音頻隊列緩沖區(qū)有時間完成
十一竞穷、Clean Up After Playing(播放完畢后清除)
當(dāng)你的音頻文件播放完畢唐责,應(yīng)該要銷毀這個音頻隊列,關(guān)閉音頻文件瘾带,同時要釋放所有資源鼠哥,下面代碼處理了這些步驟
AudioQueueDispose ( // 1
aqData.mQueue, // 2
true // 3
);
AudioFileClose (aqData.mAudioFile); // 4
free (aqData.mPacketDescs); // 5
下面介紹一下代碼的作用:
- 1、函數(shù)
AudioQueueDispose
銷毀音頻隊列和它的所有資源看政,包括它的緩沖區(qū) - 2朴恳、要銷毀的音頻隊列
- 3、使用true可以同步銷毀音頻隊列
- 4允蚣、關(guān)閉播放完畢的音頻文件于颖,函數(shù)
AudioFileClose
聲明在AudioFile.h
頭文件中 - 5、釋放用于保存數(shù)據(jù)包描述的內(nèi)存
十二嚷兔、總結(jié):
- 1森渐、實現(xiàn)上面的步驟,就可以播放本地的音頻文件了冒晰,后續(xù)工作就是優(yōu)化封裝了
- 2同衣、因為上面步驟只能實現(xiàn)播放本地音頻文件,至于網(wǎng)絡(luò)音頻文件播放就沒提壶运,后續(xù) 出的Demo 會實現(xiàn)播放網(wǎng)絡(luò)音頻文件耐齐,敬請期待
- 3、playing audio 這篇的翻譯算是完成了,歡迎大家關(guān)注我埠况,喜歡就給個like耸携,如果有問題,請留言喔询枚,謝謝