iOS開(kāi)發(fā)系列--音頻

在iOS中音頻播放從形式上可以分為音效播放和音樂(lè)播放喻频。前者主要指的是一些短音頻播放,通常作為點(diǎn)綴音頻褒颈,對(duì)于這類音頻不需要進(jìn)行進(jìn)度、循環(huán)等控制励堡。后者指的是一些較長(zhǎng)的音頻谷丸,通常是主音頻,對(duì)于這些音頻的播放通常需要進(jìn)行精確的控制应结。在iOS中播放兩類音頻分別使用AudioToolbox.framework和AVFoundation.framework來(lái)完成音效和音樂(lè)播放刨疼。
***音效
AudioToolbox.framework是一套基于C語(yǔ)言的框架,使用它來(lái)播放音效其本質(zhì)是將短音頻注冊(cè)到系統(tǒng)聲音服務(wù)(System Sound Service)鹅龄。System Sound Service是一種簡(jiǎn)單揩慕、底層的聲音播放服務(wù),但是它本身也存在著一些限制:

音頻播放時(shí)間不能超過(guò)30s
數(shù)據(jù)必須是PCM或者IMA4格式
音頻文件必須打包成.caf扮休、.aif迎卤、.wav中的一種(注意這是官方文檔的說(shuō)法,實(shí)際測(cè)試發(fā)現(xiàn)一些.mp3也可以播放)
使用System Sound Service 播放音效的步驟如下:

1.調(diào)用AudioServicesCreateSystemSoundID( CFURLRef inFileURL, SystemSoundID* outSystemSoundID)函數(shù)獲得系統(tǒng)聲音ID玷坠。
2.如果需要監(jiān)聽(tīng)播放完成操作蜗搔,則使用AudioServicesAddSystemSoundCompletion( SystemSoundID inSystemSoundID,
CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void* inClientData)方法注冊(cè)回調(diào)函數(shù)。
3.調(diào)用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID) 或者AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID) 方法播放音效(后者帶有震動(dòng)效果)侨糟。
下面是一個(gè)簡(jiǎn)單的示例程序:

//
//  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è)長(zhǎng)整形ID)
     */
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
    //如果需要在播放完之后執(zhí)行某些操作碍扔,可以調(diào)用如下方法注冊(cè)一個(gè)播放完成回調(diào)函數(shù)
    AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
    //2.播放音頻
    AudioServicesPlaySystemSound(soundID);//播放音效
//    AudioServicesPlayAlertSound(soundID);//播放音效并震動(dòng)
}

@end

音樂(lè)
如果播放較大的音頻或者要對(duì)音頻有精確的控制則System Sound Service可能就很難滿足實(shí)際需求了,通常這種情況會(huì)選擇使用AVFoundation.framework中的AVAudioPlayer來(lái)實(shí)現(xiàn)秕重。AVAudioPlayer可以看成一個(gè)播放器不同,它支持多種音頻格式,而且能夠進(jìn)行進(jìn)度溶耘、音量二拐、播放速度等控制。首先簡(jiǎn)單看一下AVAudioPlayer常用的屬性和方法:

屏幕快照 2017-06-29 下午4.25.19.png
屏幕快照 2017-06-29 下午4.25.35.png

AVAudioPlayer的使用比較簡(jiǎn)單:

1.初始化AVAudioPlayer對(duì)象凳兵,此時(shí)通常指定本地文件路徑百新。
2.設(shè)置播放器屬性,例如重復(fù)次數(shù)庐扫、音量大小等饭望。
3.調(diào)用play方法播放仗哨。
下面就使用AVAudioPlayer實(shí)現(xiàn)一個(gè)簡(jiǎn)單播放器,在這個(gè)播放器中實(shí)現(xiàn)了播放铅辞、暫停厌漂、顯示播放進(jìn)度功能,當(dāng)然例如調(diào)節(jié)音量斟珊、設(shè)置循環(huán)模式苇倡、甚至是聲波圖像(通過(guò)分析音頻分貝值)等功能都可以實(shí)現(xiàn),這里就不再一一演示囤踩。界面效果如下:

