概覽
隨著移動(dòng)互聯(lián)網(wǎng)的發(fā)展,如今的手機(jī)早已不是打電話衅斩、發(fā)短信那么簡單了盆顾,播放音樂、視頻畏梆、錄音您宪、拍照等都是很常用的功能奈懒。在iOS中對于多媒體的支持是非常強(qiáng)大的,無論是音視頻播放宪巨、錄制磷杏,還是對麥克風(fēng)、攝像頭的操作都提供了多套API揖铜,在今天的文章中將會(huì)對這些內(nèi)容進(jìn)行一一介紹茴丰。
音頻
在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
播放音效的步驟如下:
1锣笨、調(diào)用AudioServicesCreateSystemSoundID
( CFURLRef inFileURL, SystemSoundID* outSystemSoundID
)函數(shù)獲得系統(tǒng)聲音ID蝌矛。
2、如果需要監(jiān)聽播放完成操作错英,則使用AudioServicesAddSystemSoundCompletion
( SystemSoundID inSystemSoundID, CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void* inClientData
)方法注冊回調(diào)函數(shù)入撒。
3、調(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è)播放器献雅,它支持多種音頻格式,而且能夠進(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
的使用比較簡單:
1邪蛔、初始化AVAudioPlayer
對象急黎,此時(shí)通常指定本地文件路徑。
2侧到、設(shè)置播放器屬性勃教,例如重復(fù)次數(shù)、音量大小等匠抗。
3故源、調(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),這里就不再一一演示奶是。界面效果如下:
當(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)行效果:
音頻會(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è)置)
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)播放洒琢,但是無法獲得音頻控制權(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
擴(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
或者MPMediaItemCollection
蝉衣?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
類專門處理錄音操作俐镐,它同樣支持多種音頻格式矫限。與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”。
下面就使用AVAudioRecorder
創(chuàng)建一個(gè)錄音機(jī)吆豹,實(shí)現(xiàn)了錄音鱼的、暫停、停止痘煤、播放等功能鸳吸,實(shí)現(xiàn)效果大致如下:
在這個(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è)置錄音屬性猬仁,注意對于一般的錄音文件要求的采樣率帝璧、位數(shù)并不高,需要適當(dāng)設(shè)置以保證錄音文件的大小和效果湿刽。
3的烁、設(shè)置錄音機(jī)代理以便在錄音完成后播放錄音,打開錄音測量保證能夠?qū)崟r(shí)獲得錄音時(shí)的聲音強(qiáng)度诈闺。(注意聲音強(qiáng)度范圍-160到0,0代表最大輸入)
4渴庆、創(chuàng)建音頻播放器AVAudioPlayer
,用于在錄音完成之后播放錄音雅镊。
5襟雷、創(chuàng)建一個(gè)定時(shí)器以便實(shí)時(shí)刷新錄音測量值并更新錄音強(qiáng)度到UIProgressView中顯示。
6仁烹、添加錄音耸弄、暫停、恢復(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
音頻隊(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ì)列:
一個(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ù)的流程示意圖:
類似的撒犀,看一下音頻播放緩沖隊(duì)列声滥,其組成部分和錄音緩沖隊(duì)列類似副渴。
但是在音頻播放緩沖隊(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ì)的流程:
當(dāng)然玩荠,要明白音頻隊(duì)列服務(wù)的原理并不難,問題是如何實(shí)現(xiàn)這個(gè)自定義的回調(diào)函數(shù)贼邓,這其中我們有大量的工作要做阶冈,控制播放狀態(tài)、處理異常中斷立帖、進(jìn)行音頻編碼等等眼溶。由于牽扯內(nèi)容過多,而且不是本文目的晓勇,如果以后有時(shí)間將另開一篇文章重點(diǎn)介紹堂飞,目前有很多第三方優(yōu)秀框架可以直接使用灌旧,例如AudioStreamer、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)查看詳細(xì)用法
視頻
MPMoviePlayerController
在iOS中播放視頻可以使用MediaPlayer.framework
中的MPMoviePlayerController
類來完成睁蕾,它支持本地視頻和網(wǎng)絡(luò)視頻播放苞笨。這個(gè)類實(shí)現(xiàn)了MPMediaPlayback
協(xié)議,因此具備一般的播放器控制功能子眶,例如播放瀑凝、暫停、停止等臭杰。但是MPMediaPlayerController
自身并不是一個(gè)完整的視圖控制器粤咪,如果要在UI中展示視頻需要將view
屬性添加到界面中。下面列出了MPMoviePlayerController
的常用屬性和方法:
屬性 | 說明 |
---|---|
@property (nonatomic, copy) NSURL *contentURL | 播放媒體URL渴杆,這個(gè)URL可以是本地路徑寥枝,也可以是網(wǎng)絡(luò)路徑 |
@property (nonatomic, readonly) UIView *view | 播放器視圖宪塔,如果要顯示視頻必須將此視圖添加到控制器視圖中 |
@property (nonatomic, readonly) UIView *backgroundView | 播放器背景視圖 |
@property (nonatomic, readonly) MPMoviePlaybackState playbackState | 媒體播放狀態(tài),枚舉類型:MPMoviePlaybackStateStopped:停止播放MPMoviePlaybackStatePlaying:正在播放MPMoviePlaybackStatePaused:暫停MPMoviePlaybackStateInterrupted:中斷MPMoviePlaybackStateSeekingForward:向前定位MPMoviePlaybackStateSeekingBackward:向后定位 |
@property (nonatomic, readonly) MPMovieLoadState loadState | 網(wǎng)絡(luò)媒體加載狀態(tài)囊拜,枚舉類型:MPMovieLoadStateUnknown:未知類型MPMovieLoadStatePlayable:MPMovieLoadStatePlaythroughOK:這種狀態(tài)如果shouldAutoPlay為YES將自動(dòng)播放MPMovieLoadStateStalled:停滯狀態(tài) |
@property (nonatomic) MPMovieControlStyle controlStyle | 控制面板風(fēng)格某筐,枚舉類型:MPMovieControlStyleNone:無控制面板 MPMovieControlStyleEmbedded:嵌入視頻風(fēng)格 MPMovieControlStyleFullscreen:全屏 MPMovieControlStyleDefault:默認(rèn)風(fēng)格 |
@property (nonatomic) MPMovieRepeatMode repeatMode; | 重復(fù)播放模式,枚舉類型: MPMovieRepeatModeNone:不重復(fù)冠跷,默認(rèn)值MPMovieRepeatModeOne:重復(fù)播放 |
@property (nonatomic) BOOL shouldAutoplay | 當(dāng)網(wǎng)絡(luò)媒體緩存到一定數(shù)據(jù)時(shí)是否自動(dòng)播放南誊,默認(rèn)為YES |
@property (nonatomic, getter=isFullscreen) BOOL fullscreen | 是否全屏展示,默認(rèn)為NO蜜托,注意如果要通過此屬性設(shè)置全屏必須在視圖顯示完成后設(shè)置抄囚,否則無效 |
@property (nonatomic) MPMovieScalingMode scalingMode | 視頻縮放填充模式,枚舉類型:MPMovieScalingModeNone:不進(jìn)行任何縮放MPMovieScalingModeAspectFit:固定縮放比例并且盡量全部展示視頻盗冷,不會(huì)裁切視頻 MPMovieScalingModeAspectFill:固定縮放比例并填充滿整個(gè)視圖展示怠苔,可能會(huì)裁切視頻 MPMovieScalingModeFill:不固定縮放比例壓縮填充整個(gè)視圖同廉,視頻不會(huì)被裁切但是比例失衡 |
@property (nonatomic, readonly) BOOL readyForDisplay | 是否有相關(guān)媒體被播放 |
@property (nonatomic, readonly) MPMovieMediaTypeMask movieMediaTypes | 媒體類別仪糖,枚舉類型: MPMovieMediaTypeMaskNone:未知類型MPMovieMediaTypeMaskVideo:視頻 MPMovieMediaTypeMaskAudio:音頻 |
@property (nonatomic) MPMovieSourceType movieSourceType | 媒體源,枚舉類型:MPMovieSourceTypeUnknown:未知來源MPMovieSourceTypeFile:本地文件 MPMovieSourceTypeStreaming:流媒體(直播或點(diǎn)播) |
@property (nonatomic, readonly) NSTimeInterval duration | 媒體時(shí)長迫肖,如果未知?jiǎng)t返回0 |
@property (nonatomic, readonly) NSTimeInterval playableDuration | 媒體可播放時(shí)長锅劝,主要用于表示網(wǎng)絡(luò)媒體已下載視頻時(shí)長 |
@property (nonatomic, readonly) CGSize naturalSize | 視頻實(shí)際尺寸,如果未知?jiǎng)t返回CGSizeZero |
@property (nonatomic) NSTimeInterval initialPlaybackTime | 起始播放時(shí)間 |
@property (nonatomic) NSTimeInterval endPlaybackTime | 終止播放時(shí)間 |
@property (nonatomic) BOOL allowsAirPlay | 是否允許無線播放蟆湖,默認(rèn)為YES |
@property (nonatomic, readonly, getter=isAirPlayVideoActive) BOOL airPlayVideoActive | 當(dāng)前媒體是否正在通過AirPlay播放 |
@property(nonatomic, readonly) BOOL isPreparedToPlay | 是否準(zhǔn)備好播放 |
@property(nonatomic) NSTimeInterval currentPlaybackTime | 當(dāng)前播放時(shí)間故爵,單位:秒 |
@property(nonatomic) float currentPlaybackRate | 當(dāng)前播放速度,如果暫停則為0隅津,正常速度為1.0诬垂,非0數(shù)據(jù)表示倍率 |
對象方法 | 說明 |
---|---|
- (instancetype)initWithContentURL:(NSURL *)url | 使用指定的URL初始化媒體播放控制器對象 |
- (void)setFullscreen:(BOOL)fullscreen animated:(BOOL)animated | 設(shè)置視頻全屏,注意如果要通過此方法設(shè)置全屏則必須在其視圖顯示之后設(shè)置伦仍,否則無效 |
- (void)requestThumbnailImagesAtTimes:(NSArray *)playbackTimes timeOption:(MPMovieTimeOption)option | 獲取在指定播放時(shí)間的視頻縮略圖结窘,第一個(gè)參數(shù)是獲取縮略圖的時(shí)間點(diǎn)數(shù)組;第二個(gè)參數(shù)代表時(shí)間點(diǎn)精度充蓝,枚舉類型:MPMovieTimeOptionNearestKeyFrame:時(shí)間點(diǎn)附近MPMovieTimeOptionExact:準(zhǔn)確時(shí)間 |
- (void)cancelAllThumbnailImageRequests | 取消所有縮略圖獲取請求 |
- (void)prepareToPlay | 準(zhǔn)備播放隧枫,加載視頻數(shù)據(jù)到緩存,當(dāng)調(diào)用play方法時(shí)如果沒有準(zhǔn)備好會(huì)自動(dòng)調(diào)用此方法 |
- (void)play | 開始播放 |
- (void)pause | 暫停播放 |
- (void)stop | 停止播放 |
- (void)beginSeekingForward | 向前定位 |
- (void)beginSeekingBackward | 向后定位 |
- (void)endSeeking | 停止快進(jìn)/快退 |
通知 | 說明 |
---|---|
MPMoviePlayerScalingModeDidChangeNotification | 視頻縮放填充模式發(fā)生改變 |
MPMoviePlayerPlaybackDidFinishNotification | 媒體播放完成或用戶手動(dòng)退出谓苟,具體完成原因可以通過通知userInfo中的key為MPMoviePlayerPlaybackDidFinishReasonUserInfoKey的對象獲取 |
MPMoviePlayerPlaybackStateDidChangeNotification | 播放狀態(tài)改變官脓,可配合playbakcState屬性獲取具體狀態(tài) |
MPMoviePlayerLoadStateDidChangeNotification | 媒體網(wǎng)絡(luò)加載狀態(tài)改變 |
MPMoviePlayerNowPlayingMovieDidChangeNotification | 當(dāng)前播放的媒體內(nèi)容發(fā)生改變 |
MPMoviePlayerWillEnterFullscreenNotification | 將要進(jìn)入全屏 |
MPMoviePlayerDidEnterFullscreenNotification | 進(jìn)入全屏后 |
MPMoviePlayerWillExitFullscreenNotification | 將要退出全屏 |
MPMoviePlayerDidExitFullscreenNotification | 退出全屏后 |
MPMoviePlayerIsAirPlayVideoActiveDidChangeNotification | 當(dāng)媒體開始通過AirPlay播放或者結(jié)束AirPlay播放 |
MPMoviePlayerReadyForDisplayDidChangeNotification | 視頻顯示狀態(tài)改變 |
MPMovieMediaTypesAvailableNotification | 確定了媒體可用類型后 |
MPMovieSourceTypeAvailableNotification | 確定了媒體來源后 |
MPMovieDurationAvailableNotification | 確定了媒體播放時(shí)長后 |
MPMovieNaturalSizeAvailableNotification | 確定了媒體的實(shí)際尺寸后 |
MPMoviePlayerThumbnailImageRequestDidFinishNotification | 縮略圖請求完成之后 |
MPMediaPlaybackIsPreparedToPlayDidChangeNotification | 做好播放準(zhǔn)備后 |
注意MPMediaPlayerController
的狀態(tài)等信息并不是通過代理來和外界交互的,而是通過通知中心涝焙,因此從上面的列表中可以看到常用的一些通知卑笨。由于MPMoviePlayerController
本身對于媒體播放做了深度的封裝,使用起來就相當(dāng)簡單:創(chuàng)建MPMoviePlayerController
對象仑撞,設(shè)置frame
屬性赤兴,將MPMoviePlayerController
的view
添加到控制器視圖中芭商。下面的示例中將創(chuàng)建一個(gè)播放控制器并添加播放狀態(tài)改變及播放完成的通知:
//
// ViewController.m
// MPMoviePlayerController
//
// Created by Kenshin Cui on 14/03/30.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
//
#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>
@interface ViewController ()
@property (nonatomic,strong) MPMoviePlayerController *moviePlayer;//視頻播放控制器
@end
@implementation ViewController
#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
[super viewDidLoad];
//播放
[self.moviePlayer play];
//添加通知
[self addNotification];
}
-(void)dealloc{
//移除所有通知監(jiān)控
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - 私有方法
/**
* 取得本地文件路徑
*
* @return 文件路徑
*/
-(NSURL *)getFileUrl{
NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
NSURL *url=[NSURL fileURLWithPath:urlStr];
return url;
}
/**
* 取得網(wǎng)絡(luò)文件路徑
*
* @return 文件路徑
*/
-(NSURL *)getNetworkUrl{
NSString *urlStr=@"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url=[NSURL URLWithString:urlStr];
return url;
}
/**
* 創(chuàng)建媒體播放控制器
*
* @return 媒體播放控制器
*/
-(MPMoviePlayerController *)moviePlayer{
if (!_moviePlayer) {
NSURL *url=[self getNetworkUrl];
_moviePlayer=[[MPMoviePlayerController alloc]initWithContentURL:url];
_moviePlayer.view.frame=self.view.bounds;
_moviePlayer.view.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[self.view addSubview:_moviePlayer.view];
}
return _moviePlayer;
}
/**
* 添加通知監(jiān)控媒體播放控制器狀態(tài)
*/
-(void)addNotification{
NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:self.moviePlayer];
[notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
}
/**
* 播放狀態(tài)改變,注意播放完成時(shí)的狀態(tài)是暫停
*
* @param notification 通知對象
*/
-(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{
switch (self.moviePlayer.playbackState) {
case MPMoviePlaybackStatePlaying:
NSLog(@"正在播放...");
break;
case MPMoviePlaybackStatePaused:
NSLog(@"暫停播放.");
break;
case MPMoviePlaybackStateStopped:
NSLog(@"停止播放.");
break;
default:
NSLog(@"播放狀態(tài):%li",self.moviePlayer.playbackState);
break;
}
}
/**
* 播放完成
*
* @param notification 通知對象
*/
-(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{
NSLog(@"播放完成.%li",self.moviePlayer.playbackState);
}
@end
運(yùn)行效果:
從上面的API大家也不難看出其實(shí)MPMoviePlayerController
功能相當(dāng)強(qiáng)大搀缠,日常開發(fā)中作為一般的媒體播放器也完全沒有問題铛楣。MPMoviePlayerController
除了一般的視頻播放和控制外還有一些強(qiáng)大的功能,例如截取視頻縮略圖艺普。請求視頻縮略圖時(shí)只要調(diào)用- (void)requestThumbnailImagesAtTimes:(NSArray *)playbackTimes timeOption:(MPMovieTimeOption)option
方法指定獲得縮略圖的時(shí)間點(diǎn),然后監(jiān)控MPMoviePlayerThumbnailImageRequestDidFinishNotification
通知歧譬,每個(gè)時(shí)間點(diǎn)的縮略圖請求完成就會(huì)調(diào)用通知岸浑,在通知調(diào)用方法中可以通過MPMoviePlayerThumbnailImageKey
獲得UIImage
對象處理即可。例如下面的程序演示了在程序啟動(dòng)后獲得兩個(gè)時(shí)間點(diǎn)的縮略圖的過程瑰步,截圖成功后保存到相冊:
//
// ViewController.m
// MPMoviePlayerController
//
// Created by Kenshin Cui on 14/03/30.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
// 視頻截圖
#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>
@interface ViewController ()
@property (nonatomic,strong) MPMoviePlayerController *moviePlayer;//視頻播放控制器
@end
@implementation ViewController
#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
[super viewDidLoad];
//播放
[self.moviePlayer play];
//添加通知
[self addNotification];
//獲取縮略圖
[self thumbnailImageRequest];
}
-(void)dealloc{
//移除所有通知監(jiān)控
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - 私有方法
/**
* 取得本地文件路徑
*
* @return 文件路徑
*/
-(NSURL *)getFileUrl{
NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
NSURL *url=[NSURL fileURLWithPath:urlStr];
return url;
}
/**
* 取得網(wǎng)絡(luò)文件路徑
*
* @return 文件路徑
*/
-(NSURL *)getNetworkUrl{
NSString *urlStr=@"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url=[NSURL URLWithString:urlStr];
return url;
}
/**
* 創(chuàng)建媒體播放控制器
*
* @return 媒體播放控制器
*/
-(MPMoviePlayerController *)moviePlayer{
if (!_moviePlayer) {
NSURL *url=[self getNetworkUrl];
_moviePlayer=[[MPMoviePlayerController alloc]initWithContentURL:url];
_moviePlayer.view.frame=self.view.bounds;
_moviePlayer.view.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
[self.view addSubview:_moviePlayer.view];
}
return _moviePlayer;
}
/**
* 獲取視頻縮略圖
*/
-(void)thumbnailImageRequest{
//獲取13.0s矢洲、21.5s的縮略圖
[self.moviePlayer requestThumbnailImagesAtTimes:@[@13.0,@21.5] timeOption:MPMovieTimeOptionNearestKeyFrame];
}
#pragma mark - 控制器通知
/**
* 添加通知監(jiān)控媒體播放控制器狀態(tài)
*/
-(void)addNotification{
NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:self.moviePlayer];
[notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
[notificationCenter addObserver:self selector:@selector(mediaPlayerThumbnailRequestFinished:) name:MPMoviePlayerThumbnailImageRequestDidFinishNotification object:self.moviePlayer];
}
/**
* 播放狀態(tài)改變,注意播放完成時(shí)的狀態(tài)是暫停
*
* @param notification 通知對象
*/
-(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{
switch (self.moviePlayer.playbackState) {
case MPMoviePlaybackStatePlaying:
NSLog(@"正在播放...");
break;
case MPMoviePlaybackStatePaused:
NSLog(@"暫停播放.");
break;
case MPMoviePlaybackStateStopped:
NSLog(@"停止播放.");
break;
default:
NSLog(@"播放狀態(tài):%li",self.moviePlayer.playbackState);
break;
}
}
/**
* 播放完成
*
* @param notification 通知對象
*/
-(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{
NSLog(@"播放完成.%li",self.moviePlayer.playbackState);
}
/**
* 縮略圖請求完成,此方法每次截圖成功都會(huì)調(diào)用一次
*
* @param notification 通知對象
*/
-(void)mediaPlayerThumbnailRequestFinished:(NSNotification *)notification{
NSLog(@"視頻截圖完成.");
UIImage *image=notification.userInfo[MPMoviePlayerThumbnailImageKey];
//保存圖片到相冊(首次調(diào)用會(huì)請求用戶獲得訪問相冊權(quán)限)
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}
@end
截圖效果:
擴(kuò)展--使用AVFoundation生成縮略圖
通過前面的方法大家應(yīng)該已經(jīng)看到缩焦,使用MPMoviePlayerController
來生成縮略圖足夠簡單读虏,但是如果僅僅是是為了生成縮略圖而不進(jìn)行視頻播放的話,此刻使用MPMoviePlayerController
就有點(diǎn)大材小用了袁滥。其實(shí)使用AVFundation
框架中的AVAssetImageGenerator
就可以獲取視頻縮略圖盖桥。使用AVAssetImageGenerator
獲取縮略圖大致分為三個(gè)步驟:
1、創(chuàng)建AVURLAsset
對象(此類主要用于獲取媒體信息题翻,包括視頻揩徊、聲音等)。
2嵌赠、根據(jù)AVURLAsset
創(chuàng)建AVAssetImageGenerator
對象塑荒。
3、使用AVAssetImageGenerato
r的copyCGImageAtTime::
方法獲得指定時(shí)間點(diǎn)的截圖姜挺。
//
// ViewController.m
// AVAssetImageGenerator
//
// Created by Kenshin Cui on 14/03/30.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
//
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//獲取第13.0s的縮略圖
[self thumbnailImageRequest:13.0];
}
#pragma mark - 私有方法
/**
* 取得本地文件路徑
*
* @return 文件路徑
*/
-(NSURL *)getFileUrl{
NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
NSURL *url=[NSURL fileURLWithPath:urlStr];
return url;
}
/**
* 取得網(wǎng)絡(luò)文件路徑
*
* @return 文件路徑
*/
-(NSURL *)getNetworkUrl{
NSString *urlStr=@"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url=[NSURL URLWithString:urlStr];
return url;
}
/**
* 截取指定時(shí)間的視頻縮略圖
*
* @param timeBySecond 時(shí)間點(diǎn)
*/
-(void)thumbnailImageRequest:(CGFloat )timeBySecond{
//創(chuàng)建URL
NSURL *url=[self getNetworkUrl];
//根據(jù)url創(chuàng)建AVURLAsset
AVURLAsset *urlAsset=[AVURLAsset assetWithURL:url];
//根據(jù)AVURLAsset創(chuàng)建AVAssetImageGenerator
AVAssetImageGenerator *imageGenerator=[AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];
/*截圖
* requestTime:縮略圖創(chuàng)建時(shí)間
* actualTime:縮略圖實(shí)際生成的時(shí)間
*/
NSError *error=nil;
CMTime time=CMTimeMakeWithSeconds(timeBySecond, 10);//CMTime是表示電影時(shí)間信息的結(jié)構(gòu)體齿税,第一個(gè)參數(shù)表示是視頻第幾秒,第二個(gè)參數(shù)表示每秒幀數(shù).(如果要活的某一秒的第幾幀可以使用CMTimeMake方法)
CMTime actualTime;
CGImageRef cgImage= [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error];
if(error){
NSLog(@"截取視頻縮略圖時(shí)發(fā)生錯(cuò)誤初家,錯(cuò)誤信息:%@",error.localizedDescription);
return;
}
CMTimeShow(actualTime);
UIImage *image=[UIImage imageWithCGImage:cgImage];//轉(zhuǎn)化為UIImage
//保存到相冊
UIImageWriteToSavedPhotosAlbum(image,nil, nil, nil);
CGImageRelease(cgImage);
}
@end
生成的縮略圖效果:
MPMoviePlayerViewController
其實(shí)MPMoviePlayerController
如果不作為嵌入視頻來播放(例如在新聞中嵌入一個(gè)視頻)偎窘,通常在播放時(shí)都是占滿一個(gè)屏幕的,特別是在iPhone溜在、iTouch上陌知。因此從iOS3.2以后蘋果也在思考既然MPMoviePlayerController
在使用時(shí)通常都是將其視圖view添加到另外一個(gè)視圖控制器中作為子視圖,那么何不直接創(chuàng)建一個(gè)控制器視圖內(nèi)部創(chuàng)建一個(gè)MPMoviePlayerController
屬性并且默認(rèn)全屏播放掖肋,開發(fā)者在開發(fā)的時(shí)候直接使用這個(gè)視圖控制器仆葡。這個(gè)內(nèi)部有一個(gè)MPMoviePlayerController
的視圖控制器就是MPMoviePlayerViewController
,它繼承于UIViewController
。MPMoviePlayerViewController
內(nèi)部多了一個(gè)moviePlayer
屬性和一個(gè)帶有url
的初始化方法沿盅,同時(shí)它內(nèi)部實(shí)現(xiàn)了一些作為模態(tài)視圖展示所特有的功能把篓,例如默認(rèn)是全屏模式展示、彈出后自動(dòng)播放腰涧、作為模態(tài)窗口展示時(shí)如果點(diǎn)擊“Done”按鈕會(huì)自動(dòng)退出模態(tài)窗口等韧掩。在下面的示例中就不直接將播放器放到主視圖控制器,而是放到一個(gè)模態(tài)視圖控制器中窖铡,簡單演示MPMoviePlayerViewController
的使用疗锐。
//
// ViewController.m
// MPMoviePlayerViewController
//
// Created by Kenshin Cui on 14/03/30.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
// MPMoviePlayerViewController使用
#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>
@interface ViewController ()
//播放器視圖控制器
@property (nonatomic,strong) MPMoviePlayerViewController *moviePlayerViewController;
@end
@implementation ViewController
#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)dealloc{
//移除所有通知監(jiān)控
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - 私有方法
/**
* 取得本地文件路徑
*
* @return 文件路徑
*/
-(NSURL *)getFileUrl{
NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
NSURL *url=[NSURL fileURLWithPath:urlStr];
return url;
}
/**
* 取得網(wǎng)絡(luò)文件路徑
*
* @return 文件路徑
*/
-(NSURL *)getNetworkUrl{
NSString *urlStr=@"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url=[NSURL URLWithString:urlStr];
return url;
}
-(MPMoviePlayerViewController *)moviePlayerViewController{
if (!_moviePlayerViewController) {
NSURL *url=[self getNetworkUrl];
_moviePlayerViewController=[[MPMoviePlayerViewController alloc]initWithContentURL:url];
[self addNotification];
}
return _moviePlayerViewController;
}
#pragma mark - UI事件
- (IBAction)playClick:(UIButton *)sender {
self.moviePlayerViewController=nil;//保證每次點(diǎn)擊都重新創(chuàng)建視頻播放控制器視圖,避免再次點(diǎn)擊時(shí)由于不播放的問題
// [self presentViewController:self.moviePlayerViewController animated:YES completion:nil];
//注意费彼,在MPMoviePlayerViewController.h中對UIViewController擴(kuò)展兩個(gè)用于模態(tài)展示和關(guān)閉MPMoviePlayerViewController的方法滑臊,增加了一種下拉展示動(dòng)畫效果
[self presentMoviePlayerViewControllerAnimated:self.moviePlayerViewController];
}
#pragma mark - 控制器通知
/**
* 添加通知監(jiān)控媒體播放控制器狀態(tài)
*/
-(void)addNotification{
NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:self.moviePlayerViewController.moviePlayer];
[notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayerViewController.moviePlayer];
}
/**
* 播放狀態(tài)改變,注意播放完成時(shí)的狀態(tài)是暫停
*
* @param notification 通知對象
*/
-(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{
switch (self.moviePlayerViewController.moviePlayer.playbackState) {
case MPMoviePlaybackStatePlaying:
NSLog(@"正在播放...");
break;
case MPMoviePlaybackStatePaused:
NSLog(@"暫停播放.");
break;
case MPMoviePlaybackStateStopped:
NSLog(@"停止播放.");
break;
default:
NSLog(@"播放狀態(tài):%li",self.moviePlayerViewController.moviePlayer.playbackState);
break;
}
}
/**
* 播放完成
*
* @param notification 通知對象
*/
-(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{
NSLog(@"播放完成.%li",self.moviePlayerViewController.moviePlayer.playbackState);
}
@end
運(yùn)行效果:
這里需要強(qiáng)調(diào)一下箍铲,由于MPMoviePlayerViewController
的初始化方法做了大量工作(例如設(shè)置URL雇卷、自動(dòng)播放、添加點(diǎn)擊Done完成的監(jiān)控等)颠猴,所以當(dāng)再次點(diǎn)擊播放彈出新的模態(tài)窗口的時(shí)如果不銷毀之前的MPMoviePlayerViewController
关划,那么新的對象就無法完成初始化,這樣也就不能再次進(jìn)行播放芙粱。
AVPlayer
MPMoviePlayerController
足夠強(qiáng)大祭玉,幾乎不用寫幾行代碼就能完成一個(gè)播放器,但是正是由于它的高度封裝使得要自定義這個(gè)播放器變得很復(fù)雜春畔,甚至是不可能完成。例如有些時(shí)候需要自定義播放器的樣式岛都,那么如果要使用MPMoviePlayerController
就不合適了律姨,如果要對視頻有自由的控制則可以使用AVPlayer
。AVPlayer
存在于AVFoundation
中臼疫,它更加接近于底層择份,所以靈活性也更強(qiáng):
AVPlayer
本身并不能顯示視頻,而且它也不像MPMoviePlayerController
有一個(gè)view屬性烫堤。如果AVPlayer
要顯示必須創(chuàng)建一個(gè)播放器層AVPlayerLayer
用于展示荣赶,播放器層繼承于CALayer
,有了AVPlayerLayer
之添加到控制器視圖的layer
中即可鸽斟。要使用AVPlayer
首先了解一下幾個(gè)常用的類:
AVAsset
:主要用于獲取多媒體信息拔创,是一個(gè)抽象類,不能直接使用富蓄。
AVURLAsset
:AVAsset
的子類剩燥,可以根據(jù)一個(gè)URL路徑創(chuàng)建一個(gè)包含媒體信息的AVURLAsset
對象。
AVPlayerItem
:一個(gè)媒體資源管理對象立倍,管理者視頻的一些基本信息和狀態(tài)灭红,一個(gè)AVPlayerItem
對應(yīng)著一個(gè)視頻資源侣滩。
下面簡單通過一個(gè)播放器來演示AVPlayer
的使用,播放器的效果如下:
在這個(gè)自定義的播放器中實(shí)現(xiàn)了視頻播放变擒、暫停君珠、進(jìn)度展示和視頻列表功能,下面將對這些功能一一介紹娇斑。
首先說一下視頻的播放葛躏、暫停功能咪鲜,這也是最基本的功能讼积,AVPlayer
對應(yīng)著兩個(gè)方法play
、pause
來實(shí)現(xiàn)讥此。但是關(guān)鍵問題是如何判斷當(dāng)前視頻是否在播放悔醋,在前面的內(nèi)容中無論是音頻播放器還是視頻播放器都有對應(yīng)的狀態(tài)來判斷摩窃,但是AVPlaye
r卻沒有這樣的狀態(tài)屬性,通常情況下可以通過判斷播放器的播放速度來獲得播放狀態(tài)芬骄。如果rate為0說明是停止?fàn)顟B(tài)猾愿,1是則是正常播放狀態(tài)。
其次要展示播放進(jìn)度就沒有其他播放器那么簡單了账阻。在前面的播放器中通常是使用通知來獲得播放器的狀態(tài)蒂秘,媒體加載狀態(tài)等,但是無論是AVPlayer
還是AVPlayerItem
(AVPlayer
有一個(gè)屬性currentItem
是AVPlayerItem
類型淘太,表示當(dāng)前播放的視頻對象)都無法獲得這些信息姻僧。當(dāng)然AVPlayerItem
是有通知的,但是對于獲得播放狀態(tài)和加載狀態(tài)有用的通知只有一個(gè):播放完成通知AVPlayerItemDidPlayToEndTimeNotification
蒲牧。在播放視頻時(shí)撇贺,特別是播放網(wǎng)絡(luò)視頻往往需要知道視頻加載情況、緩沖情況冰抢、播放情況松嘶,這些信息可以通過KVO監(jiān)控AVPlayerItem
的status
、loadedTimeRanges
屬性來獲得挎扰。當(dāng)AVPlayerItem
的status
屬性為AVPlayerStatusReadyToPlay
是說明正在播放翠订,只有處于這個(gè)狀態(tài)時(shí)才能獲得視頻時(shí)長等信息;當(dāng)loadedTimeRanges
的改變時(shí)(每緩沖一部分?jǐn)?shù)據(jù)就會(huì)更新此屬性)可以獲得本次緩沖加載的視頻范圍(包含起始時(shí)間遵倦、本次加載時(shí)長)尽超,這樣一來就可以實(shí)時(shí)獲得緩沖情況。然后就是依靠AVPlayer
的- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block
方法獲得播放進(jìn)度骇吭,這個(gè)方法會(huì)在設(shè)定的時(shí)間間隔內(nèi)定時(shí)更新播放進(jìn)度橙弱,通過time參數(shù)通知客戶端。相信有了這些視頻信息播放進(jìn)度就不成問題了,事實(shí)上通過這些信息就算是平時(shí)看到的其他播放器的緩沖進(jìn)度顯示以及拖動(dòng)播放的功能也可以順利的實(shí)現(xiàn)棘脐。
最后就是視頻切換的功能斜筐,在前面介紹的所有播放器中每個(gè)播放器對象一次只能播放一個(gè)視頻,如果要切換視頻只能重新創(chuàng)建一個(gè)對象蛀缝,但是AVPlayer
卻提供了- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item
方法用于在不同的視頻之間切換(事實(shí)上在AVFoundation
內(nèi)部還有一個(gè)AVQueuePlayer
專門處理播放列表切換顷链,有興趣的朋友可以自行研究,這里不再贅述)屈梁。
下面附上代碼:
//
// ViewController.m
// AVPlayer
//
// Created by Kenshin Cui on 14/03/30.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
//
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()
@property (nonatomic,strong) AVPlayer *player;//播放器對象
@property (weak, nonatomic) IBOutlet UIView *container; //播放器容器
@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕
@property (weak, nonatomic) IBOutlet UIProgressView *progress;//播放進(jìn)度
@end
@implementation ViewController
#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
[self.player play];
}
-(void)dealloc{
[self removeObserverFromPlayerItem:self.player.currentItem];
[self removeNotification];
}
#pragma mark - 私有方法
-(void)setupUI{
//創(chuàng)建播放器層
AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:self.player];
playerLayer.frame=self.container.frame;
//playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;//視頻填充模式
[self.container.layer addSublayer:playerLayer];
}
/**
* 截取指定時(shí)間的視頻縮略圖
*
* @param timeBySecond 時(shí)間點(diǎn)
*/
/**
* 初始化播放器
*
* @return 播放器對象
*/
-(AVPlayer *)player{
if (!_player) {
AVPlayerItem *playerItem=[self getPlayItem:0];
_player=[AVPlayer playerWithPlayerItem:playerItem];
[self addProgressObserver];
[self addObserverToPlayerItem:playerItem];
}
return _player;
}
/**
* 根據(jù)視頻索引取得AVPlayerItem對象
*
* @param videoIndex 視頻順序索引
*
* @return AVPlayerItem對象
*/
-(AVPlayerItem *)getPlayItem:(int)videoIndex{
NSString *urlStr=[NSString stringWithFormat:@"http://192.168.1.161/%i.mp4",videoIndex];
urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *url=[NSURL URLWithString:urlStr];
AVPlayerItem *playerItem=[AVPlayerItem playerItemWithURL:url];
return playerItem;
}
#pragma mark - 通知
/**
* 添加播放器通知
*/
-(void)addNotification{
//給AVPlayerItem添加播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
}
-(void)removeNotification{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
/**
* 播放完成通知
*
* @param notification 通知對象
*/
-(void)playbackFinished:(NSNotification *)notification{
NSLog(@"視頻播放完成.");
}
#pragma mark - 監(jiān)控
/**
* 給播放器添加進(jìn)度更新
*/
-(void)addProgressObserver{
AVPlayerItem *playerItem=self.player.currentItem;
UIProgressView *progress=self.progress;
//這里設(shè)置每秒執(zhí)行一次
[self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
float current=CMTimeGetSeconds(time);
float total=CMTimeGetSeconds([playerItem duration]);
NSLog(@"當(dāng)前已經(jīng)播放%.2fs.",current);
if (current) {
[progress setProgress:(current/total) animated:YES];
}
}];
}
/**
* 給AVPlayerItem添加監(jiān)控
*
* @param playerItem AVPlayerItem對象
*/
-(void)addObserverToPlayerItem:(AVPlayerItem *)playerItem{
//監(jiān)控狀態(tài)屬性嗤练,注意AVPlayer也有一個(gè)status屬性,通過監(jiān)控它的status也可以獲得播放狀態(tài)
[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
//監(jiān)控網(wǎng)絡(luò)加載情況屬性
[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem{
[playerItem removeObserver:self forKeyPath:@"status"];
[playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}
/**
* 通過KVO監(jiān)控播放器狀態(tài)
*
* @param keyPath 監(jiān)控屬性
* @param object 監(jiān)視器
* @param change 狀態(tài)改變
* @param context 上下文
*/
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
AVPlayerItem *playerItem=object;
if ([keyPath isEqualToString:@"status"]) {
AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
if(status==AVPlayerStatusReadyToPlay){
NSLog(@"正在播放...在讶,視頻總長度:%.2f",CMTimeGetSeconds(playerItem.duration));
}
}else if([keyPath isEqualToString:@"loadedTimeRanges"]){
NSArray *array=playerItem.loadedTimeRanges;
CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩沖時(shí)間范圍
float startSeconds = CMTimeGetSeconds(timeRange.start);
float durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩沖總長度
NSLog(@"共緩沖:%.2f",totalBuffer);
//
}
}
#pragma mark - UI事件
/**
* 點(diǎn)擊播放/暫停按鈕
*
* @param sender 播放/暫停按鈕
*/
- (IBAction)playClick:(UIButton *)sender {
// AVPlayerItemDidPlayToEndTimeNotification
//AVPlayerItem *playerItem= self.player.currentItem;
if(self.player.rate==0){ //說明時(shí)暫停
[sender setImage:[UIImage imageNamed:@"player_pause"] forState:UIControlStateNormal];
[self.player play];
}else if(self.player.rate==1){//正在播放
[self.player pause];
[sender setImage:[UIImage imageNamed:@"player_play"] forState:UIControlStateNormal];
}
}
/**
* 切換選集煞抬,這里使用按鈕的tag代表視頻名稱
*
* @param sender 點(diǎn)擊按鈕對象
*/
- (IBAction)navigationButtonClick:(UIButton *)sender {
[self removeNotification];
[self removeObserverFromPlayerItem:self.player.currentItem];
AVPlayerItem *playerItem=[self getPlayItem:sender.tag];
[self addObserverToPlayerItem:playerItem];
//切換視頻
[self.player replaceCurrentItemWithPlayerItem:playerItem];
[self addNotification];
}
@end
運(yùn)行效果:
到目前為止無論是MPMoviePlayerController
還是AVPlayer
來播放視頻都相當(dāng)強(qiáng)大,但是它也存在著一些不可回避的問題构哺,那就是支持的視頻編碼格式很有限:H.264革答、MPEG-4,擴(kuò)展名(壓縮格式):.mp4曙强、.mov残拐、.m4v、.m2v碟嘴、.3gp溪食、.3g2等。但是無論是MPMoviePlayerController
還是AVPlayer
它們都支持絕大多數(shù)音頻編碼娜扇,所以大家如果純粹是為了播放音樂的話也可以考慮使用這兩個(gè)播放器错沃。那么如何支持更多視頻編碼格式呢?目前來說主要還是依靠第三方框架袱衷,在iOS上常用的視頻編碼捎废、解碼框架有:VLC、ffmpeg致燥, 具體使用方式今天就不再做詳細(xì)介紹。
攝像頭
UIImagePickerController拍照和視頻錄制
下面看一下在iOS如何拍照和錄制視頻排截。在iOS中要拍照和錄制視頻最簡單的方法就是使用UIImagePickerController
嫌蚤。UIImagePickerController
繼承于UINavigationController
,前面的文章中主要使用它來選取照片断傲,其實(shí)UIImagePickerController
的功能不僅如此脱吱,它還可以用來拍照和錄制視頻。首先看一下這個(gè)類常用的屬性和方法:
屬性 | 說明 |
---|---|
@property(nonatomic) UIImagePickerControllerSourceType sourceType | 選取源類型认罩,sourceType是枚舉類型:UIImagePickerControllerSourceTypePhotoLibrary:照片庫箱蝠,默認(rèn)值 UIImagePickerControllerSourceTypeCamera:攝像頭UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿 |
@property(nonatomic,copy) NSArray *mediaTypes | 媒體類型,默認(rèn)情況下此數(shù)組包含kUTTypeImage,所以拍照時(shí)可以不用設(shè)置;但是當(dāng)要錄像的時(shí)候必須設(shè)置宦搬,可以設(shè)置為kUTTypeVideo(視頻牙瓢,但不帶聲音)或者kUTTypeMovie(視頻并帶有聲音) |
@property(nonatomic) NSTimeInterval videoMaximumDuration | 視頻最大錄制時(shí)長,默認(rèn)為10 s |
@property(nonatomic) UIImagePickerControllerQualityType videoQuality | 視頻質(zhì)量间校,枚舉類型:UIImagePickerControllerQualityTypeHigh:高清質(zhì)量UIImagePickerControllerQualityTypeMedium:中等質(zhì)量矾克,適合WiFi傳輸U(kuò)IImagePickerControllerQualityTypeLow:低質(zhì)量,適合蜂窩網(wǎng)傳輸U(kuò)IImagePickerControllerQualityType640x480:640480 UIImagePickerControllerQualityTypeIFrame1280x720:1280720 UIImagePickerControllerQualityTypeIFrame960x540:960*540 |
@property(nonatomic) BOOL showsCameraControls | 是否顯示攝像頭控制面板憔足,默認(rèn)為YES |
@property(nonatomic,retain) UIView *cameraOverlayView | 攝像頭上覆蓋的視圖胁附,可用通過這個(gè)視頻來自定義拍照或錄像界面 |
@property(nonatomic) CGAffineTransform cameraViewTransform | 攝像頭形變 |
@property(nonatomic) UIImagePickerControllerCameraCaptureMode cameraCaptureMode | 攝像頭捕獲模式,捕獲模式是枚舉類型:UIImagePickerControllerCameraCaptureModePhoto:拍照模式UIImagePickerControllerCameraCaptureModeVideo:視頻錄制模式 |
@property(nonatomic) UIImagePickerControllerCameraDevice cameraDevice | 攝像頭設(shè)備滓彰,cameraDevice是枚舉類型:UIImagePickerControllerCameraDeviceRear:前置攝像頭UIImagePickerControllerCameraDeviceFront:后置攝像頭 |
@property(nonatomic) UIImagePickerControllerCameraFlashMode cameraFlashMode | 閃光燈模式控妻,枚舉類型:UIImagePickerControllerCameraFlashModeOff:關(guān)閉閃光燈UIImagePickerControllerCameraFlashModeAuto:閃光燈自動(dòng)UIImagePickerControllerCameraFlashModeOn:打開閃光燈 |
類方法 | 說明 |
---|---|
+ (BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)sourceType | 指定的源類型是否可用,sourceType是枚舉類型:UIImagePickerControllerSourceTypePhotoLibrary:照片庫UIImagePickerControllerSourceTypeCamera:攝像頭UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿 |
+ (NSArray *)availableMediaTypesForSourceType:(UIImagePickerControllerSourceType)sourceType | 指定的源設(shè)備上可用的媒體類型揭绑,一般就是圖片和視頻 |
+ (BOOL)isCameraDeviceAvailable:(UIImagePickerControllerCameraDevice)cameraDevice | 指定的攝像頭是否可用弓候,cameraDevice是枚舉類型:UIImagePickerControllerCameraDeviceRear:前置攝像頭UIImagePickerControllerCameraDeviceFront:后置攝像頭 |
+ (BOOL)isFlashAvailableForCameraDevice:(UIImagePickerControllerCameraDevice)cameraDevice | 指定攝像頭的閃光燈是否可用 |
+ (NSArray *)availableCaptureModesForCameraDevice:(UIImagePickerControllerCameraDevice)cameraDevice | 獲得指定攝像頭上的可用捕獲模式,捕獲模式是枚舉類型:UIImagePickerControllerCameraCaptureModePhoto:拍照模式UIImagePickerControllerCameraCaptureModeVideo:視頻錄制模式 |
對象方法 | 說明 |
---|---|
- (void)takePicture | 編程方式拍照 |
- (BOOL)startVideoCapture | 編程方式錄制視頻 |
- (void)stopVideoCapture | 編程方式停止錄制視頻 |
代理方法 | 說明 |
---|---|
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info | 媒體選取完成 |
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker | 取消選取 |
擴(kuò)展方法(主要用于保存照片洗做、視頻到相簿) | 說明 |
---|---|
UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo) | 保存照片到相簿 |
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(NSString *videoPath) | 能否將視頻保存到相簿 |
void UISaveVideoAtPathToSavedPhotosAlbum(NSString *videoPath, id completionTarget, SEL completionSelector, void *contextInfo) | 保存視頻到相簿 |
要用UIImagePickerController
來拍照或者錄制視頻通彻眩可以分為如下步驟:
1、創(chuàng)建UIImagePickerController
對象诚纸。
2撰筷、指定選取源,平時(shí)選擇照片時(shí)使用的選取源是照片庫或者相簿畦徘,此刻需要指定為攝像頭類型毕籽。
3、指定攝像頭井辆,前置攝像頭或者后置攝像頭关筒。
4、設(shè)置媒體類型mediaType
杯缺,注意如果是錄像必須設(shè)置蒸播,如果是拍照此步驟可以省略,因?yàn)?code>mediaType默認(rèn)包含kUTTypeImage
(注意媒體類型定義在MobileCoreServices.framework
中)
5萍肆、指定捕獲模式袍榆,拍照或者錄制視頻。(視頻錄制時(shí)必須先設(shè)置媒體類型再設(shè)置捕獲模式
)
6塘揣、展示UIImagePickerController
(通常以模態(tài)窗口形式打開)包雀。
7、拍照和錄制視頻結(jié)束后在代理方法中展示/保存照片或視頻亲铡。
當(dāng)然這個(gè)過程中有很多細(xì)節(jié)可以設(shè)置才写,例如是否顯示拍照控制面板葡兑,拍照后是否允許編輯等等,通過上面的屬性/方法列表相信并不難理解赞草。下面就以一個(gè)示例展示如何使用UIImagePickerController
來拍照和錄制視頻讹堤,下面的程序中只要將_isVideo
設(shè)置為YES就是視頻錄制模式,錄制完后在主視圖控制器中自動(dòng)播放房资;如果將_isVideo
設(shè)置為NO
則為拍照模式蜕劝,拍照完成之后在主視圖控制器中顯示拍攝的照片:
//
// ViewController.m
// UIImagePickerController
//
// Created by Kenshin Cui on 14/04/05.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
//
#import "ViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <AVFoundation/AVFoundation.h>
@interface ViewController ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (assign,nonatomic) int isVideo;//是否錄制視頻,如果為1表示錄制視頻轰异,0代表拍照
@property (strong,nonatomic) UIImagePickerController *imagePicker;
@property (weak, nonatomic) IBOutlet UIImageView *photo;//照片展示視圖
@property (strong ,nonatomic) AVPlayer *player;//播放器岖沛,用于錄制完視頻后播放視頻
@end
@implementation ViewController
#pragma mark - 控制器視圖事件
- (void)viewDidLoad {
[super viewDidLoad];
//通過這里設(shè)置當(dāng)前程序是拍照還是錄制視頻
_isVideo=YES;
}
#pragma mark - UI事件
//點(diǎn)擊拍照按鈕
- (IBAction)takeClick:(UIButton *)sender {
[self presentViewController:self.imagePicker animated:YES completion:nil];
}
#pragma mark - UIImagePickerController代理方法
//完成
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
NSString *mediaType=[info objectForKey:UIImagePickerControllerMediaType];
if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {//如果是拍照
UIImage *image;
//如果允許編輯則獲得編輯后的照片,否則獲取原始照片
if (self.imagePicker.allowsEditing) {
image=[info objectForKey:UIImagePickerControllerEditedImage];//獲取編輯后的照片
}else{
image=[info objectForKey:UIImagePickerControllerOriginalImage];//獲取原始照片
}
[self.photo setImage:image];//顯示照片
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);//保存到相簿
}else if([mediaType isEqualToString:(NSString *)kUTTypeMovie]){//如果是錄制視頻
NSLog(@"video...");
NSURL *url=[info objectForKey:UIImagePickerControllerMediaURL];//視頻路徑
NSString *urlStr=[url path];
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(urlStr)) {
//保存視頻到相簿搭独,注意也可以使用ALAssetsLibrary來保存
UISaveVideoAtPathToSavedPhotosAlbum(urlStr, self, @selector(video:didFinishSavingWithError:contextInfo:), nil);//保存視頻到相簿
}
}
[self dismissViewControllerAnimated:YES completion:nil];
}
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
NSLog(@"取消");
}
#pragma mark - 私有方法
-(UIImagePickerController *)imagePicker{
if (!_imagePicker) {
_imagePicker=[[UIImagePickerController alloc]init];
_imagePicker.sourceType=UIImagePickerControllerSourceTypeCamera;//設(shè)置image picker的來源婴削,這里設(shè)置為攝像頭
_imagePicker.cameraDevice=UIImagePickerControllerCameraDeviceRear;//設(shè)置使用哪個(gè)攝像頭,這里設(shè)置為后置攝像頭
if (self.isVideo) {
_imagePicker.mediaTypes=@[(NSString *)kUTTypeMovie];
_imagePicker.videoQuality=UIImagePickerControllerQualityTypeIFrame1280x720;
_imagePicker.cameraCaptureMode=UIImagePickerControllerCameraCaptureModeVideo;//設(shè)置攝像頭模式(拍照牙肝,錄制視頻)
}else{
_imagePicker.cameraCaptureMode=UIImagePickerControllerCameraCaptureModePhoto;
}
_imagePicker.allowsEditing=YES;//允許編輯
_imagePicker.delegate=self;//設(shè)置代理唉俗,檢測操作
}
return _imagePicker;
}
//視頻保存后的回調(diào)
- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
if (error) {
NSLog(@"保存視頻過程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
}else{
NSLog(@"視頻保存成功.");
//錄制完之后自動(dòng)播放
NSURL *url=[NSURL fileURLWithPath:videoPath];
_player=[AVPlayer playerWithURL:url];
AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:_player];
playerLayer.frame=self.photo.frame;
[self.photo.layer addSublayer:playerLayer];
[_player play];
}
}
@end
運(yùn)行效果(視頻錄制):
AVFoundation拍照和錄制視頻
不得不說UIImagePickerController
確實(shí)強(qiáng)大配椭,但是與MPMoviePlayerController
類似虫溜,由于它的高度封裝性,要進(jìn)行某些自定義工作就比較復(fù)雜了股缸。例如要做出一款類似于美顏相機(jī)的拍照界面就比較難以實(shí)現(xiàn)了衡楞,此時(shí)就可以考慮使用AVFoundation
來實(shí)現(xiàn)。AVFoundation
中提供了很多現(xiàn)成的播放器和錄音機(jī)敦姻,但是事實(shí)上它還有更加底層的內(nèi)容可以供開發(fā)者使用瘾境。因?yàn)?code>AVFoundation中抽了很多和底層輸入、輸出設(shè)備打交道的類镰惦,依靠這些類開發(fā)人員面對的不再是封裝好的音頻播放器AVAudioPlayer
迷守、錄音機(jī)(AVAudioRecorder
)、視頻(包括音頻)播放器AVPlayer
旺入,而是輸入設(shè)備(例如麥克風(fēng)兑凿、攝像頭)、輸出設(shè)備(圖片茵瘾、視頻)等急膀。首先了解一下使用AVFoundation
做拍照和視頻錄制開發(fā)用到的相關(guān)類:
AVCaptureSession
:媒體(音、視頻)捕獲會(huì)話龄捡,負(fù)責(zé)把捕獲的音視頻數(shù)據(jù)輸出到輸出設(shè)備中。一個(gè)AVCaptureSession
可以有多個(gè)輸入輸出:
AVCaptureDevice
:輸入設(shè)備慷暂,包括麥克風(fēng)聘殖、攝像頭晨雳,通過該對象可以設(shè)置物理設(shè)備的一些屬性(例如相機(jī)聚焦、白平衡等)奸腺。
AVCaptureDeviceInput
:設(shè)備輸入數(shù)據(jù)管理對象餐禁,可以根據(jù)AVCaptureDevice
創(chuàng)建對應(yīng)的AVCaptureDeviceInput
對象,該對象將會(huì)被添加到AVCaptureSession
中管理突照。
AVCaptureOutput
:輸出數(shù)據(jù)管理對象帮非,用于接收各類輸出數(shù)據(jù),通常使用對應(yīng)的子類AVCaptureAudioDataOutput
讹蘑、AVCaptureStillImageOutput
末盔、AVCaptureVideoDataOutput
、AVCaptureFileOutput
座慰,該對象將會(huì)被添加到AVCaptureSession
中管理陨舱。注意:前面幾個(gè)對象的輸出數(shù)據(jù)都是NSData
類型,而AVCaptureFileOutput
代表數(shù)據(jù)以文件形式輸出版仔,類似的游盲,AVCcaptureFileOutput
也不會(huì)直接創(chuàng)建使用,通常會(huì)使用其子類:AVCaptureAudioFileOutput
蛮粮、AVCaptureMovieFileOutput
益缎。當(dāng)把一個(gè)輸入或者輸出添加到AVCaptureSession
之后AVCaptureSession
就會(huì)在所有相符的輸入、輸出設(shè)備之間建立連接(AVCaptionConnection
):
AVCaptureVideoPreviewLayer
:相機(jī)拍攝預(yù)覽圖層然想,是CALayer
的子類莺奔,使用該對象可以實(shí)時(shí)查看拍照或視頻錄制效果,創(chuàng)建該對象需要指定對應(yīng)的AVCaptureSession
對象又沾。
使用AVFoundation
拍照和錄制視頻的一般步驟如下:
1弊仪、創(chuàng)建AVCaptureSession
對象。
2杖刷、使用AVCaptureDevice
的靜態(tài)方法獲得需要使用的設(shè)備励饵,例如拍照和錄像就需要獲得攝像頭設(shè)備,錄音就要獲得麥克風(fēng)設(shè)備滑燃。
3役听、利用輸入設(shè)備AVCaptureDevice
初始化AVCaptureDeviceInput
對象。
4表窘、初始化輸出數(shù)據(jù)管理對象典予,如果要拍照就初始化AVCaptureStillImageOutput
對象;如果拍攝視頻就初始化AVCaptureMovieFileOutput
對象乐严。
5瘤袖、將數(shù)據(jù)輸入對象AVCaptureDeviceInput
、數(shù)據(jù)輸出對象AVCaptureOutput
添加到媒體會(huì)話管理對象AVCaptureSession
中昂验。
6捂敌、創(chuàng)建視頻預(yù)覽圖層AVCaptureVideoPreviewLayer
并指定媒體會(huì)話艾扮,添加圖層到顯示容器中,調(diào)用AVCaptureSession
的startRuning
方法開始捕獲占婉。
7泡嘴、將捕獲的音頻或視頻數(shù)據(jù)輸出到指定文件。
拍照
下面看一下如何使用AVFoundation
實(shí)現(xiàn)一個(gè)拍照程序逆济,在這個(gè)程序中將實(shí)現(xiàn)攝像頭預(yù)覽酌予、切換前后攝像頭、閃光燈設(shè)置奖慌、對焦抛虫、拍照保存等功能。應(yīng)用大致效果如下:
在程序中定義會(huì)話升薯、輸入莱褒、輸出等相關(guān)對象。
@interface ViewController ()
@property (strong,nonatomic) AVCaptureSession *captureSession;//負(fù)責(zé)輸入和輸出設(shè)備之間的數(shù)據(jù)傳遞
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負(fù)責(zé)從AVCaptureDevice獲得輸入數(shù)據(jù)
@property (strong,nonatomic) AVCaptureStillImageOutput *captureStillImageOutput;//照片輸出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機(jī)拍攝預(yù)覽圖層
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
@property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashAutoButton;//自動(dòng)閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashOnButton;//打開閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashOffButton;//關(guān)閉閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標(biāo)
@end
在控制器視圖將要展示時(shí)創(chuàng)建并初始化會(huì)話涎劈、攝像頭設(shè)備广凸、輸入、輸出蛛枚、預(yù)覽圖層谅海,并且添加預(yù)覽圖層到視圖中,除此之外還做了一些初始化工作蹦浦,例如添加手勢(點(diǎn)擊屏幕進(jìn)行聚焦)扭吁、初始化界面等。
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//初始化會(huì)話
_captureSession=[[AVCaptureSession alloc]init];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//設(shè)置分辨率
_captureSession.sessionPreset=AVCaptureSessionPreset1280x720;
}
//獲得輸入設(shè)備
AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
if (!captureDevice) {
NSLog(@"取得后置攝像頭時(shí)出現(xiàn)問題.");
return;
}
NSError *error=nil;
//根據(jù)輸入設(shè)備初始化設(shè)備輸入對象盲镶,用于獲得輸入數(shù)據(jù)
_captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
if (error) {
NSLog(@"取得設(shè)備輸入對象時(shí)出錯(cuò)侥袜,錯(cuò)誤原因:%@",error.localizedDescription);
return;
}
//初始化設(shè)備輸出對象,用于獲得輸出數(shù)據(jù)
_captureStillImageOutput=[[AVCaptureStillImageOutput alloc]init];
NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
[_captureStillImageOutput setOutputSettings:outputSettings];//輸出設(shè)置
//將設(shè)備輸入添加到會(huì)話中
if ([_captureSession canAddInput:_captureDeviceInput]) {
[_captureSession addInput:_captureDeviceInput];
}
//將設(shè)備輸出添加到會(huì)話中
if ([_captureSession canAddOutput:_captureStillImageOutput]) {
[_captureSession addOutput:_captureStillImageOutput];
}
//創(chuàng)建視頻預(yù)覽層溉贿,用于實(shí)時(shí)展示攝像頭狀態(tài)
_captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
CALayer *layer=self.viewContainer.layer;
layer.masksToBounds=YES;
_captureVideoPreviewLayer.frame=layer.bounds;
_captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
//將視頻預(yù)覽層添加到界面中
//[layer addSublayer:_captureVideoPreviewLayer];
[layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
[self addNotificationToCaptureDevice:captureDevice];
[self addGenstureRecognizer];
[self setFlashModeButtonStatus];
}
在控制器視圖展示和視圖離開界面時(shí)啟動(dòng)枫吧、停止會(huì)話。
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self.captureSession startRunning];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.captureSession stopRunning];
}
定義閃光燈開閉及自動(dòng)模式功能宇色,注意無論是設(shè)置閃光燈九杂、白平衡還是其他輸入設(shè)備屬性,在設(shè)置之前必須先鎖定配置宣蠕,修改完后解鎖例隆。
/**
* 改變設(shè)備屬性的統(tǒng)一操作方法
*
* @param propertyChange 屬性改變操作
*/
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
NSError *error;
//注意改變設(shè)備屬性前一定要首先調(diào)用lockForConfiguration:調(diào)用完之后使用unlockForConfiguration方法解鎖
if ([captureDevice lockForConfiguration:&error]) {
propertyChange(captureDevice);
[captureDevice unlockForConfiguration];
}else{
NSLog(@"設(shè)置設(shè)備屬性過程發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
}
}
/**
* 設(shè)置閃光燈模式
*
* @param flashMode 閃光燈模式
*/
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFlashModeSupported:flashMode]) {
[captureDevice setFlashMode:flashMode];
}
}];
}
定義切換攝像頭功能抢蚀,切換攝像頭的過程就是將原有輸入移除窜醉,在會(huì)話中添加新的輸入脑蠕,但是注意動(dòng)態(tài)修改會(huì)話需要首先開啟配置丹禀,配置成功后提交配置。
#pragma mark 切換前后攝像頭
- (IBAction)toggleButtonClick:(UIButton *)sender {
AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
AVCaptureDevicePosition currentPosition=[currentDevice position];
[self removeNotificationFromCaptureDevice:currentDevice];
AVCaptureDevice *toChangeDevice;
AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
toChangePosition=AVCaptureDevicePositionBack;
}
toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
[self addNotificationToCaptureDevice:toChangeDevice];
//獲得要調(diào)整的設(shè)備輸入對象
AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
//改變會(huì)話的配置前一定要先開啟配置羡微,配置完成后提交配置改變
[self.captureSession beginConfiguration];
//移除原有輸入對象
[self.captureSession removeInput:self.captureDeviceInput];
//添加新的輸入對象
if ([self.captureSession canAddInput:toChangeDeviceInput]) {
[self.captureSession addInput:toChangeDeviceInput];
self.captureDeviceInput=toChangeDeviceInput;
}
//提交會(huì)話配置
[self.captureSession commitConfiguration];
[self setFlashModeButtonStatus];
}
添加點(diǎn)擊手勢操作,點(diǎn)按預(yù)覽視圖時(shí)進(jìn)行聚焦惶我、白平衡設(shè)置。
/**
* 設(shè)置聚焦點(diǎn)
*
* @param point 聚焦點(diǎn)
*/
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
}
if ([captureDevice isFocusPointOfInterestSupported]) {
[captureDevice setFocusPointOfInterest:point];
}
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
}
if ([captureDevice isExposurePointOfInterestSupported]) {
[captureDevice setExposurePointOfInterest:point];
}
}];
}
/**
* 添加點(diǎn)按手勢博投,點(diǎn)按時(shí)聚焦
*/
-(void)addGenstureRecognizer{
UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
[self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
CGPoint point= [tapGesture locationInView:self.viewContainer];
//將UI坐標(biāo)轉(zhuǎn)化為攝像頭坐標(biāo)
CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
[self setFocusCursorWithPoint:point];
[self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}
定義拍照功能绸贡,拍照的過程就是獲取連接,從連接中獲得捕獲的輸出數(shù)據(jù)并做保存操作毅哗。
#pragma mark 拍照
- (IBAction)takeButtonClick:(UIButton *)sender {
//根據(jù)設(shè)備輸出獲得連接
AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];
//根據(jù)連接取得設(shè)備輸出的數(shù)據(jù)
[self.captureStillImageOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer) {
NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *image=[UIImage imageWithData:imageData];
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
// ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
// [assetsLibrary writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:nil];
}
}];
}
最后附上完整代碼:
//
// ViewController.m
// AVFoundationCamera
//
// Created by Kenshin Cui on 14/04/05.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
//
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);
@interface ViewController ()
@property (strong,nonatomic) AVCaptureSession *captureSession;//負(fù)責(zé)輸入和輸出設(shè)備之間的數(shù)據(jù)傳遞
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負(fù)責(zé)從AVCaptureDevice獲得輸入數(shù)據(jù)
@property (strong,nonatomic) AVCaptureStillImageOutput *captureStillImageOutput;//照片輸出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機(jī)拍攝預(yù)覽圖層
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
@property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashAutoButton;//自動(dòng)閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashOnButton;//打開閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashOffButton;//關(guān)閉閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標(biāo)
@end
@implementation ViewController
#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//初始化會(huì)話
_captureSession=[[AVCaptureSession alloc]init];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//設(shè)置分辨率
_captureSession.sessionPreset=AVCaptureSessionPreset1280x720;
}
//獲得輸入設(shè)備
AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
if (!captureDevice) {
NSLog(@"取得后置攝像頭時(shí)出現(xiàn)問題.");
return;
}
NSError *error=nil;
//根據(jù)輸入設(shè)備初始化設(shè)備輸入對象听怕,用于獲得輸入數(shù)據(jù)
_captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
if (error) {
NSLog(@"取得設(shè)備輸入對象時(shí)出錯(cuò),錯(cuò)誤原因:%@",error.localizedDescription);
return;
}
//初始化設(shè)備輸出對象虑绵,用于獲得輸出數(shù)據(jù)
_captureStillImageOutput=[[AVCaptureStillImageOutput alloc]init];
NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
[_captureStillImageOutput setOutputSettings:outputSettings];//輸出設(shè)置
//將設(shè)備輸入添加到會(huì)話中
if ([_captureSession canAddInput:_captureDeviceInput]) {
[_captureSession addInput:_captureDeviceInput];
}
//將設(shè)備輸出添加到會(huì)話中
if ([_captureSession canAddOutput:_captureStillImageOutput]) {
[_captureSession addOutput:_captureStillImageOutput];
}
//創(chuàng)建視頻預(yù)覽層尿瞭,用于實(shí)時(shí)展示攝像頭狀態(tài)
_captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
CALayer *layer=self.viewContainer.layer;
layer.masksToBounds=YES;
_captureVideoPreviewLayer.frame=layer.bounds;
_captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
//將視頻預(yù)覽層添加到界面中
//[layer addSublayer:_captureVideoPreviewLayer];
[layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
[self addNotificationToCaptureDevice:captureDevice];
[self addGenstureRecognizer];
[self setFlashModeButtonStatus];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self.captureSession startRunning];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.captureSession stopRunning];
}
-(void)dealloc{
[self removeNotification];
}
#pragma mark - UI方法
#pragma mark 拍照
- (IBAction)takeButtonClick:(UIButton *)sender {
//根據(jù)設(shè)備輸出獲得連接
AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];
//根據(jù)連接取得設(shè)備輸出的數(shù)據(jù)
[self.captureStillImageOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer) {
NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *image=[UIImage imageWithData:imageData];
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
// ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
// [assetsLibrary writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:nil];
}
}];
}
#pragma mark 切換前后攝像頭
- (IBAction)toggleButtonClick:(UIButton *)sender {
AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
AVCaptureDevicePosition currentPosition=[currentDevice position];
[self removeNotificationFromCaptureDevice:currentDevice];
AVCaptureDevice *toChangeDevice;
AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
toChangePosition=AVCaptureDevicePositionBack;
}
toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
[self addNotificationToCaptureDevice:toChangeDevice];
//獲得要調(diào)整的設(shè)備輸入對象
AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
//改變會(huì)話的配置前一定要先開啟配置,配置完成后提交配置改變
[self.captureSession beginConfiguration];
//移除原有輸入對象
[self.captureSession removeInput:self.captureDeviceInput];
//添加新的輸入對象
if ([self.captureSession canAddInput:toChangeDeviceInput]) {
[self.captureSession addInput:toChangeDeviceInput];
self.captureDeviceInput=toChangeDeviceInput;
}
//提交會(huì)話配置
[self.captureSession commitConfiguration];
[self setFlashModeButtonStatus];
}
#pragma mark 自動(dòng)閃光燈開啟
- (IBAction)flashAutoClick:(UIButton *)sender {
[self setFlashMode:AVCaptureFlashModeAuto];
[self setFlashModeButtonStatus];
}
#pragma mark 打開閃光燈
- (IBAction)flashOnClick:(UIButton *)sender {
[self setFlashMode:AVCaptureFlashModeOn];
[self setFlashModeButtonStatus];
}
#pragma mark 關(guān)閉閃光燈
- (IBAction)flashOffClick:(UIButton *)sender {
[self setFlashMode:AVCaptureFlashModeOff];
[self setFlashModeButtonStatus];
}
#pragma mark - 通知
/**
* 給輸入設(shè)備添加通知
*/
-(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{
//注意添加區(qū)域改變捕獲通知必須首先設(shè)置設(shè)備允許捕獲
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
captureDevice.subjectAreaChangeMonitoringEnabled=YES;
}];
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
//捕獲區(qū)域發(fā)生改變
[notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
-(void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice{
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
/**
* 移除所有通知
*/
-(void)removeNotification{
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self];
}
-(void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession{
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
//會(huì)話出錯(cuò)
[notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
}
/**
* 設(shè)備連接成功
*
* @param notification 通知對象
*/
-(void)deviceConnected:(NSNotification *)notification{
NSLog(@"設(shè)備已連接...");
}
/**
* 設(shè)備連接斷開
*
* @param notification 通知對象
*/
-(void)deviceDisconnected:(NSNotification *)notification{
NSLog(@"設(shè)備已斷開.");
}
/**
* 捕獲區(qū)域改變
*
* @param notification 通知對象
*/
-(void)areaChange:(NSNotification *)notification{
NSLog(@"捕獲區(qū)域改變...");
}
/**
* 會(huì)話出錯(cuò)
*
* @param notification 通知對象
*/
-(void)sessionRuntimeError:(NSNotification *)notification{
NSLog(@"會(huì)話發(fā)生錯(cuò)誤.");
}
#pragma mark - 私有方法
/**
* 取得指定位置的攝像頭
*
* @param position 攝像頭位置
*
* @return 攝像頭設(shè)備
*/
-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{
NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *camera in cameras) {
if ([camera position]==position) {
return camera;
}
}
return nil;
}
/**
* 改變設(shè)備屬性的統(tǒng)一操作方法
*
* @param propertyChange 屬性改變操作
*/
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
NSError *error;
//注意改變設(shè)備屬性前一定要首先調(diào)用lockForConfiguration:調(diào)用完之后使用unlockForConfiguration方法解鎖
if ([captureDevice lockForConfiguration:&error]) {
propertyChange(captureDevice);
[captureDevice unlockForConfiguration];
}else{
NSLog(@"設(shè)置設(shè)備屬性過程發(fā)生錯(cuò)誤翅睛,錯(cuò)誤信息:%@",error.localizedDescription);
}
}
/**
* 設(shè)置閃光燈模式
*
* @param flashMode 閃光燈模式
*/
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFlashModeSupported:flashMode]) {
[captureDevice setFlashMode:flashMode];
}
}];
}
/**
* 設(shè)置聚焦模式
*
* @param focusMode 聚焦模式
*/
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:focusMode];
}
}];
}
/**
* 設(shè)置曝光模式
*
* @param exposureMode 曝光模式
*/
-(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:exposureMode];
}
}];
}
/**
* 設(shè)置聚焦點(diǎn)
*
* @param point 聚焦點(diǎn)
*/
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
}
if ([captureDevice isFocusPointOfInterestSupported]) {
[captureDevice setFocusPointOfInterest:point];
}
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
}
if ([captureDevice isExposurePointOfInterestSupported]) {
[captureDevice setExposurePointOfInterest:point];
}
}];
}
/**
* 添加點(diǎn)按手勢声搁,點(diǎn)按時(shí)聚焦
*/
-(void)addGenstureRecognizer{
UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
[self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
CGPoint point= [tapGesture locationInView:self.viewContainer];
//將UI坐標(biāo)轉(zhuǎn)化為攝像頭坐標(biāo)
CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
[self setFocusCursorWithPoint:point];
[self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}
/**
* 設(shè)置閃光燈按鈕狀態(tài)
*/
-(void)setFlashModeButtonStatus{
AVCaptureDevice *captureDevice=[self.captureDeviceInput device];
AVCaptureFlashMode flashMode=captureDevice.flashMode;
if([captureDevice isFlashAvailable]){
self.flashAutoButton.hidden=NO;
self.flashOnButton.hidden=NO;
self.flashOffButton.hidden=NO;
self.flashAutoButton.enabled=YES;
self.flashOnButton.enabled=YES;
self.flashOffButton.enabled=YES;
switch (flashMode) {
case AVCaptureFlashModeAuto:
self.flashAutoButton.enabled=NO;
break;
case AVCaptureFlashModeOn:
self.flashOnButton.enabled=NO;
break;
case AVCaptureFlashModeOff:
self.flashOffButton.enabled=NO;
break;
default:
break;
}
}else{
self.flashAutoButton.hidden=YES;
self.flashOnButton.hidden=YES;
self.flashOffButton.hidden=YES;
}
}
/**
* 設(shè)置聚焦光標(biāo)位置
*
* @param point 光標(biāo)位置
*/
-(void)setFocusCursorWithPoint:(CGPoint)point{
self.focusCursor.center=point;
self.focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
self.focusCursor.alpha=1.0;
[UIView animateWithDuration:1.0 animations:^{
self.focusCursor.transform=CGAffineTransformIdentity;
} completion:^(BOOL finished) {
self.focusCursor.alpha=0;
}];
}
@end
視頻錄制
其實(shí)有了前面的拍照應(yīng)用之后要在此基礎(chǔ)上做視頻錄制功能并不復(fù)雜,程序只需要做如下修改:
1捕发、添加一個(gè)音頻輸入到會(huì)話(使用[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject]
獲得輸入設(shè)備疏旨,然后根據(jù)此輸入設(shè)備創(chuàng)建一個(gè)設(shè)備輸入對象),在拍照程序中已經(jīng)添加了視頻輸入所以此時(shí)不需要添加視頻輸入扎酷。
2檐涝、創(chuàng)建一個(gè)音樂播放文件輸出對象AVCaptureMovieFileOutput
取代原來的照片輸出對象。
3法挨、將捕獲到的視頻數(shù)據(jù)寫入到臨時(shí)文件并在停止錄制之后保存到相簿(通過AVCaptureMovieFileOutput
的代理方法)谁榜。
相比拍照程序,程序的修改主要就是以上三點(diǎn)凡纳。當(dāng)然為了讓程序更加完善在下面的視頻錄制程序中加入了屏幕旋轉(zhuǎn)視頻窃植、自動(dòng)布局和后臺(tái)保存任務(wù)等細(xì)節(jié)。下面是修改后的程序:
//
// ViewController.m
// AVFoundationCamera
//
// Created by Kenshin Cui on 14/04/05.
// Copyright (c) 2014年 cmjstudio. All rights reserved.
// 視頻錄制
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);
@interface ViewController ()<AVCaptureFileOutputRecordingDelegate>//視頻文件輸出代理
@property (strong,nonatomic) AVCaptureSession *captureSession;//負(fù)責(zé)輸入和輸出設(shè)備之間的數(shù)據(jù)傳遞
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負(fù)責(zé)從AVCaptureDevice獲得輸入數(shù)據(jù)
@property (strong,nonatomic) AVCaptureMovieFileOutput *captureMovieFileOutput;//視頻輸出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機(jī)拍攝預(yù)覽圖層
@property (assign,nonatomic) BOOL enableRotation;//是否允許旋轉(zhuǎn)(注意在視頻錄制過程中禁止屏幕旋轉(zhuǎn))
@property (assign,nonatomic) CGRect *lastBounds;//旋轉(zhuǎn)的前大小
@property (assign,nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier;//后臺(tái)任務(wù)標(biāo)識(shí)
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
@property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按鈕
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標(biāo)
@end
@implementation ViewController
#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//初始化會(huì)話
_captureSession=[[AVCaptureSession alloc]init];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//設(shè)置分辨率
_captureSession.sessionPreset=AVCaptureSessionPreset1280x720;
}
//獲得輸入設(shè)備
AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
if (!captureDevice) {
NSLog(@"取得后置攝像頭時(shí)出現(xiàn)問題.");
return;
}
//添加一個(gè)音頻輸入設(shè)備
AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
NSError *error=nil;
//根據(jù)輸入設(shè)備初始化設(shè)備輸入對象惫企,用于獲得輸入數(shù)據(jù)
_captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
if (error) {
NSLog(@"取得設(shè)備輸入對象時(shí)出錯(cuò)撕瞧,錯(cuò)誤原因:%@",error.localizedDescription);
return;
}
AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error];
if (error) {
NSLog(@"取得設(shè)備輸入對象時(shí)出錯(cuò),錯(cuò)誤原因:%@",error.localizedDescription);
return;
}
//初始化設(shè)備輸出對象狞尔,用于獲得輸出數(shù)據(jù)
_captureMovieFileOutput=[[AVCaptureMovieFileOutput alloc]init];
//將設(shè)備輸入添加到會(huì)話中
if ([_captureSession canAddInput:_captureDeviceInput]) {
[_captureSession addInput:_captureDeviceInput];
[_captureSession addInput:audioCaptureDeviceInput];
AVCaptureConnection *captureConnection=[_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if ([captureConnection isVideoStabilizationSupported ]) {
captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
}
}
//將設(shè)備輸出添加到會(huì)話中
if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
[_captureSession addOutput:_captureMovieFileOutput];
}
//創(chuàng)建視頻預(yù)覽層丛版,用于實(shí)時(shí)展示攝像頭狀態(tài)
_captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
CALayer *layer=self.viewContainer.layer;
layer.masksToBounds=YES;
_captureVideoPreviewLayer.frame=layer.bounds;
_captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
//將視頻預(yù)覽層添加到界面中
//[layer addSublayer:_captureVideoPreviewLayer];
[layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
_enableRotation=YES;
[self addNotificationToCaptureDevice:captureDevice];
[self addGenstureRecognizer];
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self.captureSession startRunning];
}
-(void)viewDidDisappear:(BOOL)animated{
[super viewDidDisappear:animated];
[self.captureSession stopRunning];
}
-(BOOL)shouldAutorotate{
return self.enableRotation;
}
////屏幕旋轉(zhuǎn)時(shí)調(diào)整視頻預(yù)覽圖層的方向
//-(void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{
// [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
//// NSLog(@"%i,%i",newCollection.verticalSizeClass,newCollection.horizontalSizeClass);
// UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
// NSLog(@"%i",orientation);
// AVCaptureConnection *captureConnection=[self.captureVideoPreviewLayer connection];
// captureConnection.videoOrientation=orientation;
//
//}
//屏幕旋轉(zhuǎn)時(shí)調(diào)整視頻預(yù)覽圖層的方向
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
AVCaptureConnection *captureConnection=[self.captureVideoPreviewLayer connection];
captureConnection.videoOrientation=(AVCaptureVideoOrientation)toInterfaceOrientation;
}
//旋轉(zhuǎn)后重新設(shè)置大小
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
_captureVideoPreviewLayer.frame=self.viewContainer.bounds;
}
-(void)dealloc{
[self removeNotification];
}
#pragma mark - UI方法
#pragma mark 視頻錄制
- (IBAction)takeButtonClick:(UIButton *)sender {
//根據(jù)設(shè)備輸出獲得連接
AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
//根據(jù)連接取得設(shè)備輸出的數(shù)據(jù)
if (![self.captureMovieFileOutput isRecording]) {
self.enableRotation=NO;
//如果支持多任務(wù)則則開始多任務(wù)
if ([[UIDevice currentDevice] isMultitaskingSupported]) {
self.backgroundTaskIdentifier=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
}
//預(yù)覽圖層和視頻方向保持一致
captureConnection.videoOrientation=[self.captureVideoPreviewLayer connection].videoOrientation;
NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:@"myMovie.mov"];
NSLog(@"save path is :%@",outputFielPath);
NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];
[self.captureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
}
else{
[self.captureMovieFileOutput stopRecording];//停止錄制
}
}
#pragma mark 切換前后攝像頭
- (IBAction)toggleButtonClick:(UIButton *)sender {
AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
AVCaptureDevicePosition currentPosition=[currentDevice position];
[self removeNotificationFromCaptureDevice:currentDevice];
AVCaptureDevice *toChangeDevice;
AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
toChangePosition=AVCaptureDevicePositionBack;
}
toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
[self addNotificationToCaptureDevice:toChangeDevice];
//獲得要調(diào)整的設(shè)備輸入對象
AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
//改變會(huì)話的配置前一定要先開啟配置,配置完成后提交配置改變
[self.captureSession beginConfiguration];
//移除原有輸入對象
[self.captureSession removeInput:self.captureDeviceInput];
//添加新的輸入對象
if ([self.captureSession canAddInput:toChangeDeviceInput]) {
[self.captureSession addInput:toChangeDeviceInput];
self.captureDeviceInput=toChangeDeviceInput;
}
//提交會(huì)話配置
[self.captureSession commitConfiguration];
}
#pragma mark - 視頻輸出代理
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
NSLog(@"開始錄制...");
}
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
NSLog(@"視頻錄制完成.");
//視頻錄入完成之后在后臺(tái)將視頻存儲(chǔ)到相簿
self.enableRotation=YES;
UIBackgroundTaskIdentifier lastBackgroundTaskIdentifier=self.backgroundTaskIdentifier;
self.backgroundTaskIdentifier=UIBackgroundTaskInvalid;
ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
[assetsLibrary writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error) {
NSLog(@"保存視頻到相簿過程中發(fā)生錯(cuò)誤偏序,錯(cuò)誤信息:%@",error.localizedDescription);
}
if (lastBackgroundTaskIdentifier!=UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:lastBackgroundTaskIdentifier];
}
NSLog(@"成功保存視頻到相簿.");
}];
}
#pragma mark - 通知
/**
* 給輸入設(shè)備添加通知
*/
-(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{
//注意添加區(qū)域改變捕獲通知必須首先設(shè)置設(shè)備允許捕獲
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
captureDevice.subjectAreaChangeMonitoringEnabled=YES;
}];
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
//捕獲區(qū)域發(fā)生改變
[notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
-(void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice{
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
/**
* 移除所有通知
*/
-(void)removeNotification{
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self];
}
-(void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession{
NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
//會(huì)話出錯(cuò)
[notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
}
/**
* 設(shè)備連接成功
*
* @param notification 通知對象
*/
-(void)deviceConnected:(NSNotification *)notification{
NSLog(@"設(shè)備已連接...");
}
/**
* 設(shè)備連接斷開
*
* @param notification 通知對象
*/
-(void)deviceDisconnected:(NSNotification *)notification{
NSLog(@"設(shè)備已斷開.");
}
/**
* 捕獲區(qū)域改變
*
* @param notification 通知對象
*/
-(void)areaChange:(NSNotification *)notification{
NSLog(@"捕獲區(qū)域改變...");
}
/**
* 會(huì)話出錯(cuò)
*
* @param notification 通知對象
*/
-(void)sessionRuntimeError:(NSNotification *)notification{
NSLog(@"會(huì)話發(fā)生錯(cuò)誤.");
}
#pragma mark - 私有方法
/**
* 取得指定位置的攝像頭
*
* @param position 攝像頭位置
*
* @return 攝像頭設(shè)備
*/
-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{
NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
for (AVCaptureDevice *camera in cameras) {
if ([camera position]==position) {
return camera;
}
}
return nil;
}
/**
* 改變設(shè)備屬性的統(tǒng)一操作方法
*
* @param propertyChange 屬性改變操作
*/
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
NSError *error;
//注意改變設(shè)備屬性前一定要首先調(diào)用lockForConfiguration:調(diào)用完之后使用unlockForConfiguration方法解鎖
if ([captureDevice lockForConfiguration:&error]) {
propertyChange(captureDevice);
[captureDevice unlockForConfiguration];
}else{
NSLog(@"設(shè)置設(shè)備屬性過程發(fā)生錯(cuò)誤页畦,錯(cuò)誤信息:%@",error.localizedDescription);
}
}
/**
* 設(shè)置閃光燈模式
*
* @param flashMode 閃光燈模式
*/
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFlashModeSupported:flashMode]) {
[captureDevice setFlashMode:flashMode];
}
}];
}
/**
* 設(shè)置聚焦模式
*
* @param focusMode 聚焦模式
*/
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:focusMode];
}
}];
}
/**
* 設(shè)置曝光模式
*
* @param exposureMode 曝光模式
*/
-(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:exposureMode];
}
}];
}
/**
* 設(shè)置聚焦點(diǎn)
*
* @param point 聚焦點(diǎn)
*/
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
if ([captureDevice isFocusModeSupported:focusMode]) {
[captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
}
if ([captureDevice isFocusPointOfInterestSupported]) {
[captureDevice setFocusPointOfInterest:point];
}
if ([captureDevice isExposureModeSupported:exposureMode]) {
[captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
}
if ([captureDevice isExposurePointOfInterestSupported]) {
[captureDevice setExposurePointOfInterest:point];
}
}];
}
/**
* 添加點(diǎn)按手勢,點(diǎn)按時(shí)聚焦
*/
-(void)addGenstureRecognizer{
UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
[self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
CGPoint point= [tapGesture locationInView:self.viewContainer];
//將UI坐標(biāo)轉(zhuǎn)化為攝像頭坐標(biāo)
CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
[self setFocusCursorWithPoint:point];
[self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}
/**
* 設(shè)置聚焦光標(biāo)位置
*
* @param point 光標(biāo)位置
*/
-(void)setFocusCursorWithPoint:(CGPoint)point{
self.focusCursor.center=point;
self.focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
self.focusCursor.alpha=1.0;
[UIView animateWithDuration:1.0 animations:^{
self.focusCursor.transform=CGAffineTransformIdentity;
} completion:^(BOOL finished) {
self.focusCursor.alpha=0;
}];
}
@end
總結(jié)
前面用了大量的篇幅介紹了iOS中的音研儒、視頻播放和錄制豫缨,有些地方用到了封裝好的播放器独令、錄音機(jī)直接使用,有些是直接調(diào)用系統(tǒng)服務(wù)自己組織封裝好芭,正如本篇開頭所言燃箭,iOS對于多媒體支持相當(dāng)靈活和完善,那么開放過程中如何選擇呢舍败,下面就以一個(gè)表格簡單對比一下各個(gè)開發(fā)技術(shù)的優(yōu)缺點(diǎn)招狸。
參考:
http://www.cnblogs.com/kenshincui/p/4186022.html#mpMoviePlayerViewController