PCM音頻流播放主要步驟如下:
1何恶、確定文件格式
播放PCM音頻流前孽锥,我們首先需要確定播放的PCM音頻的格式信息,iOS中细层,有一個結(jié)構體專用于描述音頻格式信息惜辑,AudioStreamBasicDescription結(jié)構體,組成如下:
struct AudioStreamBasicDescription
{
Float64 mSampleRate; //音頻的采樣率疫赎。
AudioFormatID mFormatID; //音頻的格式ID
AudioFormatFlags mFormatFlags; //音頻的Flags盛撑,對mFormatID的補充
UInt32 mBytesPerPacket; //每個Packet的字節(jié)數(shù)
UInt32 mFramesPerPacket; //每個Packet包含的Frame數(shù)
UInt32 mBytesPerFrame; //每個Frame包含的位數(shù)
UInt32 mChannelsPerFrame; //每個Frame包含的頻道數(shù)
UInt32 mBitsPerChannel; //每個頻道Channel包含的位數(shù)
UInt32 mReserved; //標記是否對結(jié)構體進行填充進行8字節(jié)對齊,暫不設置,為默認
};
typedef struct AudioStreamBasicDescription AudioStreamBasicDescription;
我預先錄制好一個PCM文件,根據(jù)http://www.reibang.com/p/15d95e593451錄制的音頻文件捧搞。
根據(jù)我們的PCM文件抵卫,我們設置參數(shù)如下:
audioDescription.mSampleRate = 8000;
audioDescription.mFormatID = kAudioFormatLinearPCM;
audioDescription.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioDescription.mChannelsPerFrame = 1;
audioDescription.mFramesPerPacket = 1;
audioDescription.mBitsPerChannel = 16;
audioDescription.mBytesPerFrame = (audioDescription.mBitsPerChannel / 8) * audioDescription.mChannelsPerFrame;
audioDescription.mBytesPerPacket = audioDescription.mBytesPerFrame * audioDescription.mFramesPerPacket;
2、創(chuàng)建AudioQueueRef對象
格式確定后我們創(chuàng)建Audio Queue對象实牡,使用如下方法:
extern OSStatus
AudioQueueNewOutput( const AudioStreamBasicDescription *inFormat,
AudioQueueOutputCallback inCallbackProc,
void * __nullable inUserData,
CFRunLoopRef __nullable inCallbackRunLoop,
CFStringRef __nullable inCallbackRunLoopMode,
UInt32 inFlags,
AudioQueueRef __nullable * __nonnull outAQ) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
參數(shù)解釋:
const AudioStreamBasicDescription *inFormat, //音頻格式
AudioQueueOutputCallback inCallbackProc, //回調(diào)函數(shù)指針
void * __nullable inUserData, //自定義回調(diào)函數(shù)數(shù)據(jù)
CFRunLoopRef __nullable inCallbackRunLoop, //回調(diào)函數(shù)調(diào)用的RunLoop
CFStringRef __nullable inCallbackRunLoopMode, //回調(diào)函數(shù)調(diào)用的RunLoopMode
UInt32 inFlags, //保留標志位陌僵,填0
AudioQueueRef __nullable * __nonnull outAQ //存放創(chuàng)建的AudioQueueRef對象指針的地址
音頻格式填入上一步創(chuàng)建的音頻格式結(jié)構體即可,回調(diào)函數(shù)指針填入后面創(chuàng)建的函數(shù)名即可创坞,自定義回調(diào)函數(shù)數(shù)據(jù)填入自己需要的對象即可碗短,回調(diào)函數(shù)的RunLoop和RunLoopMode無特殊要求填寫NULL即使用默認值,保留保留標志位寫0题涨。創(chuàng)建AudioQueueRef對象成功則返回0偎谁,失敗則返回其他錯誤值总滩。
3、創(chuàng)建音頻緩沖區(qū)
創(chuàng)建音頻緩沖區(qū)函數(shù)如下:
extern OSStatus
AudioQueueAllocateBuffer( AudioQueueRef inAQ,
UInt32 inBufferByteSize,
AudioQueueBufferRef __nullable * __nonnull outBuffer) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
參數(shù)解釋:
AudioQueueRef inAQ //創(chuàng)建的AudioQueueRef對象
UInt32 inBufferByteSize, //緩沖區(qū)最大的大小
AudioQueueBufferRef __nullable * __nonnull outBuffer //創(chuàng)建的緩沖區(qū)AudioQueueBufferRef對象指針存放的地址
緩沖區(qū)創(chuàng)建數(shù)量越少則延遲越小巡雨,同時同步要求更高闰渔。這里就創(chuàng)建三個即可,可根據(jù)自己需求進行調(diào)整铐望。
4冈涧、調(diào)用開始方法
extern OSStatus
AudioQueueStart( AudioQueueRef inAQ,
const AudioTimeStamp * __nullable inStartTime) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
函數(shù)的第一個參數(shù)為我們創(chuàng)建的AudioQueueRef對象,第二個參數(shù)填NULL則代表即可開始正蛙,我們填寫NULL即可督弓。函數(shù)返回0則代表成功。
5乒验、填充緩沖數(shù)據(jù)到隊列中
我們先從文件中依次循環(huán)讀取二進制數(shù)據(jù)進行填充愚隧,填充函數(shù)如下:
extern OSStatus
AudioQueueEnqueueBuffer( AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer,
UInt32 inNumPacketDescs,
const AudioStreamPacketDescription * __nullable inPacketDescs) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
參數(shù)解釋:
AudioQueueRef inAQ, //Audio Queue對象
AudioQueueBufferRef inBuffer, //緩沖區(qū)對象
UInt32 inNumPacketDescs, //PacketDescs數(shù)量
const AudioStreamPacketDescription * __nullable inPacketDescs //PacketDescs參數(shù)
inAQ參數(shù)填我們剛才創(chuàng)建的AudioQueueRef對象,inNumPacketDescs填0锻全,inPacketDescs填NULL即可狂塘。inBuffer則為我們剛才創(chuàng)建的緩沖區(qū)對象,對象為結(jié)構體鳄厌,屬性如下:
typedef struct AudioQueueBuffer {
const UInt32 mAudioDataBytesCapacity;
void * const mAudioData; //指向存放數(shù)據(jù)的指針
UInt32 mAudioDataByteSize; //存放數(shù)據(jù)的大小
void * __nullable mUserData;
const UInt32 mPacketDescriptionCapacity;
AudioStreamPacketDescription * const __nullable mPacketDescriptions;
UInt32 mPacketDescriptionCount;
#ifdef __cplusplus
AudioQueueBuffer() : mAudioDataBytesCapacity(0), mAudioData(0), mPacketDescriptionCapacity(0), mPacketDescriptions(0) { }
#endif
} AudioQueueBuffer;
我們?yōu)榈诙€和第三個屬性賦值即可荞胡,根據(jù)從文件讀取到的實際值填入即可,不可超過緩沖對象的最大緩存空間了嚎。
6硝训、實現(xiàn)回調(diào)函數(shù)
回調(diào)函數(shù)類型如下:
typedef void (*AudioQueueOutputCallback)(
void * __nullable inUserData,
AudioQueueRef inAQ,
AudioQueueBufferRef inBuffer);
第一個參數(shù)為自定義的對象,第二個為我們創(chuàng)建的AudioQueueRef對象新思,第三個為已經(jīng)處理完畢的AudioQueueBufferRef緩沖區(qū)對象窖梁。
我們把函數(shù)名稱填入第二步的創(chuàng)建函數(shù)中的第二個參數(shù)即可。
static void AudioPlayerAQInputCallback(void* inUserData,AudioQueueRef inAQ, AudioQueueBufferRef inBuffer) {
//對inBuffer進行重新填數(shù)據(jù)夹囚,并填充到隊列中去纵刘。
NSLog(@"player callback");
}
當AudioQueueBufferRef播放完一個緩沖區(qū)對象的音頻數(shù)據(jù)后就會調(diào)用回調(diào)函數(shù),在回調(diào)函數(shù)返回已經(jīng)播放了的緩沖區(qū)對象荸哟,我們的回調(diào)函數(shù)里面處理空的AudioQueueBufferRef對象假哎,為空AudioQueueBufferRef對象重新填充新的數(shù)據(jù),并重新調(diào)用AudioQueueEnqueueBuffer()函數(shù)填充到播放隊列中去鞍历。循環(huán)調(diào)用回調(diào)函數(shù)舵抹,我們依次從文件讀取數(shù)據(jù)循環(huán)填充,即實現(xiàn)播放功能劣砍。
7惧蛹、停止播放
當我們需要停止播放時,調(diào)用停止的方法即可,方法也非常簡單香嗓,如下:
extern OSStatus
AudioQueueStop( AudioQueueRef inAQ,
Boolean inImmediate) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
第一個參數(shù)為需要停止的AudioQueueRef對象迅腔,第二個參數(shù)表示是否需要立即停止,我們傳入YES即可靠娱。當傳入NO時沧烈,該函數(shù)會立即返回自赔,但并不立即停止本慕,而是處理完以填充在隊里里面的緩存區(qū)對象后停止侄非,停止為異步操作楣颠;傳入YES時則會立即停止,并等待AudioQueue停止后返回被环,停止為同步操作类溢。
如果我們有需要也可以在停止后立即重置播放隊列辞做,函數(shù)如下:
extern OSStatus
AudioQueueReset( AudioQueueRef inAQ) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
參數(shù)為我們需要重置播放的隊列對象百框,該方法會重置AudioQueueRef對象,并清空正在播放的緩沖區(qū)對象牍汹,我們又可以重新開始填充播放新的音頻數(shù)據(jù)铐维。
最后歡迎大家留言交流,同時附上Demo和文檔地址慎菲。
Demo地址:https://github.com/XMSECODE/ESAudioQueueDemo
AudioQueue框架的更詳細的使用及文檔可以查閱蘋果官方文檔:https://developer.apple.com/documentation/audiotoolbox/audio_queue_services