说明:非全职写手,整个系列内容的顺序可能不是循序渐进,后期考虑调整。目前优先编写熟练度高以及最近工作中用到的的技术。
在音频领域的开发中,音频焦点是个很重要的概念。在面试中也是个高频话题。通过音频焦点的引入,可以实现多个应用对音频硬件资源的协同使用。(广义地讲,这里的应用也可以扩展到音频源,也就是我们经常看到的audio source。音频源顾名思义,就是可以不断提供音频数据的节点)
音频焦点代表音频应用对音频硬件资源的使用权。当多个应用需要使用音频硬件进行播放时,系统将根据播控矩阵进行硬件资源的分配以及对各个应用的相应处理。拥有音频焦点的程序才能按照预期进行播放。其他应用则需要根据策略进行对应的动作,比如暂停,降低音量,增加音量,退出播放。
我们先讲解android的音频焦点实现,然后再看看怎么在linux系统实现一套类似的系统。
Android音频焦点主要分为以下四种类型:
AUDIOFOCUS_GAIN:这是永久性的音频焦点。当应用程序获得这种焦点时,它表示该应用程序获得了独占性的音频焦点,可以播放长时间的音频,例如音乐播放器。
AUDIOFOCUS_GAIN_TRANSIENT:这是暂时性的音频焦点。当应用程序获得这种焦点时,它表示该应用程序获得了短暂的音频焦点,可以播放短暂的音频,例如提示音或导航指示音。
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:这是请求附带“降低音量”的暂时性焦点。当应用程序获得这种焦点时,它只希望在短时间内播放音频,并允许前一个持有焦点的应用在降低其音量输出的情况下继续播放。这特别适合于语音导航、语音助手的场景使用。
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:这是另一种暂时性的音频焦点,但它在获得焦点时,不希望系统播放任何其他音频。这种焦点类型常用于录音或者需要做语音识别的场景。
与获得焦点对应的是失去对应焦点。
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
AUDIOFOCUS_LOSS
AUDIOFOCUS_LOSS_TRANSIENT ;
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
每种音频焦点类型都有其特定的使用场景和优先级,Android系统会根据这些类型来管理和分配音频资源,确保在不同应用程序之间进行无缝的切换和管理,从而提升用户体验和设备性能。
在Android开发中,与音频焦点调用相关的接口主要集中在AudioManager类中。这些接口允许应用程序请求、放弃和监听音频焦点的变化。以下是几个关键的接口:
requestAudioFocus():
此方法用于请求音频焦点。应用程序需要创建一个AudioFocusRequest对象,并指定所需的音频焦点类型(如AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT等),然后调用此方法。系统会根据当前音频焦点的状态和请求的焦点类型来决定是否授予焦点。
abandonAudioFocus():
当应用程序不再需要音频焦点时,应调用此方法放弃焦点。这通常发生在应用程序暂停播放或退出时。放弃焦点是良好的编程实践,可以确保其他应用程序能够正常地获得和使用音频焦点。
setOnAudioFocusChangeListener():
通过此方法,应用程序可以设置一个监听器来监听音频焦点的变化。当音频焦点发生变化时(例如,其他应用程序请求或放弃焦点),系统会调用监听器的相应方法,通知应用程序焦点状态的变化。这允许应用程序根据焦点状态调整其行为,如暂停或恢复播放。
以上接口和枚举都在文件project_dir/frameworks/base/media/java/android/media/AudioManager.java
project_dir在这里指代android源码的根目录。
下面是一个音频焦点的使用示例。按照android提供接口的惯例,同样的功能会通过函数重载的方式提供不同复杂度的调用接口。一个最简单直接的接口形式是这个public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint) 。durationHint就是刚刚提到的焦点类型。
import android.content.Context;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.util.Log; public class AudioFocusHelper { private static final String TAG = "AudioFocusHelper"; private AudioManager audioManager; private AudioFocusRequest.Builder focusRequestBuilder; public AudioFocusHelper(Context context) { audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); focusRequestBuilder = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_REQUEST_GRANTED); // 根据需要设置其他参数,例如音频属性 focusRequestBuilder.setAudioAttributes( new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build() ); } /** * 申请音频焦点 */ public void requestAudioFocus() { AudioFocusRequest focusRequest = focusRequestBuilder.build(); int result = audioManager.requestAudioFocus(focusRequest); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { Log.d(TAG, "Audio focus granted"); // 音频焦点被授予,开始播放音频 startPlayingAudio(); } else { Log.d(TAG, "Audio focus not granted"); // 处理未获得音频焦点的情况 } } /** * 放弃音频焦点 */ public void abandonAudioFocus() { audioManager.abandonAudioFocus(focusRequestBuilder.build()); Log.d(TAG, "Abandoned audio focus"); // 停止播放音频 stopPlayingAudio(); } /** * 开始播放音频 */ private void startPlayingAudio() { // 实现音频播放的逻辑 Log.d(TAG, "Starting to play audio"); } /** * 停止播放音频 */ private void stopPlayingAudio() { // 实现停止音频播放的逻辑 Log.d(TAG, "Stopping audio playback"); }
}
分析audiofocus相关的API我们可以发现,Androidframework层用于决策音频焦点的因素有两个:1.获取音频焦点的类型,也就是上文列举的四种类型。 2.播放APK的stream类型,通俗的说就是播放场景。基于此,如果我们自己设计一个音频焦点的管理服务,也同样需要抽象出这两个要素。焦点类型+播放场景。播放场景或者说streamtype是Android音频框架中极其重要的一个概念,大家要有比较清晰的认识。