屏幕快照 2017-06-29 下午4.26.30.png

當(dāng)然由于AVAudioPlayer一次只能播放一個(gè)音頻文件旨椒,所有上一曲、下一曲其實(shí)可以通過(guò)創(chuàng)建多個(gè)播放器對(duì)象來(lái)完成堵漱,這里暫不實(shí)現(xiàn)综慎。播放進(jìn)度的實(shí)現(xiàn)主要依靠一個(gè)定時(shí)器實(shí)時(shí)計(jì)算當(dāng)前播放時(shí)長(zhǎng)和音頻總時(shí)長(zhǎng)的比例,另外為了演示委托方法怔锌,下面的代碼中也實(shí)現(xiàn)了播放完成委托方法寥粹,通常如果有下一曲功能的話播放完可以觸發(fā)下一曲音樂(lè)播放变过。下面是主要代碼:

//
//  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 @"劉若英 - 原來(lái)你也在這里.mp3"
#define kMusicSinger @"劉若英"
#define kMusicTitle @"原來(lái)你也在這里"

@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(@"初始化播放器過(guò)程發(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ì)取消崭孤,之后無(wú)法恢復(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(@"音樂(lè)播放完成...");
}

@end

運(yùn)行效果:

屏幕快照 2017-06-29 下午4.27.41.png

音頻會(huì)話
事實(shí)上上面的播放器還存在一些問(wèn)題类嗤,例如通常我們看到的播放器即使退出到后臺(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í)可以直接通過(guò)Xcode在Project Targets-Capabilities-Background Modes中設(shè)置)


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)程控制事件(這一步不是后臺(tái)播放必須的)
前兩步是后臺(tái)播放所必須設(shè)置的精偿,第三步主要用于接收遠(yuǎn)程事件,這部分內(nèi)容之前的文章中有詳細(xì)介紹赋兵,如果這一步不設(shè)置雖讓也能夠在后臺(tái)播放笔咽,但是無(wú)法獲得音頻控制權(quán)(如果在使用當(dāng)前應(yīng)用之前使用其他播放器播放音樂(lè)的話,此時(shí)如果按耳機(jī)播放鍵或者控制中心的播放按鈕則會(huì)播放前一個(gè)應(yīng)用的音頻)霹期,并且不能使用耳機(jī)進(jìn)行音頻控制叶组。第一步操作相信大家都很容易理解,如果應(yīng)用程序要允許運(yùn)行到后臺(tái)必須設(shè)置历造,正常情況下應(yīng)用如果進(jìn)入后臺(tái)會(huì)被掛起甩十,通過(guò)該設(shè)置可以上應(yīng)用程序繼續(xù)在后臺(tái)運(yùn)行船庇。但是第二步使用的AVAudioSession有必要進(jìn)行一下詳細(xì)的說(shuō)明。
在iOS中每個(gè)應(yīng)用都有一個(gè)音頻會(huì)話侣监,這個(gè)會(huì)話就通過(guò)AVAudioSession來(lái)表示溢十。AVAudioSession同樣存在于AVFoundation框架中,它是單例模式設(shè)計(jì)达吞,通過(guò)sharedInstance進(jìn)行訪問(wèn)张弛。在使用Apple設(shè)備時(shí)大家會(huì)發(fā)現(xiàn)有些應(yīng)用只要打開(kāi)其他音頻播放就會(huì)終止,而有些應(yīng)用卻可以和其他應(yīng)用同時(shí)播放酪劫,在多種音頻環(huán)境中如何去控制播放的方式就是通過(guò)音頻會(huì)話來(lái)完成的吞鸭。下面是音頻會(huì)話的幾種會(huì)話模式:

屏幕快照 2017-06-29 下午4.29.33.png

注意:是否遵循靜音鍵表示在播放過(guò)程中如果用戶通過(guò)硬件設(shè)置為靜音是否能關(guān)閉聲音。

