iOS Audio Queue播放PCM音頻流

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

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嫁蛇,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子露该,更是在濱河造成了極大的恐慌睬棚,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件解幼,死亡現(xiàn)場離奇詭異抑党,居然都是意外死亡,警方通過查閱死者的電腦和手機撵摆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門底靠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人特铝,你說我怎么就攤上這事暑中。” “怎么了鲫剿?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵鳄逾,是天一觀的道長。 經(jīng)常有香客問我灵莲,道長雕凹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮请琳,結(jié)果婚禮上粱挡,老公的妹妹穿的比我還像新娘。我一直安慰自己俄精,他們只是感情好询筏,可當我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著竖慧,像睡著了一般嫌套。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上圾旨,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天踱讨,我揣著相機與錄音,去河邊找鬼砍的。 笑死痹筛,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的廓鞠。 我是一名探鬼主播帚稠,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼床佳!你這毒婦竟也來了滋早?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤砌们,失蹤者是張志新(化名)和其女友劉穎杆麸,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浪感,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡昔头,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了影兽。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片减细。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赢笨,靈堂內(nèi)的尸體忽然破棺而出未蝌,到底是詐尸還是另有隱情,我是刑警寧澤茧妒,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布萧吠,位于F島的核電站,受9級特大地震影響桐筏,放射性物質(zhì)發(fā)生泄漏纸型。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狰腌。 院中可真熱鬧除破,春花似錦、人聲如沸琼腔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丹莲。三九已至光坝,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間甥材,已是汗流浹背盯另。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留洲赵,地道東北人鸳惯。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像叠萍,于是被迫代替她去往敵國和親芝发。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,512評論 2 359