当前位置: 首页 > 工具软件 > focus-android > 使用案例 >

android9监听音频焦点,Android AudioFocus音频焦点机制学习和理解

颛孙和颂
2023-12-01

1、为什么会有音频焦点机制?

我们android系统里面会安装各种多媒体软件,如果不制定一个有效合理的规则,各个应用各自为政,那么可能就会出现各种播放器、软件的混音。音频焦点机制规定某一时刻只能有一个应用获取到声音的焦点,这个时候就可以发出声音。当然,在这个应用获取到焦点之前,需要通知其他所用的应用失去焦点。

2、使用音频焦点

//获取焦点

AudioManager mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);

mAudioManager.requestAudioFocus(cl, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);

requestAudioFocus方法有三个参数

第一个参数:OnAudioFocusChangeListener ,此为一个监听控制器,通过这个监听器可以知道自己获取到焦点或者失去焦点。

第二个参数:streamType音频流类型,焦点获得之后的数据传输类型,这个参数不会影响焦点机制,不同的音频流类型同样遵守一个焦点机制。

第三个参数:durationHint,获得焦点的时间长短,定义了四种类型

a、AUDIOFOCUS_GAIN //长时间获得焦点,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS

b、AUDIOFOCUS_GAIN_TRANSIENT //短暂性获得焦点,用完应立即释放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT

c、AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK //短暂性获得焦点并降音,可混音播放,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK

d、AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE //短暂性获得焦点,录音或者语音识别,此参数会触发其他监听器的AudioManager.AUDIOFOCUS_LOSS_TRANSIENT

我们通常使用前面三种类型就可以了。

看看OnAudioFocusChangeListener 的实现:

OnAudioFocusChangeListener cl = new OnAudioFocusChangeListener() {

@Override

public void onAudioFocusChange(int focusChange) {

switch(focusChange){

case AudioManager.AUDIOFOCUS_LOSS:

//长时间丢失焦点,这个时候需要停止播放,并释放资源。根据不同的逻辑,有时候还会释放焦点

mAudioManager.abandonAudioFocus(cl);

break;

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:

//短暂失去焦点,这时可以暂停播放,但是不必要释放资源,因为很快又会获取到焦点

break;

case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:

//短暂失去焦点,但是可以跟新的焦点拥有者同时播放,并做降噪处理

break;

case AudioManager.AUDIOFOCUS_GAIN:

//获得了音频焦点,可以播放声音

break;

}

}

};

3、获取音频焦点机制流程分析

我们调用AudioManager请求焦点,并在重构方法里面判断参数合法值,然后注册监听,通过Binder通信,和系统服务AudioService通信。我们看到OnAudioFocusChangeListener这个回调监听并没有发送给AudioService,取而代之的是mAudioFocusDispatcher这个参数作为和跨进程回调的桥梁。

requestAudioFocus(OnAudioFocusChangeListener l, int streamType, int durationHint)

.......

registerAudioFocusListener(l);

.......

IAudioService service = getService();

try {

status = service.requestAudioFocus(requestAttributes, durationHint, mICallBack,

mAudioFocusDispatcher, getIdForAudioFocusListener(l),

getContext().getOpPackageName() /* package name */, flags,

ap != null ? ap.cb() : null);

} catch (RemoteException e) {

throw e.rethrowFromSystemServer();

}

IAudioFocusDispatcher 的设计很简洁,主要就是把从AudioService获取到的消息通过handler机制,交给另外的线程处理,从代码看到是交给了请求焦点的线程处理。

private final IAudioFocusDispatcher mAudioFocusDispatcher = new IAudioFocusDispatcher.Stub() {

public void dispatchAudioFocusChange(int focusChange, String id) {

final Message m = mServiceEventHandlerDelegate.getHandler().obtainMessage(

MSSG_FOCUS_CHANGE/*what*/, focusChange/*arg1*/, 0/*arg2 ignored*/, id/*obj*/);

mServiceEventHandlerDelegate.getHandler().sendMessage(m);

}

};

AudioService是运行在system_server进程里面的系统服务,其中维护了一个栈:Stack mFocusStack,此为维护焦点的关键。

申请焦点主要是如下几点:

a、检查当前栈顶的元素是否是Phone应用占用,如果Phone处于占用状态,那么focusGrantDelayed = true。

b、压栈之前,需要检查当前栈中是否已经有这个应用的记录,如果有的话就删除掉。

c、如果focusGrantDelayed = true,那么就会延迟申请,并把此次请求FocusRequester实例入栈,但是此时记录不是被压在栈顶,而是放在lastLockedFocusOwnerIndex这个位置,也就是打电话这个记录的后面;如果focusGrantDelayed = false,不需要延迟获得焦点,同样创建FocusRequester实例,但是先要通知栈里其他记录失去焦点,然后压入栈顶,最后通知自己获得焦点成功。

boolean focusGrantDelayed = false;

if (!canReassignAudioFocus()) { //这里判断焦点是否处于电话状态

if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {

return AudioManager.AUDIOFOCUS_REQUEST_FAILED;

} else {

// request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be

// granted right now, so the requester will be inserted in the focus stack

// to receive focus later

focusGrantDelayed = true;

}

}

// focus requester might already be somewhere below in the stack, remove it 此处便是移除栈里面相同clientId的记录

removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);

//创建新的FocusRequester实例,为入栈做准备

final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,

clientId, afdh, callingPackageName, Binder.getCallingUid(), this);

if (focusGrantDelayed) {

// focusGrantDelayed being true implies we can't reassign focus right

// which implies the focus stack is not empty.延迟

final int requestResult = pushBelowLockedFocusOwners(nfr);

if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {

notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);

}

return requestResult;

} else {

// propagate the focus change through the stack没有延迟

if (!mFocusStack.empty()) {

propagateFocusLossFromGain_syncAf(focusChangeHint);

}

// push focus requester at the top of the audio focus stack

mFocusStack.push(nfr);

}

notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),

AudioManager.AUDIOFOCUS_REQUEST_GRANTED);

}

4、释放音频焦点流程

释放音频焦点会有以下两种情况:

a:如果要释放的应用是在栈顶,则释放之后,还需要通知先在栈顶应用,其获得了audiofocus;

b:如果要释放的应用不是在栈顶,则只是移除这个记录,不需要更改当前audiofocus的占有情况。

private void removeFocusStackEntry(String clientToRemove, boolean signal,

boolean notifyFocusFollowers) {

// is the current top of the focus stack abandoning focus? (because of request, not death)

if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))

{ //释放焦点的应用端在栈顶

//Log.i(TAG, " removeFocusStackEntry() removing top of stack");

FocusRequester fr = mFocusStack.pop();

fr.release();

if (notifyFocusFollowers) {

final AudioFocusInfo afi = fr.toAudioFocusInfo();

afi.clearLossReceived();

notifyExtPolicyFocusLoss_syncAf(afi, false);

}

if (signal) {

// notify the new top of the stack it gained focus

notifyTopOfAudioFocusStack();

}

} else {

//释放焦点的应用端不在栈顶

// focus is abandoned by a client that's not at the top of the stack,

// no need to update focus.

// (using an iterator on the stack so we can safely remove an entry after having

// evaluated it, traversal order doesn't matter here)

IteratorstackIterator = mFocusStack.iterator();

while(stackIterator.hasNext()) {

FocusRequester fr = stackIterator.next();

if(fr.hasSameClient(clientToRemove)) {

Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "

+ clientToRemove);

stackIterator.remove();

fr.release();

}

}

}

}

 类似资料: