一、问题背景

目前麦克风是否可用的检测方案过于简单,容易出现检测不准确的情况。针对此问题做了如下优化。

推荐集成方在使用麦克风的采集和上行功能前要检测麦克风是否可用。

二、麦克风探测器功能介绍

1、录音。包括采样率、声源、采样位数、声道数等参数适配,保存音频文件生成pcm或者wav,获取音频的分贝值。

2、麦克风权限是否授予检测。

3、麦克风当前是否被占用检测。

4、音频数据的合法性检测。采集到的音频有没有被系统静默处理。

检测方案如下图所示:

三、录音机录音以及生成音频文件

系统录音机AudioRecord正常启动是麦克风占用检测的前提,系统录音机正常启动需要给录音机传入正确的参数,包括声源、采样率、声道数、采样位数,录音机才会创建成功。根据AudioRecord提供的API以及以往开发经验,绝大部分(具体数据没有统计)机型传入以下参数系统录音机能创建成功:

mAudioSource = MediaRecorder.AudioSource.MIC;
mSampleRateInHz = 16000;
mChannelConfig = AudioFormat.CHANNEL_IN_MONO;
mAudioFormat = AudioFormat.ENCODING_PCM_16BIT

极其个别机型录音参数需要适配,目前的适配:

//1、魅族、OPPO、realme的Android10系统,开启无障碍时的参数
if (context != null&& (DeviceUtil.isMeiZu() || DeviceUtil.isOppo() || DeviceUtil.isRealMe())&& (DeviceUtil.getSdkIntVersion() == 29)&& DeviceUtil.isAccessibilityEnabled(context.getApplicationContext())) {mAudioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION;mChannelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
}

读麦克风返回的数据会先缓存在内存中,最后一次读取的数据(index为负数)返回后,可保存生成pcm或者wav方便测试。麦克风录音流程图如下所示:

四、麦克风权限检测

调用系统api即可,不复杂

public static boolean isAudioPermissionGranted(Context context) {if (context == null) {return false;}PackageManager packageManager = context.getPackageManager();String packageName = context.getPackageName();return packageManager.checkPermission(Manifest.permission.RECORD_AUDIO, packageName)== PackageManager.PERMISSION_GRANTED;
}

五、麦克风占用检测以及音频合法性检测

麦克风占用情况分Android 10以及Android 10以上,以及Android 10以下,Android 10以及Android 10 以上系统麦克风共享,多个进程可同时从麦克风中读取到数据,但只有一个进程读取到的数据是有效的,其他进程读取的音频数据会被静默处理(音频无效,都是0),优先级规则如下:

  • 特权应用(Google助理、无障碍服务)高于普通应用(第三方应用、系统内置的录音机等)
  • 具有可见前台界面的应用比后台应用具有更高的优先级
  • 相较于从非隐私敏感源捕获音频的应用,从隐私敏感源(音频源为CAMCORDER或VOICE_COMMUNICATION)捕获音频的应用有着更高的优先级
  • 如果两个优先级相同的后台应用都在捕获音频,则后开始的那个优先级更高

两个普通应用都要录音时,只有一个应用接收音频,另一个应用会受到静默处理,此种场景麦克风共享规则如下:

如果两个应用都不具备隐私敏感性,则由界面位于顶部的应用接收音频。如果两个应用都没有界面,则较晚开始者接收音频。

如果其中一个应用具备隐私敏感性,则由其接收音频,另一个应用则会受到静默处理,即使后者由界面位于顶部或较晚开始捕获也是如此。

如果两个应用都具备隐私敏感性,则由最晚开始捕获的应用接收音频,另一个应用则会受到静默处理。

其他三种组合:Google助理+普通应用,无障碍服务+普通应用,语音通话+普通应用,麦克风共享规则参考Google开发者文档:共享音频输入  |  Android 开发者  |  Android Developers

Android 10以下的系统麦克风不能共享,一个应用在录音时,另一个应用可成功创建audioRecord但在startRecording()后的返回值不是RECORDSTATE_RECORDING,可以判断当前麦克风有没有被占用。

因此综合Android 10以及以上,以及Android10 以下的系统机制,麦克风是否可用判断规则如下

  • 无录音权限时,麦克风不可用;
  • 有录音权限时,startRecording()后如果是recording状态的话,说明要么是android10以下的系统并且当前没有其他进程在录音,要么是android10以及10以上的系统。上述两种情况,都录取800毫秒的音频数据,判断300到800毫秒之间的音频数据,如果有一次读取到的数据是有效的,我们就认为麦克风可正常使用。如果300到800毫秒之间采集到的音频都被静默处理了,说明我们的进程目前优先级低,此时虽然可以采集到声音,但采集到的音频无效,我们也认为当前麦克风不可用;有录音权限时,如果startRecording() 之后录音机不是recording状态的话,说明当前系统是Android 10以下,并且有其他进程在录音,此时认定麦克被占用。

