Android录音监控的实现原理

最近在公司里研究学习安卓的音频模块,发现音频管理器有这么一个有用的API:AudioManager.registerAudioRecordingCallback(@NonNull AudioRecordingCallback cb, Handler handler),它可以监控其他App的录音行为,当自己的App里设置了监听回调方法后,第三方的APP应用录音时,在自己的App里能收到监听回调的消息(如在我自己的Demo App中能收到喜马拉雅 App 录音时的回调);这引起了我的好奇心,因为喜马拉雅和我的Demo App是不同的进程呢,况且我的Demo并不是系统App(并没有跟安卓系统有过多的关系)安卓怎么能允许个人的App监听其它App的行为呢?带着这个问题,我自上而下的把整个流程梳理了一遍,具体如下:

===说明===

文章中的源码是基于Android N(API 25)为基础的,跟音频模块相关的有两大服务即AudioFlinger(简称AF,路径:frameworks\av\services\audioflinger\AudioFlinger.cpp)和AudioPolicyService(简称APS, 路径:frameworks/av/services/audiopolicy/service/AudioPolicyService.cpp),前者负责执行音频策略并将音频从硬件输入或输出到硬件,后者负责制定音频输入/输出的相关策略,AF和APS不直接对外服务,他们是通过中间媒介类AudioSystem(有AudioSystem.cpp,android_media_AudioSystem.cpp以及AudioSystem.java的相关实现);另外AudioManager(简称AM,路径:framework\base\media\java\android\media\AudioManager.java)其实是AudioService(简称AS ,路径:framework\base\media\java\android\media\AudioService.java)的代理,调用AM中的方法其实质是调用AS的同名方法

===首先===

看AM的方法registerAudioRecordingCallback()源码

mRecordCallbackList.add(new AudioRecordingCallbackInfo(cb,new ServiceEventHandlerDelegate(handler).getHandler()));

 final IAudioService service = getService();
                    try {
                        service.registerRecordingCallback(mRecCb);
                    } catch (RemoteException e) {
                        throw e.rethrowFromSystemServer();
                    }

大致意思是构建一个AudioRecordingCallbackInfo的实例,并把它添加到相应的集合里,不过构建这个AudioRecordingCallbackInfo时还需要一个ServiceEventHandlerDelegate的对象,这个对象比较简单望文生义就是handler的一个委托,收到Hander消息的时候,进行相应的处理即可,继续看后面貌似是得到了一个远程服务,然后调用了远程服务的同名方法并且传递了mRecCb参数,做Framework的同学应该比较清楚,这里的远程服务指的是AS,也即是AM通过AIDL的方式跨进程调用了AudioService;另外还有传递的参数mRecCb也是值得注意的,它是这么构建出来的 new IRecordingConfigDispatcher.Stub()看见了stub其实大致就能猜到,它有跨进程通信的能力! AM里的方法,我们暂且分析到这里,

===然后===

继续往下到AS里分析其同名方法

    // Audio policy callbacks from AudioSystem for recording configuration updates    
    private final RecordingActivityMonitor mRecordMonitor = new RecordingActivityMonitor();

    public void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {
        mRecordMonitor.registerRecordingCallback(rcdb);
    }

可以看到源码里的同名方法还是比较简单的就是调用了mRecordMonitor的同名方法,还要注意其注释大致意思是AudioSystem的回调,再跟踪到RecordingActivityMonitor类里的同名方法

 void registerRecordingCallback(IRecordingConfigDispatcher rcdb) {    
        if (rcdb == null) {
            return;
        }
        synchronized(mClients) {
            final RecMonitorClient rmc = new RecMonitorClient(rcdb);
            if (rmc.init()) {
                mClients.add(rmc);            
            }
        }
    }
也是比较简单,构建了RecMonitorClient对象,init()初始化了一下,就把它加到相应的集合里,然后仔细看RecordingActivityMonitor这个类发现它实现了 AudioSystem.AudioRecordingCallback的onRecordingConfigurationChanged(int event, int session, int source,int[] recordingInfo)方法,再仔细分析下这个方法

final List configs =updateSnapshot(event, session, source, recordingInfo);
        if (configs != null){
            synchronized(mClients) {
                final Iterator clientIterator = mClients.iterator();
                while (clientIterator.hasNext()) {
                    try {                        
                        clientIterator.next().mDispatcherCb.dispatchRecordingConfigChange(
                                configs);
                    } catch (RemoteException e) {
                        Log.w(TAG, "Could not call dispatchRecordingConfigChange() on client", e);
                    }
                }
            }
        }

就是把刚才添加到集合里的对象RecMonitorClient以迭代器的方式取出来,并且向外分发了消息 configs,而这个configs跟之前在我Demo APP中的消息回调就很接近了;RecMonitorClient对象里的mDispatcherCb变量是IRecordingConfigDispatcher类型,拥有跨进程通信的能力!到这里可以得到一个结论是:AM里的registerAudioRecordingCallback监听之所以被回调,是因为系统进程调用了 AudioSystem的内部接口AudioRecordingCallback的onRecordingConfigurationChanged()方法! 那么究竟是谁调用了它呢?

