Android录音的几个过程控制

1.如何监控其他app的录音行为?

​ 经过一番查找,发现了这个API:

android.media.AudioManager.AudioRecordingCallback

使用方式,大体是这样:

mAudioManger = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
        mRecordingCallback = new SystemRecordingCallback();
//注册一个回调先
mAudioManger.registerAudioRecordingCallback(mRecordingCallback,null);
//等待回调
public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
            super.onRecordingConfigChanged(configs);
            for (int i = 0; i < configs.size(); i++) {
                AudioRecordingConfiguration config = configs.get(i);
                Log.d(TAG, "onRecordingConfigChanged :" +
                        AudioRecordingConfiguration.toLogFriendlyString(config));
                int source = config.getClientAudioSource();
                switch (source) {
                    case MediaRecorder.AudioSource.MIC: {
                        //这里证明有其他app要录音了
                    }
                    break;
                    case MediaRecorder.AudioSource.VOICE_COMMUNICATION:
                        Log.d(TAG, "It is a Call");
                        break;
                }
            }
        }

AudioRecordingConfiguration包含这些信息:

//用一个hide方法举例可以全部了解
/**
* @hide
*/
    public static String toLogFriendlyString(AudioRecordingConfiguration arc) {
        return new String("session:" + arc.mSessionId
                + " -- source:" + MediaRecorder.toLogFriendlyAudioSource(arc.mClientSource)
                + " -- uid:" + arc.mClientUid
                + " -- patch:" + arc.mPatchHandle
                + " -- pack:" + arc.mClientPackageName
                + " -- format client=" + arc.mClientFormat.toLogFriendlyString()
                    + ", dev=" + arc.mDeviceFormat.toLogFriendlyString());
    }

经过实测,有个app录音的时候会回调两次。回调的参数里,暂时没法判断到底对方是停止录音还是开始录音。另外,还有一个API可供辅助判断:

mAudioManger.getActiveRecordingConfigurations();

我是做framwork的,不搞清楚这些机制,怎么说得过去呢?所以开始跟踪代码。

先看看AudioManager提供的这个public接口:

public void registerAudioRecordingCallback(@NonNull AudioRecordingCallback cb, Handler handler)
{
    ...
    mRecordCallbackList.add(new AudioRecordingCallbackInfo(cb,/*第二个参数没啥用,不聊*/);
    ...
    final IAudioService service = getService();
    service.registerRecordingCallback(mRecCb);
    ...
}

mRecordCallbackList定义:

//AudioRecordingCallbackInfo就是一个AudioRecordingCallback和handle
//这两个参数的封装类
private List<AudioRecordingCallbackInfo> mRecordCallbackList;

真正注册给AudioService的mRecCb又是个啥?

private final IRecordingConfigDispatcher mRecCb = new IRecordingConfigDispatcher.Stub() {
    @Override
    public void dispatchRecordingConfigChange(List<AudioRecordingConfiguration> configs) {
        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);
               }
         }
       }
    }
}

//收消息的地方
case MSSG_RECORDING_CONFIG_CHANGE: {
     final RecordConfigChangeCallbackData cbData =(RecordConfigChangeCallbackData) msg.obj;
     if (cbData.mCb != null) {
         //就是在这里回调了
         cbData.mCb.onRecordingConfigChanged(cbData.mConfigs);
   }
} break;

遍历所有调用了registerAudioRecordingCallback接口。注册了的客户端,然后挨个回调。

AudioManager这边基本清楚了,只要AudioService调用了mRecCb,客户端就会收到

onRecordingConfigChanged的回调。

那我们去AudioService那边看看:

//AudioService.java 
public void registerRecordingCallback(IRecordingConfigDispatcher rcdb) { 
    //检查MODIFY_AUDIO_ROUTING权限
    final boolean isPrivileged =
                (PackageManager.PERMISSION_GRANTED == mContext.checkCallingPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING));
        mRecordMonitor.registerRecordingCallback(rcdb, isPrivileged);
    }

引出另外一个类RecordingActivityMonitor

void registerRecordingCallback(IRecordingConfigDispatcher rcdb, boolean isPrivileged) {
	...
    final RecMonitorClient rmc = new RecMonitorClient(rcdb, isPrivileged);
    if (rmc.init()) {
        if (!isPrivileged) {
        //没有MODIFY_AUDIO_ROUTING权限的客户端
        mHasPublicClients = true;
     }
     mClients.add(rmc);
   }
}

rmc.init()就是为rcdb(观察者)设置个死亡代理,rcdb挂掉,rmc会知道,并回调:

public void binderDied() {
	Log.w(TAG, "client died");
    //取消注册
	sMonitor.unregisterRecordingCallback(mDispatcherCb);
}

回到AudioService,这个RecordingActivityMonitor类是这样运作的:

//AudioService.java
//======================
// Audio policy callbacks from AudioSystem for recording configuration updates
//======================
private final RecordingActivityMonitor mRecordMonitor;
public AudioService(Context context) {
    ...
       mRecordMonitor = new RecordingActivityMonitor(mContext); 
    ...
       mRecordMonitor.initMonitor();
    ...
}

关键就在这了initMonitor

//RecordingActivityMonitor.java
void initMonitor() {
    AudioSystem.setRecordingCallback(this);
}

这里的this当然指的是RecordingActivityMonitor的某一个对象。对AudioService来说,就是这个mRecordMonitor。

仔细看看RecordingActivityMonitor的定义:

public final class RecordingActivityMonitor implements AudioSystem.AudioRecordingCallback {
    ...

哈哈,看来,真相在AudioSystem中。

private static AudioRecordingCallback sRecordingCallback;

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

//离的太近了,先上了再说,连猜带蒙都能知道
private static void recordingCallbackFromNative(int event, int uid, int session, int source,int[] recordingFormat) {
    //cb就是AudioService的mRecordMonitor了
    cb = sRecordingCallback;
    ...
    cb.onRecordingConfigurationChanged(event, uid, session, source, recordingFormat, "");
    ...
}

那顺便也先回顾下RecordingActivityMonitor好了

//services/core/java/com/android/server/audio/RecordingActivityMonitor.java
//观察者所能得到的信息就是参数这些,由上面的AudioSystem回调而来
public void onRecordingConfigurationChanged(int event, int uid, int session, int source,int[] recordingInfo, String packName) {
    //只有系统能用的Source比如FM_TUNER直接return.不让普通app能感知
    if (MediaRecorder.isSystemOnlyAudioSource(source)) {
    	return;
    }
    //注意这个configsSystem,有别于后面的configsPublic
    final List<AudioRecordingConfiguration> configsSystem =
                updateSnapshot(event, uid, session, source, recordingInfo);
    ...
        //mHasPublicClients前面提到过,没有MODIFY_AUDIO_ROUTING权限
        final List<AudioRecordingConfiguration> configsPublic = mHasPublicClients ?anonymizeForPublicConsumption(configsSystem) :new ArrayList<AudioRecordingConfiguration>();
    //遍历所有观察者
    final Iterator<RecMonitorClient> clientIterator = mClients.iterator();
    while (clientIterator.hasNext()) {
        final RecMonitorClient rmc = clientIterator.next();
        ...
        //有特权,给configsSystem,没有特权,给configsPublic
        if (rmc.mIsPrivileged) {           		rmc.mDispatcherCb.dispatchRecordingConfigChange(configsSystem);
	} else { 	rmc.mDispatcherCb.dispatchRecordingConfigChange(configsPublic);
    }
   }
}

看看,特权和非特权有啥区别?

一个返回updateSnapshot的结果,一个返回anonymizeForPublicConsumption(configsSystem)的结果

先看看updateSnapshot

private List<AudioRecordingConfiguration> updateSnapshot(int event, int uid, int session,int source, int[] recordingInfo) {
    final boolean configChanged;
    final ArrayList<AudioRecordingConfiguration> configs;
    ...
        switch (event) {
        case AudioManager.RECORD_CONFIG_EVENT_STOP:
            //如果停止录音的,从mRecordConfigs中移除
            //如果移除成功,判定为configChanged
        	configChanged = (mRecordConfigs.remove(new Integer(session)) != null);
            break;    
        case AudioManager.RECORD_CONFIG_EVENT_START:
            ...
                //sessionKey是session的Integer封装
                //updatedConfig则是AudioSystem回调上来的
                //一堆参数的封装
              	mRecordConfigs.put(sessionKey, updatedConfig);
                configChanged = true;
            ...
        }
    if (configChanged) {
        configs = new ArrayList<AudioRecordingConfiguration>(mRecordConfigs.values());
    } else {
        configs = null;
    }
    ...
    return configs;    
}

简单总结下updateSnapshot做的事情就是,如果有变化(有新的录音启动了,或者退出了,或者,录音的session变了).则返回mRecordConfigs这个记录在案的数组。

即是configsSystem。

configsPublic:

//“公共消费”的录音配置列表。 仅在存在非系统记录活动侦听器时才计算
final List<AudioRecordingConfiguration> configsPublic = 	mHasPublicClients ?anonymizeForPublicConsumption(configsSystem) :
new ArrayList<AudioRecordingConfiguration>();

如果不是mHasPublicClients那么就只能返回一个空的ArrayList.如果mHasPublicClients,那就会获得一个anonymizeForPublicConsumption(隐去uid和pkgName?)处理过的configsSystem,擦,给我绕晕了。反正我是系统app。肯定拿到的是configsSystem。

最终的结果就是如下实测可用的判断方式:

mRecordingCallback = new SystemRecordingCallback();
mAudioManger.registerAudioRecordingCallback(mRecordingCallback,null);
private class SystemRecordingCallback extends AudioManager.AudioRecordingCallback {
        private final String TAG = "CaptureService.SystemRecordingCallback";
        @Override
        public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
            super.onRecordingConfigChanged(configs);
            int activeSize = configs.size();
            if (activeSize == 0) {
                //这时候,没有在录音的app.你自己的就可以启动了
            } else {
                for (int i = 0; i < activeSize; i++) {
                    AudioRecordingConfiguration config = configs.get(i);
                    int source = config.getClientAudioSource();
                    switch (source) {
                        case MediaRecorder.AudioSource.MIC:
                            ...
                           	//别人想要录音的时候,在这里把自己停掉吧
                            break;
                    }
                }
            }
        }
    }