根據(jù)前面對(duì)音頻會(huì)話的理解覆糟,相信大家開(kāi)發(fā)出能夠在后臺(tái)播放的音頻播放器并不難刻剥,但是注意一下,在前面的代碼中也提到設(shè)置完音頻會(huì)話類型之后需要調(diào)用setActive::方法將會(huì)話激活才能起作用滩字。類似的造虏,如果一個(gè)應(yīng)用已經(jīng)在播放音頻,打開(kāi)我們的應(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 @"劉若英 - 原來(lái)你也在這里.mp3"
#define kMusicSinger @"劉若英"
#define kMusicTitle @"原來(lái)你也在這里"

@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í)注冊(cè)遠(yuǎn)程事件
 *
 *  @param animated 是否以動(dòng)畫的形式顯示
 */
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //開(kāi)啟遠(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(@"初始化播放器過(guò)程發(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ì)取消渠啤,之后無(wú)法恢復(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 輸出改變通知對(duì)象
 */
-(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(@"音樂(lè)播放完成...");
    //根據(jù)實(shí)際情況播放完成可以將會(huì)話關(guān)閉狐肢,其他音頻應(yīng)用繼續(xù)播放
    [[AVAudioSession sharedInstance]setActive:NO error:nil];
}

@end

在上面的代碼中還實(shí)現(xiàn)了拔出耳機(jī)暫停音樂(lè)播放的功能,這也是一個(gè)比較常見(jiàn)的功能沥曹。在iOS7及以后的版本中可以通過(guò)通知獲得輸出改變的通知份名,然后拿到通知對(duì)象后根據(jù)userInfo獲得是何種改變類型,進(jìn)而根據(jù)情況對(duì)音樂(lè)進(jìn)行暫停操作架专。

擴(kuò)展--播放音樂(lè)庫(kù)中的音樂(lè)

眾所周知音樂(lè)是iOS的重要組成播放同窘,無(wú)論是iPod、iTouch部脚、iPhone還是iPad都可以在iTunes購(gòu)買音樂(lè)或添加本地音樂(lè)到音樂(lè)庫(kù)中同步到你的iOS設(shè)備想邦。在MediaPlayer.frameowork中有一個(gè)MPMusicPlayerController用于播放音樂(lè)庫(kù)中的音樂(lè)。

下面先來(lái)看一下MPMusicPlayerController的常用屬性和方法:

屏幕快照 2017-06-29 下午4.30.47.png
屏幕快照 2017-06-29 下午4.31.27.png

MPMusicPlayerController有兩種播放器:applicationMusicPlayer和systemMusicPlayer委刘,前者在應(yīng)用退出后音樂(lè)播放會(huì)自動(dòng)停止丧没,后者在應(yīng)用停止后不會(huì)退出播放狀態(tài)鹰椒。
MPMusicPlayerController加載音樂(lè)不同于前面的AVAudioPlayer是通過(guò)一個(gè)文件路徑來(lái)加載,而是需要一個(gè)播放隊(duì)列呕童。在MPMusicPlayerController中提供了兩個(gè)方法來(lái)加載播放隊(duì)列:- (void)setQueueWithQuery:(MPMediaQuery *)query和- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection漆际,正是由于它的播放音頻來(lái)源是一個(gè)隊(duì)列,因此MPMusicPlayerController支持上一曲夺饲、下一曲等操作奸汇。
那么接下來(lái)的問(wèn)題就是如何獲取MPMediaQueue或者M(jìn)PMediaItemCollection?MPMediaQueue對(duì)象有一系列的類方法來(lái)獲得媒體隊(duì)列:

  • (MPMediaQuery *)albumsQuery;
  • (MPMediaQuery *)artistsQuery;
  • (MPMediaQuery *)songsQuery;
  • (MPMediaQuery *)playlistsQuery;
  • (MPMediaQuery *)podcastsQuery;
  • (MPMediaQuery *)audiobooksQuery;
  • (MPMediaQuery *)compilationsQuery;
  • (MPMediaQuery *)composersQuery;
  • (MPMediaQuery *)genresQuery;

