iOS音頻播放

引言:

在iOS中音頻按照播放形式可以分為音效播放和音樂播放。音效主要指的是一些短音頻,通常作為點綴音頻,如游戲中的喊殺聲,對于這類音頻不需要進行進度,循環等控制。音樂主要指的是一些較長的音頻,通常是主音頻,對于這類音頻播放通常需要精確的控制。在iOS中播放一般使用AudioToolbox.framework這個框架,播放音樂一般使用AVFoundation.framework。

音效播放

AudioToolbox.framework是一套基于C語言的框架,使用它來播放音效其本質是將音效注冊到系統聲音服務(System Sound Service)中。System Sound Service是一種簡單,底層的聲音播放服務。但是它本身有一些限制:

  • 音效播放時間不能超過30s
  • 數據必須是PCM或者IMA4格式
  • 音頻文件必須打包成.caf , .aif, .wav中的一種(這是官方的說法,實際測試一些.aac .mp3格式的也可以播放)

使用System Sound Service播放音效的步驟如下:

  1. 調用AudioServicesCreateSystemSoundID( CFURLRef inFileURL, SystemSoundID* outSystemSoundID)函數獲得系統聲音ID。
  2. 如果需要監聽播放完成操作,則使用AudioServicesAddSystemSoundCompletion( SystemSoundID inSystemSoundID,
    CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void* inClientData)方法注冊回調函數。
  3. 調用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID) 或者AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID) 方法播放音效(后者帶有震動效果)。

例如:


 void playCallback(SystemSoundID ID, void  * clientData){
     NSLog(@"播放完成...");
 }

    NSURL *audioURL=[[NSBundle mainBundle] URLForResource:@"abc"  withExtension:@"aac"];
    SystemSoundID soundID;
    //Creates a system sound object.
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)(audioURL), &soundID);
    //Registers a callback function that is invoked when a specified system sound finishes playing.
    AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, &playCallback, (__bridge void * _Nullable)(self));
    // AudioServicesPlayAlertSound(soundID);
    AudioServicesPlaySystemSound(soundID);
    
音樂播放

如果播放較大的音頻或者要對音頻有精確的控制的話,通常會選擇使用AVFoundation.framework中的AVAudioPlayer來實現。AVAudioPlayer可以看成一個播放器,它支持多種音頻格式,而且能夠進行進度、音量、播放速度等控制。首先簡單看一下AVAudioPlayer常用的屬性和方法:


@property(readonly, getter=isPlaying) BOOL playing //是否正在播放,只讀
@property(readonly) NSUInteger numberOfChannels  //音頻聲道數,只讀
@property(readonly) NSTimeInterval duration //音頻時長
@property(readonly) NSURL *url  //音頻文件路徑,只讀
@property(readonly) NSData *data    //音頻數據,只讀
@property float pan  //立體聲平衡,如果為-1.0則完全左聲道,如果0.0則左右聲道平衡,如果為1.0則完全為右聲道
@property float volume  音量大小,范圍0-1.0
@property BOOL enableRate   是否允許改變播放速率
@property float rate    播放速率,范圍0.5-2.0,如果為1.0則正常播放,如果要修改播放速率則必須設置enableRate為YES
@property NSTimeInterval currentTime    當前播放時長
@property(readonly) NSTimeInterval deviceCurrentTime    輸出設備播放音頻的時間,注意如果播放中被暫停此時間也會繼續累加
@property NSInteger numberOfLoops   循環播放次數,如果為0則不循環,如果小于0則無限循環,大于0則表示循環次數
@property(readonly) NSDictionary *settings  音頻播放設置信息,只讀
@property(getter=isMeteringEnabled) BOOL meteringEnabled    是否啟用音頻測量,默認為NO,一旦啟用音頻測量可以通過updateMeters方法更新測量值

對象方法    
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError   使用文件URL初始化播放器,注意這個URL不能是HTTP URL,AVAudioPlayer不支持加載網絡媒體流,只能播放本地文件
- (instancetype)initWithData:(NSData *)data error:(NSError **)outError  使用NSData初始化播放器,注意使用此方法時必須文件格式和文件后綴一致,否則出錯,所以相比此方法更推薦使用上述方法或- (instancetype)initWithData:(NSData *)data fileTypeHint:(NSString *)utiString error:(NSError **)outError方法進行初始化
- (BOOL)prepareToPlay;  加載音頻文件到緩沖區,注意即使在播放之前音頻文件沒有加載到緩沖區程序也會隱式調用此方法。
- (BOOL)play;   播放音頻文件
- (BOOL)playAtTime:(NSTimeInterval)time 在指定的時間開始播放音頻
- (void)pause;  暫停播放
- (void)stop;   停止播放
- (void)updateMeters    更新音頻測量值,注意如果要更新音頻測量值必須設置meteringEnabled為YES,通過音頻測量值可以即時獲得音頻分貝等信息
- (float)peakPowerForChannel:(NSUInteger)channelNumber; 獲得指定聲道的分貝峰值,注意如果要獲得分貝峰值必須在此之前調用updateMeters方法
- (float)averagePowerForChannel:(NSUInteger)channelNumber   獲得指定聲道的分貝平均值,注意如果要獲得分貝平均值必須在此之前調用updateMeters方法
@property(nonatomic, copy) NSArray *channelAssignments  獲得或設置播放聲道
代理方法    說明
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 音頻播放完成
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error   音頻解碼發生錯誤