===进一步分析 AudioSystem===

AudioSystem里有两个名称相关的方法比较关键:

public static void setRecordingCallback(AudioRecordingCallback cb) {
        synchronized (AudioSystem.class) {
            sRecordingCallback = cb;
            native_register_recording_callback();
        }
    }

/**
     * Callback from native for recording configuration updates.
     * @param event
     * @param session
     * @param source
     * @param recordingFormat see
     *     {@link AudioRecordingCallback#onRecordingConfigurationChanged(int, int, int, int[])} for
     *     the description of the record format.
     */
    private static void recordingCallbackFromNative(int event, int session, int source,
            int[] recordingFormat) {
        AudioRecordingCallback cb = null;
        synchronized (AudioSystem.class) {
            cb = sRecordingCallback;
        }
        if (cb != null) {
            cb.onRecordingConfigurationChanged(event, session, source, recordingFormat);
        }
    }

前者的意思大概是设置录音监听的回调,并将其保存起来了,后者 调用了上面保存的监听回调;而且分析recordingCallbackFromNative的名称和注释应该是从native层回到JAVA层的,那么大致清楚了是从这里调用到上面RecordingActivityMonitor类里去的;另外方法setRecordingCallback还调用了本地方法 native_register_recording_callback()

static void
android_media_AudioSystem_registerRecordingCallback(JNIEnv *env, jobject thiz)
{
    AudioSystem::setRecordConfigCallback(android_media_AudioSystem_recording_callback);
}

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

一步步设置下来之后最终是AudioSystem.cpp类的gRecordConfigCallback成员变量保存了它,还值得注意的是android_media_AudioSystem_recording_callback 是以函数指针的形式(函数指针一般是让别人调用的,不是主动调用其他的代码)传递到了 AudioSystem.cpp中,具体实现如下

static void
android_media_AudioSystem_recording_callback(int event, audio_session_t session, int source,
        const audio_config_base_t *clientConfig, const audio_config_base_t *deviceConfig,
        audio_patch_handle_t patchHandle)
{
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (env == NULL) {
        return;
    }
    if (clientConfig == NULL || deviceConfig == NULL) {
        ALOGE("Unexpected null client/device configurations in recording callback");
        return;
    }

    // create an array for 2*3 integers to store the record configurations (client + device)
    //                 plus 1 integer for the patch handle
    const int REC_PARAM_SIZE = 7;
    jintArray recParamArray = env->NewIntArray(REC_PARAM_SIZE);
    if (recParamArray == NULL) {
        ALOGE("recording callback: Couldn't allocate int array for configuration data");
        return;
    }
    jint recParamData[REC_PARAM_SIZE];
    recParamData[0] = (jint) audioFormatFromNative(clientConfig->format);
    // FIXME this doesn't support index-based masks
    recParamData[1] = (jint) inChannelMaskFromNative(clientConfig->channel_mask);
    recParamData[2] = (jint) clientConfig->sample_rate;
    recParamData[3] = (jint) audioFormatFromNative(deviceConfig->format);
    // FIXME this doesn't support index-based masks
    recParamData[4] = (jint) inChannelMaskFromNative(deviceConfig->channel_mask);
    recParamData[5] = (jint) deviceConfig->sample_rate;
    recParamData[6] = (jint) patchHandle;
    env->SetIntArrayRegion(recParamArray, 0, REC_PARAM_SIZE, recParamData);   

    // callback into java
    jclass clazz = env->FindClass(kClassPathName);
    env->CallStaticVoidMethod(clazz,
            gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative,
            event, session, source, recParamArray);
    env->DeleteLocalRef(clazz);

    env->DeleteLocalRef(recParamArray);
}

前面在包装数据可以暂时忽略,看最后几行 有注释callback into java即回调到JAVA层;kClassPathName即AudioSystem对应的JAVA路径 android/media/AudioSystem ;结构体gAudioPolicyEventHandlerMethods.postRecordConfigEventFromNative即方法recordingCallbackFromNative对应的ID,总的来说一旦这个函数被调用就会回到AudioSystem.java的recordingCallbackFromNative()方法里;

那么成员变量gRecordConfigCallback(也即函数指针)是在什么时候调用的呢?

===继续分析 AudioSystem.cpp 并在该文件中找名称相关的函数===

void AudioSystem::AudioPolicyServiceClient::onRecordingConfigurationUpdate(
        int event, audio_session_t session, audio_source_t source,
        const audio_config_base_t *clientConfig, const audio_config_base_t *deviceConfig,
        audio_patch_handle_t patchHandle) {
    record_config_callback cb = NULL;
    {
        Mutex::Autolock _l(AudioSystem::gLock);
        cb = gRecordConfigCallback;
    }

    if (cb != NULL) {
        cb(event, session, source, clientConfig, deviceConfig, patchHandle);
    }   
}

