欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 音频开发之音频焦点处理

程序员文章站 2022-04-02 22:20:40
一、前言Android是多任务系统,Audio系统是竞争资源。在Android系统中允许多个应用同时播放音频,例如,我们在播放音乐的时候,点开了一个视频,如果音乐和视频声音混合在一起,这样就会让人不爽;如果,我们在听音乐的时候,收到信息,我们又希望能听到信息的提示音,可以让音乐的声音先降低,在结束音结束后再恢复音量。为了管理音频焦点,Android引入音频焦点(audio focus)这一特性。这种机制是需要各个程序合作完成的,需要所有使用Audio资源的程序都按照这种机制来做,而如果有程序在它失去Au...

一、前言

Android是多任务系统,Audio系统是竞争资源。在Android系统中允许多个应用同时播放音频,例如,我们在播放音乐的时候,点开了一个视频,如果音乐和视频声音混合在一起,这样就会让人不爽;如果,我们在听音乐的时候,收到信息,我们又希望能听到信息的提示音,可以让音乐的声音先降低,在结束音结束后再恢复音量。为了管理音频焦点,Android引入音频焦点(audio focus)这一特性。

这种机制是需要各个程序合作完成的,需要所有使用Audio资源的程序都按照这种机制来做,而如果有程序在它失去Audio Focus的时候仍然在使用Audio,Audio Focus拿它也没办法。而这一点对于开放系统的Android来说很致命的:用户可能安装没遵守这种机制的程序,或者版本太老还没引入这种机制的程序,这最终会导致很差的用户体验。

二、设计原则

如果,我们的APP没有遵循Audio Focus的机制来管理Audio资源的竞争,就会出现上述的音乐和视频声音混合在一起的情况,用户体验极差。那么对于音频焦点的处理流程,大致可以归纳为一下几点:

  1. 在使用音频前(例如播放音乐),需要先获取焦点,如果成功获取到焦点,才应该正式开始工作。
  2. 当其他app请求了焦点,而此时我们的app失去焦点,我们需要做相应的处理。例如,如果是短暂型失去焦点,我们可以暂停工作,如果是永久性失去焦点,我们就停止工作。
  3. 当完成工作后,我们需要手动释放音频焦点。

三、Audio Focus的申请与释放

3.1 Android 8.0(API 级别 26) 之前

3.1.1 Audio Focus的申请

获取Audio Focus用requestAudioFocus()方法,我们看一下的方法参数

public int requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)

第一个参数:OnAudioFocusChangeListener
申请成功之后监听Audio Focus使用情况的Listener,后续如果有别的程序要竞争Audio Focus,都是通过这个Listener的onAudioFocusChange()方法来通知这个Audio Focus的使用者的。

AudioManager.OnAudioFocusChangeListener afChangeListener =
      new AudioManager.OnAudioFocusChangeListener() {
	  	public void onAudioFocusChange(int focusChange) {
	  		if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
	  			//长时间丢失焦点,当其他应用申请的焦点为AUDIOFOCUS_GAIN时,会触发此回调事件
	  			//例如播放QQ音乐,网易云音乐等
	  			//此时应当暂停音频并释放音频相关的资源。
            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
            	//短暂性丢失焦点,当其他应用申请AUDIOFOCUS_GAIN_TRANSIENT或AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE时,会触发此回调事件
            	//例如播放短视频,拨打电话等。
                //通常需要暂停音乐播放
          	} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
          		//短暂性丢失焦点并作降音处理,当其他应用申请AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK时,会触发此回调事件
          		//通常需要降低音量
          	} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {}
          		//当其他应用申请焦点之后又释放焦点会触发此回调
                //可重新播放音乐
        	}
        };

第二个参数:streamType
这个参数和Android中的AudioManager有关系,涉及到手机上的音频管理策略。
Android将系统的声音分为以下几类常见的(未写全):

STREAM_ALARM:警告声
STREAM_MUSCI:音乐声,例如music等
STREAM_RING:铃声
STREAM_SYSTEM:系统声音 ,例如低电提示音,锁屏音等
STREAM_VOCIE_CALL:电话声音

