How to route default audio to ear piece when headphones are connected?

跟風遠走 提交于 2019-12-22 00:19:59

问题


I am developing an app in which we need to use the headphone jack as a button only.

Requirement : Play the default audio (calling) via earpiece when headsets are connected (no need of audio through headphones)

There are many example of routing audio through speaker and headphones and also bluetooth headsets but nothing about routing the audio through ear speakers of devices if headsets are connected. I have tried a lot and some links are

Android : Force audio routing (not working in my scenario)

I have checked SoundAbout(https://play.google.com/store/apps/details?id=com.woodslink.android.wiredheadphoneroutingfix&hl=en) app and it is routing the audio to various port like headset, speakers and earpieces.

I have got audio to speakers if headsets are connected: Here is my code

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);
        }

回答1:


The earpiece is never used for media in Android, and it can only be used if the phone is in "call" or "communication" (VoIP) state.

I guess you have noticed that there is no "FORCE_EARPIECE" constant, so it can't be specified in a call to setForceUse.

Also, the earpiece has the lowest priority in output device selection for calls, so if the phone has anything connected to it (and in your case there is a fake headset), that device will be selected (see https://android.googlesource.com/platform/frameworks/av/+/322b4d2/services/audiopolicy/enginedefault/src/Engine.cpp#381).

Sorry, it doesn't seem to be possible to achieve what you intend.

UPDATE

After examining media.audio_policy state while SoundAbout is enforcing the use of earpiece for media, I have discovered the following tricks that this app uses:

  1. It calls AudioSystem.setPhoneState(MODE_IN_COMMUNICATION) for enforcing "communication" phone state (usually used for VoIP calls).

  2. If a headset (or headphones) is connected, in order to prevent the sound to be routed to it due to higher priority, the app calls AudioSystem.setDeviceConnectionState(DEVICE_OUT_WIRED_HEADSET, DEVICE_STATE_UNAVAILABLE, ...) to trick Audio Manager to believe that there is no headset.

These are all hacks and require the app to monitor the phone state closely. It also doesn't work all the time.

Another drawback is that using earpiece disable on-chip audio decompression and thus has higher battery use.

In general, I wouldn't recommend using these techniques.




回答2:


After researching a lot, I found it out that there is not any way to achieve this funtionality without using reflection. First you need to put headset jack in and then call the method setWiredDeviceConnectionState() with suitable parameters then it behave like the headphone are disconnected but click works still. So it is a hack but as per my requirement, it's not a foolproof solution but working for now. Here is my code to do this,

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();
        }
    }

the intent to send :

@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);
        }
    }
}

for Lollipop and lower versions :

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;
            }
        }

Let me know if I miss something. Thanks.



来源:https://stackoverflow.com/questions/38461743/how-to-route-default-audio-to-ear-piece-when-headphones-are-connected

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!