声音
iOS 设备可以实现很棒的声音效果。在你的程序中,声音可能是核心的组成部分,或者偶尔出现增强效果。 无论声音扮演的角色如何, 你应当知道用户对声音抱着怎样的期望,应该如何来满足它。
理解用户的期望
用户在设备上调节声音, 也可能带着有线或无线的耳机。 用户也对应该如何控制声音有所期望。 虽然你可能觉得有些期望很奇怪, 但它们都遵循由用户来控制何时应听到声音的原则,而非由设备。
用户会如下情况切换成静音:
♦ 避免被不符合期望的声音打扰,比如电话或短信的提醒音
♦ 避免听到由操作带来的副产品,比如键盘音或其他反馈音、故障音或启动音乐
♦ 避免听到与玩游戏不太相干的声音,比如偶发的故障音。
例如,在剧院里用户希望把设备调成静音,以免打扰其他观众。这种情况下,用户依然想使用程序,但并不想被出乎意料的声音吓到,比如铃声或新消息提醒音。
对某些刻意设定的声音,当用户的操作触发它时,Ring/Silent 的设置不能关闭它。例如:
♦ 媒体播放器里的播放不会静音,因为播放是由用户主动请求的。
♦ 闹钟不会静音,因为它是由用户明确设定为"有声"的。
♦ 在语言学习软件中的声音片段不应静音,因为用户有明确的欲望想听到它。
♦ 语音聊天程序中的对话不应静音,因为用户打开它就是为了听声的。
用户使用设备音量键所做的调节会影响所有设备播放的声音。这包括歌曲、程序声音和设备声音。用户总是可以使用音量键静音,无论 Ring/Silent 设置在哪一档上。使用音量按钮调节程序当前的声音时,整个系统的音量都会受影响,铃声除外。
对于 iPhone 来讲,当没有任何程序在播放声音时,调节音量按钮就会调整铃声音量。
用户使用耳机来私下地听声音,同时解放双手。无论这些设备是有线、无线,用户对体验都有一些期望。
当用户插入耳机或联通无线声音设备时,他们是想私下里听这些声音。由于这个原因,他们希望当前正在播放的声音不要暂停。
当用户拔出耳机或与无线设备断开时,他们不想把刚才听到的声音自动共享给其他人。所以,他们希望当前正播放的声音可以暂停,等到用户准备好了再重新播放。
定义声音的行为
有必要的话,你可以为自己的程序设定相对的、独立的音量水平,以便产生合适的声音输出。 但是最终的声音输出还是要受系统音量的管制, 由音量键或者音量滚动条来调节。这意味着程序音量的控制权还是在用户手中。
合适的话,要给程序加上选择声道(audio route)的控件。即使当用户没有插上或拔下无线声音设备,他也希望能选择不同的声道。为了解决这个问题,iOS 自动提供了让用户选择输出通道的控件。 由于切换声道是用户触发的行为, 用户期望正在播放的声音不要停。
如果程序只是在主界面上放些伴奏声,就使用 system sound services。SSS 是一种产生警告声、界面声(UI Sounds)和振动的后台服务。当使用 SSS 播放声音时,你不能设置该声音如何与设备上的其他混响,也不能设置如何处理中断或其他设备设置的改变。
如果声音是程序的核心功能之一,使用 Audio Session Service 或者 AVAudioSession类。这些类不能直接产生声音,但可以帮你设置该声音如何与设备上的其他混响,如何处理中断或其他设备设置的改变。
在 iPhone 上,无论你用了什么样的技术去控制声音,iPhone 总能打断当前运行中的程序。因为任何程序都不应妨碍到用户接听来电。
在 Audio Session Service 中,audio session 充当程序与系统的调节者。它最重要的一面是 category,可以用来定义程序的声音行为。
为了运用 Audio Session Service 的优势,提供用户期待的声音体验,你应当选择与你的声音行为最相符的那一类。 这要看你的程序是只在前台时播放声音, 还是在后台也能播放。可以参照如下指南:
♦ 根据 category 的语义来选择,而非对行为的细节描述。这样能保证你的程序能符合用户期望。另外,如果将来类目对应的行为方式被修订了,也能保证你选择类目是合适的。
♦ 少数情况下可以在标准行为的基础上稍作定制。类别对应的标准行为与大多数用户的期望一致,所以在自定义前要考虑清楚。例如,你可能想添加"Should Duck"属性,以便你的程序音比其他声音都大(除了铃声) ,以便符合用户的需求。
♦ 考虑根据设备当前的声音环境来选择类别。这也许是合理的,因为用户也许想使用你的程序的同时听着其他声音。这样的话,当用户启动你的程序时,不要暂停用户正在听的声音或强迫用户选择音轨。
♦ 一般而言,避免在程序运行过程中改变类别。改变类别的主要原因是你的程序可能需要在不同的时候支持录音和回放。 这种情况下, 可以在录音类和回放类之间切换,而非只用"播放和录音类" 。这是因为选择录音类可以避免在录制过程中有警告声(比如新短信)出现。
表格 7-2 中列述了你可以使用的类别。
这里是一些用例,介绍如何选择与用于期望一致的声音类别。
用例 1:帮助用户学习语言的教学程序。你可以提供:
♦ 当用户触摸某个控件时提供反馈音
♦ 当用户想听正确的发音时记录单词和词汇
在这个程序里,声音是核心功能。用户使用这个程序来听所学语言的单词和短语发音。所以即使设备的声音锁掉了也应该发音。 应为用户需要清楚地听到发音, 所以希望其他声音都静下来。
为了提供符合用户期待的声音,你应该使用 playback 类。虽然这个类可以在稍加修改后支持与其他声音混响, 这个程序应当使用默认的行为, 以便保证其他程序的声音不会带来干扰。
用例 2:Voice over internet protocol(VoIP)电话程序,你应当提供:
♦ 获得声音输入
♦ 播放声音
在这个程序中,声音是核心功能。用户使用该程序与他人联络时,往往有其他程序正在运行。 用户希望能在把设备设为静音或设备被锁定时依然能接听电话, 并希望在通话过程中其他程序保持安静。用户也希望当程序跑在后台时依然能收到电话。
为了提供符合用户期待的声音,你应该使用 Play and Record 类。另外,只能在需要的时候激活 audio session,以便于在不通话的时候可以使用其他声音模式。
用例 3:允许用户扮演角色执行多种任务的游戏。你应带提供:
♦ 各种游戏音效
♦ 音乐音轨
在这个程序中,声音可以提升用户体验,但是并不是主任务的核心。而且,用户也可能想静音玩游戏,或者听着其他音乐玩。
最好的策略是看一下程序启动时用户有没有在听其他声音。 不要在开始时让用户选择想听哪个音轨。可以调用 ASS 的函数 AudioSessionGetProperty,读取kAudioSessionProperty_OtherAudioIsPlaying 的值。 基于这个值, 你可以选择 Ambient或者 Solo Ambient 类(两种类都支持用户静音玩游戏) 。
♦ 如果程序启动时用户正在听其他声音, 应该假设他想继续听, 不应该强行用当前程序的音轨代替它。这种情况下,应该选择 Ambient 类。
♦ 如果程序启动时用户没在听其他声音,选择 Solo Ambient 类。
用例 4:将用户精确实时地导航到目的地的程序。你应该提供
♦ 为行程中每一步说出方向
♦ 少许声音反馈
♦ 可以继续收听其他声音
在这个程序中,无论它在前台还是后台运行,声音导航提示都是主要任务。基于此,你应该使用 Playback 类,允许你在设备被锁或切换到静音、程序跑在后台时也能播放声音。
要想让用户运行程序的同时可以听其他声音,你可以添加 kAudioSessionProperty_O verrideCategoryMixWithOthers 属性。但是,你同时也希望用户能在其他声音播放时听到声音指示。这可以通过增加
kAudioSessionProperty_OtherMixableAudioShouldDuck 属性来实现。 这样你的声音就会比所有其他音轨的声音大(除了 iPhone 上的电话声) 。
用例 5:允许用户上传图片和文字到网页的博客程序。你应该提供
♦ 简短的启动音乐
♦ 与用户操作相匹配的音效(比如上传成功后的提示)
♦ 上传失败时的警告音。
在这个程序中,声音可以提升用户体验,但不是核心功能。主任务与声音无关,用户不依赖声音也能成功满足需求。在这个勇利用,你应该使用 SSS 来播放声音。这是因为所有该程序对声音的要求都与 SSS 的特性相符 (播放界面音效和警告音, 无视设备是否被锁,无视 Ring/Silent 的设置) 。
管理声音冲突
有时,来自多个程序播放的声音会冲突。例如,打电话的过程中电话声会和正在播放的其他声音冲突。在多任务环境中,这种冲突的频率会更高。
为了提供符合用户期望的音效,iOS 需要你:
♦ 确定你的程序会导致怎样的冲突。
♦ 在冲突解除后选择合适的应对方式。
每一个程序都需要确认可能会导致的冲突, 但并不是每一个程序都需要制定应对冲突的方案。这是因为对于大多数程序来说,在冲突解除后继续播放就是一个合适的方案。只有以录音回放为主,提供回放控件的程序需要额外地定义好应对方案。
从概念上来说, 根据导致冲突的声音类型以及用户对冲突后的期待来分, 共有两种声音冲突。
♦ 可恢复的冲突。 当用户在使用以听觉为核心的程序时被其他声音暂时干扰时, 会出现可恢复的冲突。
可恢复中断结束后, 有回放控件的程序应该恢复到中断发生前的状态,无论之前是正在播放还是暂停状态。没有回放控件的程序应该恢复到播放状态。 (After a resumable interruption ends, an app that displays media playback controls should resume what it was doing when the interruption occurred, whether this
is playing audio or remaining paused. An app that doesn't have media playback controls should resume playing audio.)
例如,当用户正用 iPhone 听音乐时,突然来了个 VoIP 电话。用户接了电话,希望在通话过程中音乐保持静音。电话打完后,用户希望回放(playback)型程序能自动恢复播放音乐。 这是因为用户在接电话前并没有主动暂停音乐, 所以希望打完电话后能继续享受歌声。否则,播放器在电话打完后就应该还是暂停状态。
♦ 不可恢复的冲突。 不可恢复的冲突是由以播放声音为核心功能的程序引起的, 比如媒体播放器。
在不可恢复冲突结束后, 显示了回放控件的程序不要恢复播放, 没有回放控件的程序应该恢复到播放状态。
例如, 当用户在听 1 号播放器的音乐时被 2 号播放器的声音打断。 用户决定听一会儿 2 号播放。再退出 2 号播放器后,用户不会希望 1 号播放器再自动播放,因为他已主动将 2 号播放器视作了主程序。
以下指南帮助你判断应提供什么样的信息,以及如何在中断结束后继续:
确定程序导致中断的类型。你可以在你的声音播放完毕后通过以下两种方式屏蔽你的声音:
♦ 如果你的程序导致的是可恢复的中断,给你的声音打上
AVAudioSessionSetActiveFlags_NotifyOthersOnDeactivation 的标。
♦ 如果你的程序导致的是不可恢复的中断,就不用打标了。
这样做可以帮助 iOS 恢复被打断的声音播放。
判断中断结束后你是否应该恢复播放。你可以根据程序所播放声音的体验类型来判断。
♦ 如果你的程序提供了让用户播放或暂停声音的媒体回放控件, 就应该在中断结束后检查 AVAudioSessionInterruptionFlags_ShouldResume 标签。
如果存在"Should Resume"标签,你应该:
♦ 如果你的程序在被打断时正播放声音,那就恢复播放
♦ 如果你的程序在被打断时没有播放声音,那就不要恢复播放
♦ 如果你的程序没有提供让用户播放或暂停声音的媒体回放控件, 就应该在中断结束后恢复刚才播放的声音,不必再去检查"Should Resume"标签是否存在。
例如,播放音轨的游戏应该在中断结束后立即恢复播放。
处理远程媒体控制事件
从 iOS4.0 起,程序可以在用户使用 iOS 媒体控制器或其他附件时接受远程控制事件。这使得无论你的程序是在前台还是后台,都能从界面以外的地方获得指令。
媒体回放程序尤其需要使当地响应这些事件,尤其是当在后台播放音乐时。
为了承担起保护隐私的责任,请遵守以下指南:
只在合理的时候接受远程控制信号。例如,如果你的程序支持阅读内容,搜索信息和听音频,那么只在用户听音频时才接受远程信号。当用户没在听音频时,就要把接收事件的权限释放出来。 这使得用户可以在使用你的程序阅读内容时方便地收听、 控制其他程序的音频。
即使某个事件对你的程序没有意义, 也不要重新定义事件的含义。 用户希望 iOS 媒体控制器和附件的事件在所有程序里具有一致的含义。不要处理与你的程序不需要的事件。所有你处理的事件就要符合用户的期望。一旦重新定义事件的含义,用户就晕了,必须要退出你的程序才能脱离困境。