第三个参数:durationHint
用于表明音频焦点的持续时间,这个很关键,它也有许多种类型。

  • AudioManager.AUDIOFOCUS_GAIN: API文档说请求的这类音频焦点持续时间是未知的,通常用来表示长时间获取焦点,可以用来播放音乐,录音等等。
  • AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: 表明请求的音频焦点持续时间比较短,通常用来播放导航路线的声音,或者播放通知声音。
  • AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 这个也是表明请求的音频焦点持续时间比较短,但是在这段时间内,它希望其他应用以较低音量继续播放。例如,我们在使用导航的时候可以听音乐,当出现导航语音的时候,音乐音量会降低以便我们能听清楚导航的语音,当导航语音播放完毕后,音乐恢复音量,继续播放。
  • AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 这个也是表明音请求的音频焦点持续时间比较短,但是在这段时间内,不希望任何应用(包括系统应用)来做任何与音频相关的事情,就算是降低音量播放音乐也是不被希望的。例如当我们进行录音或者语音识别的时候,我们不希望其他的声音出现干扰。

AudioManager.requestAudioFocus()的返回值表明请求的结果:

  • AudioManager.AUDIOFOCUS_REQUEST_FAILED表明请求焦点失败
  • AudioManager.AUDIOFOCUS_REQUEST_GRANTED表明请求焦点成功。

3.1.2 Audio Focus的释放

当我们成功请求焦点后,就可以做一些与音频有关的事情,例如播放音乐,录音,或者语音识别。当完成这些工作后,我们必须调用AudioManager.abandonAudioFocus(onAudioFocusChangeListener l)释放音频焦点。

代码示例

public class AudioFocusManager{
	private AudioManager mAudioManager;

    public AudioFocusManager(Context context) {
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }
	
	AudioManager.OnAudioFocusChangeListener afChangeListener =
      new AudioManager.OnAudioFocusChangeListener() {
	  	public void onAudioFocusChange(int focusChange) {
	  		if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
	  			//长时间丢失焦点,当其他应用申请的焦点为AUDIOFOCUS_GAIN时,会触发此回调事件
	  			//例如播放QQ音乐,网易云音乐等
	  			//此时应当暂停音频并释放音频相关的资源。
            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
            	//短暂性丢失焦点,当其他应用申请AUDIOFOCUS_GAIN_TRANSIENT或AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE时,会触发此回调事件
            	//例如播放短视频,拨打电话等。
                //通常需要暂停音乐播放
          	} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
          		//短暂性丢失焦点并作降音处理,当其他应用申请AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK时,会触发此回调事件
          		//通常需要降低音量
          	} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {}
          		//当其他应用申请焦点之后又释放焦点会触发此回调
                //可重新播放音乐
        	}
        };
    /**
     * 获取音频焦点
     */
	public void requestFocus() {
		int result = mAudioManager.requestAudioFocus(afChangeListener, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
	}
	
    /**
     * 释放音频焦点
     */
    public void releaseAudioFocus() {
            mAudioManager.abandonAudioFocus(afChangeListener);
        }
    }
}

3.2 Android 8.0(API 级别 26) 以及更高的版本

从 Android 8.0(API 级别 26)开始,当您调用 requestAudioFocus() 时,必须提供 AudioFocusRequest 参数。要释放音频焦点,请调用 abandonAudioFocusRequest() 方法,该方法也接受 AudioFocusRequest 作为参数。在请求和放弃焦点时,应使用相同的 AudioFocusRequest 实例。

要创建 AudioFocusRequest,请使用 AudioFocusRequest.Builder。由于焦点请求始终必须指定请求的类型,因此此类型会包含在构建器的构造函数中。使用构建器的方法来设置请求的其他字段。

FocusGain 字段为必需字段;所有其他字段均为可选字段。

方法 备注
setFocusGain() 每个请求中都必须包含此字段。此字段的值与 Android 8.0 之前的 requestAudioFocus() 调用中所使用的 durationHint 值相同:AUDIOFOCUS_GAIN、AUDIOFOCUS_GAIN_TRANSIENT、AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 或 AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE。
setAudioAttributes() 这个方法是用来描述app的使用情况。这方法需要传入一个AudioAttributes对象,这个对象也是使用Builder模式来构造,例如使用AudioAttributes.Builder.setUsage()来描述使用这个音频来干什么,我们可以传入一个AudioAttributes.USAGE_MEDIA来表明用这个音频来作为媒体文件来播放,也可以传入一个AudioAttributes.USAGE_ALARM来表明用这个来作为闹铃。
setWillPauseWhenDucked() 当其他应用使用 AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK 请求焦点时,持有焦点的应用通常不会收到 onAudioFocusChange() 回调,因为系统可以自行降低音量。如果您需要暂停播放而不是降低音量,请调用 setWillPauseWhenDucked(true),然后创建并设置 OnAudioFocusChangeListener,具体如自动降低音量中所述。
setAcceptsDelayedFocusGain() 当焦点被其他应用锁定时,对音频焦点的请求可能会失败。此方法可实现延迟获取焦点,即在焦点可用时异步获取焦点。请注意,要使“延迟获取焦点”起作用,您还必须在音频请求中指定 AudioManager.OnAudioFocusChangeListener,因为您的应用必须收到回调才能知道自己获取了焦点。
setOnAudioFocusChangeListener() 只有当您在请求中还指定了 willPauseWhenDucked(true) 或 setAcceptsDelayedFocusGain(true) 时,才需要 OnAudioFocusChangeListener。有两个方法可以设置监听器:一个带处理程序参数,一个不带。处理程序是运行监听器的线程。如果您未指定处理程序,则会使用与主 Looper 关联的处理程序。

