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() {@Overridepublic 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,有别于后面的configsPublicfinal 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,没有特权,给configsPublicif (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中移除//如果移除成功,判定为configChangedconfigChanged = (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";@Overridepublic 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::setInputDeviceinputDesc->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";@Overridepublic 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;}}
}
抱歉,虎头蛇尾了.
Android录音的几个过程控制相关推荐
- Android录音转为MP2的实现
Android录音转为MP2的实现 利用Android提供的AudioRecord类以及开源编码库twolame,实现了android手机边录音边编码,录音完成直接得到MP2音频文件.由于Androi ...
- Android录音下————AudioRecord源码分析
Android录音下----AudioRecord源码分析 文章目录 Android录音下----AudioRecord源码分析 一.概述 1.主要分析点 2.储备知识 二.getMinBufferS ...
- android 录音的格式,Android录音mp3格式实例详解
Android录音支持的格式有amr.aac,但这两种音频格式在跨平台上表现并不好. MP3显然才是跨平台的最佳选择. 项目地址 实现思路概述 在分析代码前,我们需要明确几个问题 1. 如何最终生成M ...
- Android音频录制方案,Android录音,录制其他App播放的声音
Android录音,录制其他App播放的声音 从Android10(SDK 29)版本开始,可以设置录音App的源为其他App,这样就可以录制其他App播放的声音 此方案有以下注意几点 设置了源为其他 ...
- Android录音amr实时转成MP3格式
文章目录 MP3 录音使用说明 步骤一:下载NDK,并配置(Mac) 步骤二:修改C代码相关路径,编译成so库 步骤三:应用层代码代码调用系统AudioRecord类开始录音 开始录音 start() ...
- Android录音并进行本地转码为MP3
** Android录音并进行本地转码 ** 通过安卓手机进行录音, 录音后,使用lame进行转码操作 开发中需要使用这个功能,只是一个简单的进行转码的工具,具体的代码信息如下 项目的基本结构图 1. ...
- Android录音-SoundTouch移植到Android
Android录音-SoundTouch移植到Android 文章目录 Android录音-SoundTouch移植到Android 一.SoundTouch介绍 二.移植SoundTouch(And ...
- Androidの录音实现
Androidの录音实现 1. 录音功能需要使用android.media.MediaRecorder来完成,这里我们列出几个重要的使用方法,也是最常用的录音接口. 1.MediaRecorder r ...
- Android录音控件
做项目一直不得空,好不容易腾出时间,赶紧把过往的知识整理一下,以下是做项目时用到的录音控件,在同事写的基础上修改改成,支持后台录音,页面比较简单.写这个组件之前做了简单的调研,如果有不 ...
最新文章
- flash中物体运动基础之三---------摩擦力,重力,风力,推力,旋转
- FQND之联想--username@host.domain跟进
- C++使用ICE实现两台主机通信实例
- UltraVNC反向连接方式的使用
- Xcode8上传成功后,商店里构建版本却没有应用
- [信息安全] 4.一次性密码 amp;amp;amp;amp; 身份认证三要素
- mysql 查询一个字段快还是一条记录快_mysql (优化)查询一条再筛选某个字段和直接查询该条的某个字段的效率比较...
- Android/Linux线程死锁demo分析
- tsmsbs项目中用到的触发器和存储过程
- micropython固件编译_Micropython编译固件的操作步骤
- C++程序设计试题及答案解析(一)
- 【经验】Namisoft盘点电机扭矩的测量方法有哪些
- 认知水平高下定义及提高认知水平的方法
- 【IP 笔记 2.】北邮 互联网协议 Internet Protocol - Transport Layer
- MySQL45讲 读书笔记 22讲MySQL有哪些“饮鸩止渴”提高性能的方法
- GitLab 设置为中文版
- python判断三位数水仙花数_Python如何判断一个数字是否为水仙花数
- 新一代三维GIS技术体系再升维
- AutoConfig工具使用指南
- 新媒体运营黎想教程:活动运营策划的简略4个方式