其他分享
首页 > 其他分享> > Android Q共享音频输入

Android Q共享音频输入

作者:互联网

api介绍

Android Q允许多个应用同时录音。下面摘录一下官方说明:
当两个应用试图捕获音频时,它们都可以接收输入信号,或者其中一个可能会受到静默处理。
四种主要方案如下:

  1. Google 助理 + 普通应用
    Google 助理属于特权应用,因为其预先安装在设备上并且拥有 RoleManager.ROLE_ASSISTANT 角色。拥有此角色的任何其他预安装应用都会受到类似处理。
    Android 根据以下规则共享输入音频:
  1. 无障碍服务 + 普通应用
    AccessibilityService 需要严格的声明。
    Android 根据以下规则共享输入音频:
  1. 两个普通应用
    当两个应用同时进行捕获时,只有一个应用接收音频,另一个应用会受到静默处理。
    Android 根据以下规则共享输入音频:
  1. 语音通话 + 普通应用
    如果 AudioManager.getMode() 返回的音频模式为 MODE_IN_CALL 或 MODE_IN_COMMUNICATION,则语音通话处于活动状态。
    Android 根据以下规则共享输入音频:

配置变更

当多个应用同时捕获音频时,只有一个或两个应用处于“活动”状态(正在接收音频),其他应用则处于静音状态(接收静音)。当活动应用发生更改时,音频框架可能会根据以下规则重新配置音频路径:

当优先级较高的应用处于活动状态时,活动应用可能会受到静默处理,因此您可以在 AudioRecordMediaRecorder 对象上注册一个 AudioManager.AudioRecordingCallback,以便在配置发生更改时收到通知。可能的更改如下:

您必须在开始捕获前调用 AudioRecord.registerAudioRecordingCallback()。仅当应用正在接收音频且发生更改时才执行回调。

onRecordingConfigChanged() 方法返回包含当前音频捕获状态的 AudioRecordingConfiguration。使用以下方法了解更改:

isClientSilenced()
如果返回到客户端的音频当前由于捕获策略而受到静默处理,则返回 true。
getAudioDevice()
返回活动音频设备。
getEffects()
返回活动预处理效果。请注意,如果客户端不是优先级最高的活动应用,则活动效果可能与 getClientEffects() 返回的效果不同。
getFormat()
返回音频流属性。请注意,客户端接收的实际音频数据始终遵循 **getClientFormat()**返回的所需格式。该框架自动执行必要的重新采样、通道,以及格式转换,即从硬件接口上使用的格式转换为客户端指定的格式。
AudioRecord.getActiveRecordingConfiguration()
返回活动录音配置。
通过调用 AudioManager.getActiveRecordingConfigurations(),您可以获得设备上所有活动录音的一般视图。

代码流程分析

通过上面的api介绍我们知道应用是需要注册AudioManager.AudioRecordingCallback,在配置发生更改时收到通知,所以我们需要分析一下这个函数。

public static abstract class AudioRecordingCallback {
        /**                                                                                                                                                  
         * Called whenever the device recording configuration has changed.
         * @param configs list containing the results of
         *      {@link AudioManager#getActiveRecordingConfigurations()}.
         */         
        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {}
    }

这是一个抽象类,是由应用自己实现的,所以需要找到是谁调用了onRecordingConfigChanged,跟踪代码发现是接收到了MSSG_RECORDING_CONFIG_CHANGE这个消息调用的,然后就去查找是在哪里发送的消息,然后找到了如下代码:

private final IRecordingConfigDispatcher mRecCb = new IRecordingConfigDispatcher.Stub() {
        @Override                   
        public void dispatchRecordingConfigChange(List<AudioRecordingConfiguration> configs) {
            synchronized(mRecordCallbackLock) {
                if (mRecordCallbackList != null) {
                    for (int i=0 ; i < mRecordCallbackList.size() ; i++) {                                                                                   
                        final AudioRecordingCallbackInfo arci = mRecordCallbackList.get(i);
                        if (arci.mHandler != null) {
                            final Message m = arci.mHandler.obtainMessage(
                                    MSSG_RECORDING_CONFIG_CHANGE/*what*/,
                                    new RecordConfigChangeCallbackData(arci.mCb, configs)/*obj*/);
                            arci.mHandler.sendMessage(m);
                        }           
                    }               
                }
            }                       
        }                           
                                    
    }; 

通过上面的代码我们发现我们需要去找到是哪里调用的dispatchRecordingConfigChange函数,然后就来到了RecordingActivityMonitor.java的dispatchCallbacks函数(下面就不挨个贴代码了,只贴重点),然后是onRecordingConfigurationChanged函数,然后就来到了AudioSystem.java的recordingCallbackFromNative函数,这里是AudioSystem.cpp的setRecordConfigCallback函数通过jni调用过来的(jni的调用比较简单,就不做过多分析)。

到这里java层的调用流程就完成了,下面重点来分析native的调用,主要实现也是在这里

首先来看一下AudioSystem::setRecordConfigCallback

/*static*/ void AudioSystem::setRecordConfigCallback(record_config_callback cb)
{                               
    Mutex::Autolock _l(gLock);
    gRecordConfigCallback = cb;
}