延迟获取焦点

在Android 8.0之前,当我们请求音频焦点的时候,只会返回两种结果,要么请求成功(AUDIOFOCUS_REQUEST_GRANTED),要么请求失败(AUDIOFOCUS_REQUEST_FAILED)。

而从Android 8.0开始,还有一种结果,延迟成功请求(AUDIOFOCUS_REQUEST_DELAYED),这个也是成功的请求,但是这个请求具有延迟性。例如当我们处于通话状态的时候,我们很显然不希望任何app来获取到音频焦点来做些事,例如播放音乐。

然而只有设置过AudioFocusRequest.Builder.setAcceptsDelayedFocusGain(true)才能获取到这种结果。那么我们怎么知道什么时候获取到了音频焦点呢,当然还需要设置AudioManager.OnAudioFocusChangeListener这个音频焦点变化的监听器,通过回调确认何时获取到了音频焦点。

自动降低音量

在Android 8.0之前,如果请求焦点使用了AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK参数,它表明希望拥有了音频焦点的其他应用降低音量来使用音频,然而并不是所有的应用都会这样做(有可能开发者忘记了优化),因为这并不是系统强制的。 从Android 8.0开始,这个降低音量的工作,就是系统默认行为了,可以说是一个良心的优化。

如果不希望系统自动降低音量,而是想自己控制,这个可以通过AudioFocusRequest.Builder.setWillPauseWhenDucked(true)方法取消系统的默认行为,然后通过监听音频焦点变化,来自己处理。

代码示例

public class AudioFocusManager{
	private AudioManager mAudioManager;
	private AudioFocusRequest focusRequest;

    public AudioFocusManager(Context context) {
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }
	
	AudioManager.OnAudioFocusChangeListener afChangeListener =
      new AudioManager.OnAudioFocusChangeListener() {
	  	public void onAudioFocusChange(int focusChange) {
	  		if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
	  			//长时间丢失焦点,当其他应用申请的焦点为AUDIOFOCUS_GAIN时,会触发此回调事件
	  			//例如播放QQ音乐,网易云音乐等
	  			//此时应当暂停音频并释放音频相关的资源。
            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
            	//短暂性丢失焦点,当其他应用申请AUDIOFOCUS_GAIN_TRANSIENT或AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE时,会触发此回调事件
            	//例如播放短视频,拨打电话等。
                //通常需要暂停音乐播放
          	} else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) {
          		//短暂性丢失焦点并作降音处理,当其他应用申请AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK时,会触发此回调事件
          		//通常需要降低音量
          	} else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {}
          		//当其他应用申请焦点之后又释放焦点会触发此回调
                //可重新播放音乐
        	}
        };
    /**
     * 获取音频焦点
     */
	public void requestFocus() {
		//版本兼容
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
			AudioAttributes playbackAttributes = new AudioAttributes.Builder()
            	.setUsage(AudioAttributes.USAGE_GAME)
            	.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            	.build();
    	
    		focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
            	.setAudioAttributes(playbackAttributes)
            	.setAcceptsDelayedFocusGain(true)
            	.setOnAudioFocusChangeListener(afChangeListener)
            	.build();
            
			int result = audioManager.requestAudioFocus(focusRequest);
		} else {
		 	int result = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
		}
	}
	
    /**
     * 释放音频焦点
     */
    public void releaseAudioFocus() {
    	//版本兼容
    	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            mAudioManager.abandonAudioFocusRequest(focusRequest);
        } else {
            mAudioManager.abandonAudioFocus(afChangeListener);
        }
        }
    }
}

官方文档

https://developer.android.google.cn/guide/topics/media-apps/audio-focus

本文地址:https://blog.csdn.net/weixin_42046829/article/details/108704873

相关标签: Android