有了這些方法往声,就可以很容易獲到歌曲擂找、播放列表、專輯媒體等媒體隊(duì)列了浩销,這樣就可以通過(guò):- (void)setQueueWithQuery:(MPMediaQuery *)query方法設(shè)置音樂(lè)來(lái)源了贯涎。又或者得到MPMediaQueue之后創(chuàng)建MPMediaItemCollection,使用- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection設(shè)置音樂(lè)來(lái)源慢洋。

有時(shí)候可能希望用戶自己來(lái)選擇要播放的音樂(lè)塘雳,這時(shí)可以使用MPMediaPickerController,它是一個(gè)視圖控制器普筹,類似于UIImagePickerController败明,選擇完播放來(lái)源后可以在其代理方法中獲得MPMediaItemCollection對(duì)象。

無(wú)論是通過(guò)哪種方式獲得MPMusicPlayerController的媒體源斑芜,可能都希望將每個(gè)媒體的信息顯示出來(lái)肩刃,這時(shí)候可以通過(guò)MPMediaItem對(duì)象獲得。一個(gè)MPMediaItem代表一個(gè)媒體文件杏头,通過(guò)它可以訪問(wèn)媒體標(biāo)題、專輯名稱沸呐、專輯封面醇王、音樂(lè)時(shí)長(zhǎng)等等。無(wú)論是MPMediaQueue還是MPMediaItemCollection都有一個(gè)items屬性崭添,它是MPMediaItem數(shù)組寓娩,通過(guò)這個(gè)屬性可以獲得MPMediaItem對(duì)象。

