连接耳机后如何将默认音频路由到耳机?
How to route default audio to ear piece when headphones are connected?
我正在开发一个只需要将耳机插孔用作按钮的应用程序。
要求:连接耳机时通过耳机播放默认音频(通话)(不需要通过耳机播放音频)
有很多通过扬声器和耳机以及蓝牙耳机路由音频的示例,但没有关于在连接耳机的情况下通过设备的耳机扬声器路由音频的示例。
我已经尝试了很多,有些链接是
(在我的场景中不起作用)
我查看了 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 中永远不会用于媒体,并且只有在 phone 处于 "call" 或 "communication" (VoIP) 状态时才能使用。
我想您已经注意到没有 "FORCE_EARPIECE" 常量,因此无法在对 setForceUse
.
的调用中指定它
此外,耳机在通话输出设备选择中的优先级最低,因此如果 phone 有 anything 连接到它(在你的情况下有假耳机),该设备将被选中(参见 https://android.googlesource.com/platform/frameworks/av/+/322b4d2/services/audiopolicy/enginedefault/src/Engine.cpp#381)。
抱歉,似乎无法达到您的目的。
更新
在检查 SoundAbout 强制使用媒体耳机时的 media.audio_policy
状态后,我发现了此应用使用的以下技巧:
它调用AudioSystem.setPhoneState(MODE_IN_COMMUNICATION)
来执行"communication"phone状态(通常用于VoIP呼叫)。
如果连接了耳机(或头phones),为了防止声音因优先级较高而被路由到它,应用程序调用AudioSystem.setDeviceConnectionState(DEVICE_OUT_WIRED_HEADSET, DEVICE_STATE_UNAVAILABLE, ...)
欺骗音频管理器相信没有耳机。
这些都是技巧,需要应用密切监控 phone 状态。它也不是一直有效。
另一个缺点是使用耳机会禁用片上音频解压缩,因此会消耗更多电量。
一般来说,我不推荐使用这些技巧。
经过大量研究,我发现不使用反射是无法实现这种功能的。
首先,您需要插入耳机插孔,然后使用合适的参数调用方法 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;
}
}
如果我遗漏了什么,请告诉我。
谢谢
我正在开发一个只需要将耳机插孔用作按钮的应用程序。
要求:连接耳机时通过耳机播放默认音频(通话)(不需要通过耳机播放音频)
有很多通过扬声器和耳机以及蓝牙耳机路由音频的示例,但没有关于在连接耳机的情况下通过设备的耳机扬声器路由音频的示例。 我已经尝试了很多,有些链接是
我查看了 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 中永远不会用于媒体,并且只有在 phone 处于 "call" 或 "communication" (VoIP) 状态时才能使用。
我想您已经注意到没有 "FORCE_EARPIECE" 常量,因此无法在对 setForceUse
.
此外,耳机在通话输出设备选择中的优先级最低,因此如果 phone 有 anything 连接到它(在你的情况下有假耳机),该设备将被选中(参见 https://android.googlesource.com/platform/frameworks/av/+/322b4d2/services/audiopolicy/enginedefault/src/Engine.cpp#381)。
抱歉,似乎无法达到您的目的。
更新
在检查 SoundAbout 强制使用媒体耳机时的 media.audio_policy
状态后,我发现了此应用使用的以下技巧:
它调用
AudioSystem.setPhoneState(MODE_IN_COMMUNICATION)
来执行"communication"phone状态(通常用于VoIP呼叫)。如果连接了耳机(或头phones),为了防止声音因优先级较高而被路由到它,应用程序调用
AudioSystem.setDeviceConnectionState(DEVICE_OUT_WIRED_HEADSET, DEVICE_STATE_UNAVAILABLE, ...)
欺骗音频管理器相信没有耳机。
这些都是技巧,需要应用密切监控 phone 状态。它也不是一直有效。
另一个缺点是使用耳机会禁用片上音频解压缩,因此会消耗更多电量。
一般来说,我不推荐使用这些技巧。
经过大量研究,我发现不使用反射是无法实现这种功能的。 首先,您需要插入耳机插孔,然后使用合适的参数调用方法 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;
}
}
如果我遗漏了什么,请告诉我。 谢谢