原文鏈接:http://www.cnblogs.com/YanPengBlog/p/5266783.html
在iOS中音頻播放從形式上可以分為音效播放和音樂播放。前者主要指的是一些短音頻播放,通常作為點(diǎn)綴音頻,對于這類音頻不需要進(jìn)行進(jìn)度、循環(huán)等控制。后者指的是一些較長的音頻,通常是主音頻,對于這些音頻的播放通常需要進(jìn)行精確的控制。在iOS中播放兩類音頻分別使用AudioToolbox.framework和AVFoundation.framework來完成音效和音樂播放。
音效
AudioToolbox.framework是一套基于C語言的框架,使用它來播放音效其本質(zhì)是將短音頻注冊到系統(tǒng)聲音服務(wù)(System Sound Service)。System Sound Service是一種簡單、底層的聲音播放服務(wù),但是它本身也存在著一些限制:
音頻播放時(shí)間不能超過30s
數(shù)據(jù)必須是PCM或者IMA4格式
音頻文件必須打包成.caf、.aif、.wav中的一種(注意這是官方文檔的說法,實(shí)際測試發(fā)現(xiàn)一些.mp3也可以播放)
使用System Sound Service 播放音效的步驟如下:
調(diào)用**AudioServicesCreateSystemSoundID( CFURLRef inFileURL, SystemSoundID* outSystemSoundID)**函數(shù)獲得系統(tǒng)聲音ID。
如果需要監(jiān)聽播放完成操作,則使用**AudioServicesAddSystemSoundCompletion( SystemSoundID inSystemSoundID,CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void* inClientData)**方法注冊回調(diào)函數(shù)。
調(diào)用**AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID) **或者**AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID)** 方法播放音效(后者帶有震動(dòng)效果)。
下面是一個(gè)簡單的示例程序:
//// KCMainViewController.m// Audio//// Created by Kenshin Cui on 14/03/30.// Copyright (c) 2014年 cmjstudio. All rights reserved.// 音效播放#import "KCMainViewController.h"#import <AudioToolbox/AudioToolbox.h>@interface KCMainViewController ()@end@implementation KCMainViewController- (void)viewDidLoad { [super viewDidLoad]; [self playSoundEffect:@"videoRing.caf"];}/** * 播放完成回調(diào)函數(shù) * * @param soundID 系統(tǒng)聲音ID * @param clientData 回調(diào)時(shí)傳遞的數(shù)據(jù) */void soundCompleteCallback(SystemSoundID soundID,void * clientData){ NSLog(@"播放完成...");}/** * 播放音效文件 * * @param name 音頻文件名稱 */-(void)playSoundEffect:(NSString *)name{ NSString *audioFile=[[NSBundle mainBundle] pathForResource:name ofType:nil]; NSURL *fileUrl=[NSURL fileURLWithPath:audioFile]; //1.獲得系統(tǒng)聲音ID SystemSoundID soundID=0; /** * inFileUrl:音頻文件url * outSystemSoundID:聲音id(此函數(shù)會(huì)將音效文件加入到系統(tǒng)音頻服務(wù)中并返回一個(gè)長整形ID) */ AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID); //如果需要在播放完之后執(zhí)行某些操作,可以調(diào)用如下方法注冊一個(gè)播放完成回調(diào)函數(shù) AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL); //2.播放音頻 AudioServicesPlaySystemSound(soundID);//播放音效// AudioServicesPlayAlertSound(soundID);//播放音效并震動(dòng)}@end
音樂
如果播放較大的音頻或者要對音頻有精確的控制則System Sound Service可能就很難滿足實(shí)際需求了,通常這種情況會(huì)選擇使用AVFoundation.framework中的AVAudioPlayer來實(shí)現(xiàn)。AVAudioPlayer可以看成一個(gè)播放器,它支持多種[音頻格式](https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVFoundation_Constants/index.html#//apple_ref/doc/constant_group/File_Format_UTIs),而且能夠進(jìn)行進(jìn)度、音量、播放速度等控制。首先簡單看一下AVAudioPlayer常用的屬性和方法:
**屬性 **
**說明 **
@property(readonly, getter=isPlaying) BOOL playing
是否正在播放,只讀
@property(readonly) NSUInteger numberOfChannels
音頻聲道數(shù),只讀
@property(readonly) NSTimeInterval duration
音頻時(shí)長
@property(readonly) NSURL *url
音頻文件路徑,只讀
@property(readonly) NSData *data
音頻數(shù)據(jù),只讀
@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則正常播放,如果要修改播放速率則必須設(shè)置enableRate為YES
@property NSTimeInterval currentTime
當(dāng)前播放時(shí)長
@property(readonly) NSTimeInterval deviceCurrentTime
輸出設(shè)備播放音頻的時(shí)間,注意如果播放中被暫停此時(shí)間也會(huì)繼續(xù)累加
@property NSInteger numberOfLoops
循環(huán)播放次數(shù),如果為0則不循環(huán),如果小于0則無限循環(huán),大于0則表示循環(huán)次數(shù)
@property(readonly) NSDictionary *settings
音頻播放設(shè)置信息,只讀
@property(getter=isMeteringEnabled) BOOL meteringEnabled
是否啟用音頻測量,默認(rèn)為NO,一旦啟用音頻測量可以通過updateMeters方法更新測量值
**對象方法**
**說明 **
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError
使用文件URL初始化播放器,注意這個(gè)URL不能是HTTP URL,AVAudioPlayer不支持加載網(wǎng)絡(luò)媒體流,只能播放本地文件
- (instancetype)initWithData:(NSData *)data error:(NSError **)outError
使用NSData初始化播放器,注意使用此方法時(shí)必須文件格式和文件后綴一致,否則出錯(cuò),所以相比此方法更推薦使用上述方法或- (instancetype)initWithData:(NSData *)data fileTypeHint:(NSString *)utiString error:(NSError **)outError方法進(jìn)行初始化
- (BOOL)prepareToPlay;
加載音頻文件到緩沖區(qū),注意即使在播放之前音頻文件沒有加載到緩沖區(qū)程序也會(huì)隱式調(diào)用此方法。
- (BOOL)play;
播放音頻文件
- (BOOL)playAtTime:(NSTimeInterval)time
在指定的時(shí)間開始播放音頻
- (void)pause;
暫停播放
- (void)stop;
停止播放
- (void)updateMeters
更新音頻測量值,注意如果要更新音頻測量值必須設(shè)置meteringEnabled為YES,通過音頻測量值可以即時(shí)獲得音頻分貝等信息
- (float)peakPowerForChannel:(NSUInteger)channelNumber;
獲得指定聲道的分貝峰值,注意如果要獲得分貝峰值必須在此之前調(diào)用updateMeters方法
- (float)averagePowerForChannel:(NSUInteger)channelNumber
獲得指定聲道的分貝平均值,注意如果要獲得分貝平均值必須在此之前調(diào)用updateMeters方法
@property(nonatomic, copy) NSArray *channelAssignments
獲得或設(shè)置播放聲道
代理方法
說明
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
音頻播放完成
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error
音頻解碼發(fā)生錯(cuò)誤
AVAudioPlayer的使用比較簡單:
初始化AVAudioPlayer對象,此時(shí)通常指定本地文件路徑。
設(shè)置播放器屬性,例如重復(fù)次數(shù)、音量大小等。
調(diào)用play方法播放。
下面就使用AVAudioPlayer實(shí)現(xiàn)一個(gè)簡單播放器,在這個(gè)播放器中實(shí)現(xiàn)了播放、暫停、顯示播放進(jìn)度功能,當(dāng)然例如調(diào)節(jié)音量、設(shè)置循環(huán)模式、甚至是聲波圖像(通過分析音頻分貝值)等功能都可以實(shí)現(xiàn),這里就不再一一演示。界面效果如下:
[](http://images.cnitblog.com/blog/62046/201412/260912543436719.png)
當(dāng)然由于AVAudioPlayer一次只能播放一個(gè)音頻文件,所有上一曲、下一曲其實(shí)可以通過創(chuàng)建多個(gè)播放器對象來完成,這里暫不實(shí)現(xiàn)。播放進(jìn)度的實(shí)現(xiàn)主要依靠一個(gè)定時(shí)器實(shí)時(shí)計(jì)算當(dāng)前播放時(shí)長和音頻總時(shí)長的比例,另外為了演示委托方法,下面的代碼中也實(shí)現(xiàn)了播放完成委托方法,通常如果有下一曲功能的話播放完可以觸發(fā)下一曲音樂播放。下面是主要代碼:
//// ViewController.m// KCAVAudioPlayer//// Created by Kenshin Cui on 14/03/30.// Copyright (c) 2014年 cmjstudio. All rights reserved.//#import "ViewController.h"#import <AVFoundation/AVFoundation.h>#define kMusicFile @"劉若英 - 原來你也在這里.mp3"#define kMusicSinger @"劉若英"#define kMusicTitle @"原來你也在這里"@interface ViewController ()<AVAudioPlayerDelegate>@property (nonatomic,strong) AVAudioPlayer *audioPlayer;//播放器@property (weak, nonatomic) IBOutlet UILabel *controlPanel; //控制面板@property (weak, nonatomic) IBOutlet UIProgressView *playProgress;//播放進(jìn)度@property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕(如果tag為0認(rèn)為是暫停狀態(tài),1是播放狀態(tài))@property (weak ,nonatomic) NSTimer *timer;//進(jìn)度更新定時(shí)器@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; [self setupUI]; }/** * 初始化UI */-(void)setupUI{ self.title=kMusicTitle; self.musicSinger.text=kMusicSinger;}-(NSTimer *)timer{ if (!_timer) { _timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:true]; } return _timer;}/** * 創(chuàng)建播放器 * * @return 音頻播放器 */-(AVAudioPlayer *)audioPlayer{ if (!_audioPlayer) { NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil]; NSURL *url=[NSURL fileURLWithPath:urlStr]; NSError *error=nil; //初始化播放器,注意這里的Url參數(shù)只能時(shí)文件路徑,不支持HTTP Url _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error]; //設(shè)置播放器屬性 _audioPlayer.numberOfLoops=0;//設(shè)置為0不循環(huán) _audioPlayer.delegate=self; [_audioPlayer prepareToPlay];//加載音頻文件到緩存 if(error){ NSLog(@"初始化播放器過程發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription); return nil; } } return _audioPlayer;}/** * 播放音頻 */-(void)play{ if (![self.audioPlayer isPlaying]) { [self.audioPlayer play]; self.timer.fireDate=[NSDate distantPast];//恢復(fù)定時(shí)器 }}/** * 暫停播放 */-(void)pause{ if ([self.audioPlayer isPlaying]) { [self.audioPlayer pause]; self.timer.fireDate=[NSDate distantFuture];//暫停定時(shí)器,注意不能調(diào)用invalidate方法,此方法會(huì)取消,之后無法恢復(fù) }}/** * 點(diǎn)擊播放/暫停按鈕 * * @param sender 播放/暫停按鈕 */- (IBAction)playClick:(UIButton *)sender { if(sender.tag){ sender.tag=0; [sender setImage:[UIImage imageNamed:@"playing_btn_play_n"] forState:UIControlStateNormal]; [sender setImage:[UIImage imageNamed:@"playing_btn_play_h"] forState:UIControlStateHighlighted]; [self pause]; }else{ sender.tag=1; [sender setImage:[UIImage imageNamed:@"playing_btn_pause_n"] forState:UIControlStateNormal]; [sender setImage:[UIImage imageNamed:@"playing_btn_pause_h"] forState:UIControlStateHighlighted]; [self play]; }}/** * 更新播放進(jìn)度 */-(void)updateProgress{ float progress= self.audioPlayer.currentTime /self.audioPlayer.duration; [self.playProgress setProgress:progress animated:true];}#pragma mark - 播放器代理方法-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{ NSLog(@"音樂播放完成...");}@end
運(yùn)行效果:
[](http://images.cnitblog.com/blog/62046/201412/260912583434030.gif)
音頻會(huì)話
事實(shí)上上面的播放器還存在一些問題,例如通常我們看到的播放器即使退出到后臺(tái)也是可以播放的,而這個(gè)播放器如果退出到后臺(tái)它會(huì)自動(dòng)暫停。如果要支持后臺(tái)播放需要做下面幾件事情:
1.設(shè)置后臺(tái)運(yùn)行模式:在plist文件中添加Required background modes,并且設(shè)置**item 0=App plays audio or streams audio/video using AirPlay**(其實(shí)可以直接通過Xcode在Project Targets-Capabilities-Background Modes中設(shè)置)
[](http://images.cnitblog.com/blog/62046/201412/260912599371744.png)
2.設(shè)置AVAudioSession的類型為AVAudioSessionCategoryPlayback并且調(diào)用setActive::方法啟動(dòng)會(huì)話。
AVAudioSession *audioSession=[AVAudioSession sharedInstance]; [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]; [audioSession setActive:YES error:nil];
3.為了能夠讓應(yīng)用退到后臺(tái)之后支持耳機(jī)控制,建議添加[遠(yuǎn)程控制事件](http://www.cnblogs.com/kenshincui/p/3950646.html#remoteControl)(這一步不是后臺(tái)播放必須的)
前兩步是后臺(tái)播放所必須設(shè)置的,第三步主要用于接收遠(yuǎn)程事件,這部分內(nèi)容之前的文章中有詳細(xì)介紹,如果這一步不設(shè)置雖讓也能夠在后臺(tái)播放,但是無法獲得音頻控制權(quán)(如果在使用當(dāng)前應(yīng)用之前使用其他播放器播放音樂的話,此時(shí)如果按耳機(jī)播放鍵或者控制中心的播放按鈕則會(huì)播放前一個(gè)應(yīng)用的音頻),并且不能使用耳機(jī)進(jìn)行音頻控制。第一步操作相信大家都很容易理解,如果應(yīng)用程序要允許運(yùn)行到后臺(tái)必須設(shè)置,正常情況下應(yīng)用如果進(jìn)入后臺(tái)會(huì)被掛起,通過該設(shè)置可以上應(yīng)用程序繼續(xù)在后臺(tái)運(yùn)行。但是第二步使用的AVAudioSession有必要進(jìn)行一下詳細(xì)的說明。
在iOS中每個(gè)應(yīng)用都有一個(gè)音頻會(huì)話,這個(gè)會(huì)話就通過AVAudioSession來表示。AVAudioSession同樣存在于AVFoundation框架中,它是單例模式設(shè)計(jì),通過sharedInstance進(jìn)行訪問。在使用Apple設(shè)備時(shí)大家會(huì)發(fā)現(xiàn)有些應(yīng)用只要打開其他音頻播放就會(huì)終止,而有些應(yīng)用卻可以和其他應(yīng)用同時(shí)播放,在多種音頻環(huán)境中如何去控制播放的方式就是通過音頻會(huì)話來完成的。下面是音頻會(huì)話的幾種會(huì)話模式:
**會(huì)話類型**
**說明**
**是否要求輸入**
**是否要求輸出**
**是否遵從靜音鍵**
AVAudioSessionCategoryAmbient
混音播放,可以與其他音頻應(yīng)用同時(shí)播放
否
是
是
AVAudioSessionCategorySoloAmbient
獨(dú)占播放
否
是
是
AVAudioSessionCategoryPlayback
后臺(tái)播放,也是獨(dú)占的
否
是
否
AVAudioSessionCategoryRecord
錄音模式,用于錄音時(shí)使用
是
否
否
AVAudioSessionCategoryPlayAndRecord
播放和錄音,此時(shí)可以錄音也可以播放
是
是
否
AVAudioSessionCategoryAudioProcessing
硬件解碼音頻,此時(shí)不能播放和錄制
否
否
否
AVAudioSessionCategoryMultiRoute
多種輸入輸出,例如可以耳機(jī)、USB設(shè)備同時(shí)播放
是
是
否
*注意:是否遵循靜音鍵表示在播放過程中如果用戶通過硬件設(shè)置為靜音是否能關(guān)閉聲音。*
根據(jù)前面對音頻會(huì)話的理解,相信大家開發(fā)出能夠在后臺(tái)播放的音頻播放器并不難,但是注意一下,在前面的代碼中也提到設(shè)置完音頻會(huì)話類型之后需要調(diào)用setActive::方法將會(huì)話激活才能起作用。類似的,如果一個(gè)應(yīng)用已經(jīng)在播放音頻,打開我們的應(yīng)用之后設(shè)置了在后臺(tái)播放的會(huì)話類型,此時(shí)其他應(yīng)用的音頻會(huì)停止而播放我們的音頻,如果希望我們的程序音頻播放完之后(關(guān)閉或退出到后臺(tái)之后)能夠繼續(xù)播放其他應(yīng)用的音頻的話則可以調(diào)用setActive::方法關(guān)閉會(huì)話。代碼如下:
//// ViewController.m// KCAVAudioPlayer//// Created by Kenshin Cui on 14/03/30.// Copyright (c) 2014年 cmjstudio. All rights reserved.// AVAudioSession 音頻會(huì)話#import "ViewController.h"#import <AVFoundation/AVFoundation.h>#define kMusicFile @"劉若英 - 原來你也在這里.mp3"#define kMusicSinger @"劉若英"#define kMusicTitle @"原來你也在這里"@interface ViewController ()<AVAudioPlayerDelegate>@property (nonatomic,strong) AVAudioPlayer *audioPlayer;//播放器@property (weak, nonatomic) IBOutlet UILabel *controlPanel; //控制面板@property (weak, nonatomic) IBOutlet UIProgressView *playProgress;//播放進(jìn)度@property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕(如果tag為0認(rèn)為是暫停狀態(tài),1是播放狀態(tài))@property (weak ,nonatomic) NSTimer *timer;//進(jìn)度更新定時(shí)器@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; [self setupUI]; }/** * 顯示當(dāng)面視圖控制器時(shí)注冊遠(yuǎn)程事件 * * @param animated 是否以動(dòng)畫的形式顯示 */-(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; //開啟遠(yuǎn)程控制 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; //作為第一響應(yīng)者 //[self becomeFirstResponder];}/** * 當(dāng)前控制器視圖不顯示時(shí)取消遠(yuǎn)程控制 * * @param animated 是否以動(dòng)畫的形式消失 */-(void)viewWillDisappear:(BOOL)animated{ [super viewWillDisappear:animated]; [[UIApplication sharedApplication] endReceivingRemoteControlEvents]; //[self resignFirstResponder];}/** * 初始化UI */-(void)setupUI{ self.title=kMusicTitle; self.musicSinger.text=kMusicSinger;}-(NSTimer *)timer{ if (!_timer) { _timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:true]; } return _timer;}/** * 創(chuàng)建播放器 * * @return 音頻播放器 */-(AVAudioPlayer *)audioPlayer{ if (!_audioPlayer) { NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil]; NSURL *url=[NSURL fileURLWithPath:urlStr]; NSError *error=nil; //初始化播放器,注意這里的Url參數(shù)只能時(shí)文件路徑,不支持HTTP Url _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error]; //設(shè)置播放器屬性 _audioPlayer.numberOfLoops=0;//設(shè)置為0不循環(huán) _audioPlayer.delegate=self; [_audioPlayer prepareToPlay];//加載音頻文件到緩存 if(error){ NSLog(@"初始化播放器過程發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription); return nil; } //設(shè)置后臺(tái)播放模式 AVAudioSession *audioSession=[AVAudioSession sharedInstance]; [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];// [audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil]; [audioSession setActive:YES error:nil]; //添加通知,拔出耳機(jī)后暫停播放 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil]; } return _audioPlayer;}/** * 播放音頻 */-(void)play{ if (![self.audioPlayer isPlaying]) { [self.audioPlayer play]; self.timer.fireDate=[NSDate distantPast];//恢復(fù)定時(shí)器 }}/** * 暫停播放 */-(void)pause{ if ([self.audioPlayer isPlaying]) { [self.audioPlayer pause]; self.timer.fireDate=[NSDate distantFuture];//暫停定時(shí)器,注意不能調(diào)用invalidate方法,此方法會(huì)取消,之后無法恢復(fù) }}/** * 點(diǎn)擊播放/暫停按鈕 * * @param sender 播放/暫停按鈕 */- (IBAction)playClick:(UIButton *)sender { if(sender.tag){ sender.tag=0; [sender setImage:[UIImage imageNamed:@"playing_btn_play_n"] forState:UIControlStateNormal]; [sender setImage:[UIImage imageNamed:@"playing_btn_play_h"] forState:UIControlStateHighlighted]; [self pause]; }else{ sender.tag=1; [sender setImage:[UIImage imageNamed:@"playing_btn_pause_n"] forState:UIControlStateNormal]; [sender setImage:[UIImage imageNamed:@"playing_btn_pause_h"] forState:UIControlStateHighlighted]; [self play]; }}/** * 更新播放進(jìn)度 */-(void)updateProgress{ float progress= self.audioPlayer.currentTime /self.audioPlayer.duration; [self.playProgress setProgress:progress animated:true];}/** * 一旦輸出改變則執(zhí)行此方法 * * @param notification 輸出改變通知對象 */-(void)routeChange:(NSNotification *)notification{ NSDictionary *dic=notification.userInfo; int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue]; //等于AVAudioSessionRouteChangeReasonOldDeviceUnavailable表示舊輸出不可用 if (changeReason==AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { AVAudioSessionRouteDescription *routeDescription=dic[AVAudioSessionRouteChangePreviousRouteKey]; AVAudioSessionPortDescription *portDescription= [routeDescription.outputs firstObject]; //原設(shè)備為耳機(jī)則暫停 if ([portDescription.portType isEqualToString:@"Headphones"]) { [self pause]; } } // [dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {// NSLog(@"%@:%@",key,obj);// }];}-(void)dealloc{ [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];}#pragma mark - 播放器代理方法-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{ NSLog(@"音樂播放完成..."); //根據(jù)實(shí)際情況播放完成可以將會(huì)話關(guān)閉,其他音頻應(yīng)用繼續(xù)播放 [[AVAudioSession sharedInstance]setActive:NO error:nil];}@end
在上面的代碼中還實(shí)現(xiàn)了拔出耳機(jī)暫停音樂播放的功能,這也是一個(gè)比較常見的功能。在iOS7及以后的版本中可以通過通知獲得輸出改變的通知,然后拿到通知對象后根據(jù)userInfo獲得是何種改變類型,進(jìn)而根據(jù)情況對音樂進(jìn)行暫停操作。
擴(kuò)展--播放音樂庫中的音樂
眾所周知音樂是iOS的重要組成播放,無論是iPod、iTouch、iPhone還是iPad都可以在iTunes購買音樂或添加本地音樂到音樂庫中同步到你的iOS設(shè)備。在MediaPlayer.frameowork中有一個(gè)MPMusicPlayerController用于播放音樂庫中的音樂。
下面先來看一下MPMusicPlayerController的常用屬性和方法:
**屬性**
**說明**
@property (nonatomic, readonly) MPMusicPlaybackState playbackState
播放器狀態(tài),枚舉類型:MPMusicPlaybackStateStopped:停止播放 MPMusicPlaybackStatePlaying:正在播放MPMusicPlaybackStatePaused:暫停播放MPMusicPlaybackStateInterrupted:播放中斷MPMusicPlaybackStateSeekingForward:向前查找MPMusicPlaybackStateSeekingBackward:向后查找
@property (nonatomic) MPMusicRepeatMode repeatMode
重復(fù)模式,枚舉類型:MPMusicRepeatModeDefault:默認(rèn)模式,使用用戶的首選項(xiàng)(系統(tǒng)音樂程序設(shè)置)MPMusicRepeatModeNone:不重復(fù)MPMusicRepeatModeOne:單曲循環(huán)MPMusicRepeatModeAll:在當(dāng)前列表內(nèi)循環(huán)
@property (nonatomic) MPMusicShuffleMode shuffleMode
隨機(jī)播放模式,枚舉類型:MPMusicShuffleModeDefault:默認(rèn)模式,使用用戶首選項(xiàng)(系統(tǒng)音樂程序設(shè)置)MPMusicShuffleModeOff:不隨機(jī)播放MPMusicShuffleModeSongs:按歌曲隨機(jī)播放MPMusicShuffleModeAlbums:按專輯隨機(jī)播放
@property (nonatomic, copy) MPMediaItem *nowPlayingItem
正在播放的音樂項(xiàng)
@property (nonatomic, readonly) NSUInteger indexOfNowPlayingItem
當(dāng)前正在播放的音樂在播放隊(duì)列中的索引
@property(nonatomic, readonly) BOOL isPreparedToPlay
是否準(zhǔn)好播放準(zhǔn)備
@property(nonatomic) NSTimeInterval currentPlaybackTime
當(dāng)前已播放時(shí)間,單位:秒
@property(nonatomic) float currentPlaybackRate
當(dāng)前播放速度,是一個(gè)播放速度倍率,0表示暫停播放,1代表正常速度
類方法
說明
+ (MPMusicPlayerController *)applicationMusicPlayer;
獲取應(yīng)用播放器,注意此類播放器無法在后臺(tái)播放
+ (MPMusicPlayerController *)systemMusicPlayer
獲取系統(tǒng)播放器,支持后臺(tái)播放
對象方法
說明
- (void)setQueueWithQuery:(MPMediaQuery *)query
使用媒體隊(duì)列設(shè)置播放源媒體隊(duì)列
- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection
使用媒體項(xiàng)集合設(shè)置播放源媒體隊(duì)列
- (void)skipToNextItem
下一曲
- (void)skipToBeginning
從起始位置播放
- (void)skipToPreviousItem
上一曲
- (void)beginGeneratingPlaybackNotifications
開啟播放通知,注意不同于其他播放器,MPMusicPlayerController要想獲得通知必須首先開啟,默認(rèn)情況無法獲得通知
- (void)endGeneratingPlaybackNotifications
關(guān)閉播放通知
- (void)prepareToPlay
做好播放準(zhǔn)備(加載音頻到緩沖區(qū)),在使用play方法播放時(shí)如果沒有做好準(zhǔn)備回自動(dòng)調(diào)用該方法
- (void)play
開始播放
- (void)pause
暫停播放
- (void)stop
停止播放
- (void)beginSeekingForward
開始向前查找(快進(jìn))
- (void)beginSeekingBackward
開始向后查找(快退)
- (void)endSeeking
結(jié)束查找
通知
說明(注意:要想獲得MPMusicPlayerController通知必須首先調(diào)用beginGeneratingPlaybackNotifications開啟通知)
MPMusicPlayerControllerPlaybackStateDidChangeNotification
播放狀態(tài)改變
MPMusicPlayerControllerNowPlayingItemDidChangeNotification
當(dāng)前播放音頻改變
MPMusicPlayerControllerVolumeDidChangeNotification
聲音大小改變
MPMediaPlaybackIsPreparedToPlayDidChangeNotification
準(zhǔn)備好播放
MPMusicPlayerController有兩種播放器:applicationMusicPlayer和systemMusicPlayer,前者在應(yīng)用退出后音樂播放會(huì)自動(dòng)停止,后者在應(yīng)用停止后不會(huì)退出播放狀態(tài)。
MPMusicPlayerController加載音樂不同于前面的AVAudioPlayer是通過一個(gè)文件路徑來加載,而是需要一個(gè)播放隊(duì)列。在MPMusicPlayerController中提供了兩個(gè)方法來加載播放隊(duì)列**:- (void)setQueueWithQuery:(MPMediaQuery *)query**和**- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection**,正是由于它的播放音頻來源是一個(gè)隊(duì)列,因此MPMusicPlayerController支持上一曲、下一曲等操作。
那么接下來的問題就是如何獲取MPMediaQueue或者M(jìn)PMediaItemCollection?MPMediaQueue對象有一系列的類方法來獲得媒體隊(duì)列:
*+ (MPMediaQuery *)albumsQuery;+ (MPMediaQuery *)artistsQuery;+ (MPMediaQuery *)songsQuery;+ (MPMediaQuery *)playlistsQuery;+ (MPMediaQuery *)podcastsQuery;+ (MPMediaQuery *)audiobooksQuery;+ (MPMediaQuery *)compilationsQuery;+ (MPMediaQuery *)composersQuery;+ (MPMediaQuery *)genresQuery;*
有了這些方法,就可以很容易獲到歌曲、播放列表、專輯媒體等媒體隊(duì)列了,這樣就可以通過**:- (void)setQueueWithQuery:(MPMediaQuery *)query**方法設(shè)置音樂來源了**。**又或者得到**MPMediaQueue**之后創(chuàng)建**MPMediaItemCollection,**使用**- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection**設(shè)置音樂來源。
有時(shí)候可能希望用戶自己來選擇要播放的音樂,這時(shí)可以使用MPMediaPickerController,它是一個(gè)視圖控制器,類似于UIImagePickerController,選擇完播放來源后可以在其代理方法中獲得MPMediaItemCollection對象。
無論是通過哪種方式獲得MPMusicPlayerController的媒體源,可能都希望將每個(gè)媒體的信息顯示出來,這時(shí)候可以通過MPMediaItem對象獲得。一個(gè)MPMediaItem代表一個(gè)媒體文件,通過它可以訪問媒體標(biāo)題、專輯名稱、專輯封面、音樂時(shí)長等等。無論是MPMediaQueue還是MPMediaItemCollection都有一個(gè)items屬性,它是MPMediaItem數(shù)組,通過這個(gè)屬性可以獲得MPMediaItem對象。
下面就簡單看一下MPMusicPlayerController的使用,在下面的例子中簡單演示了音樂的選擇、播放、暫停、通知、下一曲、上一曲功能,相信有了上面的概念,代碼讀起來并不復(fù)雜(示例中是直接通過MPMeidaPicker進(jìn)行音樂選擇的,但是仍然提供了兩個(gè)方法getLocalMediaQuery和getLocalMediaItemCollection來演示如何直接通過MPMediaQueue獲得媒體隊(duì)列或媒體集合):
//// ViewController.m// MPMusicPlayerController//// Created by Kenshin Cui 14/03/30// Copyright (c) 2014年 cmjstudio. All rights reserved.//#import "ViewController.h"#import <MediaPlayer/MediaPlayer.h>@interface ViewController ()<MPMediaPickerControllerDelegate>@property (nonatomic,strong) MPMediaPickerController *mediaPicker;//媒體選擇控制器@property (nonatomic,strong) MPMusicPlayerController *musicPlayer; //音樂播放器@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad];}-(void)dealloc{ [self.musicPlayer endGeneratingPlaybackNotifications];}/** * 獲得音樂播放器 * * @return 音樂播放器 */-(MPMusicPlayerController *)musicPlayer{ if (!_musicPlayer) { _musicPlayer=[MPMusicPlayerController systemMusicPlayer]; [_musicPlayer beginGeneratingPlaybackNotifications];//開啟通知,否則監(jiān)控不到MPMusicPlayerController的通知 [self addNotification];//添加通知 //如果不使用MPMediaPickerController可以使用如下方法獲得音樂庫媒體隊(duì)列 //[_musicPlayer setQueueWithItemCollection:[self getLocalMediaItemCollection]]; } return _musicPlayer;}/** * 創(chuàng)建媒體選擇器 * * @return 媒體選擇器 */-(MPMediaPickerController *)mediaPicker{ if (!_mediaPicker) { //初始化媒體選擇器,這里設(shè)置媒體類型為音樂,其實(shí)這里也可以選擇視頻、廣播等// _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeMusic]; _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeAny]; _mediaPicker.allowsPickingMultipleItems=YES;//允許多選// _mediaPicker.showsCloudItems=YES;//顯示icloud選項(xiàng) _mediaPicker.prompt=@"請選擇要播放的音樂"; _mediaPicker.delegate=self;//設(shè)置選擇器代理 } return _mediaPicker;}/** * 取得媒體隊(duì)列 * * @return 媒體隊(duì)列 */-(MPMediaQuery *)getLocalMediaQuery{ MPMediaQuery *mediaQueue=[MPMediaQuery songsQuery]; for (MPMediaItem *item in mediaQueue.items) { NSLog(@"標(biāo)題:%@,%@",item.title,item.albumTitle); } return mediaQueue;}/** * 取得媒體集合 * * @return 媒體集合 */-(MPMediaItemCollection *)getLocalMediaItemCollection{ MPMediaQuery *mediaQueue=[MPMediaQuery songsQuery]; NSMutableArray *array=[NSMutableArray array]; for (MPMediaItem *item in mediaQueue.items) { [array addObject:item]; NSLog(@"標(biāo)題:%@,%@",item.title,item.albumTitle); } MPMediaItemCollection *mediaItemCollection=[[MPMediaItemCollection alloc]initWithItems:[array copy]]; return mediaItemCollection;}#pragma mark - MPMediaPickerController代理方法//選擇完成-(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection{ MPMediaItem *mediaItem=[mediaItemCollection.items firstObject];//第一個(gè)播放音樂 //注意很多音樂信息如標(biāo)題、專輯、表演者、封面、時(shí)長等信息都可以通過MPMediaItem的valueForKey:方法得到,但是從iOS7開始都有對應(yīng)的屬性可以直接訪問// NSString *title= [mediaItem valueForKey:MPMediaItemPropertyAlbumTitle];// NSString *artist= [mediaItem valueForKey:MPMediaItemPropertyAlbumArtist];// MPMediaItemArtwork *artwork= [mediaItem valueForKey:MPMediaItemPropertyArtwork]; //UIImage *image=[artwork imageWithSize:CGSizeMake(100, 100)];//專輯圖片 NSLog(@"標(biāo)題:%@,表演者:%@,專輯:%@",mediaItem.title ,mediaItem.artist,mediaItem.albumTitle); [self.musicPlayer setQueueWithItemCollection:mediaItemCollection]; [self dismissViewControllerAnimated:YES completion:nil];}//取消選擇-(void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker{ [self dismissViewControllerAnimated:YES completion:nil];}#pragma mark - 通知/** * 添加通知 */-(void)addNotification{ NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(playbackStateChange:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.musicPlayer];}/** * 播放狀態(tài)改變通知 * * @param notification 通知對象 */-(void)playbackStateChange:(NSNotification *)notification{ switch (self.musicPlayer.playbackState) { case MPMusicPlaybackStatePlaying: NSLog(@"正在播放..."); break; case MPMusicPlaybackStatePaused: NSLog(@"播放暫停."); break; case MPMusicPlaybackStateStopped: NSLog(@"播放停止."); break; default: break; }}#pragma mark - UI事件- (IBAction)selectClick:(UIButton *)sender { [self presentViewController:self.mediaPicker animated:YES completion:nil];}- (IBAction)playClick:(UIButton *)sender { [self.musicPlayer play];}- (IBAction)puaseClick:(UIButton *)sender { [self.musicPlayer pause];}- (IBAction)stopClick:(UIButton *)sender { [self.musicPlayer stop];}- (IBAction)nextClick:(UIButton *)sender { [self.musicPlayer skipToNextItem];}- (IBAction)prevClick:(UIButton *)sender { [self.musicPlayer skipToPreviousItem];}@end
錄音
除了上面說的,在AVFoundation框架中還要一個(gè)AVAudioRecorder類專門處理錄音操作,它同樣支持[多種音頻格式](https://developer.apple.com/library/ios/documentation/MusicAudio/Reference/CoreAudioDataTypesRef/index.html#//apple_ref/doc/constant_group/Audio_Data_Format_Identifiers)。與AVAudioPlayer類似,你完全可以將它看成是一個(gè)錄音機(jī)控制類,下面是常用的屬性和方法:
屬性
說明
@property(readonly, getter=isRecording) BOOL recording;
是否正在錄音,只讀
@property(readonly) NSURL *url
錄音文件地址,只讀
@property(readonly) NSDictionary *settings
錄音文件設(shè)置,只讀
@property(readonly) NSTimeInterval currentTime
錄音時(shí)長,只讀,注意僅僅在錄音狀態(tài)可用
@property(readonly) NSTimeInterval deviceCurrentTime
輸入設(shè)置的時(shí)間長度,只讀,注意此屬性一直可訪問
@property(getter=isMeteringEnabled) BOOL meteringEnabled;
是否啟用錄音測量,如果啟用錄音測量可以獲得錄音分貝等數(shù)據(jù)信息
@property(nonatomic, copy) NSArray *channelAssignments
當(dāng)前錄音的通道
對象方法
說明
- (instancetype)initWithURL:(NSURL *)url settings:(NSDictionary *)settings error:(NSError **)outError
錄音機(jī)對象初始化方法,注意其中的url必須是本地文件url,settings是錄音格式、編碼等設(shè)置
- (BOOL)prepareToRecord
準(zhǔn)備錄音,主要用于創(chuàng)建緩沖區(qū),如果不手動(dòng)調(diào)用,在調(diào)用record錄音時(shí)也會(huì)自動(dòng)調(diào)用
- (BOOL)record
開始錄音
- (BOOL)recordAtTime:(NSTimeInterval)time
在指定的時(shí)間開始錄音,一般用于錄音暫停再恢復(fù)錄音
- (BOOL)recordForDuration:(NSTimeInterval) duration
按指定的時(shí)長開始錄音
- (BOOL)recordAtTime:(NSTimeInterval)time forDuration:(NSTimeInterval) duration
在指定的時(shí)間開始錄音,并指定錄音時(shí)長
- (void)pause;
暫停錄音
- (void)stop;
停止錄音
- (BOOL)deleteRecording;
刪除錄音,注意要?jiǎng)h除錄音此時(shí)錄音機(jī)必須處于停止?fàn)顟B(tài)
- (void)updateMeters;
更新測量數(shù)據(jù),注意只有meteringEnabled為YES此方法才可用
- (float)peakPowerForChannel:(NSUInteger)channelNumber;
指定通道的測量峰值,注意只有調(diào)用完updateMeters才有值
- (float)averagePowerForChannel:(NSUInteger)channelNumber
指定通道的測量平均值,注意只有調(diào)用完updateMeters才有值
代理方法
說明
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag
完成錄音
- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError *)error
錄音編碼發(fā)生錯(cuò)誤
AVAudioRecorder很多屬性和方法跟AVAudioPlayer都是類似的,但是它的創(chuàng)建有所不同,在創(chuàng)建錄音機(jī)時(shí)除了指定路徑外還必須指定錄音設(shè)置信息,因?yàn)殇浺魴C(jī)必須知道錄音文件的格式、采樣率、通道數(shù)、每個(gè)采樣點(diǎn)的位數(shù)等信息,但是也并不是所有的信息都必須設(shè)置,通常只需要幾個(gè)常用設(shè)置。關(guān)于錄音設(shè)置詳見幫助文檔中的“[AV Foundation Audio Settings Constants](https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVFoundationAudioSettings_Constants/index.html)”。
下面就使用AVAudioRecorder創(chuàng)建一個(gè)錄音機(jī),實(shí)現(xiàn)了錄音、暫停、停止、播放等功能,實(shí)現(xiàn)效果大致如下:
[](http://images.cnitblog.com/blog/62046/201412/260913012025258.png)
在這個(gè)示例中將實(shí)行一個(gè)完整的錄音控制,包括錄音、暫停、恢復(fù)、停止,同時(shí)還會(huì)實(shí)時(shí)展示用戶錄音的聲音波動(dòng),當(dāng)用戶點(diǎn)擊完停止按鈕還會(huì)自動(dòng)播放錄音文件。程序的構(gòu)建主要分為以下幾步:
設(shè)置音頻會(huì)話類型為AVAudioSessionCategoryPlayAndRecord,因?yàn)槌绦蛑袪砍兜戒浺艉筒シ挪僮鳌?創(chuàng)建錄音機(jī)AVAudioRecorder,指定錄音保存的路徑并且設(shè)置錄音屬性,注意對于一般的錄音文件要求的采樣率、位數(shù)并不高,需要適當(dāng)設(shè)置以保證錄音文件的大小和效果。
設(shè)置錄音機(jī)代理以便在錄音完成后播放錄音,打開錄音測量保證能夠?qū)崟r(shí)獲得錄音時(shí)的聲音強(qiáng)度。(注意聲音強(qiáng)度范圍-160到0,0代表最大輸入)
創(chuàng)建音頻播放器AVAudioPlayer,用于在錄音完成之后播放錄音。
創(chuàng)建一個(gè)定時(shí)器以便實(shí)時(shí)刷新錄音測量值并更新錄音強(qiáng)度到UIProgressView中顯示。
添加錄音、暫停、恢復(fù)、停止操作,需要注意錄音的恢復(fù)操作其實(shí)是有音頻會(huì)話管理的,恢復(fù)時(shí)只要再次調(diào)用record方法即可,無需手動(dòng)管理恢復(fù)時(shí)間等。
下面是主要代碼:
//// ViewController.m// AVAudioRecorder//// Created by Kenshin Cui on 14/03/30.// Copyright (c) 2014年 cmjstudio. All rights reserved.//#import "ViewController.h"#import <AVFoundation/AVFoundation.h>#define kRecordAudioFile @"myRecord.caf"@interface ViewController ()<AVAudioRecorderDelegate>@property (nonatomic,strong) AVAudioRecorder *audioRecorder;//音頻錄音機(jī)@property (nonatomic,strong) AVAudioPlayer *audioPlayer;//音頻播放器,用于播放錄音文件@property (nonatomic,strong) NSTimer *timer;//錄音聲波監(jiān)控(注意這里暫時(shí)不對播放進(jìn)行監(jiān)控)@property (weak, nonatomic) IBOutlet UIButton *record;//開始錄音@property (weak, nonatomic) IBOutlet UIButton *pause;//暫停錄音@property (weak, nonatomic) IBOutlet UIButton *resume;//恢復(fù)錄音@property (weak, nonatomic) IBOutlet UIButton *stop;//停止錄音@property (weak, nonatomic) IBOutlet UIProgressView *audioPower;//音頻波動(dòng)@end@implementation ViewController#pragma mark - 控制器視圖方法- (void)viewDidLoad { [super viewDidLoad]; [self setAudioSession];}#pragma mark - 私有方法/** * 設(shè)置音頻會(huì)話 */-(void)setAudioSession{ AVAudioSession *audioSession=[AVAudioSession sharedInstance]; //設(shè)置為播放和錄音狀態(tài),以便可以在錄制完之后播放錄音 [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil]; [audioSession setActive:YES error:nil];}/** * 取得錄音文件保存路徑 * * @return 錄音文件路徑 */-(NSURL *)getSavePath{ NSString *urlStr=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]; urlStr=[urlStr stringByAppendingPathComponent:kRecordAudioFile]; NSLog(@"file path:%@",urlStr); NSURL *url=[NSURL fileURLWithPath:urlStr]; return url;}/** * 取得錄音文件設(shè)置 * * @return 錄音設(shè)置 */-(NSDictionary *)getAudioSetting{ NSMutableDictionary *dicM=[NSMutableDictionary dictionary]; //設(shè)置錄音格式 [dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey]; //設(shè)置錄音采樣率,8000是電話采樣率,對于一般錄音已經(jīng)夠了 [dicM setObject:@(8000) forKey:AVSampleRateKey]; //設(shè)置通道,這里采用單聲道 [dicM setObject:@(1) forKey:AVNumberOfChannelsKey]; //每個(gè)采樣點(diǎn)位數(shù),分為8、16、24、32 [dicM setObject:@(8) forKey:AVLinearPCMBitDepthKey]; //是否使用浮點(diǎn)數(shù)采樣 [dicM setObject:@(YES) forKey:AVLinearPCMIsFloatKey]; //....其他設(shè)置等 return dicM;}/** * 獲得錄音機(jī)對象 * * @return 錄音機(jī)對象 */-(AVAudioRecorder *)audioRecorder{ if (!_audioRecorder) { //創(chuàng)建錄音文件保存路徑 NSURL *url=[self getSavePath]; //創(chuàng)建錄音格式設(shè)置 NSDictionary *setting=[self getAudioSetting]; //創(chuàng)建錄音機(jī) NSError *error=nil; _audioRecorder=[[AVAudioRecorder alloc]initWithURL:url settings:setting error:&error]; _audioRecorder.delegate=self; _audioRecorder.meteringEnabled=YES;//如果要監(jiān)控聲波則必須設(shè)置為YES if (error) { NSLog(@"創(chuàng)建錄音機(jī)對象時(shí)發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription); return nil; } } return _audioRecorder;}/** * 創(chuàng)建播放器 * * @return 播放器 */-(AVAudioPlayer *)audioPlayer{ if (!_audioPlayer) { NSURL *url=[self getSavePath]; NSError *error=nil; _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error]; _audioPlayer.numberOfLoops=0; [_audioPlayer prepareToPlay]; if (error) { NSLog(@"創(chuàng)建播放器過程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription); return nil; } } return _audioPlayer;}/** * 錄音聲波監(jiān)控定制器 * * @return 定時(shí)器 */-(NSTimer *)timer{ if (!_timer) { _timer=[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(audioPowerChange) userInfo:nil repeats:YES]; } return _timer;}/** * 錄音聲波狀態(tài)設(shè)置 */-(void)audioPowerChange{ [self.audioRecorder updateMeters];//更新測量值 float power= [self.audioRecorder averagePowerForChannel:0];//取得第一個(gè)通道的音頻,注意音頻強(qiáng)度范圍時(shí)-160到0 CGFloat progress=(1.0/160.0)*(power+160.0); [self.audioPower setProgress:progress];}#pragma mark - UI事件/** * 點(diǎn)擊錄音按鈕 * * @param sender 錄音按鈕 */- (IBAction)recordClick:(UIButton *)sender { if (![self.audioRecorder isRecording]) { [self.audioRecorder record];//首次使用應(yīng)用時(shí)如果調(diào)用record方法會(huì)詢問用戶是否允許使用麥克風(fēng) self.timer.fireDate=[NSDate distantPast]; }}/** * 點(diǎn)擊暫定按鈕 * * @param sender 暫停按鈕 */- (IBAction)pauseClick:(UIButton *)sender { if ([self.audioRecorder isRecording]) { [self.audioRecorder pause]; self.timer.fireDate=[NSDate distantFuture]; }}/** * 點(diǎn)擊恢復(fù)按鈕 * 恢復(fù)錄音只需要再次調(diào)用record,AVAudioSession會(huì)幫助你記錄上次錄音位置并追加錄音 * * @param sender 恢復(fù)按鈕 */- (IBAction)resumeClick:(UIButton *)sender { [self recordClick:sender];}/** * 點(diǎn)擊停止按鈕 * * @param sender 停止按鈕 */- (IBAction)stopClick:(UIButton *)sender { [self.audioRecorder stop]; self.timer.fireDate=[NSDate distantFuture]; self.audioPower.progress=0.0;}#pragma mark - 錄音機(jī)代理方法/** * 錄音完成,錄音完成后播放錄音 * * @param recorder 錄音機(jī)對象 * @param flag 是否成功 */-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{ if (![self.audioPlayer isPlaying]) { [self.audioPlayer play]; } NSLog(@"錄音完成!");}@end
運(yùn)行效果:
[](http://images.cnitblog.com/blog/62046/201412/260913026556014.gif)
音頻隊(duì)列服務(wù)
大家應(yīng)該已經(jīng)注意到了,無論是前面的錄音還是音頻播放均不支持網(wǎng)絡(luò)流媒體播放,當(dāng)然對于錄音來說這種需求可能不大,但是對于音頻播放來說有時(shí)候就很有必要了。AVAudioPlayer只能播放本地文件,并且是一次性加載所以音頻數(shù)據(jù),初始化AVAudioPlayer時(shí)指定的URL也只能是File URL而不能是HTTP URL。當(dāng)然,將音頻文件下載到本地然后再調(diào)用AVAudioPlayer來播放也是一種播放網(wǎng)絡(luò)音頻的辦法,但是這種方式最大的弊端就是必須等到整個(gè)音頻播放完成才能播放,而不能使用流式播放,這往往在實(shí)際開發(fā)中是不切實(shí)際的。那么在iOS中如何播放網(wǎng)絡(luò)流媒體呢?就是使用AudioToolbox框架中的音頻隊(duì)列服務(wù)Audio Queue Services。
使用音頻隊(duì)列服務(wù)完全可以做到音頻播放和錄制,首先看一下錄音音頻服務(wù)隊(duì)列:
[](http://images.cnitblog.com/blog/62046/201412/260913047965925.png)
一個(gè)音頻服務(wù)隊(duì)列Audio Queue有三部分組成:
三個(gè)緩沖器Buffers:每個(gè)緩沖器都是一個(gè)存儲(chǔ)音頻數(shù)據(jù)的臨時(shí)倉庫。
一個(gè)緩沖隊(duì)列Buffer Queue:一個(gè)包含音頻緩沖器的有序隊(duì)列。
一個(gè)回調(diào)Callback:一個(gè)自定義的隊(duì)列回調(diào)函數(shù)。
聲音通過輸入設(shè)備進(jìn)入緩沖隊(duì)列中,首先填充第一個(gè)緩沖器;當(dāng)?shù)谝粋€(gè)緩沖器填充滿之后自動(dòng)填充下一個(gè)緩沖器,同時(shí)會(huì)調(diào)用回調(diào)函數(shù);在回調(diào)函數(shù)中需要將緩沖器中的音頻數(shù)據(jù)寫入磁盤,同時(shí)將緩沖器放回到緩沖隊(duì)列中以便重用。下面是Apple官方關(guān)于音頻隊(duì)列服務(wù)的流程示意圖:
[](http://images.cnitblog.com/blog/62046/201412/260913060773669.png)
類似的,看一下音頻播放緩沖隊(duì)列,其組成部分和錄音緩沖隊(duì)列類似。
[](http://images.cnitblog.com/blog/62046/201412/260913092187408.png)
但是在音頻播放緩沖隊(duì)列中,回調(diào)函數(shù)調(diào)用的時(shí)機(jī)不同于音頻錄制緩沖隊(duì)列,流程剛好相反。將音頻讀取到緩沖器中,一旦一個(gè)緩沖器填充滿之后就放到緩沖隊(duì)列中,然后繼續(xù)填充其他緩沖器;當(dāng)開始播放時(shí),則從第一個(gè)緩沖器中讀取音頻進(jìn)行播放;一旦播放完之后就會(huì)觸發(fā)回調(diào)函數(shù),開始播放下一個(gè)緩沖器中的音頻,同時(shí)填充第一個(gè)緩沖器放;填充滿之后再次放回到緩沖隊(duì)列。下面是詳細(xì)的流程:
[](http://images.cnitblog.com/blog/62046/201412/260913105306908.png)
當(dāng)然,要明白音頻隊(duì)列服務(wù)的原理并不難,問題是如何實(shí)現(xiàn)這個(gè)自定義的回調(diào)函數(shù),這其中我們有大量的工作要做,控制播放狀態(tài)、處理異常中斷、進(jìn)行音頻編碼等等。由于牽扯內(nèi)容過多,而且不是本文目的,如果以后有時(shí)間將另開一篇文章重點(diǎn)介紹,目前有很多第三方優(yōu)秀框架可以直接使用,例如[AudioStreamer](https://github.com/mattgallagher/AudioStreamer)、[FreeStreamer](https://github.com/muhku/FreeStreamer)。由于前者當(dāng)前只有非ARC版本,所以下面不妨使用FreeStreamer來簡單演示在線音頻播放的過程,當(dāng)然在使用之前要做如下準(zhǔn)備工作:
1.拷貝FreeStreamer中的Reachability.h、Reachability.m和Common、astreamer兩個(gè)文件夾中的內(nèi)容到項(xiàng)目中。
2.添加FreeStreamer使用的類庫:CFNetwork.framework、AudioToolbox.framework、AVFoundation.framework、libxml2.dylib、MediaPlayer.framework。
3.如果引用libxml2.dylib編譯不通過,需要在Xcode的Targets-Build Settings-Header Build Path中添加**$(SDKROOT)/usr/include/libxml2。**
4.將FreeStreamer中的FreeStreamerMobile-Prefix.pch文件添加到項(xiàng)目中并將**Targets-Build Settings-Precompile Prefix Header**設(shè)置為**YES**,在**Targets-Build Settings-Prefix Header**設(shè)置為**$(SRCROOT)/項(xiàng)目名稱/FreeStreamerMobile-Prefix.pch**(因?yàn)閄code6默認(rèn)沒有pch文件)
然后就可以編寫代碼播放網(wǎng)絡(luò)音頻了:
//// ViewController.m// AudioQueueServices//// Created by Kenshin Cui on 14/03/30.// Copyright (c) 2014年 cmjstudio. All rights reserved.// 使用FreeStreamer實(shí)現(xiàn)網(wǎng)絡(luò)音頻播放#import "ViewController.h"#import "FSAudioStream.h"@interface ViewController ()@property (nonatomic,strong) FSAudioStream *audioStream;@end@implementation ViewController- (void)viewDidLoad { [super viewDidLoad]; [self.audioStream play];}/** * 取得本地文件路徑 * * @return 文件路徑 */-(NSURL *)getFileUrl{ NSString *urlStr=[[NSBundle mainBundle]pathForResource:@"劉若英 - 原來你也在這里.mp3" ofType:nil]; NSURL *url=[NSURL fileURLWithPath:urlStr]; return url;}-(NSURL *)getNetworkUrl{ NSString *urlStr=@"http://192.168.1.102/liu.mp3"; NSURL *url=[NSURL URLWithString:urlStr]; return url;}/** * 創(chuàng)建FSAudioStream對象 * * @return FSAudioStream對象 */-(FSAudioStream *)audioStream{ if (!_audioStream) { NSURL *url=[self getNetworkUrl]; //創(chuàng)建FSAudioStream對象 _audioStream=[[FSAudioStream alloc]initWithUrl:url]; _audioStream.onFailure=^(FSAudioStreamError error,NSString *description){ NSLog(@"播放過程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",description); }; _audioStream.onCompletion=^(){ NSLog(@"播放完成!"); }; [_audioStream setVolume:0.5];//設(shè)置聲音 } return _audioStream;}@end
其實(shí)FreeStreamer的功能很強(qiáng)大,不僅僅是播放本地、網(wǎng)絡(luò)音頻那么簡單,它還支持播放列表、檢查包內(nèi)容、RSS訂閱、播放中斷等很多強(qiáng)大的功能,甚至還包含了一個(gè)音頻分析器,有興趣的朋友可以訪問[官網(wǎng)](http://freestreamer.io/)查看詳細(xì)用法