随着移动互联网的发展,如今的手机早已不是打电话、发短信那么简单了,播放音乐、视频、录音、拍照等都是很常用的功能。在ios中对于多媒体的支持是非常强大的,无论是音视频播放、录制,还是对麦克风、摄像头的操作都提供了多套api。在今天的文章中将会对这些内容进行一一介绍:
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#audio">音频</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#soundeffect">音效</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#music">音乐</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#audiosession">音频会话</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#audiorecord">录音</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#audioqueueservices">音频队列服务</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#video">视频</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#mpmovieplayercontroller">mpmovieplayercontroller</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#mpmovieplayerviewcontroller">mpmovieplayerviewcontroller</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#avplayer">avplayer</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#camera">摄像头</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#uiimagepickercontroller">uiimagepickercontroller拍照和视频录制</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#avfoundationcamera">avfoundation拍照和录制视频</a>
<a href="http://www.cnblogs.com/kenshincui/p/4186022.html#summary">总结</a>
目 录
在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) 方法播放音效(后者带有震动效果)。
下面是一个简单的示例程序:
属性
说明
@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实现一个简单播放器,在这个播放器中实现了播放、暂停、显示播放进度功能,当然例如调节音量、设置循环模式、甚至是声波图像(通过分析音频分贝值)等功能都可以实现,这里就不再一一演示。界面效果如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLyMDNzgTNzcTNyETOwYjMvwlMxQTMwIzLcZDNwIjNvw1ZvxmYvwVbvNmLn9GbiRXauNmLzV2Zh1Wavw1LcpDc0RHaiojIsJye.png)
当然由于avaudioplayer一次只能播放一个音频文件,所有上一曲、下一曲其实可以通过创建多个播放器对象来完成,这里暂不实现。播放进度的实现主要依靠一个定时器实时计算当前播放时长和音频总时长的比例,另外为了演示委托方法,下面的代码中也实现了播放完成委托方法,通常如果有下一曲功能的话播放完可以触发下一曲音乐播放。下面是主要代码:
运行效果:
事实上上面的播放器还存在一些问题,例如通常我们看到的播放器即使退出到后台也是可以播放的,而这个播放器如果退出到后台它会自动暂停。如果要支持后台播放需要做下面几件事情:
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有必要进行一下详细的说明。
在ios中每个应用都有一个音频会话,这个会话就通过avaudiosession来表示。avaudiosession同样存在于avfoundation框架中,它是单例模式设计,通过sharedinstance进行访问。在使用apple设备时大家会发现有些应用只要打开其他音频播放就会终止,而有些应用却可以和其他应用同时播放,在多种音频环境中如何去控制播放的方式就是通过音频会话来完成的。下面是音频会话的几种会话模式:
会话类型
是否要求输入
是否要求输出
是否遵从静音键
avaudiosessioncategoryambient
混音播放,可以与其他音频应用同时播放
否
是
avaudiosessioncategorysoloambient
独占播放
avaudiosessioncategoryplayback
后台播放,也是独占的
avaudiosessioncategoryrecord
录音模式,用于录音时使用
avaudiosessioncategoryplayandrecord
播放和录音,此时可以录音也可以播放
avaudiosessioncategoryaudioprocessing
硬件解码音频,此时不能播放和录制
avaudiosessioncategorymultiroute
多种输入输出,例如可以耳机、usb设备同时播放
注意:是否遵循静音键表示在播放过程中如果用户通过硬件设置为静音是否能关闭声音。
根据前面对音频会话的理解,相信大家开发出能够在后台播放的音频播放器并不难,但是注意一下,在前面的代码中也提到设置完音频会话类型之后需要调用setactive::方法将会话激活才能起作用。类似的,如果一个应用已经在播放音频,打开我们的应用之后设置了在后台播放的会话类型,此时其他应用的音频会停止而播放我们的音频,如果希望我们的程序音频播放完之后(关闭或退出到后台之后)能够继续播放其他应用的音频的话则可以调用setactive::方法关闭会话。代码如下:
在上面的代码中还实现了拔出耳机暂停音乐播放的功能,这也是一个比较常见的功能。在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获得媒体队列或媒体集合):
@property(readonly, getter=isrecording) bool recording;
是否正在录音,只读
录音文件地址,只读
录音文件设置,只读
@property(readonly) nstimeinterval currenttime
录音时长,只读,注意仅仅在录音状态可用
输入设置的时间长度,只读,注意此属性一直可访问
@property(getter=ismeteringenabled) bool meteringenabled;
是否启用录音测量,如果启用录音测量可以获得录音分贝等数据信息
当前录音的通道
- (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
在指定的时间开始录音,并指定录音时长
暂停录音
停止录音
- (bool)deleterecording;
删除录音,注意要删除录音此时录音机必须处于停止状态
- (void)updatemeters;
更新测量数据,注意只有meteringenabled为yes此方法才可用
指定通道的测量峰值,注意只有调用完updatemeters才有值
指定通道的测量平均值,注意只有调用完updatemeters才有值
- (void)audiorecorderdidfinishrecording:(avaudiorecorder *)recorder successfully:(bool)flag
完成录音
- (void)audiorecorderencodeerrordidoccur:(avaudiorecorder *)recorder error:(nserror *)error
录音编码发生错误
下面就使用avaudiorecorder创建一个录音机,实现了录音、暂停、停止、播放等功能,实现效果大致如下:
在这个示例中将实行一个完整的录音控制,包括录音、暂停、恢复、停止,同时还会实时展示用户录音的声音波动,当用户点击完停止按钮还会自动播放录音文件。程序的构建主要分为以下几步:
设置音频会话类型为avaudiosessioncategoryplayandrecord,因为程序中牵扯到录音和播放操作。
创建录音机avaudiorecorder,指定录音保存的路径并且设置录音属性,注意对于一般的录音文件要求的采样率、位数并不高,需要适当设置以保证录音文件的大小和效果。
设置录音机代理以便在录音完成后播放录音,打开录音测量保证能够实时获得录音时的声音强度。(注意声音强度范围-160到0,0代表最大输入)
创建音频播放器avaudioplayer,用于在录音完成之后播放录音。
创建一个定时器以便实时刷新录音测量值并更新录音强度到uiprogressview中显示。
添加录音、暂停、恢复、停止操作,需要注意录音的恢复操作其实是有音频会话管理的,恢复时只要再次调用record方法即可,无需手动管理恢复时间等。
下面是主要代码:
大家应该已经注意到了,无论是前面的录音还是音频播放均不支持网络流媒体播放,当然对于录音来说这种需求可能不大,但是对于音频播放来说有时候就很有必要了。avaudioplayer只能播放本地文件,并且是一次性加载所以音频数据,初始化avaudioplayer时指定的url也只能是file url而不能是http url。当然,将音频文件下载到本地然后再调用avaudioplayer来播放也是一种播放网络音频的办法,但是这种方式最大的弊端就是必须等到整个音频播放完成才能播放,而不能使用流式播放,这往往在实际开发中是不切实际的。那么在ios中如何播放网络流媒体呢?就是使用audiotoolbox框架中的音频队列服务audio queue services。
使用音频队列服务完全可以做到音频播放和录制,首先看一下录音音频服务队列:
一个音频服务队列audio queue有三部分组成:
三个缓冲器buffers:每个缓冲器都是一个存储音频数据的临时仓库。
一个缓冲队列buffer queue:一个包含音频缓冲器的有序队列。
一个回调callback:一个自定义的队列回调函数。
声音通过输入设备进入缓冲队列中,首先填充第一个缓冲器;当第一个缓冲器填充满之后自动填充下一个缓冲器,同时会调用回调函数;在回调函数中需要将缓冲器中的音频数据写入磁盘,同时将缓冲器放回到缓冲队列中以便重用。下面是apple官方关于音频队列服务的流程示意图:
类似的,看一下音频播放缓冲队列,其组成部分和录音缓冲队列类似。
但是在音频播放缓冲队列中,回调函数调用的时机不同于音频录制缓冲队列,流程刚好相反。将音频读取到缓冲器中,一旦一个缓冲器填充满之后就放到缓冲队列中,然后继续填充其他缓冲器;当开始播放时,则从第一个缓冲器中读取音频进行播放;一旦播放完之后就会触发回调函数,开始播放下一个缓冲器中的音频,同时填充第一个缓冲器放;填充满之后再次放回到缓冲队列。下面是详细的流程:
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文件)
然后就可以编写代码播放网络音频了:
在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播放
是否准备好播放
当前播放时间,单位:秒
当前播放速度,如果暂停则为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
取消所有缩略图获取请求
准备播放,加载视频数据到缓存,当调用play方法时如果没有准备好会自动调用此方法
向前定位
向后定位
停止快进/快退
mpmovieplayerscalingmodedidchangenotification
视频缩放填充模式发生改变
mpmovieplayerplaybackdidfinishnotification
媒体播放完成或用户手动退出,具体完成原因可以通过通知userinfo中的key为mpmovieplayerplaybackdidfinishreasonuserinfokey的对象获取
mpmovieplayerplaybackstatedidchangenotification
播放状态改变,可配合playbakcstate属性获取具体状态
mpmovieplayerloadstatedidchangenotification
媒体网络加载状态改变
mpmovieplayernowplayingmoviedidchangenotification
当前播放的媒体内容发生改变
mpmovieplayerwillenterfullscreennotification
将要进入全屏
mpmovieplayerdidenterfullscreennotification
进入全屏后
mpmovieplayerwillexitfullscreennotification
将要退出全屏
mpmovieplayerdidexitfullscreennotification
退出全屏后
mpmovieplayerisairplayvideoactivedidchangenotification
当媒体开始通过airplay播放或者结束airplay播放
mpmovieplayerreadyfordisplaydidchangenotification
视频显示状态改变
mpmoviemediatypesavailablenotification
确定了媒体可用类型后
mpmoviesourcetypeavailablenotification
确定了媒体来源后
mpmoviedurationavailablenotification
确定了媒体播放时长后
mpmovienaturalsizeavailablenotification
确定了媒体的实际尺寸后
mpmovieplayerthumbnailimagerequestdidfinishnotification
缩略图请求完成之后
做好播放准备后
注意mpmediaplayercontroller的状态等信息并不是通过代理来和外界交互的,而是通过通知中心,因此从上面的列表中可以看到常用的一些通知。由于mpmovieplayercontroller本身对于媒体播放做了深度的封装,使用起来就相当简单:创建mpmovieplayercontroller对象,设置frame属性,将mpmovieplayercontroller的view添加到控制器视图中。下面的示例中将创建一个播放控制器并添加播放状态改变及播放完成的通知:
从上面的api大家也不难看出其实mpmovieplayercontroller功能相当强大,日常开发中作为一般的媒体播放器也完全没有问题。mpmovieplayercontroller除了一般的视频播放和控制外还有一些强大的功能,例如截取视频缩略图。请求视频缩略图时只要调用- (void)requestthumbnailimagesattimes:(nsarray *)playbacktimes timeoption:(mpmovietimeoption)option方法指定获得缩略图的时间点,然后监控mpmovieplayerthumbnailimagerequestdidfinishnotification通知,每个时间点的缩略图请求完成就会调用通知,在通知调用方法中可以通过mpmovieplayerthumbnailimagekey获得uiimage对象处理即可。例如下面的程序演示了在程序启动后获得两个时间点的缩略图的过程,截图成功后保存到相册:
截图效果:
通过前面的方法大家应该已经看到,使用mpmovieplayercontroller来生成缩略图足够简单,但是如果仅仅是是为了生成缩略图而不进行视频播放的话,此刻使用mpmovieplayercontroller就有点大材小用了。其实使用avfundation框架中的avassetimagegenerator就可以获取视频缩略图。使用avassetimagegenerator获取缩略图大致分为三个步骤:
创建avurlasset对象(此类主要用于获取媒体信息,包括视频、声音等)。
根据avurlasset创建avassetimagegenerator对象。
使用avassetimagegenerator的copycgimageattime::方法获得指定时间点的截图。
生成的缩略图效果:
其实mpmovieplayercontroller如果不作为嵌入视频来播放(例如在新闻中嵌入一个视频),通常在播放时都是占满一个屏幕的,特别是在iphone、itouch上。因此从ios3.2以后苹果也在思考既然mpmovieplayercontroller在使用时通常都是将其视图view添加到另外一个视图控制器中作为子视图,那么何不直接创建一个控制器视图内部创建一个mpmovieplayercontroller属性并且默认全屏播放,开发者在开发的时候直接使用这个视图控制器。这个内部有一个mpmovieplayercontroller的视图控制器就是mpmovieplayerviewcontroller,它继承于uiviewcontroller。mpmovieplayerviewcontroller内部多了一个movieplayer属性和一个带有url的初始化方法,同时它内部实现了一些作为模态视图展示所特有的功能,例如默认是全屏模式展示、弹出后自动播放、作为模态窗口展示时如果点击“done”按钮会自动退出模态窗口等。在下面的示例中就不直接将播放器放到主视图控制器,而是放到一个模态视图控制器中,简单演示mpmovieplayerviewcontroller的使用。
这里需要强调一下,由于mpmovieplayerviewcontroller的初始化方法做了大量工作(例如设置url、自动播放、添加点击done完成的监控等),所以当再次点击播放弹出新的模态窗口的时如果不销毁之前的mpmovieplayerviewcontroller,那么新的对象就无法完成初始化,这样也就不能再次进行播放。
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专门处理播放列表切换,有兴趣的朋友可以自行研究,这里不再赘述)。
下面附上代码:
下面看一下在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是枚举类型:
+ (nsarray *)availablemediatypesforsourcetype:(uiimagepickercontrollersourcetype)sourcetype
指定的源设备上可用的媒体类型,一般就是图片和视频
+ (bool)iscameradeviceavailable:(uiimagepickercontrollercameradevice)cameradevice
指定的摄像头是否可用,cameradevice是枚举类型:
+ (bool)isflashavailableforcameradevice:(uiimagepickercontrollercameradevice)cameradevice
指定摄像头的闪光灯是否可用
+ (nsarray *)availablecapturemodesforcameradevice:(uiimagepickercontrollercameradevice)cameradevice
获得指定摄像头上的可用捕获模式,捕获模式是枚举类型:
- (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则为拍照模式,拍照完成之后在主视图控制器中显示拍摄的照片:
运行效果(视频录制):
不得不说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实现一个拍照程序,在这个程序中将实现摄像头预览、切换前后摄像头、闪光灯设置、对焦、拍照保存等功能。应用大致效果如下:
在程序中定义会话、输入、输出等相关对象。
在控制器视图将要展示时创建并初始化会话、摄像头设备、输入、输出、预览图层,并且添加预览图层到视图中,除此之外还做了一些初始化工作,例如添加手势(点击屏幕进行聚焦)、初始化界面等。
在控制器视图展示和视图离开界面时启动、停止会话。
定义闪光灯开闭及自动模式功能,注意无论是设置闪光灯、白平衡还是其他输入设备属性,在设置之前必须先锁定配置,修改完后解锁。
定义切换摄像头功能,切换摄像头的过程就是将原有输入移除,在会话中添加新的输入,但是注意动态修改会话需要首先开启配置,配置成功后提交配置。
添加点击手势操作,点按预览视图时进行聚焦、白平衡设置。
定义拍照功能,拍照的过程就是获取连接,从连接中获得捕获的输出数据并做保存操作。
最后附上完整代码:
其实有了前面的拍照应用之后要在此基础上做视频录制功能并不复杂,程序只需要做如下修改:
添加一个音频输入到会话(使用[[avcapturedevice deviceswithmediatype:avmediatypeaudio] firstobject]获得输入设备,然后根据此输入设备创建一个设备输入对象),在拍照程序中已经添加了视频输入所以此时不需要添加视频输入。
创建一个音乐播放文件输出对象avcapturemoviefileoutput取代原来的照片输出对象。
将捕获到的视频数据写入到临时文件并在停止录制之后保存到相簿(通过avcapturemoviefileoutput的代理方法)。
相比拍照程序,程序的修改主要就是以上三点。当然为了让程序更加完善在下面的视频录制程序中加入了屏幕旋转视频、自动布局和后台保存任务等细节。下面是修改后的程序:
前面用了大量的篇幅介绍了ios中的音、视频播放和录制,有些地方用到了封装好的播放器、录音机直接使用,有些是直接调用系统服务自己组织封装,正如本篇开头所言,ios对于多媒体支持相当灵活和完善,那么开放过程中如何选择呢,下面就以一个表格简单对比一下各个开发技术的优缺点。
提示:从本文及以后的文章中可能慢慢使用storyboard或xib,原因如下:1.苹果官方目前主推storyboard;2.后面的文章中做屏幕适配牵扯到很多内容都是storyboard中进行(尽管纯代码也可以实现,但是纯代码对autolayout支持不太好)3.通过前面的一系列文章大家对于纯代码编程应该已经有一定的积累了(纯代码确实可以另初学者更加了解程序运行原理)。