检测 Android 设备上缺少听筒(仅限扬声器)

Posted

技术标签:

【中文标题】检测 Android 设备上缺少听筒(仅限扬声器)【英文标题】:Detect lack of earpiece (speakerphone only) on an Android device 【发布时间】:2011-08-04 14:18:44 【问题描述】:

我有一个应用程序,它在手机上使用时会显示扬声器切换按钮。拨动开关在手机听筒和免提电话之间切换音频路由。但是,当应用程序在平板电脑(或任何没有听筒的设备)上运行时,我想移除切换开关,因为所有音频都通过免提电话路由。

理想情况下,我想使用某种isEarpiecePresent() 调用,或者检查某个配置对象上的标志来查找此信息,但我在 API 中找不到任何此类信息。

我试图通过调用AudioManager.setSpeakerphoneOn(false) 来解决这个问题,然后检查AudioManager.isSpeakerphoneOn(),希望它仍然会返回true,我可以关闭它。系统返回false,即使音频仍在通过扬声器路由。

我目前正在考虑检查电话功能,尽管这并不完全适合。还有其他想法吗?

【问题讨论】:

仅供参考,我认为电话功能不可靠,例如我拥有的三星 Galaxy Tab 支持电话,但没有耳机扬声器。 en.wikipedia.org/wiki/Samsung_Galaxy_Tab 你检查了吗? developer.android.com/training/managing-audio/audio-output.html 感谢 stetocina,但不幸的是,这些文档中没有关于检测音频输出硬件的内容。 还有一些 Galaxy Tab 设备不支持电话(没有蜂窝硬件)但有一个听筒,据说是用于 VoIP 应用程序的。 【参考方案1】:

所以丑陋的部分是 - Google 忽略了这个问题。漂亮的部分是我设法实际检查了听筒,并且在 5/5 设备上它运行良好(在 5.0、5.1、6.0 上测试)。再一次,丑陋的部分是这只是糟糕的、不稳定的代码,而且它很hacky,但是在他们发布更好的 API 之前,我做不了太多......

这里是hack

private Boolean mHasEarpiece = null; // intentionally 'B' instead of 'b'

public boolean hasEarpiece() 
    if (mHasEarpiece != null) 
        return mHasEarpiece;
    

    // not calculated yet, do it now
    try 
        Method method = AudioManager.class.getMethod("getDevicesForStream", Integer.TYPE);
        Field field = AudioManager.class.getField("DEVICE_OUT_EARPIECE");
        int earpieceFlag = field.getInt(null);
        int bitmaskResult = (int) method.invoke(mAudioManager, AudioManager.STREAM_VOICE_CALL);

        // check if masked by the earpiece flag
        if ((bitmaskResult & earpieceFlag) == earpieceFlag) 
            mHasEarpiece = Boolean.TRUE;
         else 
            mHasEarpiece = Boolean.FALSE;
        
     catch (Throwable error) 
        Log.e(TAG, "Error while checking earpiece! ", error);
        mHasEarpiece = Boolean.FALSE;
    

    return mHasEarpiece;

如果你有更好的东西,我愿意进行改进编辑:)

【讨论】:

警告购买者:我没有调试太多,但我已经看到这个方法在肯定有听筒的 BLU Advance (Android 5.1) 手机上返回“true”和“false”。当然,我正在使用这种方法以及一些与 AudioManager 混在一起的遗留代码,但我认为 DEVICE_OUT_EARPIECE 之类的字段将是静态的并且独立于混混? 是的,这有点奇怪,不知道为什么会这样。【参考方案2】:

我对此进行了研究,但遗憾的是我找不到任何 API 支持来检测耳机是否存在。

我现在已向 Google 提出此功能请求:http://code.google.com/p/android/issues/detail?id=37623

如果您同意该功能,请点击 Google 问题页面底部的星号为它投票!

很多用户提出了 Google Talk 不提供扬声器/耳机切换选项的问题,这可能表明缺少合适的 API...http://code.google.com/p/android/issues/detail?id=19221

作为一种解决方法,您可能希望在近似于的设备上隐藏该功能(所有这些方法都有缺点):

具有超大屏幕的设备(例如平板电脑) 那些没有电话支持的人 对没有耳机的主要设备使用可配置的白/黑名单。

