Android AudioManager
API
AudioManager(audio翻译过来就是声音、音频):
AudioManager,音频管理类,它主要提供了丰富的API让开发者对应用的音量和铃声模式进行控制以及访问。主要内容涉及到音频流、声音、蓝牙、扩音器、耳机等等。AudioManager类位于android.Media 包中。
A:获取实例
由于音频管理涉及到多媒体,因此这个AudioManager获取实例的方式是这样的:
AudioManager audio = (AudioManager)Context.getSystemService(Context.AUDIO_SERVICE);
B:丰富的API
音频管理类提供了大量的API,这些API是我们经常看到或者用到的,比如,调节音量,我相信对于很多人来说,调节音量这个姿势是很常见的,比如你打开某视频APP、某音乐APP其中肯定有调节音量大小的手势,那么调节音量内部的逻辑可以使用 adjustStreamVolume(int streamType, int direction, int flags)
参数预览:
streamType :要调整的音频流类型。类型有以下几种:
STREAM_VOICE_CALL(电话的音频流),
STREAM_SYSTEM(系统声音的音频流),
STREAM_RING(电话铃声的音频流),
STREAM_MUSIC(用于音乐播放的音频流)
STREAM_ALARM(警报的音频流)
STREAM_ACCESSIBILITY(无障碍的音频流)
STREAM_NOTIFICATION(通知的音频流)
区分流类型的目的是让用户能够单独地控制不同的种类的音频。但上述音频种类中,大多数都是被系统限制。除非应用需要做替换闹钟的铃声的操作,不然的话你只能通过STREAM_MUSIC来播放你的音频。也就是说我们最常见操作的就是STREAM_MUSIC这个类型。
direction :调整音量的方向。其中: ADJUST_LOWER(减少铃声音量),ADJUST_RAISE(增加铃声音量)或 ADJUST_SAME(保持之前的铃声音量)
flags :一个或多个标志。可能这里的标志不是很好理解,是这样,AudioManager提供了一些常量,我们可以将这些系统已经准备好的常量设置为这里的flags,比如:
FLAG_ALLOW_RINGER_MODES(更改音量时是否包括振铃模式作为可能的选项),
FLAG_PLAY_SOUND(是否在改变音量时播放声音),
FLAG_REMOVE_SOUND_AND_VIBRATE(删除可能在队列中或正在播放的任何声音/振动(与更改音量有关)),
FLAG_SHOW_UI(显示包含当前音量的吐司),
FLAG_VIBRATE(是否进入振动振铃模式时是否振动)
比如我现在想要增加音量,就可以这样写:
AudioManager . adjustStreamVolume (AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
这句代码的意思是指:指定调节类型为 音乐的音频,增大音量,显示音量图形示意。举一反三下面就是降低音量的代码:
AudioManager . adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FLAG_PLAY_SOUND);
比如,我想要获取手机的音量,可以调取getStreamVolume(int streamType); 这里的streamType指获得手机的当前流类型的音量,最大值为15(不同手机可能不同)最小值为0。
setStreamVolume(int streamType, int index,int flags)这个API顾名思义就是根据音频流类型去设置音量大小的。主要这里的index不能超过最大索引,也就是15。
getMode()
返回当前音频模式,如 NORMAL(普通), RINGTONE(铃声), orIN_CALL(通话)
setMode()
设置声音模式,可取值NORMAL(普通), RINGTONE(铃声), or IN_CALL(通话)
getRingerMode()
返回当前的铃声模式。如RINGER_MODE_NORMAL(普通)、RINGER_MODE_SILENT(静音)、RINGER_MODE_VIBRATE(震动)
setRingerMode(int ringerMode)
改变铃声模式
getStreamVolume(int streamType)
取得当前手机的音量,最大值为15,最小值为0,当为0时,手机自动将模式调整为"震动模式"。
getStreamMaxVolume(int streamType)
获得当前手机最大铃声。
setStreamMute(int streamType, boolean state) 静音或不静音音频流 设置指定声音类型(streamType)是否为静音。如果state为true,则设置为静音;否则,不设置为静音。
麦克风相关:
setMicrophoneMute(boolean on):设置麦克风静音开启或关闭。( 设置true 关闭麦克风也就是麦克风静音; 设置false,即关闭静音打开麦克风)
setSpeakerphoneOn(boolean on):这个方法主要是判断是否打开扩音器(设置true,即打开免提电话; false将其关闭)
isMicrophoneMute():判断麦克风是否静音或是否打开(如果麦克风静音则为true,否则为false)
isMusicActive():判断是否有音乐处于活跃状态(如果任何音乐曲目有效,则为true)
比如蓝牙相关的API:
isBluetoothA2dpOn() :这个方法是检查是否打开或关闭了到蓝牙耳机的A2DP音频路由。
isBluetoothScoOn(): 这个方法主要是检查通信是否使用蓝牙SCO。
startBluetoothSco(): 启动蓝牙SCO音频连接
setBluetoothScoOn(boolean on): 请求使用蓝牙SCO耳机进行通信。(设置true 代表用于通信的蓝牙SCO; 设置false 即不使用蓝牙SCO进行通信)
void stopBluetoothSco(): 停止蓝牙SCO音频连接。
等等,具体的更丰富更全面的API可以参考 AudioManager官方文档
代码
获取AudioManager实例
要使用AudioManager类,首先需要获取一个AudioManager实例。可以通过以下代码来获取:
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
音量控制
通过AudioManager类,我们可以控制设备的各种音量,包括媒体音量、闹钟音量、通话音量等等。下面是一些常用的方法:
获取当前音量
可以使用getStreamVolume()方法来获取当前音量。下面的代码演示了如何获取媒体音量:
int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
设置音量
可以使用setStreamVolume()方法来设置音量。下面的代码演示了如何将媒体音量设置为最大:
int maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, maxVolume, 0);
调整音量
如果希望逐步调整音量,可以使用adjustStreamVolume()方法。下面的代码演示了如何将媒体音量调低一个单位:
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, 0);
铃声模式控制
除了音量控制外,AudioManager还允许我们管理设备的铃声模式。以下是一些常用的方法:
获取当前铃声模式
使用getRingerMode()方法可以获取当前的铃声模式。返回值可以是以下之一:RINGER_MODE_NORMAL、RINGER_MODE_SILENT、RINGER_MODE_VIBRATE。
int ringerMode = audioManager.getRingerMode();
设置铃声模式
可以使用setRingerMode()方法来设置铃声模式。以下是一些常用的设置:
-
RINGER_MODE_NORMAL:设置为正常模式,即响铃和震动都开启。
-
RINGER_MODE_SILENT:设置为静音模式,即不响铃也不震动。
-
RINGER_MODE_VIBRATE:设置为震动模式,即不响铃但会震动。
audioManager.setRingerMode(AudioManager.RINGER_MODE_SILENT);
监听音频焦点
在开发中,有时候需要监听音频焦点的变化,比如当其他应用开始播放音频时暂停当前应用的音乐。通过AudioManager类,我们可以实现这样的功能。
请求音频焦点
使用requestAudioFocus()方法可以请求音频焦点,并指定焦点类型、音频焦点变化的回调等参数。下面的代码演示了如何请求音频焦点:
AudioManager.OnAudioFocusChangeListener focusChangeListener = new AudioManager.OnAudioFocusChangeListener(){
@Override
public void onAudioFocusChange(int focusChange) {
// 处理音频焦点变化事件
}
};
int result = audioManager.requestAudioFocus(focusChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
释放音频焦点
当不再需要音频焦点时,可以调用abandonAudioFocus()方法来释放焦点。
audioManager.abandonAudioFocus(focusChangeListener);
监听系统音量控制按键
除了我们手动去改之外,用户也可以通过物理按键
或是耳机
来控制音量,这时,我们理应也要做出相应的改变,所以,还需要对音量按键做监听才行。
这里就用到熟悉的老方法了,重写Activity
的onKeyDown
方法:
javascript
/**
* 监听并接管系统的音量按键,
* 注意:最好保持原有逻辑不变
*/
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
//音量+按键
KeyEvent.KEYCODE_VOLUME_UP -> {
return true
}
//音量-按键
KeyEvent.KEYCODE_VOLUME_DOWN -> {
return true
}
}
return super.onKeyDown(keyCode, event)
}
音频焦点
介绍
两个或两个以上的 Android App可同时向同一输出流(比如手机的蓝牙、手机的喇叭)播放音频,系统会将所有 音频流(就是音频数据了)混合在一起。这是一项有意思的技术,但却会出现混音。为了避免所有音乐应用同时播放,Android 引入了 "音频焦点"的概念。 音频焦点机制是Android系统提供的一种道德约定 ,它倡导的东西有三点:
1、 只有一个App持有音频焦点;
2 、播放声音前申请音频焦点,不需要播放的时候释放音频焦点;
3 、失去音频焦点应该暂停播放或者降低音量。
音频焦点是Android系统进程管理的一个值,这个值就记录了当前音频焦点属于哪个应用,类型等。
使用中注意的点:
① 为 requestAudioFocus 方法传入的第3个参数:
- 如果计划在将来一段时间内播放音频,并且希望前一个持有音频焦点的应用停止播放,则应该请求永久性的音频焦点 (AUDIOFOCUS_GAIN)。
- 如果只希望在短时间内播放音频,并且希望前一个持有音频焦点的应用暂停播放,则应该请求暂时性的焦点 (AUDIOFOCUS_GAIN_TRANSIENT)。
- 如果只希望在短时间内播放音频,并允许前一个持有焦点的应用在降低音量的情况下继续播放,则应该请求"降低音量"的暂时性焦点(AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) 。这两个音频会混合到音频流中同时输出。
② AudioManager.OnAudioFocusChangeListener回调回来的音频焦点类型,如下:
- AUDIOFOCUS_GAIN:获取得到音频焦点。
- AUDIOFOCUS_LOSS:永久性失去音频焦点,后续不会再收到 AUDIOFOCUS_GAIN 回调。应用应立即暂停播放,此时其他应用会播放音频。
- AUDIOFOCUS_LOSS_TRANSIENT:暂时性失去音频焦点,应用应暂停播放。当抢占焦点的应用放弃焦点时、自己的应用可以收到 AUDIOFOCUS_GAIN 回调。
- AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:暂时性失去音频焦点,应用应降低音量。当抢占焦点的应用放弃焦点时、自己的应用可以收到 AUDIOFOCUS_GAIN 回调。
实现音频焦点的步骤
要实现音频焦点,可以遵循以下步骤:
-
请求音频焦点
-
维护音频焦点变更:
-
- AUDIOFOCUS_GAIN:应用程序已经拥有了焦点,可以播放音频并且恢复至最近一次播放的状态。
- AUDIOFOCUS_LOSS:失去了焦点,需要停止播放并清理音频资源。
- AUDIOFOCUS_LOSS_TRANSIENT:失去焦点,但很快就能返回,并且和AUDIOFOCUS_LOSS的关键区别是不需要释放音频资源,因为等待时间很短。
- AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:失去焦点,但可以在音量稍稍降低后继续播放音频。
-
释放音频焦点
代码
public class MediaPlayerDemo extends Activity implements
OnAudioFocusChangeListener {
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
MediaPlayer mediaPlayer;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
private boolean requestAudioFocus() {
int result = audioManager.requestAudioFocus(this,
AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
return true;
}
return false;
}
private void releaseAudioFocus() {
audioManager.abandonAudioFocus(this);
}
@Override
public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_GAIN:
// 恢复音频播放
if (mediaPlayer == null) {
initMediaPlayer();
}
else if (!mediaPlayer.isPlaying()) {
mediaPlayer.start();
}
mediaPlayer.setVolume(1.0f, 1.0f);
break;
case AudioManager.AUDIOFOCUS_LOSS:
// 失去音频焦点退出程序
if (mediaPlayer.isPlaying()) {
mediaPlayer.stop();
}
mediaPlayer.release();
mediaPlayer = null;
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
// 暂时失去焦点,暂停播放等待重新获得音频焦点
if (mediaPlayer.isPlaying()) {
mediaPlayer.pause();
}
break;
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
// 暂时失去焦点,音量减小,等待重新获得音频焦点
if (mediaPlayer.isPlaying()) {
mediaPlayer.setVolume(0.1f, 0.1f);
}
break;
}
}
}
其中,requestAudioFocus()方法请求音频焦点,releaseAudioFocus()方法释放音频焦点。onAudioFocusChange()方法响应音频焦点的状态变化。
焦点类型AUDIOFOCUS_GAIN_TRANSIENT表示当一段时间内需要持有音频焦点。例如,一些短暂的提示音效在它们播放期间重新获取音频焦点会更好。相对于AUDIOFOCUS_GAIN,AUDIOFOCUS_GAIN_TRANSIENT可以更快地响应其他应用程序的请求。
焦点类型AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK表示音频源可能在持有焦点播放时更小地响应。例如,当正在播放音乐的应用程序收到来自其他应用程序的通知时,音量将自动减小。
注意
大多数情况下,按照上述流程使用音频焦点是没有问题的。但线上有部分用户反馈无法使用语音唤醒功能(即无法通过语音来和应用交互),经过排查,发现有如下可优化的地方:
-
问题1:不是所有应用都会将音频焦点释放,有的应用也会不释放,比如手机系统的录音机。这看似违背最上面说的音频焦点管理规则,但录音机是有它特殊性的,不管它位于前台还是后台,都应该拥有录音的功能。这就会导致只要不杀死录音机应用进程,它就会一直占用音频焦点,导致其他应用无法使用音频焦点。或者是其他应用把原本属于本应用的音频焦点抢占了,也会导致本应用无法使用音频焦点,比如某些应用内的语音通话功能就会抢占音频焦点。
处理策略:当AudioManager.OnAudioFocusChangeListener监听器监听到音频焦点已失去时(AUDIOFOCUS_LOSS、AUDIOFOCUS_LOSS_TRANSIENT),就主动通过弹窗或toast提示用户【关闭其他应用或重启手机】,以期望达到让其他应用释放音频焦点的目的。
-
问题2:正常来说,当B应用把音频焦点释放后,A应用是可以重新获取到音频焦点的回调(AUDIOFOCUS_GAIN),但某些Android手机可能会有系统bug,即不会主动通知A应用音频焦点已经回来了,A应用无法拿到AUDIOFOCUS_GAIN这个回调,A应用也就无法再恢复播放音频。
处理策略:增加音频功能的调用时机。比如在切页或者做其他操作的时候,主动尝试使用语音唤醒的音频功能,而不是通过AudioManager.OnAudioFocusChangeListener监听器被动地等待回调。当主动尝试使用语音唤醒成功时,说明音频焦点已经回来了。
26版本之上
请求焦点
mAudioFocusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
.setOnAudioFocusChangeListener(focusChangeListener)
.setAudioAttributes(new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).build())
.setFocusGain(AudioManager.AUDIOFOCUS_GAIN)
.setForceDucking(true)
.setWillPauseWhenDucked(true)
//可以设置其他的参数及listener
.build();
mAudioManager.requestAudioFocus(mAudioFocusRequest);
放弃焦点
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mAudioManager.abandonAudioFocusRequest(mAudioFocusRequest);
}
参考
音频焦点