AVAudioPlayer的使用比較簡單:

  1. 初始化AVAudioPlayer對象,此時通常指定本地文件路徑。
  2. 設置播放器屬性,例如重復次數、音量大小等。
  3. 調用play方法播放。

當然由于AVAudioPlayer一次只能播放一個音頻文件,所有上一曲、下一曲其實可以通過創建多個播放器對象來完成,播放進度的實現主要依靠一個定時器實時計算當前播放時長和音頻總時長的比例。


    NSString *urlStr=[[NSBundle mainBundle]pathForResource:@"abc" ofType:@"aac"];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    NSError *error=nil;
    //初始化播放器,注意這里的Url參數只能時文件路徑,不支持HTTP Url
     _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
    //設置播放器屬性
    _audioPlayer.numberOfLoops=0;//設置為0不循環
    //audioPlayer.delegate=self;
    [_audioPlayer prepareToPlay];//加載音頻文件到緩存
    
    [_audioPlayer play];

音頻會話

在iOS中每個應用都有一個音頻會話,這個會話就通過AVAudioSession來表示。AVAudioSession同樣存在于AVFoundation框架中,它是單例模式設計,通過sharedInstance進行訪問。在使用Apple設備時大家會發現有些應用只要打開其他音頻播放就會終止,而有些應用卻可以和其他應用同時播放,在多種音頻環境中如何去控制播放的方式就是通過音頻會話來完成的。下面是音頻會話的幾種會話模式:

如果要讓一個播放器推到后臺后還繼續播放,則需要做下面幾件事:

  1. 設置后臺運行模式:在plist文件中添加Required background modes,并且設置item 0=App plays audio or streams audio/video using AirPlay(其實可以直接通過Xcode在Project Targets-Capabilities-Background Modes中設置)
  1. 設置AVAudioSession的類型為AVAudioSessionCategoryPlayback并且調用setActive::方法啟動會話。

AVAudioSession *audioSession=[AVAudioSession sharedInstance];

[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];

[audioSession setActive:YES error:nil];

  1. 為了能夠讓應用退到后臺之后支持耳機控制,建議添加遠程控制事件(這一步不是后臺播放必須的)

前兩步是后臺播放所必須設置的,第三步主要用于接收遠程事件.如果這一步不設置雖讓也能夠在后臺播放,但是無法獲得音頻控制權(如果在使用當前應用之前使用其他播放器播放音樂的話,此時如果按耳機播放鍵或者控制中心的播放按鈕則會播放前一個應用的音頻),并且不能使用耳機進行音頻控制。第一步操作相信大家都很容易理解,如果應用程序要允許運行到后臺必須設置,正常情況下應用如果進入后臺會被掛起,通過該設置可以上應用程序繼續在后臺運行。此部分參考了這里

音頻隊列服務

大家應該已經注意到了,無論是前面的錄音還是音頻播放均不支持網絡流媒體播放。AVAudioPlayer只能播放本地文件,并且是一次性加載所有音頻數據,初始化AVAudioPlayer時指定的URL也只能是File URL而不能是HTTP URL。當然,將音頻文件下載到本地然后再調用AVAudioPlayer來播放也是一種播放網絡音頻的辦法,但是這種方式最大的弊端就是必須等到整個音頻播放完成才能播放,而不能使用流式播放,這往往在實際開發中是不切實際的。那么在iOS中如何播放網絡流媒體呢?就是使用AudioToolbox框架中的音頻隊列服務Audio Queue Services。

使用音頻隊列服務完全可以做到音頻播放和錄制,首先看一下錄音音頻服務隊列:

一個音頻服務隊列Audio Queue有三部分組成:

三個緩沖器Buffers:每個緩沖器都是一個存儲音頻數據的臨時倉庫。

一個緩沖隊列Buffer Queue:一個包含音頻緩沖器的有序隊列。

一個回調Callback:一個自定義的隊列回調函數。

聲音通過輸入設備進入緩沖隊列中,首先填充第一個緩沖器;當第一個緩沖器填充滿之后自動填充下一個緩沖器,同時會調用回調函數;在回調函數中需要將緩沖器中的音頻數據寫入磁盤,同時將緩沖器放回到緩沖隊列中以便重用。下面是Apple官方關于音頻隊列服務的流程示意圖:


類似的,看一下音頻播放緩沖隊列,其組成部分和錄音緩沖隊列類似:

但是在音頻播放緩沖隊列中,回調函數調用的時機不同于音頻錄制緩沖隊列,流程剛好相反。將音頻讀取到緩沖器中,一旦一個緩沖器填充滿之后就放到緩沖隊列中,然后繼續填充其他緩沖器;當開始播放時,則從第一個緩沖器中讀取音頻進行播放;一旦播放完之后就會觸發回調函數,開始播放下一個緩沖器中的音頻,同時填充第一個緩沖器放;填充滿之后再次放回到緩沖隊列。下面是詳細的流程:


當然,要明白音頻隊列服務的原理并不難,問題是如何實現這個自定義的回調函數,這其中我們有大量的工作要做,控制播放狀態、處理異常中斷、進行音頻編碼等等。由于牽扯內容過多,而且不是本文目的,如果以后有時間將另開一篇文章重點介紹,目前有很多第三方優秀框架可以直接使用,例如AudioStreamer、FreeStreamer

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容