FTR,如果您使用this answer 将音频路由到听筒,在 Xoom (OS 3.1) 或 Samsung Galaxy Tab (OS 2.2) 上运行该代码似乎没有任何负面影响 -音频只是继续通过外部扬声器播放。我还没有机会测试是否与蓝牙耳机有任何交互。

【讨论】:

我设法破解了它,它在我刚刚测试过的 Lollipop 上运行良好。有关详细信息,请参阅我的答案【参考方案3】:

您现在可能知道,使用当前的 Android API 无法做到这一点。

音频硬件接口是专有的,即由制造商为其硬件编写,并且必须支持 Android 系统的某些调用,以使其与使用 Android SDK 开发的应用程序兼容。

即使您找到解决某一设备缺乏 API 支持的方法,这也可能不适用于所有设备。您的AudioManager.setSpeakerphoneOn(false) 示例可能非常适用于设备,因为这完全取决于音频硬件接口的编写方式。我的假设是,如果没有听筒,大多数平板电脑都会将听筒和扬声器的音频路由视为相同。

如果您想查看 Nexus 7 的音频硬件接口代码,it's here

【讨论】:

【参考方案4】:

从 Android S/12 (API 31) 开始,我们有了检查内置听筒是否存在的新方法。下面全面的可复制粘贴方法,在旧系统版本上,它使用来自@milosmns 答案的反射(谢谢!)

private static Boolean hasEarpiece = null;

public static boolean hasEarpiece(Context context) 
    if (hasEarpiece != null) return hasEarpiece;

    AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

    if (Build.VERSION.SDK_INT >= 31) 
        List<AudioDeviceInfo> devices = audioManager.getAvailableCommunicationDevices();
        for (AudioDeviceInfo device : devices) 
            if (device.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE) 
                hasEarpiece = true;
                break;
            
        
        if (hasEarpiece == null) 
            // just for safety
            AudioDeviceInfo currDevice = audioManager.getCommunicationDevice();
            hasEarpiece = currDevice != null &&
                    currDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_EARPIECE;
        
        Log.i("EarpieceBuiltInChecker", "hasEarpiece detected:%s", hasEarpiece);
        return hasEarpiece;
    

    try 
        Method method = AudioManager.class.getMethod("getDevicesForStream", Integer.TYPE);
        Field field = AudioManager.class.getField("DEVICE_OUT_EARPIECE");
        int earpieceFlag = field.getInt(null);
        int bitmaskResult = (int) method.invoke(audioManager, AudioManager.STREAM_VOICE_CALL);

        hasEarpiece = (bitmaskResult & earpieceFlag) == earpieceFlag;
        Log.i("EarpieceBuiltInChecker", "hasEarpiece detected:" + hasEarpiece);
     catch (Throwable error) 
        hasEarpiece = false;
        Log.i("EarpieceBuiltInChecker", "hasEarpiece detection FAILED!");
    

    return hasEarpiece;

我的流是AudioManager.STREAM_VOICE_CALL 类型(如果适用)

【讨论】:

【参考方案5】:
public boolean isEarPieceConnected() 
        AudioManager audio = (AudioManager) this
                .getSystemService(Context.AUDIO_SERVICE);
        if (audio.isWiredHeadsetOn()) 
            Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();
            return true;
         else 
            Toast.makeText(this, "Not Connected", Toast.LENGTH_SHORT).show();
            return false;
        
    

您还可以检查 audio.isSpeakerphoneOn()

如果我解决了你的问题,我希望这能奏效。

【讨论】:

抱歉,不是 - 问题是关于内置听筒,而不是有线耳机。此外,isWiredHeadsetOn() 已被弃用,其文档状态“这不是音频播放实际上是通过有线耳机进行的有效指示,因为音频路由取决于其他条件。”【参考方案6】:

您可以使用音频管理器进行检查

Audio Manager

也看看

Head Set Plugged

【讨论】:

抱歉,这个问题与有线耳机无关。原题引用了AudioManager中的方法,所以我们已经知道了。

以上是关于检测 Android 设备上缺少听筒(仅限扬声器)的主要内容,如果未能解决你的问题,请参考以下文章

如何通过内置听筒扬声器播放音频

iOS 判断听筒还是扬声器

uniapp实战笔记实现语音播放功能

uniapp实现语音播放功能

uniapp实现语音播放功能

uniapp实现语音播放功能