​ 差点忘了接着说AudioSystem中onRecordingConfigurationUpdate.

​ 前面说到,是因为这个函数被回调了,才会引起最终app收到onRecordingConfigChanged的回调.请注意,这两个方法,一个是Configuration,一个是Config(app就不配用全拼,只配用缩写).

​ AudioSystem.java中的setRecordingCallback注册之后,实际注册到了AudioSystem(native)类里.

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

void AudioSystem::AudioPolicyServiceClient::onRecordingConfigurationUpdate(
    ...
    record_config_callback cb = NULL;
    {
        Mutex::Autolock _l(AudioSystem::gLock);
        cb = gRecordConfigCallback;
    }
	...
        cb(event, clientInfo, clientConfig, deviceConfig, patchHandle);
    ...
}

就是说只要回调了onRecordingConfigurationUpdate就会收到onRecordingConfigChanged回调

全局搜索之后,发现这两个地方会回调这个onRecordingConfigurationUpdate方法:

//frameworks/av/services/audiopolicy/common/managerdefinitions/src/AudioSession.cpp
uint32_t AudioSession::changeActiveCount(int delta)
{
    ...
        mClientInterface->onRecordingConfigurationUpdate(event, &mRecordClientInfo,
                    &mConfig, &deviceConfig, patchHandle);
    ...
}

void AudioSession::onSessionInfoUpdate() const
{
    ...
        mClientInterface->onRecordingConfigurationUpdate(RECORD_CONFIG_EVENT_START,
                    &mRecordClientInfo, &mConfig, &deviceConfig, patchHandle);
    ...
}
changeActiveCount
//frameworks/av/services/audiopolicy/managerdefault/
AudioPolicyManager.cpp	
2036 audioSession->changeActiveCount(1); in startInput() 
2107 audioSession->changeActiveCount(-1); in stopInput() 

简单写点解释吧,虽然已经很明显了.APM调用startInput的时候,app会收到回调,参数是1.stopInput的时候回调参数是-1.参数的意义参见:

AudioSession::changeActiveCount函数.

onSessionInfoUpdate

我已经查好了,直接show下调用堆栈:

status_t AudioPolicyManager::setInputDevice
	inputDesc->setPatchHandle(patchDesc->mHandle);
		mSessions.onSessionInfoUpdate();

以我目前的理解,主要用于createAudioPatch这种情况.这块我也不是很清楚,就不卖弄了

2.接通hfp电话之后,录音为何会被standby?

为何在hfp通话过程中,会存在两个一直打开的RecordThread(由updateCallRouting中的createAudioPatch创建),有啥用?

经git log查看是为了usb voice call什么的

这段其实是没搞太懂.就没脸写出来了(好像是我自己瞎改改出来的).我的做法是把updateCallRouting注释掉了,反正用不到什么usb voice call.

为了防止第二个问题,按下面第三个问题操作即可:

3.如何在电话打通的时候,暂停录音,电话挂断的时候,恢复录音?

这个问题比较简单,直接上我写的垃圾代码

private class MyPhoneStateListener extends PhoneStateListener {
        String TAG = "CaptureService.MyPhoneStateListener";
        @Override
        public void onCallStateChanged(int state, String phoneNumber) 	{
            super.onCallStateChanged(state, phoneNumber);
            Log.d(TAG, "MyPhoneStateListener state: "+ String.valueOf(state));
        switch (state) {
            case CALL_STATE_IDLE:
                //恢复录音
                mCaptureThread.startCapturing();
                break;
            case TelephonyManager.CALL_STATE_RINGING:
                Log.d(TAG, "CustomPhoneStateListener onCallStateChanged endCall");
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                //暂停录音
                mCaptureThread.stopCapturing();
                break;
        }
   }
}

抱歉,虎头蛇尾了.

你可能感兴趣的:(安卓,Audio)