callback的回调,和gRecordConfigCallback关联,通过gRecordConfigCallback找到了AudioSystem::AudioPolicyServiceClient::onRecordingConfigurationUpdate函数,然后去找是谁调用的这个函数,来到了AudioPolicyService::NotificationClient::onRecordingConfigurationUpdate函数,继续反向查找来到AudioPolicyService::doOnRecordingConfigurationUpdate,然后继续反向查找,跟踪代码确定是AudioPolicyService::onRecordingConfigurationUpdate通过RECORDING_CONFIGURATION_UPDATE这个消息调过来的,继续跟踪找到了AudioPolicyService::AudioPolicyClient::onRecordingConfigurationUpdate函数,接下来就到了AudioInputDescriptor::updateClientRecordingConfiguration函数,继续反向跟踪来到了AudioInputDescriptor::setAppState函数,然后就跟踪到了AudioPolicyService::setAppState_l函数,最终来到AudioPolicyService::updateUidStates_l这个函数,对于应用是否需要静默处理都是在这里做的,后面我们来仔细分析一下这个函数。
到这里api到native service的代码流程就分析完了,为了方便,我们用一个uml图来总结这个流程
在这里插入图片描述最终决定应用是否需要静默处理的是updateUidStates_l函数
注:native的调用很多都是binder,因为这不是本文的重点,所以在这里就不做过多的赘述

AudioPolicyService::updateUidStates_l函数解析

首先看一下重要部分的代码

// By default allow capture if:
        //     The assistant is not on TOP
        //     AND is on TOP or latest started
        //     AND there is no active privacy sensitive capture or call
        //             OR client has CAPTURE_AUDIO_OUTPUT privileged permission
        bool allowCapture = !isAssistantOnTop
                && ((isTopOrLatestActive && !isLatestSensitive) || isLatestSensitive)
                && !(isSensitiveActive && !(isLatestSensitive || current->canCaptureOutput))                                                                 
                && !(isInCall && !current->canCaptureOutput);
                
if (isVirtualSource(source)) {
            // Allow capture for virtual (remote submix, call audio TX or RX...) sources
            allowCapture = true;
        } else if (mUidPolicy->isAssistantUid(current->uid)) {
            // For assistant allow capture if:
            //     An accessibility service is on TOP or a RTT call is active
            //            AND the source is VOICE_RECOGNITION or HOTWORD
            //     OR is on TOP AND uses VOICE_RECOGNITION
            //            OR uses HOTWORD
            //         AND there is no active privacy sensitive capture or call
            //             OR client has CAPTURE_AUDIO_OUTPUT privileged permission
            if (isA11yOnTop || rttCallActive) {
                if (source == AUDIO_SOURCE_HOTWORD || source == AUDIO_SOURCE_VOICE_RECOGNITION) {
                    allowCapture = true;
                }            
            } else {         
                if (((isAssistantOnTop && source == AUDIO_SOURCE_VOICE_RECOGNITION) ||
                        source == AUDIO_SOURCE_HOTWORD) &&
                        (!(isSensitiveActive || isInCall) || current->canCaptureOutput)) {
                    allowCapture = true;
                }            
            }                
        } else if (mUidPolicy->isA11yUid(current->uid)) {
            // For accessibility service allow capture if:
            //     Is on TOP 
            //          AND the source is VOICE_RECOGNITION or HOTWORD
            //     Or        
            //          The assistant is not on TOP
            //          AND there is no active privacy sensitive capture or call
            //             OR client has CAPTURE_AUDIO_OUTPUT privileged permission
            if (isA11yOnTop) {
                if (source == AUDIO_SOURCE_VOICE_RECOGNITION || source == AUDIO_SOURCE_HOTWORD) {
                    allowCapture = true;
                }            
            } else {
                if (!isAssistantOnTop                                                                                                                        
                        && (!(isSensitiveActive || isInCall) || current->canCaptureOutput)) {
                    allowCapture = true;
                }           
            }
        } 
        setAppState_l(current->uid,
                      allowCapture ? apmStatFromAmState(mUidPolicy->getUidState(current->uid)) :
                                APP_STATE_IDLE);

通过上面的代码确定,调用setAppState_l函数传入的参数值是取决于allowCapture这个变量的,所以接下来重点跟踪一下这个变量的赋值就能找到频输入的策略管理了。

  1. 对于这个值的初始值,注释上的解释很明确。默认情况下,满足以下条件允许捕获音频输入:
  1. isVirtualSource(source)条件为true的时候,这个条件是判断是否需要捕获语音通话的tx或者rx的。
  2. 满足mUidPolicy->isAssistantUid(current->uid)条件,这个是判断是否是Google助理。如果是,那么以下情况允许捕获:
  1. 满足mUidPolicy->isA11yUid(current->uid)条件,这个条件满足以下情况允许捕获:

通过上面的条件我们可以确定,只要是应用满足CAPTURE_AUDIO_OUTPUT就可以捕获音频输入,所以我们如果想自己订制的话,可以自己在这些if条件下添加自己的条件,允许自己的应用也可以捕获音频输入。

标签:VOICE,函数,捕获,共享,应用,Android,AUDIO,音频
来源: https://blog.csdn.net/u013490411/article/details/106780723