下面就簡(jiǎn)單看一下MPMusicPlayerController的使用呼渣,在下面的例子中簡(jiǎn)單演示了音樂(lè)的選擇棘伴、播放、暫停屁置、通知焊夸、下一曲、上一曲功能蓝角,相信有了上面的概念阱穗,代碼讀起來(lái)并不復(fù)雜(示例中是直接通過(guò)MPMeidaPicker進(jìn)行音樂(lè)選擇的饭冬,但是仍然提供了兩個(gè)方法getLocalMediaQuery和getLocalMediaItemCollection來(lái)演示如何直接通過(guò)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; //音樂(lè)播放器

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

-(void)dealloc{
    [self.musicPlayer endGeneratingPlaybackNotifications];
}

/**
 *  獲得音樂(lè)播放器
 *
 *  @return 音樂(lè)播放器
 */
-(MPMusicPlayerController *)musicPlayer{
    if (!_musicPlayer) {
        _musicPlayer=[MPMusicPlayerController systemMusicPlayer];
        [_musicPlayer beginGeneratingPlaybackNotifications];//開(kāi)啟通知,否則監(jiān)控不到MPMusicPlayerController的通知
        [self addNotification];//添加通知
        //如果不使用MPMediaPickerController可以使用如下方法獲得音樂(lè)庫(kù)媒體隊(duì)列
        //[_musicPlayer setQueueWithItemCollection:[self getLocalMediaItemCollection]];
    }
    return _musicPlayer;
}

/**
 *  創(chuàng)建媒體選擇器
 *
 *  @return 媒體選擇器
 */
-(MPMediaPickerController *)mediaPicker{
    if (!_mediaPicker) {
        //初始化媒體選擇器揪阶,這里設(shè)置媒體類型為音樂(lè)昌抠,其實(shí)這里也可以選擇視頻、廣播等
//        _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeMusic];
        _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeAny];
        _mediaPicker.allowsPickingMultipleItems=YES;//允許多選
//        _mediaPicker.showsCloudItems=YES;//顯示icloud選項(xiàng)
        _mediaPicker.prompt=@"請(qǐng)選擇要播放的音樂(lè)";
        _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è)播放音樂(lè)
    //注意很多音樂(lè)信息如標(biāo)題鲁僚、專輯炊苫、表演者、封面冰沙、時(shí)長(zhǎng)等信息都可以通過(guò)MPMediaItem的valueForKey:方法得到,但是從iOS7開(kāi)始都有對(duì)應(yīng)的屬性可以直接訪問(wèn)
//    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 通知對(duì)象
 */
-(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

錄音
除了上面說(shuō)的劝评,在AVFoundation框架中還要一個(gè)AVAudioRecorder類專門處理錄音操作,它同樣支持多種音頻格式倦淀。與AVAudioPlayer類似蒋畜,你完全可以將它看成是一個(gè)錄音機(jī)控制類,下面是常用的屬性和方法:

屏幕快照 2017-06-29 下午4.34.39.png

屏幕快照 2017-06-29 下午4.34.49.png

AVAudioRecorder很多屬性和方法跟AVAudioPlayer都是類似的,但是它的創(chuàng)建有所不同撞叽,在創(chuàng)建錄音機(jī)時(shí)除了指定路徑外還必須指定錄音設(shè)置信息姻成,因?yàn)殇浺魴C(jī)必須知道錄音文件的格式、采樣率愿棋、通道數(shù)科展、每個(gè)采樣點(diǎn)的位數(shù)等信息,但是也并不是所有的信息都必須設(shè)置糠雨,通常只需要幾個(gè)常用設(shè)置才睹。關(guān)于錄音設(shè)置詳見(jiàn)幫助文檔中的“AV Foundation Audio Settings Constants”。
下面就使用AVAudioRecorder創(chuàng)建一個(gè)錄音機(jī)甘邀,實(shí)現(xiàn)了錄音琅攘、暫停、停止松邪、播放等功能坞琴,實(shí)現(xiàn)效果大致如下:
屏幕快照 2017-06-29 下午4.35.34.png

在這個(gè)示例中將實(shí)行一個(gè)完整的錄音控制,包括錄音逗抑、暫停剧辐、恢復(fù)、停止邮府,同時(shí)還會(huì)實(shí)時(shí)展示用戶錄音的聲音波動(dòng)荧关,當(dāng)用戶點(diǎn)擊完停止按鈕還會(huì)自動(dòng)播放錄音文件。程序的構(gòu)建主要分為以下幾步:

1.設(shè)置音頻會(huì)話類型為AVAudioSessionCategoryPlayAndRecord褂傀,因?yàn)槌绦蛑袪砍兜戒浺艉筒シ挪僮鳌?br> 2.創(chuàng)建錄音機(jī)AVAudioRecorder忍啤,指定錄音保存的路徑并且設(shè)置錄音屬性,注意對(duì)于一般的錄音文件要求的采樣率紊服、位數(shù)并不高檀轨,需要適當(dāng)設(shè)置以保證錄音文件的大小和效果胸竞。
3.設(shè)置錄音機(jī)代理以便在錄音完成后播放錄音,打開(kāi)錄音測(cè)量保證能夠?qū)崟r(shí)獲得錄音時(shí)的聲音強(qiáng)度参萄。(注意聲音強(qiáng)度范圍-160到0,0代表最大輸入)
4.創(chuàng)建音頻播放器AVAudioPlayer卫枝,用于在錄音完成之后播放錄音。
5.創(chuàng)建一個(gè)定時(shí)器以便實(shí)時(shí)刷新錄音測(cè)量值并更新錄音強(qiáng)度到UIProgressView中顯示讹挎。
6.添加錄音校赤、暫停、恢復(fù)筒溃、停止操作马篮,需要注意錄音的恢復(fù)操作其實(shí)是有音頻會(huì)話管理的,恢復(fù)時(shí)只要再次調(diào)用record方法即可怜奖,無(wú)需手動(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í)不對(duì)播放進(jìn)行監(jiān)控)

@property (weak, nonatomic) IBOutlet UIButton *record;//開(kāi)始錄音
@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是電話采樣率,對(duì)于一般錄音已經(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ī)對(duì)象
 *
 *  @return 錄音機(jī)對(duì)象
 */