此外,麦克风检测添加了超时机制,从开始检测计时如果1秒后还没有拿到检测结果的话,超时机制启动,给集成方返回检测结果(麦克风可用)

代码示意如下:

private static final int DETECT_DURATION = 800; // 开启录音后,读取800毫秒数据
private static final int DETECT_START_TIME = 300; // 检测300-800毫秒之间的数据
private static final int TIME_OUT_PERIOD = 1000;//超时检测时间MicStatus micStatus = null;try {isDetecting = true;if (!MicUtil.isAudioPermissionGranted(mContext)) {//无权限micStatus = new MicStatus(ErrorCode.MIC_STATUS_NOT_GRANTED,ErrorMessage.MIC_STATUS_NOT_GRANTED_MSG);} else {//有权限mRecorderWrapper.start();final long startTime = System.currentTimeMillis();int audioNum = 0;while (System.currentTimeMillis() - startTime <= DETECT_DURATION) {RecorderWrapper.ReadAudioData readAudioData = mRecorderWrapper.read();AudioData audioData = null;if (readAudioData != null) {int ret = readAudioData.getReadLength();byte[] audioBytes = readAudioData.getReadBytes();double decibel =AudioUtil.getVoiceDecibel(ByteShortUtil.byteArray2ShortArray(audioBytes));audioNum++;audioData = new AudioData(audioBytes, audioNum, decibel);// 第一次读取到数据后再回调onStartif (Math.abs(audioNum) == 1) {if (mAudioEventListener != null) {mAudioEventListener.onStart(String.valueOf(System.currentTimeMillis()));}}if ((System.currentTimeMillis() - startTime > DETECT_START_TIME)) {//检测300-800毫秒的数据if (mAudioEventListener != null) {mAudioEventListener.onNext(audioData);}boolean isAudioAvailable = AudioUtil.isAvailable(readAudioData.getReadBytes());//300 - 800ms内读取到一次有效数据,就认为麦克风可用if (isAudioAvailable) {micStatus = new MicStatus(ErrorCode.MIC_STATUS_AVAILABLE,ErrorMessage.MIC_STATUS_AVAILABLE);break;}}} else {//兜底策略micStatus =new MicStatus(ErrorCode.MIC_STATUS_AVAILABLE, ErrorMessage.MIC_STATUS_AVAILABLE);break;}}// 800毫秒读到的数据都无效if (micStatus == null) {micStatus = new MicStatus(ErrorCode.MIC_STATUS_NOT_AVAILABLE,ErrorMessage.MIC_STATUS_NOT_AVAILABLE + "能读到静默数据");}}} catch (KeAudioError keAudioError) {if (keAudioError != null) {micStatus = new MicStatus(keAudioError.getErrorCode(), keAudioError.getErrorMessage());}} finally {mRecorderWrapper.release();mHandler.removeCallbacks(mRunnable);if (mMicDetectorListener != null) {mMicDetectorListener.onDetectMicStatus(micStatus);}isDetecting = false;if (mAudioEventListener != null) {mAudioEventListener.onComplete();}}

六、使用

// 判断麦克风是否可用
public static void isMicAvailable(Context context, final Action1<MicStatus> action1) {if (context == null) {if (action1 != null) {action1.call(new MicStatus(ErrorCode.MIC_STATUS_NOT_AVAILABLE, "context is null"));}return;}new MicDetector(context, new MicDetector.MicDetectorListener() {@Overridepublic void onDetectMicStatus(MicStatus micStatus) {if (micStatus == null) {micStatus = new MicStatus(ErrorCode.MIC_STATUS_NOT_AVAILABLE, "未知原因");}if (action1 != null) {action1.call(micStatus);}}}, null).detectMicStatus();
}

七、埋点

是否授予了权限、读取到的音频数据是否有效、是否启动了超时机制、从开始检测到拿到检测结果的耗时,这些我们都做好了埋点工作。

八、注意事项

1、直接开线程不友好,考虑单一线程池。

2、某些机型录音会失败,需要结合机型信息收集设备可用的录音参数,做埋点。

3、极其个别机型还需要录音参数适配,两种方案:2中收集的录音参数,客户端做适配策略;或者反编译最新版本的wx app,参考wx在最新机型以及特殊机型的录音参数适配策略。

4、检测结束(不论是正常的结束,还是检测超时)一定要释放麦克风,避免麦克风占用问题,而且释放麦克风时要日志埋点用来证明麦克风确实被释放了。

