【问题标题】:How to route default audio to ear piece when headphones are connected?连接耳机时如何将默认音频路由到耳机?
【发布时间】:2016-07-19 14:44:39
【问题描述】:

我正在开发一个应用,我们只需要将耳机插孔用作按钮。

要求:连接耳机时通过听筒播放默认音频(通话)(无需通过耳机播放音频)

有很多通过扬声器和耳机以及蓝牙耳机路由音频的示例,但如果连接了耳机,则没有关于通过设备的耳机扬声器路由音频的示例。 我已经尝试了很多,有些链接是

Android : Force audio routing(在我的场景中不起作用)

我已经检查了 SoundAbout(https://play.google.com/store/apps/details?id=com.woodslink.android.wiredheadphoneroutingfix&hl=en) 应用程序,它将音频路由到耳机、扬声器和听筒等各种端口。

如果连接了耳机,我的扬声器有音频: 这是我的代码

if (Build.VERSION.SDK_INT >= 21) {
            ForegroundService.audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
            ForegroundService.audioManager.setSpeakerphoneOn(true);
            SplashScreen.preferences.edit().putBoolean("isKey", true).commit();
        } else {
            Class audioSystemClass = null;
            try {
                audioSystemClass = Class.forName("android.media.AudioSystem");
                Method setForceUse = audioSystemClass.getMethod("setForceUse", int.class, int.class);
                setForceUse.invoke(null, FOR_MEDIA, FORCE_SPEAKER);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }


            SplashScreen.preferences.edit().putBoolean("isKey", true).commit();
            ForegroundService.audioManager.setSpeakerphoneOn(true);
        }

【问题讨论】:

    标签: android android-audiomanager


    【解决方案1】:

    听筒在 Android 中从不用于媒体,只有在手机处于“通话”或“通信”(VoIP)状态时才能使用。

    我猜你已经注意到没有“FORCE_EARPIECE”常量,所以不能在对setForceUse的调用中指定它。

    此外,耳机在通话输出设备选择中的优先级最低,因此如果手机连接了任何东西(在您的情况下是假耳机),该设备将被已选中(请参阅https://android.googlesource.com/platform/frameworks/av/+/322b4d2/services/audiopolicy/enginedefault/src/Engine.cpp#381)。

    抱歉,似乎无法实现您的目标。

    更新

    在 SoundAbout 强制将耳机用于媒体时检查 media.audio_policy 状态后,我发现了此应用使用的以下技巧:

    1. 它调用 AudioSystem.setPhoneState(MODE_IN_COMMUNICATION) 以强制执行“通信”电话状态(通常用于 VoIP 呼叫)。

    2. 如果连接了耳机(或耳机),为了防止声音由于优先级更高而被路由到它,应用程序调用AudioSystem.setDeviceConnectionState(DEVICE_OUT_WIRED_HEADSET, DEVICE_STATE_UNAVAILABLE, ...)欺骗音频管理器相信没有耳机。

    这些都是黑客行为,需要应用密切监控手机状态。它也不是一直有效。

    另一个缺点是使用听筒会禁用片上音频解压缩,因此会消耗更高的电池电量。

    一般来说,我不建议使用这些技术。

    【讨论】:

    • 感谢@Mikhail 分享您的想法。我知道这是不可能的。看起来 Google 不想提供任何方法来为开发人员提供直接按钮。我对此进行了很多研究,发现所有可以访问它的方法都已被删除或隐藏或贬低。但是有些应用程序仍然这样做,例如 playstore 上的 mSwitch、Keycut、iKey,所以这是可能的,但我不知道如何,但我需要在我的应用程序中使用这种功能。我认为他们正在使用反射以编程方式断开耳机,然后使用您知道的优先级将音频像往常一样路由到听筒。
    • 至少 mSwitch (KeyCut) 需要 root 手机。使用 root 访问权限,您绝对可以更改音频策略配置以执行您需要的任何操作。
    • 我不想要 root 访问权限并且我测试的设备没有 root。请检查 Playstore 上的 SoundAbout 和 Kclick 应用程序。选择路由听筒,您将在听筒而不是耳机中获得音频。
    • 我已经检查了 SoundAbout(感谢您的指点!)——当您选择耳机作为媒体的输出时,应用程序会将手机置于 COMMUNICATION 状态(通过反射调用 AudioSystem.setPhoneState ),因此它们会干扰 VoIP 应用程序并需要跟踪手机上发生的事情。另一个缺点是手机硬件不支持听筒的 MP3/AAC 解码,所以解码是在软件中进行的,会消耗更多的电池。我仍然不确定它是如何在连接耳机时将输出输出到听筒的——稍后会检查。
    • 为了让耳机在连接耳机时工作,SoundAbout 似乎使用AudioSystem.setDeviceConnectionState 来欺骗音频策略管理器,使其认为没有耳机。这是一个严重的黑客行为。但为了完整起见,我会用这些发现更新我的答案。
    【解决方案2】:

    经过大量研究,我发现不使用反射没有任何方法可以实现此功能。 首先,您需要插入耳机插孔,然后使用合适的参数调用 setWiredDeviceConnectionState() 方法,然后它的行为就像耳机断开连接但单击仍然有效。 所以这是一个黑客,但根据我的要求,它不是一个万无一失的解决方案,但现在可以工作。 这是我的代码,

    private void sendIntent(Intent i) {
            Method m;
            Log.i(TAG, "Device sdk = " + Build.VERSION.SDK_INT);
            try {
                if (Build.VERSION.SDK_INT < 16) {
                    Class<?> clazz = Class.forName("android.app.ActivityManagerNative");
                    m = clazz.getMethod("broadcastStickyIntent", Intent.class, String.class);
                    m.setAccessible(true);
                    m.invoke(clazz, i, null);
                    return;
                } else if (Build.VERSION.SDK_INT < 23) {
                    //int type, int state, String address, String name
                    m = am.getClass().getMethod("setWiredDeviceConnectionState", Integer.TYPE, Integer.TYPE, String.class);
                    m.setAccessible(true);
                    Object[] objArr = new Object[3];
                    objArr[0] = (i.getIntExtra("microphone", 0) == 0) ? 8 : 4;
                    objArr[1] = i.getIntExtra("state", 0);
                    objArr[2] = i.getStringExtra("name");
                    m.invoke(am, objArr);
                } else {
                    //int type, int state, String address, String name
                    m = am.getClass().getMethod("setWiredDeviceConnectionState", Integer.TYPE, Integer.TYPE, String.class, String.class);
                    m.setAccessible(true);
                    Object[] objArr = new Object[4];
                    objArr[0] = (i.getIntExtra("microphone", 0) == 0) ? 8 : 4;
                    objArr[1] = i.getIntExtra("state", 0);
                    objArr[2] = i.getStringExtra("address");
                    objArr[3] = i.getStringExtra("name");
                    m.invoke(am, objArr);
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    

    发送意图:

    @TargetApi(Build.VERSION_CODES.M)
    public class HeadSetJackReciever extends AudioDeviceCallback {
        public static boolean isAudioChecked;
    
        public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) {
            if (addedDevices.length != 0) {
                for (int i = 0; i < addedDevices.length; i++) {
                    if (addedDevices[i].getType() == AudioDeviceInfo.TYPE_WIRED_HEADSET) {
                        AudioDeviceInfo audioDeviceInfo = addedDevices[i];
                        int microphone = audioDeviceInfo.getType();
                        String headsetName = "DCS";
                        String headsetAddress = "";
                        try {
                            Method method = audioDeviceInfo.getClass().getMethod("getAddress");
                            method.setAccessible(true);
                            headsetAddress = (String) method.invoke(audioDeviceInfo);
                        } catch (NoSuchMethodException e) {
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
                            e.printStackTrace();
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                        Log.e("TEST", "microphone:"+microphone);
                        Log.e("TEST", "headsetName:"+headsetName);
                        Log.e("TEST", "headsetAddress:"+headsetAddress );
                        Intent intent = new Intent(ForegroundService.context, SelectAudioOutput.class);
                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        intent.putExtra("microphone",microphone);
                        intent.putExtra("headsetName",headsetName);
                        intent.putExtra("headsetAddress",headsetAddress);
                        ForegroundService.context.startActivity(intent);
                    }
                }
            }
        }
    
        public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) {
            if (removedDevices.length != 0) {
                Log.e("TEST", "Audio deinserted");
                if (SplashScreen.preferences.getBoolean("isKey", false)) {
                    Intent startIntent = new Intent(ForegroundService.context, ForegroundService.class);
                    startIntent.setAction(Constants.ACTION.STARTNOTIFICATION_ACTION);
                    ForegroundService.context.startService(startIntent);
                } else {
                    Intent startIntent = new Intent(ForegroundService.context, ForegroundService.class);
                    startIntent.setAction(Constants.ACTION.STOPNOTIFICATION_ACTION);
                    ForegroundService.context.startService(startIntent);
                }
                ForegroundService.audioManager.setMode(AudioManager.MODE_IN_CALL);
                ForegroundService.audioManager.setSpeakerphoneOn(false);
            }
        }
    }
    

    对于 Lollipop 及更低版本:

    if (intent.getAction().equals(Intent.ACTION_HEADSET_PLUG)) {
                headsetName = intent.getStringExtra("name");
                microphone = intent.getIntExtra("microphone", 0);
    
                int state = intent.getIntExtra("state", -1);
                switch (state) {
                    case 0:
                        Log.d("onReceive", "Headset unplugged");
                        Log.e("TEST", "Audio deinserted");
                        if (SplashScreen.preferences.getBoolean("isKey", false)) {
                            Intent startIntent = new Intent(ForegroundService.context, ForegroundService.class);
                            startIntent.setAction(Constants.ACTION.STARTNOTIFICATION_ACTION);
                            context.startService(startIntent);
                        } else {
                            Intent startIntent = new Intent(ForegroundService.context, ForegroundService.class);
                            startIntent.setAction(Constants.ACTION.STOPNOTIFICATION_ACTION);
                            context.startService(startIntent);
                        }
                        ForegroundService.audioManager.setMode(AudioManager.MODE_IN_CALL);
                        ForegroundService.audioManager.setSpeakerphoneOn(false);
                        break;
                    case 1:
    
                        Log.d("onReceive", "Headset plugged");
                        Log.e("TEST", "microphone:"+microphone);
                        Log.e("TEST", "headsetName:"+headsetName);
                        Log.e("TEST", "headsetAddress:"+headsetAddress );
                        Intent intentone = new Intent(ForegroundService.context, SelectAudioOutput.class);
                        intentone.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        intentone.putExtra("microphone",microphone);
                        intentone.putExtra("headsetName",headsetName);
                        intentone.putExtra("headsetAddress",headsetAddress);
                        context.startActivity(intentone);
                        break;
                }
            }
    

    如果我错过了什么,请告诉我。 谢谢。

    【讨论】:

    • 在 HeadsetJackReceiver addedDevices[] 数组中,我得到 2 个耳机和 1 个耳机。所以代码发送了两个意图并且音频没有被路由。我正在使用 Android M(23)。你能帮忙吗?
    • @DCS 为了将这个逻辑集成到我的程序中,我会要求您提供关于ForegroundServiceSelectAudioOutputSplashScreenConstants 的简短信息? /跨度>
    猜你喜欢
    • 1970-01-01
    • 2011-01-23
    • 1970-01-01
    • 1970-01-01
    • 2020-03-23
    • 2012-12-22
    • 2016-01-30
    • 2011-01-09
    • 1970-01-01
    相关资源
    最近更新 更多