-(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ī)對(duì)象時(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)建播放器過(guò)程中發(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];//更新測(cè)量值
    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ì)詢問(wèn)用戶是否允許使用麥克風(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ī)對(duì)象
 *  @param flag     是否成功
 */
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
    if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer play];
    }
    NSLog(@"錄音完成!");
}

@end

運(yùn)行效果:


屏幕快照 2017-06-29 下午4.36.59.png

音頻隊(duì)列服務(wù)
大家應(yīng)該已經(jīng)注意到了,無(wú)論是前面的錄音還是音頻播放均不支持網(wǎng)絡(luò)流媒體播放慌烧,當(dāng)然對(duì)于錄音來(lái)說(shuō)這種需求可能不大逐抑,但是對(duì)于音頻播放來(lái)說(shuō)有時(shí)候就很有必要了。AVAudioPlayer只能播放本地文件屹蚊,并且是一次性加載所以音頻數(shù)據(jù),初始化AVAudioPlayer時(shí)指定的URL也只能是File URL而不能是HTTP URL进每。當(dāng)然汹粤,將音頻文件下載到本地然后再調(diào)用AVAudioPlayer來(lái)播放也是一種播放網(wǎng)絡(luò)音頻的辦法,但是這種方式最大的弊端就是必須等到整個(gè)音頻播放完成才能播放田晚,而不能使用流式播放嘱兼,這往往在實(shí)際開(kāi)發(fā)中是不切實(shí)際的。那么在iOS中如何播放網(wǎng)絡(luò)流媒體呢贤徒?就是使用AudioToolbox框架中的音頻隊(duì)列服務(wù)Audio Queue Services芹壕。

使用音頻隊(duì)列服務(wù)完全可以做到音頻播放和錄制汇四,首先看一下錄音音頻服務(wù)隊(duì)列:


屏幕快照 2017-06-29 下午4.37.31.png

一個(gè)音頻服務(wù)隊(duì)列Audio Queue有三部分組成:

三個(gè)緩沖器Buffers:每個(gè)緩沖器都是一個(gè)存儲(chǔ)音頻數(shù)據(jù)的臨時(shí)倉(cāng)庫(kù)。

一個(gè)緩沖隊(duì)列Buffer Queue:一個(gè)包含音頻緩沖器的有序隊(duì)列踢涌。

一個(gè)回調(diào)Callback:一個(gè)自定義的隊(duì)列回調(diào)函數(shù)通孽。

聲音通過(guò)輸入設(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ù)的流程示意圖:


屏幕快照 2017-06-29 下午4.38.01.png

類似的,看一下音頻播放緩沖隊(duì)列钳降,其組成部分和錄音緩沖隊(duì)列類似厚宰。


屏幕快照 2017-06-29 下午4.38.51.png

但是在音頻播放緩沖隊(duì)列中,回調(diào)函數(shù)調(diào)用的時(shí)機(jī)不同于音頻錄制緩沖隊(duì)列遂填,流程剛好相反铲觉。將音頻讀取到緩沖器中,一旦一個(gè)緩沖器填充滿之后就放到緩沖隊(duì)列中城菊,然后繼續(xù)填充其他緩沖器备燃;當(dāng)開(kāi)始播放時(shí),則從第一個(gè)緩沖器中讀取音頻進(jìn)行播放凌唬;一旦播放完之后就會(huì)觸發(fā)回調(diào)函數(shù)建车,開(kāi)始播放下一個(gè)緩沖器中的音頻,同時(shí)填充第一個(gè)緩沖器放游添;填充滿之后再次放回到緩沖隊(duì)列蹈胡。下面是詳細(xì)的流程:
屏幕快照 2017-06-29 下午4.39.24.png

