Android Q共享音频输入

api介绍

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

  • Google 助理 + 普通应用
  • 无障碍服务 + 普通应用
  • 两个普通应用
  • 语音通话 + 普通应用
  1. Google 助理 + 普通应用
    Google 助理属于特权应用,因为其预先安装在设备上并且拥有 RoleManager.ROLE_ASSISTANT 角色。拥有此角色的任何其他预安装应用都会受到类似处理。
    Android 根据以下规则共享输入音频:
  • 除非另一个使用隐私敏感音频源的应用已在捕获音频,否则 Google 助理 可以接收音频(无论在前台还是后台)。
  • 除非 Google 助理在屏幕顶部具有可见的界面组件,否则应用会接收音频。
    请注意,这两个应用仅在 Google 助理位于后台且其他应用未从隐私敏感音频源进行捕获时才接收音频。
  1. 无障碍服务 + 普通应用
    AccessibilityService 需要严格的声明。
    Android 根据以下规则共享输入音频:
  • 如果服务的界面位于顶部,则服务和应用都将接收音频输入。此行为提供使用语音指令控制语音通话或视频捕获等功能。
  • 如果该服务不在顶部,则此情况的处理方式与下述两个普通应用的情况一样。
  1. 两个普通应用
    当两个应用同时进行捕获时,只有一个应用接收音频,另一个应用会受到静默处理。
    Android 根据以下规则共享输入音频:
  • 如果两个应用都不具备隐私敏感性,则由界面位于顶部的应用接收音频。如果两个应用都没有界面,则较晚开始者接收音频。
  • 如果其中一个应用具备隐私敏感性,则由其接收音频,另一个应用则会受到静默处理,即使后者由界面位于顶部或较晚开始捕获也是如此。
  • 如果两个应用都具备隐私敏感性,则由最晚开始捕获的应用接收音频,另一个应用则会受到静默处理。
  1. 语音通话 + 普通应用
    如果 AudioManager.getMode() 返回的音频模式为 MODE_IN_CALL 或 MODE_IN_COMMUNICATION,则语音通话处于活动状态。
    Android 根据以下规则共享输入音频:
  • 通话始终接收音频。
  • 如果属于无障碍服务,则应用可以捕获音频。
  • 如果是拥有 CAPTURE_AUDIO_OUTPUT 权限的特权(预安装)应用,则应用可以捕获语音通话。
  • 如要捕获语音通话的上行链路 (TX)、下行链路 (RX) 或这两者,则应用必须指定音频源 MediaRecorder.AudioSource.VOICE_UPLINK 或 MediaRecorder.AudioSource.VOICE_DOWNLINK 和/或设备 AudioDeviceInfo.TYPE_TELEPHONY。

配置变更

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

  • 每个活动应用的音频输入设备可能会更改(例如,从内置麦克风更改为已连接的蓝牙耳机)。
  • 启用与最高优先级活动应用相关联的预处理。其他预处理都将被忽略。

当优先级较高的应用处于活动状态时,活动应用可能会受到静默处理,因此您可以在 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图来总结这个流程
Android Q共享音频输入最终决定应用是否需要静默处理的是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. 对于这个值的初始值,注释上的解释很明确。默认情况下,满足以下条件允许捕获音频输入:
  • 助手不在顶部
  • 位于top或者最晚开始
  • 并且没有有效的隐私敏感捕获或呼叫
  • 或者客户端具有CAPTURE_AUDIO_OUTPUT特权
  1. isVirtualSource(source)条件为true的时候,这个条件是判断是否需要捕获语音通话的tx或者rx的。
  2. 满足mUidPolicy->isAssistantUid(current->uid)条件,这个是判断是否是Google助理。如果是,那么以下情况允许捕获:
  • 辅助功能服务已接通TOP或RTT通话处于活动状态
  • 来源是VOICE_RECOGNITION或HOTWORD
  • OR位于TOP上并使用VOICE_RECOGNITION
  • 或使用HOTWORD并且没有有效的隐私敏感捕获或呼叫
  • 或客户端具有CAPTURE_AUDIO_OUTPUT特权
  1. 满足mUidPolicy->isA11yUid(current->uid)条件,这个条件满足以下情况允许捕获:
  • 在TOP上
  • 来源是VOICE_RECOGNITION或HOTWORD
  • 助手不在顶部并且没有有效的隐私敏感捕获或呼叫
  • 客户端具有CAPTURE_AUDIO_OUTPUT特权

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

上一篇:音频R


下一篇:HTML5学习(10)video和audio