日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS开发系列--音频播放、录音、视频播放、拍照、视频录制(转)

發布時間:2023/12/19 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS开发系列--音频播放、录音、视频播放、拍照、视频录制(转) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

概覽

隨著移動互聯網的發展,如今的手機早已不是打電話、發短信那么簡單了,播放音樂、視頻、錄音、拍照等都是很常用的功能。在iOS中對于多媒體的支持是非常強大的,無論是音視頻播放、錄制,還是對麥克風、攝像頭的操作都提供了多套API。在今天的文章中將會對這些內容進行一一介紹:

  • 音頻
  • 音效
  • 音樂
  • 音頻會話
  • 錄音
  • 音頻隊列服務
  • 視頻
  • MPMoviePlayerController
  • MPMoviePlayerViewController
  • AVPlayer
  • 攝像頭
  • UIImagePickerController拍照和視頻錄制
  • AVFoundation拍照和錄制視頻
  • 總結
  • 目?錄
  • 音頻

    在iOS中音頻播放從形式上可以分為音效播放和音樂播放。前者主要指的是一些短音頻播放,通常作為點綴音頻,對于這類音頻不需要進行進度、循環等控制。后者指的是一些較長的音頻,通常是主音頻,對于這些音頻的播放通常需要進行精確的控制。在iOS中播放兩類音頻分別使用AudioToolbox.framework和AVFoundation.framework來完成音效和音樂播放。

    音效

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

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

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

  • 調用AudioServicesCreateSystemSoundID(?? CFURLRef? inFileURL, SystemSoundID*?? outSystemSoundID)函數獲得系統聲音ID。
  • 如果需要監聽播放完成操作,則使用AudioServicesAddSystemSoundCompletion(? SystemSoundID inSystemSoundID,
    CFRunLoopRef? inRunLoop, CFStringRef? inRunLoopMode, AudioServicesSystemSoundCompletionProc? inCompletionRoutine, void*? inClientData)
    方法注冊回調函數。
  • 調用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID)?或者AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID)?方法播放音效(后者帶有震動效果)。
  • 下面是一個簡單的示例程序:

    // // 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"]; }/*** 播放完成回調函數** @param soundID 系統聲音ID* @param clientData 回調時傳遞的數據*/ 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.獲得系統聲音IDSystemSoundID soundID=0;/*** inFileUrl:音頻文件url* outSystemSoundID:聲音id(此函數會將音效文件加入到系統音頻服務中并返回一個長整形ID)*/AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);//如果需要在播放完之后執行某些操作,可以調用如下方法注冊一個播放完成回調函數AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);//2.播放音頻AudioServicesPlaySystemSound(soundID);//播放音效 // AudioServicesPlayAlertSound(soundID);//播放音效并震動 }@end

    音樂

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

    屬性說明
    @property(readonly, getter=isPlaying) BOOL playing是否正在播放,只讀
    @property(readonly) NSUInteger numberOfChannels音頻聲道數,只讀
    @property(readonly) NSTimeInterval duration音頻時長
    @property(readonly) NSURL *url音頻文件路徑,只讀
    @property(readonly) NSData *data音頻數據,只讀
    @property float pan立體聲平衡,如果為-1.0則完全左聲道,如果0.0則左右聲道平衡,如果為1.0則完全為右聲道
    @property float volume音量大小,范圍0-1.0
    @property BOOL enableRate是否允許改變播放速率
    @property float rate播放速率,范圍0.5-2.0,如果為1.0則正常播放,如果要修改播放速率則必須設置enableRate為YES
    @property NSTimeInterval currentTime當前播放時長
    @property(readonly) NSTimeInterval deviceCurrentTime輸出設備播放音頻的時間,注意如果播放中被暫停此時間也會繼續累加
    @property NSInteger numberOfLoops循環播放次數,如果為0則不循環,如果小于0則無限循環,大于0則表示循環次數
    @property(readonly) NSDictionary *settings音頻播放設置信息,只讀
    @property(getter=isMeteringEnabled) BOOL meteringEnabled是否啟用音頻測量,默認為NO,一旦啟用音頻測量可以通過updateMeters方法更新測量值
    對象方法說明
    - (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError使用文件URL初始化播放器,注意這個URL不能是HTTP URL,AVAudioPlayer不支持加載網絡媒體流,只能播放本地文件
    - (instancetype)initWithData:(NSData *)data error:(NSError **)outError使用NSData初始化播放器,注意使用此方法時必須文件格式和文件后綴一致,否則出錯,所以相比此方法更推薦使用上述方法或- (instancetype)initWithData:(NSData *)data fileTypeHint:(NSString *)utiString error:(NSError **)outError方法進行初始化
    - (BOOL)prepareToPlay;加載音頻文件到緩沖區,注意即使在播放之前音頻文件沒有加載到緩沖區程序也會隱式調用此方法。
    - (BOOL)play;播放音頻文件
    - (BOOL)playAtTime:(NSTimeInterval)time在指定的時間開始播放音頻
    - (void)pause;暫停播放
    - (void)stop;停止播放
    - (void)updateMeters更新音頻測量值,注意如果要更新音頻測量值必須設置meteringEnabled為YES,通過音頻測量值可以即時獲得音頻分貝等信息
    - (float)peakPowerForChannel:(NSUInteger)channelNumber;獲得指定聲道的分貝峰值,注意如果要獲得分貝峰值必須在此之前調用updateMeters方法
    - (float)averagePowerForChannel:(NSUInteger)channelNumber獲得指定聲道的分貝平均值,注意如果要獲得分貝平均值必須在此之前調用updateMeters方法
    @property(nonatomic, copy) NSArray *channelAssignments獲得或設置播放聲道
    代理方法說明
    - (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag音頻播放完成
    - (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error音頻解碼發生錯誤

    AVAudioPlayer的使用比較簡單:

  • 初始化AVAudioPlayer對象,此時通常指定本地文件路徑。
  • 設置播放器屬性,例如重復次數、音量大小等。
  • 調用play方法播放。
  • 下面就使用AVAudioPlayer實現一個簡單播放器,在這個播放器中實現了播放、暫停、顯示播放進度功能,當然例如調節音量、設置循環模式、甚至是聲波圖像(通過分析音頻分貝值)等功能都可以實現,這里就不再一一演示。界面效果如下:

    當然由于AVAudioPlayer一次只能播放一個音頻文件,所有上一曲、下一曲其實可以通過創建多個播放器對象來完成,這里暫不實現。播放進度的實現主要依靠一個定時器實時計算當前播放時長和音頻總時長的比例,另外為了演示委托方法,下面的代碼中也實現了播放完成委托方法,通常如果有下一曲功能的話播放完可以觸發下一曲音樂播放。下面是主要代碼:

    // // 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;//播放進度 @property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者 @property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕(如果tag為0認為是暫停狀態,1是播放狀態)@property (weak ,nonatomic) NSTimer *timer;//進度更新定時器@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; }/*** 創建播放器** @return 音頻播放器*/ -(AVAudioPlayer *)audioPlayer{if (!_audioPlayer) {NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil];NSURL *url=[NSURL fileURLWithPath:urlStr];NSError *error=nil;//初始化播放器,注意這里的Url參數只能時文件路徑,不支持HTTP Url_audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];//設置播放器屬性_audioPlayer.numberOfLoops=0;//設置為0不循環_audioPlayer.delegate=self;[_audioPlayer prepareToPlay];//加載音頻文件到緩存if(error){NSLog(@"初始化播放器過程發生錯誤,錯誤信息:%@",error.localizedDescription);return nil;}}return _audioPlayer; }/*** 播放音頻*/ -(void)play{if (![self.audioPlayer isPlaying]) {[self.audioPlayer play];self.timer.fireDate=[NSDate distantPast];//恢復定時器} }/*** 暫停播放*/ -(void)pause{if ([self.audioPlayer isPlaying]) {[self.audioPlayer pause];self.timer.fireDate=[NSDate distantFuture];//暫停定時器,注意不能調用invalidate方法,此方法會取消,之后無法恢復} }/*** 點擊播放/暫停按鈕** @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];} }/*** 更新播放進度*/ -(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 運行效果:

    音頻會話

    事實上上面的播放器還存在一些問題,例如通常我們看到的播放器即使退出到后臺也是可以播放的,而這個播放器如果退出到后臺它會自動暫停。如果要支持后臺播放需要做下面幾件事情:

    1.設置后臺運行模式:在plist文件中添加Required background modes,并且設置item 0=App plays audio or streams audio/video using AirPlay(其實可以直接通過Xcode在Project Targets-Capabilities-Background Modes中設置)

    2.設置AVAudioSession的類型為AVAudioSessionCategoryPlayback并且調用setActive::方法啟動會話。

    AVAudioSession *audioSession=[AVAudioSession sharedInstance];[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];[audioSession setActive:YES error:nil];

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

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

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

    會話類型說明是否要求輸入是否要求輸出是否遵從靜音鍵
    AVAudioSessionCategoryAmbient混音播放,可以與其他音頻應用同時播放
    AVAudioSessionCategorySoloAmbient獨占播放
    AVAudioSessionCategoryPlayback后臺播放,也是獨占的
    AVAudioSessionCategoryRecord錄音模式,用于錄音時使用
    AVAudioSessionCategoryPlayAndRecord播放和錄音,此時可以錄音也可以播放
    AVAudioSessionCategoryAudioProcessing硬件解碼音頻,此時不能播放和錄制
    AVAudioSessionCategoryMultiRoute多種輸入輸出,例如可以耳機、USB設備同時播放

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

    根據前面對音頻會話的理解,相信大家開發出能夠在后臺播放的音頻播放器并不難,但是注意一下,在前面的代碼中也提到設置完音頻會話類型之后需要調用setActive::方法將會話激活才能起作用。類似的,如果一個應用已經在播放音頻,打開我們的應用之后設置了在后臺播放的會話類型,此時其他應用的音頻會停止而播放我們的音頻,如果希望我們的程序音頻播放完之后(關閉或退出到后臺之后)能夠繼續播放其他應用的音頻的話則可以調用setActive::方法關閉會話。代碼如下:

    // // ViewController.m // KCAVAudioPlayer // // Created by Kenshin Cui on 14/03/30. // Copyright (c) 2014年 cmjstudio. All rights reserved. // AVAudioSession 音頻會話#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;//播放進度 @property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者 @property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕(如果tag為0認為是暫停狀態,1是播放狀態)@property (weak ,nonatomic) NSTimer *timer;//進度更新定時器@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];[self setupUI];}/*** 顯示當面視圖控制器時注冊遠程事件** @param animated 是否以動畫的形式顯示*/ -(void)viewWillAppear:(BOOL)animated{[super viewWillAppear:animated];//開啟遠程控制[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];//作為第一響應者//[self becomeFirstResponder]; } /*** 當前控制器視圖不顯示時取消遠程控制** @param animated 是否以動畫的形式消失*/ -(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; }/*** 創建播放器** @return 音頻播放器*/ -(AVAudioPlayer *)audioPlayer{if (!_audioPlayer) {NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil];NSURL *url=[NSURL fileURLWithPath:urlStr];NSError *error=nil;//初始化播放器,注意這里的Url參數只能時文件路徑,不支持HTTP Url_audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];//設置播放器屬性_audioPlayer.numberOfLoops=0;//設置為0不循環_audioPlayer.delegate=self;[_audioPlayer prepareToPlay];//加載音頻文件到緩存if(error){NSLog(@"初始化播放器過程發生錯誤,錯誤信息:%@",error.localizedDescription);return nil;}//設置后臺播放模式AVAudioSession *audioSession=[AVAudioSession sharedInstance];[audioSession setCategory:AVAudioSessionCategoryPlayback error:nil]; // [audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil];[audioSession setActive:YES error:nil];//添加通知,拔出耳機后暫停播放[[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];//恢復定時器} }/*** 暫停播放*/ -(void)pause{if ([self.audioPlayer isPlaying]) {[self.audioPlayer pause];self.timer.fireDate=[NSDate distantFuture];//暫停定時器,注意不能調用invalidate方法,此方法會取消,之后無法恢復} }/*** 點擊播放/暫停按鈕** @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];} }/*** 更新播放進度*/ -(void)updateProgress{float progress= self.audioPlayer.currentTime /self.audioPlayer.duration;[self.playProgress setProgress:progress animated:true]; }/*** 一旦輸出改變則執行此方法** @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];//原設備為耳機則暫停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(@"音樂播放完成...");//根據實際情況播放完成可以將會話關閉,其他音頻應用繼續播放[[AVAudioSession sharedInstance]setActive:NO error:nil]; }@end

    在上面的代碼中還實現了拔出耳機暫停音樂播放的功能,這也是一個比較常見的功能。在iOS7及以后的版本中可以通過通知獲得輸出改變的通知,然后拿到通知對象后根據userInfo獲得是何種改變類型,進而根據情況對音樂進行暫停操作。

    擴展--播放音樂庫中的音樂

    眾所周知音樂是iOS的重要組成播放,無論是iPod、iTouch、iPhone還是iPad都可以在iTunes購買音樂或添加本地音樂到音樂庫中同步到你的iOS設備。在MediaPlayer.frameowork中有一個MPMusicPlayerController用于播放音樂庫中的音樂。

    下面先來看一下MPMusicPlayerController的常用屬性和方法:

    屬性說明
    @property (nonatomic, readonly) MPMusicPlaybackState playbackState播放器狀態,枚舉類型:
    MPMusicPlaybackStateStopped:停止播放 MPMusicPlaybackStatePlaying:正在播放
    MPMusicPlaybackStatePaused:暫停播放
    MPMusicPlaybackStateInterrupted:播放中斷
    MPMusicPlaybackStateSeekingForward:向前查找
    MPMusicPlaybackStateSeekingBackward:向后查找
    @property (nonatomic) MPMusicRepeatMode repeatMode重復模式,枚舉類型:
    MPMusicRepeatModeDefault:默認模式,使用用戶的首選項(系統音樂程序設置)
    MPMusicRepeatModeNone:不重復
    MPMusicRepeatModeOne:單曲循環
    MPMusicRepeatModeAll:在當前列表內循環
    @property (nonatomic) MPMusicShuffleMode shuffleMode隨機播放模式,枚舉類型:
    MPMusicShuffleModeDefault:默認模式,使用用戶首選項(系統音樂程序設置)
    MPMusicShuffleModeOff:不隨機播放
    MPMusicShuffleModeSongs:按歌曲隨機播放
    MPMusicShuffleModeAlbums:按專輯隨機播放
    @property (nonatomic, copy) MPMediaItem *nowPlayingItem正在播放的音樂項
    @property (nonatomic, readonly) NSUInteger indexOfNowPlayingItem當前正在播放的音樂在播放隊列中的索引
    @property(nonatomic, readonly) BOOL isPreparedToPlay是否準好播放準備
    @property(nonatomic) NSTimeInterval currentPlaybackTime當前已播放時間,單位:秒
    @property(nonatomic) float currentPlaybackRate當前播放速度,是一個播放速度倍率,0表示暫停播放,1代表正常速度
    類方法說明
    + (MPMusicPlayerController *)applicationMusicPlayer;獲取應用播放器,注意此類播放器無法在后臺播放
    + (MPMusicPlayerController *)systemMusicPlayer獲取系統播放器,支持后臺播放
    對象方法說明
    - (void)setQueueWithQuery:(MPMediaQuery *)query使用媒體隊列設置播放源媒體隊列
    - (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection使用媒體項集合設置播放源媒體隊列
    - (void)skipToNextItem下一曲
    - (void)skipToBeginning從起始位置播放
    - (void)skipToPreviousItem上一曲
    - (void)beginGeneratingPlaybackNotifications開啟播放通知,注意不同于其他播放器,MPMusicPlayerController要想獲得通知必須首先開啟,默認情況無法獲得通知
    - (void)endGeneratingPlaybackNotifications關閉播放通知
    - (void)prepareToPlay做好播放準備(加載音頻到緩沖區),在使用play方法播放時如果沒有做好準備回自動調用該方法
    - (void)play開始播放
    - (void)pause暫停播放
    - (void)stop停止播放
    - (void)beginSeekingForward開始向前查找(快進)
    - (void)beginSeekingBackward開始向后查找(快退)
    - (void)endSeeking結束查找
    通知說明
    (注意:要想獲得MPMusicPlayerController通知必須首先調用beginGeneratingPlaybackNotifications開啟通知)
    MPMusicPlayerControllerPlaybackStateDidChangeNotification播放狀態改變
    MPMusicPlayerControllerNowPlayingItemDidChangeNotification當前播放音頻改變
    MPMusicPlayerControllerVolumeDidChangeNotification聲音大小改變
    MPMediaPlaybackIsPreparedToPlayDidChangeNotification準備好播放
    • MPMusicPlayerController有兩種播放器:applicationMusicPlayer和systemMusicPlayer,前者在應用退出后音樂播放會自動停止,后者在應用停止后不會退出播放狀態。
    • MPMusicPlayerController加載音樂不同于前面的AVAudioPlayer是通過一個文件路徑來加載,而是需要一個播放隊列。在MPMusicPlayerController中提供了兩個方法來加載播放隊列:- (void)setQueueWithQuery:(MPMediaQuery *)query- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection,正是由于它的播放音頻來源是一個隊列,因此MPMusicPlayerController支持上一曲、下一曲等操作。

    那么接下來的問題就是如何獲取MPMediaQueue或者MPMediaItemCollection?MPMediaQueue對象有一系列的類方法來獲得媒體隊列:

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

    有了這些方法,就可以很容易獲到歌曲、播放列表、專輯媒體等媒體隊列了,這樣就可以通過:- (void)setQueueWithQuery:(MPMediaQuery *)query方法設置音樂來源了又或者得到MPMediaQueue之后創建MPMediaItemCollection,使用- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection設置音樂來源。

    有時候可能希望用戶自己來選擇要播放的音樂,這時可以使用MPMediaPickerController,它是一個視圖控制器,類似于UIImagePickerController,選擇完播放來源后可以在其代理方法中獲得MPMediaItemCollection對象。

    無論是通過哪種方式獲得MPMusicPlayerController的媒體源,可能都希望將每個媒體的信息顯示出來,這時候可以通過MPMediaItem對象獲得。一個MPMediaItem代表一個媒體文件,通過它可以訪問媒體標題、專輯名稱、專輯封面、音樂時長等等。無論是MPMediaQueue還是MPMediaItemCollection都有一個items屬性,它是MPMediaItem數組,通過這個屬性可以獲得MPMediaItem對象。

    下面就簡單看一下MPMusicPlayerController的使用,在下面的例子中簡單演示了音樂的選擇、播放、暫停、通知、下一曲、上一曲功能,相信有了上面的概念,代碼讀起來并不復雜(示例中是直接通過MPMeidaPicker進行音樂選擇的,但是仍然提供了兩個方法getLocalMediaQuery和getLocalMediaItemCollection來演示如何直接通過MPMediaQueue獲得媒體隊列或媒體集合):

    // // 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];//開啟通知,否則監控不到MPMusicPlayerController的通知[self addNotification];//添加通知//如果不使用MPMediaPickerController可以使用如下方法獲得音樂庫媒體隊列//[_musicPlayer setQueueWithItemCollection:[self getLocalMediaItemCollection]];}return _musicPlayer; }/*** 創建媒體選擇器** @return 媒體選擇器*/ -(MPMediaPickerController *)mediaPicker{if (!_mediaPicker) {//初始化媒體選擇器,這里設置媒體類型為音樂,其實這里也可以選擇視頻、廣播等 // _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeMusic];_mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeAny];_mediaPicker.allowsPickingMultipleItems=YES;//允許多選 // _mediaPicker.showsCloudItems=YES;//顯示icloud選項_mediaPicker.prompt=@"請選擇要播放的音樂";_mediaPicker.delegate=self;//設置選擇器代理}return _mediaPicker; }/*** 取得媒體隊列** @return 媒體隊列*/ -(MPMediaQuery *)getLocalMediaQuery{MPMediaQuery *mediaQueue=[MPMediaQuery songsQuery];for (MPMediaItem *item in mediaQueue.items) {NSLog(@"標題:%@,%@",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(@"標題:%@,%@",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];//第一個播放音樂//注意很多音樂信息如標題、專輯、表演者、封面、時長等信息都可以通過MPMediaItem的valueForKey:方法得到,但是從iOS7開始都有對應的屬性可以直接訪問 // NSString *title= [mediaItem valueForKey:MPMediaItemPropertyAlbumTitle]; // NSString *artist= [mediaItem valueForKey:MPMediaItemPropertyAlbumArtist]; // MPMediaItemArtwork *artwork= [mediaItem valueForKey:MPMediaItemPropertyArtwork];//UIImage *image=[artwork imageWithSize:CGSizeMake(100, 100)];//專輯圖片NSLog(@"標題:%@,表演者:%@,專輯:%@",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]; }/*** 播放狀態改變通知** @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框架中還要一個AVAudioRecorder類專門處理錄音操作,它同樣支持多種音頻格式。與AVAudioPlayer類似,你完全可以將它看成是一個錄音機控制類,下面是常用的屬性和方法:

    屬性說明
    @property(readonly, getter=isRecording) BOOL recording;是否正在錄音,只讀
    @property(readonly) NSURL *url錄音文件地址,只讀
    @property(readonly) NSDictionary *settings錄音文件設置,只讀
    @property(readonly) NSTimeInterval currentTime錄音時長,只讀,注意僅僅在錄音狀態可用
    @property(readonly) NSTimeInterval deviceCurrentTime輸入設置的時間長度,只讀,注意此屬性一直可訪問
    @property(getter=isMeteringEnabled) BOOL meteringEnabled;是否啟用錄音測量,如果啟用錄音測量可以獲得錄音分貝等數據信息
    @property(nonatomic, copy) NSArray *channelAssignments當前錄音的通道
    對象方法說明
    - (instancetype)initWithURL:(NSURL *)url settings:(NSDictionary *)settings error:(NSError **)outError錄音機對象初始化方法,注意其中的url必須是本地文件url,settings是錄音格式、編碼等設置
    - (BOOL)prepareToRecord準備錄音,主要用于創建緩沖區,如果不手動調用,在調用record錄音時也會自動調用
    - (BOOL)record開始錄音
    - (BOOL)recordAtTime:(NSTimeInterval)time在指定的時間開始錄音,一般用于錄音暫停再恢復錄音
    - (BOOL)recordForDuration:(NSTimeInterval) duration按指定的時長開始錄音
    - (BOOL)recordAtTime:(NSTimeInterval)time forDuration:(NSTimeInterval) duration在指定的時間開始錄音,并指定錄音時長
    - (void)pause;暫停錄音
    - (void)stop;停止錄音
    - (BOOL)deleteRecording;刪除錄音,注意要刪除錄音此時錄音機必須處于停止狀態
    - (void)updateMeters;更新測量數據,注意只有meteringEnabled為YES此方法才可用
    - (float)peakPowerForChannel:(NSUInteger)channelNumber;指定通道的測量峰值,注意只有調用完updateMeters才有值
    - (float)averagePowerForChannel:(NSUInteger)channelNumber指定通道的測量平均值,注意只有調用完updateMeters才有值
    代理方法說明
    - (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag完成錄音
    - (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError *)error錄音編碼發生錯誤

    AVAudioRecorder很多屬性和方法跟AVAudioPlayer都是類似的,但是它的創建有所不同,在創建錄音機時除了指定路徑外還必須指定錄音設置信息,因為錄音機必須知道錄音文件的格式、采樣率、通道數、每個采樣點的位數等信息,但是也并不是所有的信息都必須設置,通常只需要幾個常用設置。關于錄音設置詳見幫助文檔中的“AV Foundation Audio Settings Constants”。

    下面就使用AVAudioRecorder創建一個錄音機,實現了錄音、暫停、停止、播放等功能,實現效果大致如下:

    在這個示例中將實行一個完整的錄音控制,包括錄音、暫停、恢復、停止,同時還會實時展示用戶錄音的聲音波動,當用戶點擊完停止按鈕還會自動播放錄音文件。程序的構建主要分為以下幾步:

  • 設置音頻會話類型為AVAudioSessionCategoryPlayAndRecord,因為程序中牽扯到錄音和播放操作。
  • 創建錄音機AVAudioRecorder,指定錄音保存的路徑并且設置錄音屬性,注意對于一般的錄音文件要求的采樣率、位數并不高,需要適當設置以保證錄音文件的大小和效果。
  • 設置錄音機代理以便在錄音完成后播放錄音,打開錄音測量保證能夠實時獲得錄音時的聲音強度。(注意聲音強度范圍-160到0,0代表最大輸入)
  • 創建音頻播放器AVAudioPlayer,用于在錄音完成之后播放錄音。
  • 創建一個定時器以便實時刷新錄音測量值并更新錄音強度到UIProgressView中顯示。
  • 添加錄音、暫停、恢復、停止操作,需要注意錄音的恢復操作其實是有音頻會話管理的,恢復時只要再次調用record方法即可,無需手動管理恢復時間等。
  • 下面是主要代碼:

    // // 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;//音頻錄音機 @property (nonatomic,strong) AVAudioPlayer *audioPlayer;//音頻播放器,用于播放錄音文件 @property (nonatomic,strong) NSTimer *timer;//錄音聲波監控(注意這里暫時不對播放進行監控)@property (weak, nonatomic) IBOutlet UIButton *record;//開始錄音 @property (weak, nonatomic) IBOutlet UIButton *pause;//暫停錄音 @property (weak, nonatomic) IBOutlet UIButton *resume;//恢復錄音 @property (weak, nonatomic) IBOutlet UIButton *stop;//停止錄音 @property (weak, nonatomic) IBOutlet UIProgressView *audioPower;//音頻波動@end@implementation ViewController#pragma mark - 控制器視圖方法 - (void)viewDidLoad {[super viewDidLoad];[self setAudioSession]; }#pragma mark - 私有方法 /*** 設置音頻會話*/ -(void)setAudioSession{AVAudioSession *audioSession=[AVAudioSession sharedInstance];//設置為播放和錄音狀態,以便可以在錄制完之后播放錄音[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; }/*** 取得錄音文件設置** @return 錄音設置*/ -(NSDictionary *)getAudioSetting{NSMutableDictionary *dicM=[NSMutableDictionary dictionary];//設置錄音格式[dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey];//設置錄音采樣率,8000是電話采樣率,對于一般錄音已經夠了[dicM setObject:@(8000) forKey:AVSampleRateKey];//設置通道,這里采用單聲道[dicM setObject:@(1) forKey:AVNumberOfChannelsKey];//每個采樣點位數,分為8、16、24、32[dicM setObject:@(8) forKey:AVLinearPCMBitDepthKey];//是否使用浮點數采樣[dicM setObject:@(YES) forKey:AVLinearPCMIsFloatKey];//....其他設置等return dicM; }/*** 獲得錄音機對象** @return 錄音機對象*/ -(AVAudioRecorder *)audioRecorder{if (!_audioRecorder) {//創建錄音文件保存路徑NSURL *url=[self getSavePath];//創建錄音格式設置NSDictionary *setting=[self getAudioSetting];//創建錄音機NSError *error=nil;_audioRecorder=[[AVAudioRecorder alloc]initWithURL:url settings:setting error:&error];_audioRecorder.delegate=self;_audioRecorder.meteringEnabled=YES;//如果要監控聲波則必須設置為YESif (error) {NSLog(@"創建錄音機對象時發生錯誤,錯誤信息:%@",error.localizedDescription);return nil;}}return _audioRecorder; }/*** 創建播放器** @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(@"創建播放器過程中發生錯誤,錯誤信息:%@",error.localizedDescription);return nil;}}return _audioPlayer; }/*** 錄音聲波監控定制器** @return 定時器*/ -(NSTimer *)timer{if (!_timer) {_timer=[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(audioPowerChange) userInfo:nil repeats:YES];}return _timer; }/*** 錄音聲波狀態設置*/ -(void)audioPowerChange{[self.audioRecorder updateMeters];//更新測量值float power= [self.audioRecorder averagePowerForChannel:0];//取得第一個通道的音頻,注意音頻強度范圍時-160到0CGFloat progress=(1.0/160.0)*(power+160.0);[self.audioPower setProgress:progress]; } #pragma mark - UI事件 /*** 點擊錄音按鈕** @param sender 錄音按鈕*/ - (IBAction)recordClick:(UIButton *)sender {if (![self.audioRecorder isRecording]) {[self.audioRecorder record];//首次使用應用時如果調用record方法會詢問用戶是否允許使用麥克風self.timer.fireDate=[NSDate distantPast];} }/*** 點擊暫定按鈕** @param sender 暫停按鈕*/ - (IBAction)pauseClick:(UIButton *)sender {if ([self.audioRecorder isRecording]) {[self.audioRecorder pause];self.timer.fireDate=[NSDate distantFuture];} }/*** 點擊恢復按鈕* 恢復錄音只需要再次調用record,AVAudioSession會幫助你記錄上次錄音位置并追加錄音** @param sender 恢復按鈕*/ - (IBAction)resumeClick:(UIButton *)sender {[self recordClick:sender]; }/*** 點擊停止按鈕** @param sender 停止按鈕*/ - (IBAction)stopClick:(UIButton *)sender {[self.audioRecorder stop];self.timer.fireDate=[NSDate distantFuture];self.audioPower.progress=0.0; }#pragma mark - 錄音機代理方法 /*** 錄音完成,錄音完成后播放錄音** @param recorder 錄音機對象* @param flag 是否成功*/ -(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{if (![self.audioPlayer isPlaying]) {[self.audioPlayer play];}NSLog(@"錄音完成!"); }@end

    運行效果:

    音頻隊列服務

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

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

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

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

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

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

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

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

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

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

    1.拷貝FreeStreamer中的Reachability.h、Reachability.m和Common、astreamer兩個文件夾中的內容到項目中。

    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文件添加到項目中并將Targets-Build Settings-Precompile Prefix Header設置為YES,在Targets-Build Settings-Prefix Header設置為$(SRCROOT)/項目名稱/FreeStreamerMobile-Prefix.pch(因為Xcode6默認沒有pch文件)

    然后就可以編寫代碼播放網絡音頻了:

    // // ViewController.m // AudioQueueServices // // Created by Kenshin Cui on 14/03/30. // Copyright (c) 2014年 cmjstudio. All rights reserved. // 使用FreeStreamer實現網絡音頻播放#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; }/*** 創建FSAudioStream對象** @return FSAudioStream對象*/ -(FSAudioStream *)audioStream{if (!_audioStream) {NSURL *url=[self getNetworkUrl];//創建FSAudioStream對象_audioStream=[[FSAudioStream alloc]initWithUrl:url];_audioStream.onFailure=^(FSAudioStreamError error,NSString *description){NSLog(@"播放過程中發生錯誤,錯誤信息:%@",description);};_audioStream.onCompletion=^(){NSLog(@"播放完成!");};[_audioStream setVolume:0.5];//設置聲音}return _audioStream; }@end 其實FreeStreamer的功能很強大,不僅僅是播放本地、網絡音頻那么簡單,它還支持播放列表、檢查包內容、RSS訂閱、播放中斷等很多強大的功能,甚至還包含了一個音頻分析器,有興趣的朋友可以訪問官網查看詳細用法

    視頻

    MPMoviePlayerController

    在iOS中播放視頻可以使用MediaPlayer.framework種的MPMoviePlayerController類來完成,它支持本地視頻和網絡視頻播放。這個類實現了MPMediaPlayback協議,因此具備一般的播放器控制功能,例如播放、暫停、停止等。但是MPMediaPlayerController自身并不是一個完整的視圖控制器,如果要在UI中展示視頻需要將view屬性添加到界面中。下面列出了MPMoviePlayerController的常用屬性和方法:

    屬性說明
    @property (nonatomic, copy) NSURL *contentURL播放媒體URL,這個URL可以是本地路徑,也可以是網絡路徑
    @property (nonatomic, readonly) UIView *view播放器視圖,如果要顯示視頻必須將此視圖添加到控制器視圖中
    @property (nonatomic, readonly) UIView *backgroundView播放器背景視圖
    @property (nonatomic, readonly) MPMoviePlaybackState playbackState媒體播放狀態,枚舉類型:
    MPMoviePlaybackStateStopped:停止播放
    MPMoviePlaybackStatePlaying:正在播放
    MPMoviePlaybackStatePaused:暫停
    MPMoviePlaybackStateInterrupted:中斷
    MPMoviePlaybackStateSeekingForward:向前定位
    MPMoviePlaybackStateSeekingBackward:向后定位
    @property (nonatomic, readonly) MPMovieLoadState loadState網絡媒體加載狀態,枚舉類型:
    MPMovieLoadStateUnknown:位置類型
    MPMovieLoadStatePlayable:
    MPMovieLoadStatePlaythroughOK:這種狀態如果shouldAutoPlay為YES將自動播放
    MPMovieLoadStateStalled:停滯狀態
    @property (nonatomic) MPMovieControlStyle controlStyle控制面板風格,枚舉類型:
    MPMovieControlStyleNone:無控制面板?
    MPMovieControlStyleEmbedded:嵌入視頻風格?
    MPMovieControlStyleFullscreen:全屏?
    MPMovieControlStyleDefault:默認風格
    @property (nonatomic) MPMovieRepeatMode repeatMode;重復播放模式,枚舉類型:
    MPMovieRepeatModeNone:不重復,默認值
    MPMovieRepeatModeOne:重復播放
    @property (nonatomic) BOOL shouldAutoplay當網絡媒體緩存到一定數據時是否自動播放,默認為YES
    @property (nonatomic, getter=isFullscreen) BOOL fullscreen是否全屏展示,默認為NO,注意如果要通過此屬性設置全屏必須在視圖顯示完成后設置,否則無效
    @property (nonatomic) MPMovieScalingMode scalingMode視頻縮放填充模式,枚舉類型:
    MPMovieScalingModeNone:不進行任何縮放
    MPMovieScalingModeAspectFit:固定縮放比例并且盡量全部展示視頻,不會裁切視頻
    MPMovieScalingModeAspectFill:固定縮放比例并填充滿整個視圖展示,可能會裁切視頻
    MPMovieScalingModeFill:不固定縮放比例壓縮填充整個視圖,視頻不會被裁切但是比例失衡
    @property (nonatomic, readonly) BOOL readyForDisplay是否有相關媒體被播放
    @property (nonatomic, readonly) MPMovieMediaTypeMask movieMediaTypes媒體類別,枚舉類型:
    MPMovieMediaTypeMaskNone:未知類型
    MPMovieMediaTypeMaskVideo:視頻
    MPMovieMediaTypeMaskAudio:音頻
    @property (nonatomic) MPMovieSourceType movieSourceType媒體源,枚舉類型:
    MPMovieSourceTypeUnknown:未知來源
    MPMovieSourceTypeFile:本地文件
    MPMovieSourceTypeStreaming:流媒體(直播或點播)
    @property (nonatomic, readonly) NSTimeInterval duration媒體時長,如果未知則返回0
    @property (nonatomic, readonly) NSTimeInterval playableDuration媒體可播放時長,主要用于表示網絡媒體已下載視頻時長
    @property (nonatomic, readonly) CGSize naturalSize視頻實際尺寸,如果未知則返回CGSizeZero
    @property (nonatomic) NSTimeInterval initialPlaybackTime起始播放時間
    @property (nonatomic) NSTimeInterval endPlaybackTime終止播放時間
    @property (nonatomic) BOOL allowsAirPlay是否允許無線播放,默認為YES
    @property (nonatomic, readonly, getter=isAirPlayVideoActive) BOOL airPlayVideoActive當前媒體是否正在通過AirPlay播放
    @property(nonatomic, readonly) BOOL isPreparedToPlay是否準備好播放
    @property(nonatomic) NSTimeInterval currentPlaybackTime當前播放時間,單位:秒
    @property(nonatomic) float currentPlaybackRate當前播放速度,如果暫停則為0,正常速度為1.0,非0數據表示倍率
    對象方法說明
    - (instancetype)initWithContentURL:(NSURL *)url使用指定的URL初始化媒體播放控制器對象
    - (void)setFullscreen:(BOOL)fullscreen animated:(BOOL)animated設置視頻全屏,注意如果要通過此方法設置全屏則必須在其視圖顯示之后設置,否則無效
    - (void)requestThumbnailImagesAtTimes:(NSArray *)playbackTimes timeOption:(MPMovieTimeOption)option獲取在指定播放時間的視頻縮略圖,第一個參數是獲取縮略圖的時間點數組;第二個參數代表時間點精度,枚舉類型:
    MPMovieTimeOptionNearestKeyFrame:時間點附近
    MPMovieTimeOptionExact:準確時間
    - (void)cancelAllThumbnailImageRequests取消所有縮略圖獲取請求
    - (void)prepareToPlay準備播放,加載視頻數據到緩存,當調用play方法時如果沒有準備好會自動調用此方法
    - (void)play開始播放
    - (void)pause暫停播放
    - (void)stop停止播放
    - (void)beginSeekingForward向前定位
    - (void)beginSeekingBackward向后定位
    - (void)endSeeking停止快進/快退
    通知說明
    MPMoviePlayerScalingModeDidChangeNotification視頻縮放填充模式發生改變
    MPMoviePlayerPlaybackDidFinishNotification媒體播放完成或用戶手動退出,具體完成原因可以通過通知userInfo中的key為MPMoviePlayerPlaybackDidFinishReasonUserInfoKey的對象獲取
    MPMoviePlayerPlaybackStateDidChangeNotification播放狀態改變,可配合playbakcState屬性獲取具體狀態
    MPMoviePlayerLoadStateDidChangeNotification媒體網絡加載狀態改變
    MPMoviePlayerNowPlayingMovieDidChangeNotification當前播放的媒體內容發生改變
    MPMoviePlayerWillEnterFullscreenNotification將要進入全屏
    MPMoviePlayerDidEnterFullscreenNotification進入全屏后
    MPMoviePlayerWillExitFullscreenNotification將要退出全屏
    MPMoviePlayerDidExitFullscreenNotification退出全屏后
    MPMoviePlayerIsAirPlayVideoActiveDidChangeNotification當媒體開始通過AirPlay播放或者結束AirPlay播放
    MPMoviePlayerReadyForDisplayDidChangeNotification視頻顯示狀態改變
    MPMovieMediaTypesAvailableNotification確定了媒體可用類型后
    MPMovieSourceTypeAvailableNotification確定了媒體來源后
    MPMovieDurationAvailableNotification確定了媒體播放時長后
    MPMovieNaturalSizeAvailableNotification確定了媒體的實際尺寸后
    MPMoviePlayerThumbnailImageRequestDidFinishNotification縮略圖請求完成之后
    MPMediaPlaybackIsPreparedToPlayDidChangeNotification做好播放準備后

    注意MPMediaPlayerController的狀態等信息并不是通過代理來和外界交互的,而是通過通知中心,因此從上面的列表中可以看到常用的一些通知。由于MPMoviePlayerController本身對于媒體播放做了深度的封裝,使用起來就相當簡單:創建MPMoviePlayerController對象,設置frame屬性,將MPMoviePlayerController的view添加到控制器視圖中。下面的示例中將創建一個播放控制器并添加播放狀態改變及播放完成的通知:

    // // 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{//移除所有通知監控[[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; }/*** 取得網絡文件路徑** @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; }/*** 創建媒體播放控制器** @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)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];}/*** 播放狀態改變,注意播放完成時的狀態是暫停** @param notification 通知對象*/ -(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{switch (self.moviePlayer.playbackState) {case MPMoviePlaybackStatePlaying:NSLog(@"正在播放...");break;case MPMoviePlaybackStatePaused:NSLog(@"暫停播放.");break;case MPMoviePlaybackStateStopped:NSLog(@"停止播放.");break;default:NSLog(@"播放狀態:%li",self.moviePlayer.playbackState);break;} }/*** 播放完成** @param notification 通知對象*/ -(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{NSLog(@"播放完成.%li",self.moviePlayer.playbackState); }@end

    運行效果:


    從上面的API大家也不難看出其實MPMoviePlayerController功能相當強大,日常開發中作為一般的媒體播放器也完全沒有問題。MPMoviePlayerController除了一般的視頻播放和控制外還有一些強大的功能,例如截取視頻縮略圖。請求視頻縮略圖時只要調用- (void)requestThumbnailImagesAtTimes:(NSArray *)playbackTimes timeOption:(MPMovieTimeOption)option方法指定獲得縮略圖的時間點,然后監控MPMoviePlayerThumbnailImageRequestDidFinishNotification通知,每個時間點的縮略圖請求完成就會調用通知,在通知調用方法中可以通過MPMoviePlayerThumbnailImageKey獲得UIImage對象處理即可。例如下面的程序演示了在程序啟動后獲得兩個時間點的縮略圖的過程,截圖成功后保存到相冊:

    // // 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{//移除所有通知監控[[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; }/*** 取得網絡文件路徑** @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; }/*** 創建媒體播放控制器** @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 - 控制器通知 /*** 添加通知監控媒體播放控制器狀態*/ -(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];}/*** 播放狀態改變,注意播放完成時的狀態是暫停** @param notification 通知對象*/ -(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{switch (self.moviePlayer.playbackState) {case MPMoviePlaybackStatePlaying:NSLog(@"正在播放...");break;case MPMoviePlaybackStatePaused:NSLog(@"暫停播放.");break;case MPMoviePlaybackStateStopped:NSLog(@"停止播放.");break;default:NSLog(@"播放狀態:%li",self.moviePlayer.playbackState);break;} }/*** 播放完成** @param notification 通知對象*/ -(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{NSLog(@"播放完成.%li",self.moviePlayer.playbackState); }/*** 縮略圖請求完成,此方法每次截圖成功都會調用一次** @param notification 通知對象*/ -(void)mediaPlayerThumbnailRequestFinished:(NSNotification *)notification{NSLog(@"視頻截圖完成.");UIImage *image=notification.userInfo[MPMoviePlayerThumbnailImageKey];//保存圖片到相冊(首次調用會請求用戶獲得訪問相冊權限)UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil); }@end

    截圖效果:

    ?????

    擴展--使用AVFoundation生成縮略圖

    通過前面的方法大家應該已經看到,使用MPMoviePlayerController來生成縮略圖足夠簡單,但是如果僅僅是是為了生成縮略圖而不進行視頻播放的話,此刻使用MPMoviePlayerController就有點大材小用了。其實使用AVFundation框架中的AVAssetImageGenerator就可以獲取視頻縮略圖。使用AVAssetImageGenerator獲取縮略圖大致分為三個步驟:

  • 創建AVURLAsset對象(此類主要用于獲取媒體信息,包括視頻、聲音等)。
  • 根據AVURLAsset創建AVAssetImageGenerator對象。
  • 使用AVAssetImageGenerator的copyCGImageAtTime::方法獲得指定時間點的截圖。
  • // // 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; }/*** 取得網絡文件路徑** @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; }/*** 截取指定時間的視頻縮略圖** @param timeBySecond 時間點*/ -(void)thumbnailImageRequest:(CGFloat )timeBySecond{//創建URLNSURL *url=[self getNetworkUrl];//根據url創建AVURLAssetAVURLAsset *urlAsset=[AVURLAsset assetWithURL:url];//根據AVURLAsset創建AVAssetImageGeneratorAVAssetImageGenerator *imageGenerator=[AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];/*截圖* requestTime:縮略圖創建時間* actualTime:縮略圖實際生成的時間*/NSError *error=nil;CMTime time=CMTimeMakeWithSeconds(timeBySecond, 10);//CMTime是表示電影時間信息的結構體,第一個參數表示是視頻第幾秒,第二個參數表示每秒幀數.(如果要活的某一秒的第幾幀可以使用CMTimeMake方法)CMTime actualTime;CGImageRef cgImage= [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error];if(error){NSLog(@"截取視頻縮略圖時發生錯誤,錯誤信息:%@",error.localizedDescription);return;}CMTimeShow(actualTime);UIImage *image=[UIImage imageWithCGImage:cgImage];//轉化為UIImage//保存到相冊UIImageWriteToSavedPhotosAlbum(image,nil, nil, nil);CGImageRelease(cgImage); }@end

    生成的縮略圖效果:

    MPMoviePlayerViewController

    其實MPMoviePlayerController如果不作為嵌入視頻來播放(例如在新聞中嵌入一個視頻),通常在播放時都是占滿一個屏幕的,特別是在iPhone、iTouch上。因此從iOS3.2以后蘋果也在思考既然MPMoviePlayerController在使用時通常都是將其視圖view添加到另外一個視圖控制器中作為子視圖,那么何不直接創建一個控制器視圖內部創建一個MPMoviePlayerController屬性并且默認全屏播放,開發者在開發的時候直接使用這個視圖控制器。這個內部有一個MPMoviePlayerController的視圖控制器就是MPMoviePlayerViewController,它繼承于UIViewController。MPMoviePlayerViewController內部多了一個moviePlayer屬性和一個帶有url的初始化方法,同時它內部實現了一些作為模態視圖展示所特有的功能,例如默認是全屏模式展示、彈出后自動播放、作為模態窗口展示時如果點擊“Done”按鈕會自動退出模態窗口等。在下面的示例中就不直接將播放器放到主視圖控制器,而是放到一個模態視圖控制器中,簡單演示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{//移除所有通知監控[[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; }/*** 取得網絡文件路徑** @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;//保證每次點擊都重新創建視頻播放控制器視圖,避免再次點擊時由于不播放的問題 // [self presentViewController:self.moviePlayerViewController animated:YES completion:nil];//注意,在MPMoviePlayerViewController.h中對UIViewController擴展兩個用于模態展示和關閉MPMoviePlayerViewController的方法,增加了一種下拉展示動畫效果[self presentMoviePlayerViewControllerAnimated:self.moviePlayerViewController]; }#pragma mark - 控制器通知 /*** 添加通知監控媒體播放控制器狀態*/ -(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];}/*** 播放狀態改變,注意播放完成時的狀態是暫停** @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(@"播放狀態:%li",self.moviePlayerViewController.moviePlayer.playbackState);break;} }/*** 播放完成** @param notification 通知對象*/ -(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{NSLog(@"播放完成.%li",self.moviePlayerViewController.moviePlayer.playbackState); }@end

    運行效果:

    這里需要強調一下,由于MPMoviePlayerViewController的初始化方法做了大量工作(例如設置URL、自動播放、添加點擊Done完成的監控等),所以當再次點擊播放彈出新的模態窗口的時如果不銷毀之前的MPMoviePlayerViewController,那么新的對象就無法完成初始化,這樣也就不能再次進行播放。

    AVPlayer

    MPMoviePlayerController足夠強大,幾乎不用寫幾行代碼就能完成一個播放器,但是正是由于它的高度封裝使得要自定義這個播放器變得很復雜,甚至是不可能完成。例如有些時候需要自定義播放器的樣式,那么如果要使用MPMoviePlayerController就不合適了,如果要對視頻有自由的控制則可以使用AVPlayer。AVPlayer存在于AVFoundation中,它更加接近于底層,所以靈活性也更強:

    AVPlayer本身并不能顯示視頻,而且它也不像MPMoviePlayerController有一個view屬性。如果AVPlayer要顯示必須創建一個播放器層AVPlayerLayer用于展示,播放器層繼承于CALayer,有了AVPlayerLayer之添加到控制器視圖的layer中即可。要使用AVPlayer首先了解一下幾個常用的類:

    AVAsset:主要用于獲取多媒體信息,是一個抽象類,不能直接使用。

    AVURLAsset:AVAsset的子類,可以根據一個URL路徑創建一個包含媒體信息的AVURLAsset對象。

    AVPlayerItem:一個媒體資源管理對象,管理者視頻的一些基本信息和狀態,一個AVPlayerItem對應著一個視頻資源。

    下面簡單通過一個播放器來演示AVPlayer的使用,播放器的效果如下:

    在這個自定義的播放器中實現了視頻播放、暫停、進度展示和視頻列表功能,下面將對這些功能一一介紹。

    首先說一下視頻的播放、暫停功能,這也是最基本的功能,AVPlayer對應著兩個方法play、pause來實現。但是關鍵問題是如何判斷當前視頻是否在播放,在前面的內容中無論是音頻播放器還是視頻播放器都有對應的狀態來判斷,但是AVPlayer卻沒有這樣的狀態屬性,通常情況下可以通過判斷播放器的播放速度來獲得播放狀態。如果rate為0說明是停止狀態,1是則是正常播放狀態。

    其次要展示播放進度就沒有其他播放器那么簡單了。在前面的播放器中通常是使用通知來獲得播放器的狀態,媒體加載狀態等,但是無論是AVPlayer還是AVPlayerItem(AVPlayer有一個屬性currentItem是AVPlayerItem類型,表示當前播放的視頻對象)都無法獲得這些信息。當然AVPlayerItem是有通知的,但是對于獲得播放狀態和加載狀態有用的通知只有一個:播放完成通知AVPlayerItemDidPlayToEndTimeNotification。在播放視頻時,特別是播放網絡視頻往往需要知道視頻加載情況、緩沖情況、播放情況,這些信息可以通過KVO監控AVPlayerItem的status、loadedTimeRanges屬性來獲得。當AVPlayerItem的status屬性為AVPlayerStatusReadyToPlay是說明正在播放,只有處于這個狀態時才能獲得視頻時長等信息;當loadedTimeRanges的改變時(每緩沖一部分數據就會更新此屬性)可以獲得本次緩沖加載的視頻范圍(包含起始時間、本次加載時長),這樣一來就可以實時獲得緩沖情況。然后就是依靠AVPlayer的- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block方法獲得播放進度,這個方法會在設定的時間間隔內定時更新播放進度,通過time參數通知客戶端。相信有了這些視頻信息播放進度就不成問題了,事實上通過這些信息就算是平時看到的其他播放器的緩沖進度顯示以及拖動播放的功能也可以順利的實現。

    最后就是視頻切換的功能,在前面介紹的所有播放器中每個播放器對象一次只能播放一個視頻,如果要切換視頻只能重新創建一個對象,但是AVPlayer卻提供了- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item方法用于在不同的視頻之間切換(事實上在AVFoundation內部還有一個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;//播放進度@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{//創建播放器層AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:self.player];playerLayer.frame=self.container.frame;//playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;//視頻填充模式[self.container.layer addSublayer:playerLayer]; }/*** 截取指定時間的視頻縮略圖** @param timeBySecond 時間點*//*** 初始化播放器** @return 播放器對象*/ -(AVPlayer *)player{if (!_player) {AVPlayerItem *playerItem=[self getPlayItem:0];_player=[AVPlayer playerWithPlayerItem:playerItem];[self addProgressObserver];[self addObserverToPlayerItem:playerItem];}return _player; }/*** 根據視頻索引取得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 - 監控 /*** 給播放器添加進度更新*/ -(void)addProgressObserver{AVPlayerItem *playerItem=self.player.currentItem;UIProgressView *progress=self.progress;//這里設置每秒執行一次[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(@"當前已經播放%.2fs.",current);if (current) {[progress setProgress:(current/total) animated:YES];}}]; }/*** 給AVPlayerItem添加監控** @param playerItem AVPlayerItem對象*/ -(void)addObserverToPlayerItem:(AVPlayerItem *)playerItem{//監控狀態屬性,注意AVPlayer也有一個status屬性,通過監控它的status也可以獲得播放狀態[playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];//監控網絡加載情況屬性[playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil]; } -(void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem{[playerItem removeObserver:self forKeyPath:@"status"];[playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"]; } /*** 通過KVO監控播放器狀態** @param keyPath 監控屬性* @param object 監視器* @param change 狀態改變* @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];//本次緩沖時間范圍float startSeconds = CMTimeGetSeconds(timeRange.start);float durationSeconds = CMTimeGetSeconds(timeRange.duration);NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩沖總長度NSLog(@"共緩沖:%.2f",totalBuffer); //} }#pragma mark - UI事件 /*** 點擊播放/暫停按鈕** @param sender 播放/暫停按鈕*/ - (IBAction)playClick:(UIButton *)sender { // AVPlayerItemDidPlayToEndTimeNotification//AVPlayerItem *playerItem= self.player.currentItem;if(self.player.rate==0){ //說明時暫停[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 點擊按鈕對象*/ - (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

    運行效果:

    到目前為止無論是MPMoviePlayerController還是AVPlayer來播放視頻都相當強大,但是它也存在著一些不可回避的問題,那就是支持的視頻編碼格式很有限:H.264、MPEG-4,擴展名(壓縮格式):.mp4、.mov、.m4v、.m2v、.3gp、.3g2等。但是無論是MPMoviePlayerController還是AVPlayer它們都支持絕大多數音頻編碼,所以大家如果純粹是為了播放音樂的話也可以考慮使用這兩個播放器。那么如何支持更多視頻編碼格式呢?目前來說主要還是依靠第三方框架,在iOS上常用的視頻編碼、解碼框架有:VLC、ffmpeg, 具體使用方式今天就不再做詳細介紹。

    攝像頭

    UIImagePickerController拍照和視頻錄制

    下面看一下在iOS如何拍照和錄制視頻。在iOS中要拍照和錄制視頻最簡單的方法就是使用UIImagePickerController。UIImagePickerController繼承于UINavigationController,前面的文章中主要使用它來選取照片,其實UIImagePickerController的功能不僅如此,它還可以用來拍照和錄制視頻。首先看一下這個類常用的屬性和方法:

    屬性說明
    @property(nonatomic)?????????? UIImagePickerControllerSourceType???? sourceType拾取源類型,sourceType是枚舉類型:
    UIImagePickerControllerSourceTypePhotoLibrary:照片庫
    ,默認值
    UIImagePickerControllerSourceTypeCamera:攝像頭
    UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿
    @property(nonatomic,copy)????? NSArray????????????????????????????? *mediaTypes媒體類型,默認情況下此數組包含kUTTypeImage,所以拍照時可以不用設置;但是當要錄像的時候必須設置,可以設置為kUTTypeVideo(視頻,但不帶聲音)或者kUTTypeMovie(視頻并帶有聲音)
    @property(nonatomic)?????????? NSTimeInterval??????????????????????? videoMaximumDuration視頻最大錄制時長,默認為10 s
    @property(nonatomic)?????????? UIImagePickerControllerQualityType??? videoQuality視頻質量,枚舉類型:
    UIImagePickerControllerQualityTypeHigh:高清質量
    UIImagePickerControllerQualityTypeMedium:中等質量,適合WiFi傳輸
    UIImagePickerControllerQualityTypeLow:低質量,適合蜂窩網傳輸
    UIImagePickerControllerQualityType640x480:640*480
    UIImagePickerControllerQualityTypeIFrame1280x720:1280*720
    UIImagePickerControllerQualityTypeIFrame960x540:960*540
    @property(nonatomic)?????????? BOOL????????????????????????????????? showsCameraControls是否顯示攝像頭控制面板,默認為YES
    @property(nonatomic,retain)??? UIView??????????????????????????????? *cameraOverlayView攝像頭上覆蓋的視圖,可用通過這個視頻來自定義拍照或錄像界面
    @property(nonatomic)?????????? CGAffineTransform???????????????????? cameraViewTransform攝像頭形變
    @property(nonatomic) UIImagePickerControllerCameraCaptureMode cameraCaptureMode攝像頭捕獲模式,捕獲模式是枚舉類型:
    UIImagePickerControllerCameraCaptureModePhoto:拍照模式
    UIImagePickerControllerCameraCaptureModeVideo:視頻錄制模式
    @property(nonatomic) UIImagePickerControllerCameraDevice????? cameraDevice攝像頭設備,cameraDevice是枚舉類型:
    UIImagePickerControllerCameraDeviceRear:前置攝像頭
    UIImagePickerControllerCameraDeviceFront:后置攝像頭
    @property(nonatomic) UIImagePickerControllerCameraFlashMode?? cameraFlashMode閃光燈模式,枚舉類型:
    UIImagePickerControllerCameraFlashModeOff:關閉閃光燈
    UIImagePickerControllerCameraFlashModeAuto:閃光燈自動
    UIImagePickerControllerCameraFlashModeOn:打開閃光燈
    類方法說明
    + (BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)sourceType指定的源類型是否可用,sourceType是枚舉類型:
    UIImagePickerControllerSourceTypePhotoLibrary:照片庫
    UIImagePickerControllerSourceTypeCamera:攝像頭
    UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿
    + (NSArray *)availableMediaTypesForSourceType:(UIImagePickerControllerSourceType)sourceType指定的源設備上可用的媒體類型,一般就是圖片和視頻
    + (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取消拾取
    擴展方法(主要用于保存照片、視頻到相簿)說明
    UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo)保存照片到相簿
    UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(NSString *videoPath)能否將視頻保存到相簿
    void UISaveVideoAtPathToSavedPhotosAlbum(NSString *videoPath, id completionTarget, SEL completionSelector, void *contextInfo)保存視頻到相簿

    要用UIImagePickerController來拍照或者錄制視頻通常可以分為如下步驟:

  • 創建UIImagePickerController對象。
  • 指定拾取源,平時選擇照片時使用的拾取源是照片庫或者相簿,此刻需要指定為攝像頭類型。
  • 指定攝像頭,前置攝像頭或者后置攝像頭。
  • 設置媒體類型mediaType,注意如果是錄像必須設置,如果是拍照此步驟可以省略,因為mediaType默認包含kUTTypeImage(注意媒體類型定義在MobileCoreServices.framework中)
  • 指定捕獲模式,拍照或者錄制視頻。(視頻錄制時必須先設置媒體類型再設置捕獲模式
  • 展示UIImagePickerController(通常以模態窗口形式打開)。
  • 拍照和錄制視頻結束后在代理方法中展示/保存照片或視頻。
  • 當然這個過程中有很多細節可以設置,例如是否顯示拍照控制面板,拍照后是否允許編輯等等,通過上面的屬性/方法列表相信并不難理解。下面就以一個示例展示如何使用UIImagePickerController來拍照和錄制視頻,下面的程序中只要將_isVideo設置為YES就是視頻錄制模式,錄制完后在主視圖控制器中自動播放;如果將_isVideo設置為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];//通過這里設置當前程序是拍照還是錄制視頻_isVideo=YES; }#pragma mark - UI事件 //點擊拍照按鈕 - (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;//設置image picker的來源,這里設置為攝像頭_imagePicker.cameraDevice=UIImagePickerControllerCameraDeviceRear;//設置使用哪個攝像頭,這里設置為后置攝像頭if (self.isVideo) {_imagePicker.mediaTypes=@[(NSString *)kUTTypeMovie];_imagePicker.videoQuality=UIImagePickerControllerQualityTypeIFrame1280x720;_imagePicker.cameraCaptureMode=UIImagePickerControllerCameraCaptureModeVideo;//設置攝像頭模式(拍照,錄制視頻)}else{_imagePicker.cameraCaptureMode=UIImagePickerControllerCameraCaptureModePhoto;}_imagePicker.allowsEditing=YES;//允許編輯_imagePicker.delegate=self;//設置代理,檢測操作}return _imagePicker; }//視頻保存后的回調 - (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{if (error) {NSLog(@"保存視頻過程中發生錯誤,錯誤信息:%@",error.localizedDescription);}else{NSLog(@"視頻保存成功.");//錄制完之后自動播放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

    運行效果(視頻錄制):

    AVFoundation拍照和錄制視頻

    不得不說UIImagePickerController確實強大,但是與MPMoviePlayerController類似,由于它的高度封裝性,要進行某些自定義工作就比較復雜了。例如要做出一款類似于美顏相機的拍照界面就比較難以實現了,此時就可以考慮使用AVFoundation來實現。AVFoundation中提供了很多現成的播放器和錄音機,但是事實上它還有更加底層的內容可以供開發者使用。因為AVFoundation中抽了很多和底層輸入、輸出設備打交道的類,依靠這些類開發人員面對的不再是封裝好的音頻播放器AVAudioPlayer、錄音機(AVAudioRecorder)、視頻(包括音頻)播放器AVPlayer,而是輸入設備(例如麥克風、攝像頭)、輸出設備(圖片、視頻)等。首先了解一下使用AVFoundation做拍照和視頻錄制開發用到的相關類:

    AVCaptureSession:媒體(音、視頻)捕獲會話,負責把捕獲的音視頻數據輸出到輸出設備中。一個AVCaptureSession可以有多個輸入輸出:

    AVCaptureDevice:輸入設備,包括麥克風、攝像頭,通過該對象可以設置物理設備的一些屬性(例如相機聚焦、白平衡等)。

    AVCaptureDeviceInput:設備輸入數據管理對象,可以根據AVCaptureDevice創建對應的AVCaptureDeviceInput對象,該對象將會被添加到AVCaptureSession中管理。

    AVCaptureOutput:輸出數據管理對象,用于接收各類輸出數據,通常使用對應的子類AVCaptureAudioDataOutput、AVCaptureStillImageOutput、AVCaptureVideoDataOutput、AVCaptureFileOutput,該對象將會被添加到AVCaptureSession中管理。注意:前面幾個對象的輸出數據都是NSData類型,而AVCaptureFileOutput代表數據以文件形式輸出,類似的,AVCcaptureFileOutput也不會直接創建使用,通常會使用其子類:AVCaptureAudioFileOutput、AVCaptureMovieFileOutput。當把一個輸入或者輸出添加到AVCaptureSession之后AVCaptureSession就會在所有相符的輸入、輸出設備之間建立連接(AVCaptionConnection):

    AVCaptureVideoPreviewLayer:相機拍攝預覽圖層,是CALayer的子類,使用該對象可以實時查看拍照或視頻錄制效果,創建該對象需要指定對應的AVCaptureSession對象。

    使用AVFoundation拍照和錄制視頻的一般步驟如下:

  • 創建AVCaptureSession對象。
  • 使用AVCaptureDevice的靜態方法獲得需要使用的設備,例如拍照和錄像就需要獲得攝像頭設備,錄音就要獲得麥克風設備。
  • 利用輸入設備AVCaptureDevice初始化AVCaptureDeviceInput對象。
  • 初始化輸出數據管理對象,如果要拍照就初始化AVCaptureStillImageOutput對象;如果拍攝視頻就初始化AVCaptureMovieFileOutput對象。
  • 將數據輸入對象AVCaptureDeviceInput、數據輸出對象AVCaptureOutput添加到媒體會話管理對象AVCaptureSession中。
  • 創建視頻預覽圖層AVCaptureVideoPreviewLayer并指定媒體會話,添加圖層到顯示容器中,調用AVCaptureSession的startRuning方法開始捕獲。
  • 將捕獲的音頻或視頻數據輸出到指定文件。
  • 拍照

    下面看一下如何使用AVFoundation實現一個拍照程序,在這個程序中將實現攝像頭預覽、切換前后攝像頭、閃光燈設置、對焦、拍照保存等功能。應用大致效果如下:

    在程序中定義會話、輸入、輸出等相關對象。

    @interface ViewController () @property (strong,nonatomic) AVCaptureSession *captureSession;//負責輸入和輸出設備之間的數據傳遞 @property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負責從AVCaptureDevice獲得輸入數據 @property (strong,nonatomic) AVCaptureStillImageOutput *captureStillImageOutput;//照片輸出流 @property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機拍攝預覽圖層 @property (weak, nonatomic) IBOutlet UIView *viewContainer; @property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按鈕 @property (weak, nonatomic) IBOutlet UIButton *flashAutoButton;//自動閃光燈按鈕 @property (weak, nonatomic) IBOutlet UIButton *flashOnButton;//打開閃光燈按鈕 @property (weak, nonatomic) IBOutlet UIButton *flashOffButton;//關閉閃光燈按鈕 @property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標 @end

    在控制器視圖將要展示時創建并初始化會話、攝像頭設備、輸入、輸出、預覽圖層,并且添加預覽圖層到視圖中,除此之外還做了一些初始化工作,例如添加手勢(點擊屏幕進行聚焦)、初始化界面等。

    -(void)viewWillAppear:(BOOL)animated{[super viewWillAppear:animated];//初始化會話_captureSession=[[AVCaptureSession alloc]init];if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//設置分辨率_captureSession.sessionPreset=AVCaptureSessionPreset1280x720;}//獲得輸入設備AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭if (!captureDevice) {NSLog(@"取得后置攝像頭時出現問題.");return;}NSError *error=nil;//根據輸入設備初始化設備輸入對象,用于獲得輸入數據_captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];if (error) {NSLog(@"取得設備輸入對象時出錯,錯誤原因:%@",error.localizedDescription);return;}//初始化設備輸出對象,用于獲得輸出數據_captureStillImageOutput=[[AVCaptureStillImageOutput alloc]init];NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};[_captureStillImageOutput setOutputSettings:outputSettings];//輸出設置//將設備輸入添加到會話中if ([_captureSession canAddInput:_captureDeviceInput]) {[_captureSession addInput:_captureDeviceInput];}//將設備輸出添加到會話中if ([_captureSession canAddOutput:_captureStillImageOutput]) {[_captureSession addOutput:_captureStillImageOutput];}//創建視頻預覽層,用于實時展示攝像頭狀態_captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];CALayer *layer=self.viewContainer.layer;layer.masksToBounds=YES;_captureVideoPreviewLayer.frame=layer.bounds;_captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式//將視頻預覽層添加到界面中//[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]; }

    定義閃光燈開閉及自動模式功能,注意無論是設置閃光燈、白平衡還是其他輸入設備屬性,在設置之前必須先鎖定配置,修改完后解鎖。

    /*** 改變設備屬性的統一操作方法** @param propertyChange 屬性改變操作*/ -(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{AVCaptureDevice *captureDevice= [self.captureDeviceInput device];NSError *error;//注意改變設備屬性前一定要首先調用lockForConfiguration:調用完之后使用unlockForConfiguration方法解鎖if ([captureDevice lockForConfiguration:&error]) {propertyChange(captureDevice);[captureDevice unlockForConfiguration];}else{NSLog(@"設置設備屬性過程發生錯誤,錯誤信息:%@",error.localizedDescription);} }/*** 設置閃光燈模式** @param flashMode 閃光燈模式*/ -(void)setFlashMode:(AVCaptureFlashMode )flashMode{[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {if ([captureDevice isFlashModeSupported:flashMode]) {[captureDevice setFlashMode:flashMode];}}]; }

    定義切換攝像頭功能,切換攝像頭的過程就是將原有輸入移除,在會話中添加新的輸入,但是注意動態修改會話需要首先開啟配置,配置成功后提交配置。

    #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];//獲得要調整的設備輸入對象AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];//改變會話的配置前一定要先開啟配置,配置完成后提交配置改變[self.captureSession beginConfiguration];//移除原有輸入對象[self.captureSession removeInput:self.captureDeviceInput];//添加新的輸入對象if ([self.captureSession canAddInput:toChangeDeviceInput]) {[self.captureSession addInput:toChangeDeviceInput];self.captureDeviceInput=toChangeDeviceInput;}//提交會話配置[self.captureSession commitConfiguration];[self setFlashModeButtonStatus]; }

    添加點擊手勢操作,點按預覽視圖時進行聚焦、白平衡設置。

    /*** 設置聚焦點** @param point 聚焦點*/ -(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];}}]; }/*** 添加點按手勢,點按時聚焦*/ -(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坐標轉化為攝像頭坐標CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];[self setFocusCursorWithPoint:point];[self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint]; }

    定義拍照功能,拍照的過程就是獲取連接,從連接中獲得捕獲的輸出數據并做保存操作。

    #pragma mark 拍照 - (IBAction)takeButtonClick:(UIButton *)sender {//根據設備輸出獲得連接AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];//根據連接取得設備輸出的數據[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;//負責輸入和輸出設備之間的數據傳遞 @property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負責從AVCaptureDevice獲得輸入數據 @property (strong,nonatomic) AVCaptureStillImageOutput *captureStillImageOutput;//照片輸出流 @property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機拍攝預覽圖層 @property (weak, nonatomic) IBOutlet UIView *viewContainer; @property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按鈕 @property (weak, nonatomic) IBOutlet UIButton *flashAutoButton;//自動閃光燈按鈕 @property (weak, nonatomic) IBOutlet UIButton *flashOnButton;//打開閃光燈按鈕 @property (weak, nonatomic) IBOutlet UIButton *flashOffButton;//關閉閃光燈按鈕 @property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標@end@implementation ViewController#pragma mark - 控制器視圖方法 - (void)viewDidLoad {[super viewDidLoad];}-(void)viewWillAppear:(BOOL)animated{[super viewWillAppear:animated];//初始化會話_captureSession=[[AVCaptureSession alloc]init];if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//設置分辨率_captureSession.sessionPreset=AVCaptureSessionPreset1280x720;}//獲得輸入設備AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭if (!captureDevice) {NSLog(@"取得后置攝像頭時出現問題.");return;}NSError *error=nil;//根據輸入設備初始化設備輸入對象,用于獲得輸入數據_captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];if (error) {NSLog(@"取得設備輸入對象時出錯,錯誤原因:%@",error.localizedDescription);return;}//初始化設備輸出對象,用于獲得輸出數據_captureStillImageOutput=[[AVCaptureStillImageOutput alloc]init];NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};[_captureStillImageOutput setOutputSettings:outputSettings];//輸出設置//將設備輸入添加到會話中if ([_captureSession canAddInput:_captureDeviceInput]) {[_captureSession addInput:_captureDeviceInput];}//將設備輸出添加到會話中if ([_captureSession canAddOutput:_captureStillImageOutput]) {[_captureSession addOutput:_captureStillImageOutput];}//創建視頻預覽層,用于實時展示攝像頭狀態_captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];CALayer *layer=self.viewContainer.layer;layer.masksToBounds=YES;_captureVideoPreviewLayer.frame=layer.bounds;_captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式//將視頻預覽層添加到界面中//[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 {//根據設備輸出獲得連接AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];//根據連接取得設備輸出的數據[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];//獲得要調整的設備輸入對象AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];//改變會話的配置前一定要先開啟配置,配置完成后提交配置改變[self.captureSession beginConfiguration];//移除原有輸入對象[self.captureSession removeInput:self.captureDeviceInput];//添加新的輸入對象if ([self.captureSession canAddInput:toChangeDeviceInput]) {[self.captureSession addInput:toChangeDeviceInput];self.captureDeviceInput=toChangeDeviceInput;}//提交會話配置[self.captureSession commitConfiguration];[self setFlashModeButtonStatus]; }#pragma mark 自動閃光燈開啟 - (IBAction)flashAutoClick:(UIButton *)sender {[self setFlashMode:AVCaptureFlashModeAuto];[self setFlashModeButtonStatus]; } #pragma mark 打開閃光燈 - (IBAction)flashOnClick:(UIButton *)sender {[self setFlashMode:AVCaptureFlashModeOn];[self setFlashModeButtonStatus]; } #pragma mark 關閉閃光燈 - (IBAction)flashOffClick:(UIButton *)sender {[self setFlashMode:AVCaptureFlashModeOff];[self setFlashModeButtonStatus]; }#pragma mark - 通知 /*** 給輸入設備添加通知*/ -(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{//注意添加區域改變捕獲通知必須首先設置設備允許捕獲[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {captureDevice.subjectAreaChangeMonitoringEnabled=YES;}];NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];//捕獲區域發生改變[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];//會話出錯[notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession]; }/*** 設備連接成功** @param notification 通知對象*/ -(void)deviceConnected:(NSNotification *)notification{NSLog(@"設備已連接..."); } /*** 設備連接斷開** @param notification 通知對象*/ -(void)deviceDisconnected:(NSNotification *)notification{NSLog(@"設備已斷開."); } /*** 捕獲區域改變** @param notification 通知對象*/ -(void)areaChange:(NSNotification *)notification{NSLog(@"捕獲區域改變..."); }/*** 會話出錯** @param notification 通知對象*/ -(void)sessionRuntimeError:(NSNotification *)notification{NSLog(@"會話發生錯誤."); }#pragma mark - 私有方法/*** 取得指定位置的攝像頭** @param position 攝像頭位置** @return 攝像頭設備*/ -(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];for (AVCaptureDevice *camera in cameras) {if ([camera position]==position) {return camera;}}return nil; }/*** 改變設備屬性的統一操作方法** @param propertyChange 屬性改變操作*/ -(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{AVCaptureDevice *captureDevice= [self.captureDeviceInput device];NSError *error;//注意改變設備屬性前一定要首先調用lockForConfiguration:調用完之后使用unlockForConfiguration方法解鎖if ([captureDevice lockForConfiguration:&error]) {propertyChange(captureDevice);[captureDevice unlockForConfiguration];}else{NSLog(@"設置設備屬性過程發生錯誤,錯誤信息:%@",error.localizedDescription);} }/*** 設置閃光燈模式** @param flashMode 閃光燈模式*/ -(void)setFlashMode:(AVCaptureFlashMode )flashMode{[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {if ([captureDevice isFlashModeSupported:flashMode]) {[captureDevice setFlashMode:flashMode];}}]; } /*** 設置聚焦模式** @param focusMode 聚焦模式*/ -(void)setFocusMode:(AVCaptureFocusMode )focusMode{[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {if ([captureDevice isFocusModeSupported:focusMode]) {[captureDevice setFocusMode:focusMode];}}]; } /*** 設置曝光模式** @param exposureMode 曝光模式*/ -(void)setExposureMode:(AVCaptureExposureMode)exposureMode{[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {if ([captureDevice isExposureModeSupported:exposureMode]) {[captureDevice setExposureMode:exposureMode];}}]; } /*** 設置聚焦點** @param point 聚焦點*/ -(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];}}]; }/*** 添加點按手勢,點按時聚焦*/ -(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坐標轉化為攝像頭坐標CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];[self setFocusCursorWithPoint:point];[self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint]; }/*** 設置閃光燈按鈕狀態*/ -(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;} }/*** 設置聚焦光標位置** @param point 光標位置*/ -(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

    運行效果:

    視頻錄制

    其實有了前面的拍照應用之后要在此基礎上做視頻錄制功能并不復雜,程序只需要做如下修改:

  • 添加一個音頻輸入到會話(使用[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject]獲得輸入設備,然后根據此輸入設備創建一個設備輸入對象),在拍照程序中已經添加了視頻輸入所以此時不需要添加視頻輸入。
  • 創建一個音樂播放文件輸出對象AVCaptureMovieFileOutput取代原來的照片輸出對象。
  • 將捕獲到的視頻數據寫入到臨時文件并在停止錄制之后保存到相簿(通過AVCaptureMovieFileOutput的代理方法)。
  • 相比拍照程序,程序的修改主要就是以上三點。當然為了讓程序更加完善在下面的視頻錄制程序中加入了屏幕旋轉視頻、自動布局和后臺保存任務等細節。下面是修改后的程序:

    // // 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;//負責輸入和輸出設備之間的數據傳遞 @property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負責從AVCaptureDevice獲得輸入數據 @property (strong,nonatomic) AVCaptureMovieFileOutput *captureMovieFileOutput;//視頻輸出流 @property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機拍攝預覽圖層 @property (assign,nonatomic) BOOL enableRotation;//是否允許旋轉(注意在視頻錄制過程中禁止屏幕旋轉) @property (assign,nonatomic) CGRect *lastBounds;//旋轉的前大小 @property (assign,nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier;//后臺任務標識 @property (weak, nonatomic) IBOutlet UIView *viewContainer; @property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按鈕 @property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標@end@implementation ViewController#pragma mark - 控制器視圖方法 - (void)viewDidLoad {[super viewDidLoad]; }-(void)viewWillAppear:(BOOL)animated{[super viewWillAppear:animated];//初始化會話_captureSession=[[AVCaptureSession alloc]init];if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//設置分辨率_captureSession.sessionPreset=AVCaptureSessionPreset1280x720;}//獲得輸入設備AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭if (!captureDevice) {NSLog(@"取得后置攝像頭時出現問題.");return;}//添加一個音頻輸入設備AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];NSError *error=nil;//根據輸入設備初始化設備輸入對象,用于獲得輸入數據_captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];if (error) {NSLog(@"取得設備輸入對象時出錯,錯誤原因:%@",error.localizedDescription);return;}AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error];if (error) {NSLog(@"取得設備輸入對象時出錯,錯誤原因:%@",error.localizedDescription);return;}//初始化設備輸出對象,用于獲得輸出數據_captureMovieFileOutput=[[AVCaptureMovieFileOutput alloc]init];//將設備輸入添加到會話中if ([_captureSession canAddInput:_captureDeviceInput]) {[_captureSession addInput:_captureDeviceInput];[_captureSession addInput:audioCaptureDeviceInput];AVCaptureConnection *captureConnection=[_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];if ([captureConnection isVideoStabilizationSupported ]) {captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;}}//將設備輸出添加到會話中if ([_captureSession canAddOutput:_captureMovieFileOutput]) {[_captureSession addOutput:_captureMovieFileOutput];}//創建視頻預覽層,用于實時展示攝像頭狀態_captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];CALayer *layer=self.viewContainer.layer;layer.masksToBounds=YES;_captureVideoPreviewLayer.frame=layer.bounds;_captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式//將視頻預覽層添加到界面中//[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; }屏幕旋轉時調整視頻預覽圖層的方向 //-(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; // //} //屏幕旋轉時調整視頻預覽圖層的方向 -(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{AVCaptureConnection *captureConnection=[self.captureVideoPreviewLayer connection];captureConnection.videoOrientation=(AVCaptureVideoOrientation)toInterfaceOrientation; } //旋轉后重新設置大小 -(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{_captureVideoPreviewLayer.frame=self.viewContainer.bounds; }-(void)dealloc{[self removeNotification]; } #pragma mark - UI方法 #pragma mark 視頻錄制 - (IBAction)takeButtonClick:(UIButton *)sender {//根據設備輸出獲得連接AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];//根據連接取得設備輸出的數據if (![self.captureMovieFileOutput isRecording]) {self.enableRotation=NO;//如果支持多任務則則開始多任務if ([[UIDevice currentDevice] isMultitaskingSupported]) {self.backgroundTaskIdentifier=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];}//預覽圖層和視頻方向保持一致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];//獲得要調整的設備輸入對象AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];//改變會話的配置前一定要先開啟配置,配置完成后提交配置改變[self.captureSession beginConfiguration];//移除原有輸入對象[self.captureSession removeInput:self.captureDeviceInput];//添加新的輸入對象if ([self.captureSession canAddInput:toChangeDeviceInput]) {[self.captureSession addInput:toChangeDeviceInput];self.captureDeviceInput=toChangeDeviceInput;}//提交會話配置[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(@"視頻錄制完成.");//視頻錄入完成之后在后臺將視頻存儲到相簿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(@"保存視頻到相簿過程中發生錯誤,錯誤信息:%@",error.localizedDescription);}if (lastBackgroundTaskIdentifier!=UIBackgroundTaskInvalid) {[[UIApplication sharedApplication] endBackgroundTask:lastBackgroundTaskIdentifier];}NSLog(@"成功保存視頻到相簿.");}];}#pragma mark - 通知 /*** 給輸入設備添加通知*/ -(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{//注意添加區域改變捕獲通知必須首先設置設備允許捕獲[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {captureDevice.subjectAreaChangeMonitoringEnabled=YES;}];NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];//捕獲區域發生改變[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];//會話出錯[notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession]; }/*** 設備連接成功** @param notification 通知對象*/ -(void)deviceConnected:(NSNotification *)notification{NSLog(@"設備已連接..."); } /*** 設備連接斷開** @param notification 通知對象*/ -(void)deviceDisconnected:(NSNotification *)notification{NSLog(@"設備已斷開."); } /*** 捕獲區域改變** @param notification 通知對象*/ -(void)areaChange:(NSNotification *)notification{NSLog(@"捕獲區域改變..."); }/*** 會話出錯** @param notification 通知對象*/ -(void)sessionRuntimeError:(NSNotification *)notification{NSLog(@"會話發生錯誤."); }#pragma mark - 私有方法/*** 取得指定位置的攝像頭** @param position 攝像頭位置** @return 攝像頭設備*/ -(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];for (AVCaptureDevice *camera in cameras) {if ([camera position]==position) {return camera;}}return nil; }/*** 改變設備屬性的統一操作方法** @param propertyChange 屬性改變操作*/ -(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{AVCaptureDevice *captureDevice= [self.captureDeviceInput device];NSError *error;//注意改變設備屬性前一定要首先調用lockForConfiguration:調用完之后使用unlockForConfiguration方法解鎖if ([captureDevice lockForConfiguration:&error]) {propertyChange(captureDevice);[captureDevice unlockForConfiguration];}else{NSLog(@"設置設備屬性過程發生錯誤,錯誤信息:%@",error.localizedDescription);} }/*** 設置閃光燈模式** @param flashMode 閃光燈模式*/ -(void)setFlashMode:(AVCaptureFlashMode )flashMode{[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {if ([captureDevice isFlashModeSupported:flashMode]) {[captureDevice setFlashMode:flashMode];}}]; } /*** 設置聚焦模式** @param focusMode 聚焦模式*/ -(void)setFocusMode:(AVCaptureFocusMode )focusMode{[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {if ([captureDevice isFocusModeSupported:focusMode]) {[captureDevice setFocusMode:focusMode];}}]; } /*** 設置曝光模式** @param exposureMode 曝光模式*/ -(void)setExposureMode:(AVCaptureExposureMode)exposureMode{[self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {if ([captureDevice isExposureModeSupported:exposureMode]) {[captureDevice setExposureMode:exposureMode];}}]; } /*** 設置聚焦點** @param point 聚焦點*/ -(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];}}]; }/*** 添加點按手勢,點按時聚焦*/ -(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坐標轉化為攝像頭坐標CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];[self setFocusCursorWithPoint:point];[self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint]; }/*** 設置聚焦光標位置** @param point 光標位置*/ -(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

    運行效果:

    總結

    前面用了大量的篇幅介紹了iOS中的音、視頻播放和錄制,有些地方用到了封裝好的播放器、錄音機直接使用,有些是直接調用系統服務自己組織封裝,正如本篇開頭所言,iOS對于多媒體支持相當靈活和完善,那么開放過程中如何選擇呢,下面就以一個表格簡單對比一下各個開發技術的優缺點。

    提示:從本文及以后的文章中可能慢慢使用storyboard或xib,原因如下:1.蘋果官方目前主推storyboard;2.后面的文章中做屏幕適配牽扯到很多內容都是storyboard中進行(盡管純代碼也可以實現,但是純代碼對autolayout支持不太好)3.通過前面的一系列文章大家對于純代碼編程應該已經有一定的積累了(純代碼確實可以另初學者更加了解程序運行原理)。

    轉載于:https://www.cnblogs.com/huangfang1314/p/5676743.html

    總結

    以上是生活随笔為你收集整理的iOS开发系列--音频播放、录音、视频播放、拍照、视频录制(转)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    色婷婷狠狠五月综合天色拍 | 精油按摩av | 丁香影院在线 | 夜夜躁天天躁很躁波 | 99久久精品日本一区二区免费 | 婷婷在线播放 | 在线国产91 | 亚洲天堂自拍视频 | 国产一区二区精品 | www.久久婷婷 | 天天爽夜夜爽人人爽一区二区 | 激情五月亚洲 | 三级黄色a | 500部大龄熟乱视频使用方法 | 国产精国产精品 | 在线亚洲欧美日韩 | 久久美女免费视频 | 久久国产精品一区二区三区四区 | 在线视频免费观看 | 日韩高清一二三区 | 日韩毛片在线播放 | 婷婷99| 狠狠地日 | 欧美坐爱视频 | 欧美精品久久人人躁人人爽 | 日日草天天草 | 91社区国产高清 | 五月婷婷丁香六月 | 国产成人福利在线 | 97色在线观看 | 天天操夜夜操天天射 | 成人午夜电影久久影院 | 欧美日韩裸体免费视频 | 国产精品欧美日韩在线观看 | 色九九在线| h文在线观看免费 | 在线成人免费电影 | 麻豆视传媒官网免费观看 | 国产精品短视频 | 亚洲国产小视频在线观看 | 黄网站app在线观看免费视频 | 五月花婷婷 | 天天操天天操天天操天天操天天操 | 狠狠狠色狠狠色综合 | 激情视频91| 国产99久久久久久免费看 | 99精品国产一区二区三区不卡 | 在线一区电影 | 国产精品av免费观看 | 97超级碰| 一区二区三区在线观看免费视频 | 中文在线8新资源库 | 91久久精品一区二区三区 | 在线观看日韩视频 | 一区二区 不卡 | 黄色小说免费观看 | 黄色三级免费片 | 国产在线a视频 | 国产一级电影 | 亚洲午夜av久久乱码 | 亚洲国产精品电影 | 国产精品美女久久久久久网站 | 亚洲国产欧洲综合997久久, | 在线国产小视频 | 色吊丝在线永久观看最新版本 | 久久人人艹 | 在线免费观看涩涩 | 成人在线视频观看 | 久久久久久久久久久久久影院 | 久久99久国产精品黄毛片入口 | 亚洲精品影院在线观看 | 免费a视频| 69国产精品视频 | 免费涩涩网站 | 国产精品资源在线 | 亚洲精品18日本一区app | 欧美日韩一级久久久久久免费看 | 免费在线观看日韩欧美 | 精品久久久久国产免费第一页 | 激情视频一区二区三区 | 日韩久久久 | 久久亚洲视频 | 久久av电影 | 天天狠狠操 | 美女久久久久久久久久 | 久久精品久久精品 | 中文字幕成人av | 午夜av大片 | 婷婷六月天丁香 | 亚洲mv大片欧洲mv大片免费 | a视频免费 | 亚洲精品乱码久久久久久高潮 | 13日本xxxxxⅹxxx20| 亚洲一级片在线观看 | 99在线视频精品 | 狠狠五月天 | 西西大胆免费视频 | 18女毛片| 992tv在线观看 | 亚洲欧洲在线视频 | 麻豆免费视频网站 | 中文字幕在线观看网站 | 视频国产| 免费在线观看91 | aⅴ精品av导航 | 五月激情丁香图片 | 久久综合五月天婷婷伊人 | 伊人网综合在线观看 | 天天在线视频色 | 国产精品久久久久久久久久 | 久久久免费看视频 | 精品福利片 | 午夜久久福利 | 久久黄色a级片 | 月丁香婷婷| 亚洲一区网 | 亚洲黄色精品 | 国产精品 日本 | 中文字幕日韩国产 | 成人久久久精品国产乱码一区二区 | 狠狠狠狠狠狠天天爱 | 麻豆综合网 | 欧美国产三区 | 91九色综合 | 色资源网在线观看 | 最近中文字幕高清字幕免费mv | 日韩va亚洲va欧美va久久 | 精品国内自产拍在线观看视频 | 久久草在线免费 | 色综合天天射 | 亚洲国产成人在线播放 | 久久久黄色免费网站 | av片子在线观看 | 一区二区三区四区免费视频 | 91精品在线免费 | 亚洲欧洲国产精品 | 在线av资源 | 91原创在线观看 | 精品一区欧美 | 天天干 夜夜操 | 国产精品一区一区三区 | 亚洲国产无 | 中文字幕在线观 | 国产一区二区不卡视频 | 日韩三级视频 | 天天综合网~永久入口 | 成人av日韩 | 成人资源站 | 欧美二区视频 | 国产999精品久久久影片官网 | 亚洲精品乱码久久久久久蜜桃动漫 | 婷婷射五月| 欧美a级免费视频 | 成人教育av| 四虎4hu永久免费 | 午夜精选视频 | 亚洲做受高潮欧美裸体 | 尤物97国产精品久久精品国产 | 久久精品国产亚洲a | 午夜视频播放 | 在线 日韩 av | av天天澡天天爽天天av | 伊人在线视频 | 日日综合| 国产高清专区 | 欧美99久久 | 色在线观看网站 | 最新的av网站 | 国产精品久久久久久久午夜片 | 新版资源中文在线观看 | 91亚洲精品国产 | 三级黄色大片在线观看 | 久久字幕网 | 日韩久久久久久久久 | 成人av高清在线观看 | 亚洲精品资源 | 狠狠干干 | 98涩涩国产露脸精品国产网 | 国产尤物在线 | 黄色av电影免费观看 | 人人爽影院 | 黄a网站| 久久精品网站视频 | 欧美 日韩 性 | 五月天激情综合 | 国产精品成久久久久三级 | 国产99久久九九精品免费 | 欧美精品乱码99久久影院 | 在线国产日韩 | 又粗又长又大又爽又黄少妇毛片 | 天天拍天天操 | 国产很黄很色的视频 | 一级电影免费在线观看 | 五月激情亚洲 | 日韩精品免费一区二区在线观看 | 欧美激情视频在线观看免费 | 日韩中文字幕a | 日日夜夜精品免费视频 | 久久国产精品99精国产 | 亚洲 欧洲av | 国产成人av在线影院 | 国产五月色婷婷六月丁香视频 | 成人在线免费av | 久久99热这里只有精品国产 | 婷婷国产精品 | 网址你懂的在线观看 | 久久区二区 | 996久久国产精品线观看 | 免费的国产精品 | 日韩艹| 国产99久久久久 | 欧美中文字幕久久 | 亚洲一级片av | 手机在线日韩视频 | 国产精品久久久久久久av大片 | 尤物一区二区三区 | 欧美日韩一区二区三区免费视频 | 国产成人精品一区二区三区 | 1024在线看片| 81国产精品久久久久久久久久 | 久久视频免费看 | 国产 日韩 欧美 中文 在线播放 | aaa亚洲精品一二三区 | 日韩素人在线观看 | 中文字幕乱在线伦视频中文字幕乱码在线 | 97偷拍视频 | 国产一级免费视频 | 中文字幕精品视频 | 综合网天天| 91精品视频免费看 | 精品国产免费观看 | 欧美视频xxx | 久久久99精品免费观看 | 久久精品99国产国产 | 久久久久欧美精品 | 天天天操操操 | 午夜丰满寂寞少妇精品 | 999久久国产 | 福利区在线观看 | 国产中文字幕国产 | 久久超级碰 | 天天干,天天射,天天操,天天摸 | 久久精品视频99 | 国产高清区 | 日韩精品 在线视频 | 中文字幕精 | 日日天天干| 国产精品999久久久 久产久精国产品 | 免费成人在线观看视频 | 久久久国产99久久国产一 | 超碰av在线 | av网站免费看 | 欧美成人理伦片 | 亚洲黄色高清 | 在线看v片 | 国产乱老熟视频网88av | 91视视频在线直接观看在线看网页在线看 | 特级毛片网 | 午夜资源站| 亚洲国产中文字幕在线视频综合 | 91av视频在线观看 | 天天摸日日操 | 亚洲精品mv在线观看 | 99视频一区| 亚洲精品免费视频 | 91福利视频久久久久 | 免费成视频 | 久久久久免费精品 | 成人教育av | 成人a v视频| 丁香花中文在线免费观看 | 国产91亚洲 | 久久久久久久久久久久久9999 | 天天干天天玩天天操 | 国产91精品看黄网站在线观看动漫 | 久久免费美女视频 | 免费看的国产视频网站 | 成人国产精品一区二区 | 美女网站视频久久 | 色综合天天狠狠 | 精品三级av | 波多野结衣一区三区 | 欧美在线久久 | 黄色av免费 | 免费成人av电影 | 国产a级片免费观看 | 亚洲电影成人 | 国产精品热 | 99理论片| 97看片网 | 婷婷成人亚洲综合国产xv88 | 日本精品中文字幕 | 97成人啪啪网 | 久久xx视频 | 日韩一区二区三区免费视频 | 亚洲一级电影 | 91中文字幕在线播放 | 久久久久综合 | 香蕉一区 | 免费在线一区二区 | 最新日本中文字幕 | 亚洲二级片| av再线观看 | 久久久黄色免费网站 | 91麻豆精品国产91 | 久久婷婷国产色一区二区三区 | 欧美黄色免费 | 97精品一区 | 在线免费观看视频一区二区三区 | 在线免费观看视频一区 | 制服丝袜一区二区 | 日韩区欠美精品av视频 | 精品 一区 在线 | 日韩成人黄色av | 中文字幕在线观看第三页 | 成人网在线免费视频 | 欧美日韩亚洲在线观看 | 国产精品久久久久久av | 在线网站黄 | 中文字幕无吗 | 国产欧美久久久精品影院 | 最新日韩视频在线观看 | 亚洲精品男人天堂 | 91精品秘密在线观看 | 久久99精品国产99久久6尤 | 成人动图 | www99久久 | 国产99免费 | 欧美一级大片在线观看 | 精品一区精品二区 | 久久精品视频2 | 欧美精品在线观看 | 五月婷色| 国产女人40精品一区毛片视频 | 国产免费国产 | www.色婷婷.com| 在线中文字幕av观看 | 国产精品99久久久久久久久久久久 | 久草免费在线观看 | 婷婷日日 | 色偷偷中文字幕 | 久久久久伊人 | 00av视频| 国产不卡免费av | 免费欧美高清视频 | 97在线公开视频 | 亚洲一级在线观看 | 天天操天天操天天操天天 | 国产丝袜在线 | 国产系列在线观看 | 不卡的av中文字幕 | 97碰在线视频 | 欧美视频二区 | 二区中文字幕 | 免费看一级 | 天天草天天色 | 午夜手机电影 | 日韩精品高清视频 | 亚州五月| 麻豆91网站 | 成人黄色在线观看视频 | 国产+日韩欧美 | 91高清完整版在线观看 | 色婷婷综合久久久中文字幕 | 亚洲婷婷免费 | 在线观看免费一区 | 久久久久久影视 | 在线看片日韩 | 黄色的视频 | 欧美日韩在线精品一区二区 | 成人一级片免费看 | 日韩av专区| 成人黄色电影在线观看 | 婷婷国产视频 | 午夜久草 | 人人爱人人舔 | 高清免费av在线 | 午夜视频播放 | 日本中文字幕系列 | 国产一级精品在线观看 | 青草草在线视频 | 日韩欧美综合精品 | 天天视频色版 | 免费看污的网站 | 香蕉久草 | 精品美女在线视频 | 日韩特黄一级欧美毛片特黄 | 91免费版在线观看 | 久久久久久久久毛片精品 | 日韩精品久久一区二区三区 | 日韩中文字幕视频在线观看 | 国产美女被啪进深处喷白浆视频 | 国产在线视频一区二区 | 日本精品va在线观看 | 国产精品久久99 | 久久在线免费视频 | 97电院网手机版 | 日韩精品观看 | 久久久国产精品免费 | 玖玖玖在线 | 亚洲va欧洲va国产va不卡 | 成人免费观看av | 香蕉视频在线免费 | 国产精品一区二区在线观看 | 日韩精品一区二区三区电影 | 日韩丝袜在线观看 | 国产二区免费视频 | 丁五月婷婷| 国产小视频在线免费观看视频 | 国产成人精品一区二区三区福利 | 97操操操| 天天天天色射综合 | 国产麻豆视频在线观看 | 久久久国产一区二区三区 | 日韩在线理论 | 日本久久综合网 | 国产a国产 | 国产免费影院 | 国产一二区精品 | www.夜夜操 | 黄色免费网站大全 | 成人在线免费av | 国产精品一区二区白浆 | 婷婷丁香在线 | 九九涩涩av台湾日本热热 | 欧美性久久久 | www.国产毛片 | 国产日韩精品在线 | 97爱爱爱 | 欧美一级xxxx| 免费在线观看av网址 | 国产精品麻豆三级一区视频 | 99视频播放 | 久久久久国产精品免费网站 | 国产精品18久久久久久久久 | 国产精品美女免费看 | 免费av片在线 | 婷婷久久网 | 91久久精品一区二区三区 | 九九九九色 | 欧美日韩一级视频 | 欧美精品久久久久久久亚洲调教 | 国产精品你懂的在线观看 | 久草网站在线观看 | 五月婷婷一区二区三区 | 精品国产一区二区三区在线观看 | 麻豆免费视频观看 | 国产免费亚洲 | 久久精品欧美一区二区三区麻豆 | 婷婷色在线 | 99久久综合狠狠综合久久 | 九色免费视频 | 丁香视频免费观看 | 国产在线精品福利 | 97人人模人人爽人人喊网 | 国产精品福利在线观看 | 天堂va欧美va亚洲va老司机 | 中文字幕中文中文字幕 | 日韩精品在线视频免费观看 | 午夜电影 电影 | 日本黄色大片儿 | 国产一区黄色 | 三日本三级少妇三级99 | 久久免费黄色大片 | 欧美精品久久久久久久 | 国产欧美精品一区二区三区 | 99久久久国产精品 | 久久精品黄色 | 天天操夜夜操 | 开心色停停| 亚洲精品国久久99热 | 国产精品欧美久久久久久 | 久久久免费精品视频 | 国产精品成人av在线 | 国产视频一区二区在线 | 免费成人av在线看 | 四虎影视成人 | 国产精品丝袜久久久久久久不卡 | 日韩欧美有码在线 | 伊人色综合久久天天 | 人人爱人人舔 | 99久视频 | 激情影院在线观看 | 国产成人一区二区三区 | 欧美资源在线观看 | 99久久精品免费看国产免费软件 | 国产成人99av超碰超爽 | 色婷婷亚洲综合 | 精品国产三级 | 久久精品牌麻豆国产大山 | 久久99热这里只有精品 | 日日夜夜人人精品 | 欧美日韩视频 | 97超级碰 | 欧美日韩精品在线 | 免费国产黄线在线观看视频 | 九九视频精品免费 | 97电影在线| 久久久久在线观看 | 99精品国自产在线 | 日韩欧美精品一区 | 日韩成人免费电影 | 91在线视频在线 | 亚洲乱码中文字幕综合 | 国产区免费 | 二区视频在线观看 | 国产小视频在线 | 激情av在线播放 | av网址在线播放 | 久久精品99久久久久久 | 欧美国产高清 | 91成人在线观看喷潮 | www.久久久| 黄色午夜网站 | 亚洲精品一区二区三区高潮 | 六月丁香激情综合色啪小说 | 国产精品麻豆一区二区三区 | 亚洲免费婷婷 | a天堂中文在线 | 国产精品午夜久久久久久99热 | 国产亚洲字幕 | 免费在线播放av电影 | 国产九九热 | 久久久久久综合 | 91桃花视频 | 国产99免费 | 欧美超碰在线 | 性色av免费观看 | 欧美在线视频一区二区三区 | 黄在线免费看 | 91tv国产成人福利 | 久久综合偷偷噜噜噜色 | 男女激情免费网站 | 亚洲成人xxx | 黄色一级在线视频 | 97视频免费在线 | 久久久久日本精品一区二区三区 | 91香蕉视频污在线 | 六月色婷 | 91高清完整版在线观看 | 国产伦理久久精品久久久久_ | 午夜久久久精品 | 久久天天躁夜夜躁狠狠躁2022 | 欧美日韩一区二区三区在线免费观看 | 天天操天天射天天添 | 国产黄色大片免费看 | 在线看成人片 | 久久久久久免费视频 | 香蕉精品视频在线观看 | www日日| 在线免费三级 | 在线一区av| 不卡av在线播放 | 亚洲免费在线看 | 黄色一集片 | 97电影在线看视频 | 99热这里只有精品在线观看 | 国产精品视屏 | 国产中文字幕视频在线观看 | 亚洲伦理电影在线 | 亚洲午夜激情网 | 欧美日性视频 | av一区二区三区在线 | 久草在线免费看视频 | 亚洲精品白浆高清久久久久久 | 亚洲乱码精品久久久 | 久久久精品高清 | 深爱激情婷婷网 | 娇妻呻吟一区二区三区 | 婷婷午夜| 免费色视频网址 | 99久久久久| 欧美日韩网站 | 国产99久久久国产精品免费二区 | 日本久久久久久久久久 | 亚洲经典视频在线观看 | 狠狠亚洲 | 国产精品 日韩 欧美 | 久草视频观看 | 国产精品美女免费 | 欧美成人黄色片 | 黄色中文字幕在线 | 麻豆91在线观看 | 草久久精品 | av观看久久久 | 国产亚洲一级高清 | 色综合在 | 国产精品欧美日韩在线观看 | 欧美日韩午夜爽爽 | 国产精品久久久久影视 | 蜜桃av久久久亚洲精品 | 国产精品18久久久久久久 | 国产理伦在线 | 国产一区二区久久久久 | 国产三级午夜理伦三级 | 四虎国产精品免费观看视频优播 | 久久免费的精品国产v∧ | 国产小视频在线观看免费 | av大全在线| 久久久国产影视 | 天天干干 | 婷婷丁香九月 | 在线精品播放 | 91爱爱免费观看 | 国产精品高潮久久av | 福利电影久久 | 综合五月婷婷 | 日av免费 | 色99视频 | 精品一二三四五区 | 国产精品毛片一区二区 | 97色婷婷成人综合在线观看 | 伊人久久影视 | 国产三级国产精品国产专区50 | 五月婷婷网站 | 日韩在线视频国产 | 国产人成精品一区二区三 | 奇米网8888| 国产精品久久久毛片 | 狠狠躁天天躁综合网 | 中文字幕av在线不卡 | 婷婷网在线| 这里只有精品视频在线 | 夜夜天天干 | 中文字幕在线观看完整版电影 | 亚洲久草在线 | 日本黄色特级片 | 久久久久久蜜av免费网站 | 操操操夜夜操 | 亚洲91av| 久久久久观看 | 久久久久免费精品国产 | 黄色午夜网站 | 中文字幕在线看 | 国产精品久99 | 国产精品入口a级 | 人人爽爽人人 | 久久久精品电影 | 午夜久久福利视频 | 又紧又大又爽精品一区二区 | 国产精品av久久久久久无 | 97精品电影院 | 草免费视频 | 一区精品在线 | 亚洲综合激情网 | 亚洲黄色在线观看 | 久久精品成人 | 中文字幕在线观看完整 | 最近日韩免费视频 | 精品久久久久久久久亚洲 | 久爱综合 | av网址aaa| 中文字幕观看av | 国产精品免费人成网站 | 亚洲狠狠操| 久久优 | 婷婷视频在线播放 | 国产精品一区电影 | 欧美日韩高清在线 | 国产精品久久久久三级 | 亚洲精品玖玖玖av在线看 | 亚洲草视频 | 成人午夜毛片 | 日韩天堂网 | 97超碰人人模人人人爽人人爱 | 伊人日日干 | 国产视频午夜 | 欧美调教网站 | www.com在线观看 | 欧美精品久久久久久久久免 | 日韩视频免费 | 国产精品一区二区在线 | 国产成人精品一区一区一区 | 国产精品国产三级国产专区53 | 午夜精品婷婷 | 精品一区二区在线播放 | 中文字幕丝袜一区二区 | 天天干天天射天天操 | 婷婷视频导航 | 999ZYZ玖玖资源站永久 | 色九九影院 | 91香蕉国产在线观看软件 | 日黄网站 | 亚洲片在线资源 | 久久av在线播放 | 国产成人黄色在线 | 天天操天操 | 99久高清在线观看视频99精品热在线观看视频 | 美女网站色在线观看 | 久久久国产精品一区二区中文 | 久久精品这里热有精品 | 日韩美一区二区三区 | 日韩激情精品 | 国产日韩三级 | 美女视频网站久久 | 色婷婷午夜 | 国产群p| 最新日韩中文字幕 | 欧美日韩中文在线观看 | 婷婷丁香导航 | 狠狠艹夜夜干 | 亚洲精品tv久久久久久久久久 | 久久久久久国产精品亚洲78 | 成人亚洲网 | 99久在线精品99re8热视频 | 国产97视频在线 | 婷婷国产在线观看 | 伊人天堂av | 国产区 在线| 在线亚洲成人 | 国产蜜臀av | 欧美在线观看视频 | 狠狠色丁香 | 久久久久久久99 | 久久午夜精品影院一区 | 亚洲午夜精品一区 | 免费观看国产成人 | 国产免费一区二区三区网站免费 | 免费观看的av | 欧美日韩亚洲精品在线 | 97超碰成人在线 | 夜色资源网 | 久操视频在线播放 | 午夜精品久久久久久久99水蜜桃 | 96av视频| 久久综合中文字幕 | 亚洲综合视频在线观看 | 亚洲国产免费av | av高清网站在线观看 | 99久久精品免费视频 | 奇米影视777影音先锋 | 成人中心免费视频 | 成人在线免费看视频 | 亚洲成人xxx | 免费又黄又爽的视频 | 在线播放精品一区二区三区 | 国产资源av | 操操操夜夜操 | 亚洲日本韩国一区二区 | 成人国产一区 | 麻豆精品国产传媒 | 久久久人| 成人欧美一区二区三区黑人麻豆 | 操操综合网 | 国产福利一区二区三区视频 | 视频在线亚洲 | a天堂中文在线 | 亚洲黄色在线免费观看 | 日韩精品中字 | 国内精品美女在线观看 | 99精品视频在线看 | 一区二区三区免费看 | 伊人久久av | 日韩午夜视频在线观看 | 婷婷丁香自拍 | 久久综合色8888 | 右手影院亚洲欧美 | 超级碰碰碰碰 | 91九色国产视频 | 91久久黄色 | 五月天久久| 欧美色综合久久 | 亚洲一区二区精品3399 | 精品一区精品二区 | 国产精品99久久久久久大便 | 欧美久久久久久久久久久久久 | 成人精品影视 | 99视频精品全部免费 在线 | 国产一级在线观看视频 | 激情婷婷网| 99视频网站 | 欧美性大战久久久久 | 国产视频精选 | 99在线看| 91麻豆国产福利在线观看 | 日日射天天射 | 欧美一区二视频在线免费观看 | 中文字幕一区二区三区在线观看 | 黄色av一区二区三区 | 亚洲女欲精品久久久久久久18 | 福利视频一区二区 | 500部大龄熟乱视频 欧美日本三级 | 欧美日韩亚洲第一 | 国产精成人品免费观看 | 狠狠综合| 性色av免费看 | 国产高清99 | 黄色成人在线 | 麻豆免费在线视频 | 狠狠躁夜夜躁人人爽超碰97香蕉 | 最近免费在线观看 | 久操视频在线播放 | 四虎在线观看视频 | 天天做天天看 | 丁香九月婷婷综合 | 欧美在线久久 | 欧美 亚洲 另类 激情 另类 | 97国产在线观看 | 免费看的黄色网 | 国产精品久久久久久久久久了 | 福利久久| 黄污在线看 | 国产精品刺激对白麻豆99 | 国产一级视频免费看 | 日韩欧美在线一区 | 色99视频 | 午夜精品在线看 | 久久国内精品视频 | 免费91麻豆精品国产自产在线观看 | 日本久久免费电影 | 亚洲日韩中文字幕 | 免费看亚洲毛片 | 亚洲欧美精品在线 | 欧美高清视频不卡网 | 美女网站黄免费 | 在线亚洲播放 | 草久久影院 | 超碰国产97 | 免费97视频 | 91激情在线视频 | 亚洲精品天天 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 中文字幕亚洲五码 | 成人在线免费观看视视频 | 91av免费看 | 久久福利在线 | 丝袜美腿在线播放 | 日本系列中文字幕 | 一区二区三区影院 | 激情五月***国产精品 | 免费福利片2019潦草影视午夜 | 狠狠的操你 | 黄色免费视频在线观看 | 欧美日产在线观看 | 久久99热国产 | 国产一级片一区二区三区 | 91九色视频观看 | 三级大片网站 | 天天舔天天射天天操 | 中文av影院 | 91大神电影 | 国产黄色在线看 | 日韩欧美91 | 亚洲精品久久在线 | 欧美在线视频一区二区三区 | 亚洲综合色激情五月 | 在线观看色网站 | 欧美一级视频在线观看 | 久久久久看片 | 免费av电影网站 | 狠狠躁夜夜躁人人爽超碰91 | 国产午夜三级 | 精品欧美一区二区在线观看 | 91欧美日韩国产 | 国产成人免费在线观看 | 国产精品美女久久久久久久网站 | 97超碰人人澡人人 | 夜又临在线观看 | 人人爽久久久噜噜噜电影 | 色六月婷婷 | 久久香蕉电影网 | 天天插天天干天天操 | 超碰人人干人人 | 中文字幕在线乱 | 亚洲精品www久久久 www国产精品com | 免费福利片 | 一区二区不卡在线观看 | 91麻豆精品国产91久久久久久 | 黄色影院在线播放 | 国产精品一区二区在线 | 亚洲日本国产精品 | 国产精品乱码久久久久 | 成人av手机在线 | 欧美日韩精品影院 | 久久视频免费在线观看 | 日韩毛片在线免费观看 | 国产视频一区精品 | 日日夜夜狠狠干 | 久久国产系列 | 极品久久久久久久 | 国内精品久久久久久久97牛牛 | 亚洲免费在线观看视频 | 久久99久国产精品黄毛片入口 | 欧美黑人性猛交 | 大胆欧美gogo免费视频一二区 | 亚洲欧美成aⅴ人在线观看 四虎在线观看 | 亚洲欧美婷婷六月色综合 | 搡bbbb搡bbb视频 | 午夜私人影院久久久久 | 国产成人精品不卡 | 欧美日韩在线视频一区 | 久久网站免费 | 亚洲 综合 专区 | 在线观看爱爱视频 | 日韩精品一区二区免费视频 | 婷香五月 | 天天干视频在线 | 婷婷五天天在线视频 | 欧美日韩国产亚洲乱码字幕 | 国产一级视频在线免费观看 | 亚洲精品福利在线观看 | 在线观看91网站 | 99人久久精品视频最新地址 | 国产日产欧美在线观看 | 国产综合在线视频 | 91豆花在线观看 | 久久99九九99精品 | 在线成人一区二区 | 国产一区二区高清不卡 | 黄色a视频 | 91av免费看 | 久久久久久久电影 | 国产1区在线 | 97成人在线观看 | 久久美女精品 | 久久综合久久八八 | 国产精品你懂的在线观看 | 中文av免费 | 99色视频 | 免费在线播放视频 | 国产亚洲aⅴaaaaaa毛片 | 狠狠狠狠狠狠狠狠干 | 久久天天躁狠狠躁夜夜不卡公司 | 久久国产露脸精品国产 | 国产精品免费看久久久8精臀av | 国产精品自在欧美一区 | 成人一级电影在线观看 | 日本公妇在线观看 | 五月天高清欧美mv | 久久人人爽人人爽人人片av免费 | 91精品久久久久久久91蜜桃 | 97在线观看免费 | 在线看国产 | 色综合久久88色综合天天人守婷 | 亚洲九九影院 | 免费中文字幕视频 | 久久精品一区二区国产 | 国产精品高清在线观看 | 免费色视频网址 | 色妞色视频一区二区三区四区 | 午夜精品久久久久久久99婷婷 | 日本三级香港三级人妇99 | 免费视频色 | 久久精品视频一 | 国产夫妻自拍av | 在线国产激情视频 | 中文字幕免 | 国产免费三级在线观看 | 国产精品毛片一区二区三区 | 中文字幕你懂的 | 欧美最猛性xxxx| 欧美另类xxx| 国产精品一区二区三区免费看 | 久草国产精品 | 激情综合色综合久久 | 久久精品99国产精品亚洲最刺激 | 亚洲一区久久 | 999色视频| 天天躁日日躁狠狠躁av麻豆 | 亚洲综合成人专区片 | 中文字幕激情 | 久久伊人精品天天 | 亚洲天堂网站视频 | 国产中文字幕一区 | 精品国产一区二区三区四 | 精品你懂的 | 国产一区二区免费看 | a电影在线观看 | 欧美日韩国产综合网 | 免费日韩 精品中文字幕视频在线 | 中文字幕国产 | 99精品福利 | 涩涩爱夜夜爱 | 日韩中文字幕免费在线观看 | 亚洲国产一区在线观看 | 久久久精华网 | 91三级视频 | 久久久久国产一区二区三区 | 欧美老人xxxx18 | 在线观看国产永久免费视频 | 亚洲国产午夜精品 | 夜夜夜夜夜夜操 | 国产综合精品一区二区三区 | 日韩欧美在线视频一区二区三区 | 激情在线网址 | 天天干天天操人体 | 激情影音先锋 | 最新的av网站 | 久久精品免费观看 | 最近中文字幕免费av | 国产午夜在线观看 | 国产精品精品国产色婷婷 | 国产小视频在线看 | 天天操天天曰 | 精品免费视频123区 午夜久久成人 | 77国产精品 | 久久手机精品视频 | 婷婷综合网 |