经过一番查找,发现了这个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);
...
}
//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函数.
我已经查好了,直接show下调用堆栈:
status_t AudioPolicyManager::setInputDevice
inputDesc->setPatchHandle(patchDesc->mHandle);
mSessions.onSessionInfoUpdate();
以我目前的理解,主要用于createAudioPatch这种情况.这块我也不是很清楚,就不卖弄了
为何在hfp通话过程中,会存在两个一直打开的RecordThread(由updateCallRouting中的createAudioPatch创建),有啥用?
经git log查看是为了usb voice call什么的
这段其实是没搞太懂.就没脸写出来了(好像是我自己瞎改改出来的).我的做法是把updateCallRouting注释掉了,反正用不到什么usb voice call.
为了防止第二个问题,按下面第三个问题操作即可:
这个问题比较简单,直接上我写的垃圾代码
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;
}
}
}
抱歉,虎头蛇尾了.