Android麦克风探测器相关推荐

  1. Android 麦克风录音动画

    Android 麦克风录音动画 源代码 RecordView.java R.drawable.voice_recording 源代码 RecordView.java import android.co ...

  2. android 麦克风耳机,Android force AudioRecord使用耳机麦克风

    我使用AudioRecord来录制音乐,但是当我录制它时使用手机麦克风. 我该如何强迫他使用耳机的频道?Android force AudioRecord使用耳机麦克风 我用这个代码: int min ...

  3. android 麦克风 动画,录音麦克风动画效果

    我们常用的语音输入法会根据我们说话的大小产生一些波动动画,那么这个动画是怎么实现的呢?其实很简单.原理:一张空的麦克风图像,一张满的麦克风图像,先绘制一张空的,然后再绘制一些满的,但是满的绘制之前,有 ...

  4. android 麦克风pc,电脑没有麦克风?让手机充当电脑麦克风!

    电脑没有麦克风但又要录音或者通话怎么办?其实可以把手机当成电脑麦克风!手机是必然有麦克风的,让手机充当电脑麦克风,完全可行!那么要怎么做?今天笔者就来为大家介绍一款可以让安卓手机充当电脑麦克风的App ...

  5. android麦克风问题吗,如何检测Android中是否存在麦克风?

    我的应用程序中有一个语音识别部分来捕获用户的语音输入. 这就是我的工作 Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZ ...

  6. Android麦克风录音的实现

    最近公司有一个业务,就是通过IM发送音频,我用的是环信的第三方,自定义发送音频,写了一个录音的demo,录制完成之后发送. 这个Demo测试之后感觉还不错,分享一下. 一.添加权限: <uses ...

  7. android 麦克风 动画,Android实现直接播放麦克风采集到的声音

    本文实例讲述了Android实现直接播放麦克风采集到的声音.分享给大家供大家参考.具体如下: 这是一个直接播放麦克风采集到的声音线程类: class RecordThread extends Thre ...

  8. android麦克风监听动画效果,微信小程序实现录音时的麦克风动画效果实例

    前言 这个简单的麦克风demo的创意是来源于"包你说"中的录音效果,实现的方式其实也并不难,但对于小程序中的简易动画的使用的确很实用. 效果 先来看个demo,gif帧数比较低,实 ...

  9. android麦克风被禁用怎么办,为什么微信麦克风被禁用?如何开启?

    今天小编一打开微信,想要和自己的朋友进行微信聊天,刚想发语音,但被提示说麦克风被禁止,不能发语音,这是为什么呢?难道是设置除了问题吗?下面小编就来为大家说说详细的设置步骤,请看下文的教程! 微信无法获 ...

  10. android麦克风录音格式,Android 中使用MIC(麦克风)录音

    1.需要一个File 和 MediaRecorder类 File myFile = File.createTempFile("Sample_13_5", ".amr&qu ...

最新文章

  1. 一位刚刚成功上岸的智能车队员对于参赛经历总结与对比赛的建议
  2. DAY9-python并发之多线程理论
  3. 皮一皮:编!继续编啊你...
  4. 阿里云数据中台全新产品DataTrust聚焦企业数据安全保障
  5. 个人成长:2021年终记
  6. 青年报告_了解青年的情绪
  7. 使用parted划分GPT分区(转)
  8. win7系统任务管理器如何强制关闭程序
  9. shell 获取MD5值
  10. @程序员,你的技术为啥十年八年也没有进步?
  11. Mac 配置PHP运行环境
  12. TiDB 在平安核心系统的引入及应用
  13. 分布式事务解决方案之2PC(两阶段提交)介绍
  14. 使用JS实现表单验证
  15. 辞职时被领导挽留,要不要留下?
  16. 微博、微信,媒体选择何去何从
  17. matlab计算三角格网面积,MATLAB中plot的用法
  18. n3k配置vpc是否还需要配置hsrp_连结7000系列交换机使用HSRP配置示例
  19. 不改一行代码!快速迁移 Flask 应用上云
  20. 初学,用python获取B站视频

热门文章

  1. 斯蒂文斯理工学院计算机科学硕士,斯蒂文斯理工学院计算机科学硕士
  2. 网格计算——下一代分布式计算
  3. 如何在Ubuntu MATE 18.04中安装GNOME 3?
  4. ubuntu 安装gnome3
  5. WebService使用axis2框架发布服务与获取服务
  6. Win11 PE下如何快速设置IP如何新建共享文件夹并设置为everyone完全控制权限
  7. BitTorrent协议规范
  8. Preferences DataStore------JAVA
  9. 金融数据中心建设模式浅析
  10. vue无法加载ps1