ios开发播放音频,iOS音频处理
iOS 音频系列之一:Core Audio简介
任何吸引人的游戏都少不了声音。iOS开发者在游戏中需要使用声音时有多种选择,取决于对游戏中音频的控制需求,可以选择简单的内置服务,也可以选择更高级的API(比如OpenAL)。
成都创新互联是一家专业提供都昌企业网站建设,专注与成都网站设计、成都网站制作、H5开发、小程序制作等业务。10年已为都昌众多企业、政府机构等服务。创新互联专业网站设计公司优惠进行中。
通过音频API,可以实现流式音频,播放简短音效,甚至模拟3d空间的音频。有些游戏可以通过音轨让玩家沉浸在特定的心境中玩游戏,设置鼓励用户使用耳机来获得更完美的体验。
本系列文章中,会陆续整理近几年来在工作中涉及到的音频的相关知识,以算做对自己知识体系的一次梳理吧,大体包括Core Audio、OpenAL 以及Cocos2d引擎中的音效部分等三个方面。
• Core Audio 是什么?
• Core Audio 中提供的音频服务
• Core Audio 中的有关音频框架
• 有关 Core Audio 的变化及更新
Core Audio 是什么?
Core Audio 是iOS和 MAC 的关于数字音频处理的基础,它提供应用程序用来处理音频的一组软件框架,所有关于IOS音频开发的接口都是由Core Audio来提供或者经过它提供的接口来进行封装的,按照官方的说法是集播放、音频处理、录制为一体的专业技术,通过它我们的程序可以同时录制,播放一个或者多个音频流,自动适应耳机,蓝牙耳机等硬件,响应各种电话中断,静音,震动等,甚至提供3D效果的音乐播放。
相关链接:
Core Audio Overview
Audio Video Starting Point
Core Audio Glossary
Core Audio中提供的音频服务
Core Audio 本身是一个很庞大的话题,涉及到多个领域中的不同服务,为了更方便的使用Core Audio,通常可以将其分割为更小的模块。图一展示了根据应用程序服务层分解的示意图。构建在应用程序栈最下面的是底层硬件。接下来往上是驱动程序层。构建在驱动层之上的每一层都是苹果提供给开发人员的应用层服务,包括各类音频API和框架。
主要的几类服务:
Audio Unit
Audio Unit 是Core Audio 在应用层中最底层的服务。在使用其他音频API时,最终在底层都会调用到Audio Unit。在所有的API中,Audio Unit 是延迟最短且最灵活的,但代价就是它的使用相当的复杂,幸运的是在实际使用中,我们很少直接使用Audio Unit。
相关链接:
Audio Unit Framework Reference
相关项目工程:
Core Audio Utility Classes
Audio File Service
通过Audio File Service 提供的API可以打开并读取或者写入磁盘上存储的文件。
Audio File Stream Service
它是对Audio File Service 的扩展补充。Audio File Service 对存储到磁盘上的音频文件进行操作,而Audio File Stream Service
并不一定关联到某个文件上,它更适合基于网络的音频应用程序。
Audio Conversion Service
通过它可以将数据转换为PCM格式或者从PCM格式转换成数据。
Extended Audio File Service
可以将它理解为Audio File Service 和 Audio File Service 的组合。通过这种API 可以直接加在并转换音频文件。
Audio Session Service
和Core Audio中的其他API不同,它的主要用于 iOS 系统中协调应用程序之间的音频播放的 API 的。例如,当有电话打进来时,音频的播放就会被暂停;在用户启动电影时,音乐的播放就会停止。我们需要使用这些 API 来确保一个应用程序能够正确响应并处理这类事件。
System Sound Service
它是一种允许播放短音效和警告的基本服务,还具有提供振动功能的独特能力,Core Audio中的其他任何服务都不能访问振动系统。
Audio Queue Service
它可以对播放音频进行精细的控制,比如暂停、继续、循环播放和音频同步等,因此特别适合于播放和录制持续时间很长的音频。在游戏中进行语音叙述等情景时,需要音乐或者长时间的播放文件,便会需要它。
AVFoundation
它是Core Audio中唯一基于Objective-C的框架。这个框架提供了AVAudioPlayer类用于播放,AVAudioReconder类用于录音,以及AVAudioSession类用于设置音频回话。和其他高层API一样,我们需要在易用性和功能之间做出权衡。如果在此框架中找不到我们需要的特性或者功能,那么就必须深入底层服务并直接使用底层的API。
相关链接:
AV Foundation Framework Reference
AV Foundation Programming Guide
Audio Session Programming Guide
相关的项目工程:
AVCaptureAudioDataOutput To AudioUnit iOS
OpenAL
和其他专用API不同,OpenAL是一个狂平台的用于播放和捕捉音频的工业标准。OpenAL更适合播放空间音频(spatialized sound)或者定位音频(positional sound)。可以将空间音频理解成3D空间中的声音,通过OpanAL可以对音效添加一些效果,比如位置属性,这样会使远程的声音比近处的声音听起来要弱一些。
相关链接:
OpenAL FAQ for iPhone OS
相关的项目工程:
oalTouch
Core Audio中的有关音频框架
Core Audio 中的服务和框架并没有一对一的对应关系,应用层的服务实际上分为5个不同的框架:Core Audio、Audio Toolbox、Audio Unit、AVFoundtaion、OpenAL。图二中很好的展示了这些框架和服务之间的映射关系。
Audio Unit、AVFoundation和OpenAL的框架非常明了,和他们同名的服务直接对应,其中AVFoundtion有三个Objective-C类组成:AVAudioPlayer、AVAudioRecorder和AVAudioSession。
Audio Toolbox 框架提供了前面列出的其他剩下的应用层服务,包括非常重要的Audio Session Service。
相关链接:
Audio Toolbox Framework Reference
其他相关框架:
Media Player Framework
它是一个用于音频和视频播放的高层级接口,它包含了一个可以在应用中直接使用的默认的用户界面,可以使用它来播放用户在 iPod 库中的项目,或者播放本地文件以及网络流。另外,这个框架也包括了查找用户媒体库中内容的 API,同时还可以配置像是在锁屏界面或者控制中心里的音频控件。
相关链接:
Media Player Framework Reference
Core MIDI Framework
提供与MIDI设备通讯的标准方式,包括硬件键盘和合成器。可以使用这个框架来发送和接收MIDI消息以及与通过dock连接器或网络连接到iOS设备的MIDI外设交互。
相关链接:
Core MIDI Framework Reference
OS 4.0以后的功能变化如下:
iOS 7.1
Support for External Media Players (CarPlay相关的)
iOS 7.0
新增 Inter-App Audio和 AudioCopy
强化 Media Player / AV Foundation Framework
弃用 Audio Toolbox framework内的Audio Session API
iOS 6.0
新增 Audio UnitのComponent
强化 Media Player / Core Media / AV Foundation Framework
iOS 5.0
新增 Audio UnitのComponent
强化 Media Player / AV Foundation / AudioToolbox Frameworks
iOS 4.3
强化 AV Foundation
强化 Media Player / Audio Unit / Audio Toolbox Frameworks
iOS 4.2
新增 Core MIDI framework
强化 Media Player Framework
新增 AirPlay
iOS 4.1
强化 AV Foundation
iOS 4.0
新增 Core Media Framework
强化 AV Foundation
相关链接:What's New in iOS
[img]iOS 音视频开发 - 系统中断音频(Swift语言)
注册 AVAudioSession.interruptionNotification 的通知,可以收到播放打断通知。 系统将此通知发布到主线程。
其中userinfo有如下字段:
分began与end,用来表示打断开始与打断结束。
只在打断结束时返回。选项用 shouldResume 来指示:另一个音频会话的中断已结束,应用程序可以恢复其音频会话。
该属性只在打断开始时返回。存在于版本iOS 10.3-14.5。
用于确定这次中断是否由于系统挂起App所致。
获取到的是一个NSNumber表示的Bool。为true表示中断是由于系统挂起,false是被另一音频打断。
该属性只在打断开始时返回。存在于版本iOS 14.5以后,用来代替 AVAudioSessionInterruptionWasSuspendedKey 。
default :因为另一个音频会话被激活,音频中断。(例如后台播着音频,此时播放微信语音)
appWasSuspended :由于APP被系统挂起,音频中断
builtInMicMuted :音频因内置麦克风静音而中断(例如iPad智能关闭套【iPad's Smart Folio】合上)
IOS后台运行 之 后台播放音乐
iOS 4开始引入的multitask,我们可以实现像ipod程序那样在后台播放音频了。如果音频操作是用苹果官方的AVFoundation.framework实现,像用AvAudioPlayer,AvPlayer播放的话,要实现完美的后台音频播放,依据app的功能需要,可能需要实现几个关键的功能。
首先,播放音频之前先要设置AVAudioSession模式,通常只用来播放的App可以设为AVAudioSessionCategoryPlayback即可。模式意义及其他模式请参考文档。
1 //后台播放音频设置
2 AVAudioSession *session = [AVAudioSession sharedInstance];
3 [session setActive:YES error:nil];
4 [session setCategory:AVAudioSessionCategoryPlayback error:nil];
1.通知IOS该app支持background audio。缺省情况下,当按下home键时,当前正在运行的程序被suspend,状态从active变成in-active,也就是说如果正在播放音频,按下HOME后就会停止。这里需要让app在按在HOME后,转到后台运行而非被suspend,解决办法是在程序的-info.plist中增加required background modes这个key项,并选择App plays audio or streams audio/video using AirPlay这个value项(如果用过Xcode5.0,在TARGETS-Capabilities-Background Modes设置为ON,勾选Audio and AirPlay选项)。
2.如果你在后台播放使用的时加载网络音频,恰巧网速很慢,音频被停止下来这时候程序也随之suspend,曾经有山寨的解决办法是专门起一个player的实例连续不停的放同一无声音片断,阻止程序被suspend。这里提供的方法是通过申请后台taskID达到后台切换播放文件的功能。
即使声明taskID也最多只能在后台运行600秒钟。(在ios7sdk中可以使用NSURLSession来实现后台缓冲)
(一般情况下,按HOME将程序送到后台,可以有5或10秒时间可以进行一些收尾工作,具体时间[[UIApplication sharedApplication] backgroundTimeRemaining]返回值,超时后app会被suspend。)
3.ipod播放程序在后台时,双击HOME键,会有个控制界面,可以对它进行播放控制(暂停开始、上一曲、下一曲)。如果您想让您的app可以像ipod一样在后台也可以方便的通过双击HOME键来控制(在ios7中是使用上拉菜单控制),就要用到远程控制事件了。
首先在viewdidload等初始化的地方声明App接收远程控制事件,并在相应地方结束声明
- (void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
- (BOOL)canBecomeFirstResponder
{
return YES;
}
当然也不一定是在viewcontroller中,也可以是在applicationDidEnterBackground:方法中开始接受远程控制,applicationDidBecomeActive:中结束接受远程控制,但是当前的appdelegate中要继承与UIResponder,因为在激活远程控制以后要把当前类变成第一响应,重写canBecomeFirstResponder方法。
最后定义 remoteControlReceivedWithEvent,处理具体的播放、暂停、前进、后退等具体事件
//重写父类方法,接受外部事件的处理
- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {
if (receivedEvent.type == UIEventTypeRemoteControl) {
switch (receivedEvent.subtype) {
case UIEventSubtypeRemoteControlTogglePlayPause:
[self playAndStopSong:self.playButton];
break;
case UIEventSubtypeRemoteControlPreviousTrack:
[self playLastButton:self.lastButton];
break;
case UIEventSubtypeRemoteControlNextTrack:
[self playNextSong:self.nextButton];
break;
case UIEventSubtypeRemoteControlPlay:
[self playAndStopSong:self.playButton];
break;
case UIEventSubtypeRemoteControlPause:
[self playAndStopSong:self.playButton];
break;
default:
break;
}
}
}
其它外部事件也可通过这种方式实现,如“摇一摇”响应等。
4. 至此,您有播放App已经基本完成了,其次插拔耳机是否响应停止播放时间需要进一步研究耳机检测和声音路由切换的问题,再次不详细讲述。
5. 还有一些开发者可能会发现,有一些音视频app在定义的时候自定一些控件可以调节系统的音量大小,不需要用户调整音量按钮。经查看相关的资料总结出有两种方法:
一种是调用控件MPVolumeView在屏幕中创建一个音量条,拖动可以改变系统的音量大小。
另一种是使用MPMusicPlayerController类,可以自定义控件调整系统音量的大小(但是在ios7sdk中已经被弃用,估计以后几个版本中可能找不到这个方法了)。
MPMusicPlayerController *mpc = [MPMusicPlayerController applicationMusicPlayer];
mpc.volume = 0; //0.0~1.0
6. 在一些其他的音乐播放软件中如:酷我、qq音乐等,你会发在播放的时候,当设备锁屏以后依然可以看到用户播放的音乐名称、演唱者、专辑名称、音乐时长、专辑图片等信息。这些就需要在用户切换完歌去的时候,在程序中设置信息了。
//设置锁屏状态,显示的歌曲信息
-(void)configNowPlayingInfoCenter{
if (NSClassFromString(@"MPNowPlayingInfoCenter")) {
NSDictionary *info = [self.musicList objectAtIndex:_playIndex];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
//歌曲名称
[dict setObject:[info objectForKey:@"name"] forKey:MPMediaItemPropertyTitle];
//演唱者
[dict setObject:[info objectForKey:@"singer"] forKey:MPMediaItemPropertyArtist];
//专辑名
[dict setObject:[info objectForKey:@"album"] forKey:MPMediaItemPropertyAlbumTitle];
//专辑缩略图
UIImage *image = [UIImage imageNamed:[info objectForKey:@"image"]];
MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image];
[dict setObject:artwork forKey:MPMediaItemPropertyArtwork];
//音乐剩余时长
[dict setObject:[NSNumber numberWithDouble:self.player.duration] forKey:MPMediaItemPropertyPlaybackDuration];
//音乐当前播放时间 在计时器中修改
//[dict setObject:[NSNumber numberWithDouble:0.0] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
//设置锁屏状态下屏幕显示播放音乐信息
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
}
}
上面的if (NSClassFromString(@”MPNowPlayingInfoCenter”))语句,说是为了避免了版本兼容问题,这个API貌似只出现在5里面。
7. 下面就在计时器中不断刷新锁屏状态下的播放进度条了。
//计时器修改进度
- (void)changeProgress:(NSTimer *)sender{
if(self.player){
//当前播放时间
NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithDictionary:[[MPNowPlayingInfoCenter defaultCenter] nowPlayingInfo]];
[dict setObject:[NSNumber numberWithDouble:self.player.currentTime] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; //音乐当前已经过时间
[[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:dict];
}
}
8. 当前的很多常见的播放器都可以在锁屏状态下显示显示歌词,经过一番查找后,终于找到方法(详情: 点击查看 ),大致就是根据播放的时间和歌词显示时间,利用计时器不断的用歌词和专辑封面合成图片,达到显示歌词的效果。还有就是在屏幕变暗停止这一操作、屏幕点亮的时候开始计时器,以节省电量和cpu,有两种方法可以监听上述现象:
一种是监听内核层DarwinNotification,在Darwin中,有很多的系统事件,但apple的api文档描述这些api使用有限制,也就是灰色地带的api,所以能不用则不用;
另一种方法可以通过notify_get_state来获取com.apple.springboard.hasBlankedScreen 的状态值,通过状态值我们可以判断屏幕状态,屏幕亮或者暗系统会给出不同状态值,然后根据状态值,通过NotificationCenter发送消息通知给相应的函数处理。
我的 iOS 音频处理总结
前段时间在阅读苹果音频文档(均列在参考资料一节里面了),并做了一些音频相关的开发(主要是带回音消除的录音)。这里做一个总结。
每一个 app 带有一个 AVAudioSession 的单例(也就是说正常情况下你无法获得第二个 AVAudioSession 实例)。iOS 系统上每个 app 有各自不同的 AVAudioSession 实例。通过使用这个实例的方法可以告诉系统当前的 app 是怎样使用手机的音频服务的,然后系统会根据每个 app 的配置进行相应的协调, 尽量 满足所有 app 的请求,当无法满足的时候,系统尽量满足前台 app 的要求或者系统电话服务等。比如说,如果当前另外一个 app 正在播放的话,当前 app 可能希望能将其播放的音频和其他 app 的音频一起播放,而不是暂停其他 app 的音频服务;又比如说当前 app 需要播放音频或者进行录音;比如当前 app 只是播放音频;比如当前 app 只是录音;比如当前 app 播放音频时候屏蔽所有其他 app 的音频等等,总之就是告诉系统当前 app 是如何使用它的音频服务的。
Audio Session 有三个比较重要的概念:
通过配置这三个内容,表达了当前 app 的使用音频服务的具体意图。
在某些情况下,我们不需要配置 Audio Session 的 category,比如如果使用 AVAudioRecorder 来录音,并不需要配置 category 为 AVAudioSessionCategoryRecord ,因为系统在我们使用 AVAudioRecorder 的录音服务的时候已经为我们配置了。同时 Audio Session 有默认配置(如果当前 app 不进行配置的话)。当默认配置无法满足需求的时候,就可以手动配置 Audio Session
Audio Session 另一个重要功能是配置系统音频服务硬件参数,比如配置输入的声道数,采样率,IO 缓存时间等等。其 API 中 setPreferred__ 开头的方法作用就是这些。
配置完 Audio Session 以后,当我们要求的音频服务受到打断(比如,电话来了,则系统要停止录音和播放;比如,app 退到后台运行了,如果没有配置后台运行的话,系统也会停止当前 app 的音频服务;),我们可以使用通知中心的方式来监听,并做一下相应的处理。音频服务中断有两个概念比较重要,就是中断开始以及中断结束,我们可以在中断开始的时候记录当前播放时间点,中断结束的时候重新开始播放(当然系统默认行为是会在中断结束时重新开始播放音频,但是如果默认行为无法满足需求时候,就需要自行处理了)。
Audio Session 另一个重要我们需要监听的变化是路由变化 (Route Change)。比如有新的输出源来了(比如用户把耳机插进去或者是用户开始使用蓝牙耳机),或者原来的输出源不可用了(用户拔掉耳机等)。
还有一些其他的功能,比如当前其他 app 是否在播放音频,请求麦克风权限等,可以查看具体的 API 文档 AVAudioSession 。
使用 Audio Queue Service 我们可以做到录音或者播放音频。当然我们使用 AVAudioPlayer 也能很简单的做播放音频功能,那为什么要用到 Audio Queue Service 呢?它有几个优点
理解 Audio Queue Service 比较重要的是它的 buffer queue。拿录音来说,一般设置的缓存是3个。首先通过 AudioQueueEnqueueBuffer 将可用缓存提供给相应的 queue。然后系统开始将记录下的音频数据放到第一个缓存,当缓存满的时候,回调函数会将该 buffer 返回给你并将该缓存出列,在回调函数中我们可以对这些数据进行处理,与此同时系统开始将数据写到第二个缓存,当我们的回调函数处理完第一个返回的缓存时候,我们需要重新使用 AudioQueueEnqueueBuffer 将该缓存入列,以便系统再次使用。当第二个缓存返回的时候,系统开始往第三个缓存写数据,写完之后返回第三个缓存,并开始往之前返回的第一个缓存写数据。这就是一个典型的队列结构(先进先出,后进后出)。
Audio Unit 是所有 iOS 以及 macOS 上音频框架的最底层,无论使用的是 AVAudioRecorder、AVAudioPlayer、或者 Audio Queue Service、OpenAL 等,最终底层实现都是通过 Audio Unit 来完成的。
在 iOS 上可用的 audio unit 是有限的,macOS 上面可以自定义一个 audio unit 但是 iOS 上不行,只能使用系统提供的 audio unit。
什么时候使用 Audio Unit ?官方的说法是,当你需要高度可控的、高性能、高灵活性或者需要某种特别的功能(比如回音消除,只在 audio unit 提供支持,所有高层 API 均不支持回音消除)的时候,才需要使用 audio unit。
有4类 audio unit(具体用途看名字就能理解):
使用 audio unit 有两种方式:
第一种方式是对于比较简单的结构。
第二种方式是用于构建复杂的音频处理流程。配置具体的 audio unit 的属性的时候还是会用到直接使用种的方法。
audio unit 重要的概念是 scope 和 element。scope 包含 element。
scope 分三种:
scope 概念有一点抽象,可以这样理解 scope,比如 input scope 表示里面所有的 element 都需要一个输入。output scope 表示里面所有的 element 都会输出到某个地方。至于 global scope,应该是用来配置一些和输入输出概念无关的属性。
element 官方的解释是可以理解成 bus,就是将数据从 element 的一头传到另一头。
这里有些问题我也不知道如何解答,若有了解的,请多多指教一下。
分享标题:ios开发播放音频,iOS音频处理
本文链接:http://scyanting.com/article/dsopjic.html