當(dāng)然,要明白音頻隊(duì)列服務(wù)的原理并不難更耻,問(wèn)題是如何實(shí)現(xiàn)這個(gè)自定義的回調(diào)函數(shù)测垛,這其中我們有大量的工作要做,控制播放狀態(tài)秧均、處理異常中斷食侮、進(jìn)行音頻編碼等等。由于牽扯內(nèi)容過(guò)多目胡,而且不是本文目的锯七,如果以后有時(shí)間將另開(kāi)一篇文章重點(diǎn)介紹,目前有很多第三方優(yōu)秀框架可以直接使用誉己,例如AudioStreamer眉尸、FreeStreamer。由于前者當(dāng)前只有非ARC版本,所以下面不妨使用FreeStreamer來(lái)簡(jiǎn)單演示在線音頻播放的過(guò)程噪猾,當(dāng)然在使用之前要做如下準(zhǔn)備工作:
1.拷貝FreeStreamer中的Reachability.h霉祸、Reachability.m和Common、astreamer兩個(gè)文件夾中的內(nèi)容到項(xiàng)目中袱蜡。
2.添加FreeStreamer使用的類庫(kù):CFNetwork.framework丝蹭、AudioToolbox.framework、AVFoundation.framework戒劫、libxml2.dylib半夷、MediaPlayer.framework。
3.如果引用libxml2.dylib編譯不通過(guò)迅细,需要在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)沒(méi)有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:@"劉若英 - 原來(lái)你也在這里.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對(duì)象
 *
 *  @return FSAudioStream對(duì)象
 */
-(FSAudioStream *)audioStream{
    if (!_audioStream) {
        NSURL *url=[self getNetworkUrl];
        //創(chuàng)建FSAudioStream對(duì)象
        _audioStream=[[FSAudioStream alloc]initWithUrl:url];
        _audioStream.onFailure=^(FSAudioStreamError error,NSString *description){
            NSLog(@"播放過(guò)程中發(fā)生錯(cuò)誤茵典,錯(cuò)誤信息:%@",description);
        };
        _audioStream.onCompletion=^(){
            NSLog(@"播放完成!");
        };
        [_audioStream setVolume:0.5];//設(shè)置聲音
    }
    return _audioStream;
}

@end

其實(shí)FreeStreamer的功能很強(qiáng)大湘换,不僅僅是播放本地、網(wǎng)絡(luò)音頻那么簡(jiǎn)單统阿,它還支持播放列表彩倚、檢查包內(nèi)容、RSS訂閱扶平、播放中斷等很多強(qiáng)大的功能帆离,甚至還包含了一個(gè)音頻分析器,有興趣的朋友可以訪問(wèn)官網(wǎng)查看詳細(xì)用法
原著網(wǎng)址:http://www.cnblogs.com/kenshincui/p/4186022.html#soundEffect

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末结澄,一起剝皮案震驚了整個(gè)濱河市哥谷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌麻献,老刑警劉巖们妥,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異勉吻,居然都是意外死亡监婶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門齿桃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)惑惶,“玉大人,你說(shuō)我怎么就攤上這事短纵〖铮” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵踩娘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)养渴,這世上最難降的妖魔是什么雷绢? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮理卑,結(jié)果婚禮上翘紊,老公的妹妹穿的比我還像新娘。我一直安慰自己藐唠,他們只是感情好帆疟,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著宇立,像睡著了一般踪宠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妈嘹,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天柳琢,我揣著相機(jī)與錄音,去河邊找鬼润脸。 笑死柬脸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毙驯。 我是一名探鬼主播倒堕,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼爆价!你這毒婦竟也來(lái)了垦巴?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤允坚,失蹤者是張志新(化名)和其女友劉穎魂那,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體稠项,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡涯雅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了展运。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片活逆。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖拗胜,靈堂內(nèi)的尸體忽然破棺而出蔗候,到底是詐尸還是另有隱情,我是刑警寧澤埂软,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布锈遥,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏所灸。R本人自食惡果不足惜丽惶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望爬立。 院中可真熱鬧钾唬,春花似錦、人聲如沸侠驯。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)吟策。三九已至儒士,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間踊挠,已是汗流浹背乍桂。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留效床,地道東北人睹酌。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像剩檀,于是被迫代替她去往敵國(guó)和親憋沿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容