原来是在内部类AudioPolicyServiceClient的onRecordingConfigurationUpdate()函数被调用后调用的!到此与AF相关的分析也就到底了,看着关键字应该是到 APS这边来了!

============================继续分析APS==================================

APS中有几个内部类,然后与名称recordingConfiguration相关的函数有好几个

    内部类AudioCommand线程
        函数recordingConfigurationUpdateCommand 
    内部类(同名的头文件中)AudioPolicyClient继承于AudioPolicyClientInterface    AudioPolicyManager实例化的时候需要这个客户端对象       
    内部类NotificationClient继承于Binder的死亡代理
        成员变量spmAudioPolicyServiceClient
        函数onRecordingConfigurationUpdate中调用了上面智能指针的同名函数
        在AudioFlinger.cpp中有同名的类,同名的函数registerClient()中同样声明了这个类并保存到了mNotificationClients集合中
    成员变量DefaultKeyedVector< uid_t, sp

    函数onFirstRef()开启了三个AudioCommandThread线程,加载了音频的硬件模块,根据系统中的配置文件,加载了基础的音频策略,基础的音效
    函数registerClient() 声明了NotificationClient内部类的对象并保存到了mNotificationClients容器中,只要是有进程请求APS的            服  务,该进程就会被抽象成客户端的形式保存到该容器中
    函数doOnRecordingConfigurationUpdate() 遍历mNotificationClients容器,调用单个客户端的onRecordingConfigurationUpdate函数,进而通知注册过的客户端录音状态改变
    函数onRecordingConfigurationUpdate 调用了mOutputCommandThread的onRecordingConfigurationUpdate函数,           mOutputCommandThread也即是AudioCommand,对应的onRecordingConfigurationUpdate函数是将参数以Command命令的形式发送出去了

 

另外与APS这边相关的还有几个类:

frameworks\av\media\libmedia\AudioSystem.cpp 
    内部类AudioFlingerClient 实现了接口 IAudioFlingerClient.h
    内部类AudioPolicyServiceClient实现了接口 IAudioPolicyServiceClient.h    
    内部类AudioPolicyServiceClient(它是Binder的Native端)的函数onRecordingConfigurationUpdate
    函数get_audio_policy_service通过Binder接口获取到APS服务,同时向APS注册了调用进程,而APS那边会将该进程抽象为客户端并保存到容器中,具体实现是
        先获取ServiceManager,然后通过APS在刚开始启动时注册的名字media.audio_policy找到它,刚开始找到的是Binder,然后对Binder进行强制类型转换即可
    函数get_audio_flinger通过Binder接口获取到AudioFlinger.cpp音频输出服务,同时向AF注册了这个客户端,而在AF那边会将该客户端保存容器里,具体的实现原理同上,有异曲同工之妙

 

frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp

  简称APM,管理着APS的客户端

  函数getInputForDevice(获取输入设备,准备录音的时候) 实例化了AudioSession对象

 

frameworks/av/services/audiopolicy/service/AudioPolicyClientImpl.cpp

    实现了APS中的内部类AudioPolicyClient定义的相关函数
    函数onRecordingConfigurationUpdate 调用了APS中的onRecordingConfigurationUpdate函数


frameworks/av/services/audiopolicy/common/managerdefinitions/src/AudioSession.cpp

    在APM中实例化了该对象
    成员变量AudioPolicyClientInterface 
    函数changeActiveCount() 回调成员变量的mClientInterface->onRecordingConfigurationUpdate()函数
    函数onSessionInfoUpdate()

    日志表明调用安卓的录音机就会用到该类

 

最后还有两个接口类

frameworks/av/media/libmedia/IAudioPolicyService.cpp(它是Binder的Proxy端)

frameworks/av/media/libmedia/IAudioPolicyServiceClient.cpp

主要是将参数封装成Parcel形式或者从Parcel中解封装出来,为数据实现跨进程的传输提供了支持

 

========================最后经过日志分析,大致的调用流程是=======================

AS运行在System_Server进程中,AF和APS服务运行在AudioServer进程中,System_Server进程以客户端的形式注册到了APS服务中,当第三方进程喜马拉雅录音时会请求APS服务,实例化AudioSession对象,APS收到录音的回调后,会通知之前注册到里面的客户端,客户端中就包含AF,AF收到录音消息后,所在的进程AudioServer会利用IRecordingConfigDispatcher类 跨进程通信到我的Demo APP中。设置录音回调从JAVA层到Native层,中间跨过了好几个系统服务进程,再从Native层回到JAVA层

 

参考:https://blog.csdn.net/bberdong/article/details/85761548

你可能感兴趣的:(车载音频)