手机投屏处理音频流转问题
背景
公司项目中有手机投屏平板,声音默认是在手机端播放,也可以切换到平板上播放,这里使用的是反射android.media.AudioSystem
类的setDeviceConnectionState
方法实现的,这个在低于Android13的版本是没有问题的,然而到了Android13手机上确出现了问题,经过对比Android12和Android13发现,android.media.AudioSystem
类的setDeviceConnectionState
方法实现有差别
问题分析
Android12的源码
先看一下Android12的android.media.AudioSystem
类的setDeviceConnectionState
方法
- device:音频输出设备,因为是录制的是系统声音,所以是
AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
- state:流转状态,0:手机端,1:非手机端
- device_address:设备地址,可以传空
- device_name:设备名称,可以传空
- codecFormat:编码格式,这里直接传0
反射Android12的代码如下:
public static void setDeviceConnectionStateTablet() {
LogUtils.i(TAG, "set Connection State to Tablet");
try {
Class<?> audioSystem = Class.forName("android.media.AudioSystem");
Method setDeviceConnectionState = audioSystem.getDeclaredMethod("setDeviceConnectionState",
int.class, int.class, String.class, String.class, int.class);
setDeviceConnectionState.setAccessible(true);
setDeviceConnectionState.invoke(audioSystem, 0x8000, 1, "", "", 0);
} catch (ClassNotFoundException
| NoSuchMethodException
| InvocationTargetException
| IllegalAccessException e) {
LogUtils.e(TAG, "SET CONNECTION STATE Error1: " + e.getCause());
}
}
public static void setDeviceConnectionStatePhone() {
LogUtils.i(TAG, "set Connection State to Phone" );
try {
Class<?> audioSystem = Class.forName("android.media.AudioSystem");
Method setDeviceConnectionState = audioSystem.getDeclaredMethod("setDeviceConnectionState",
int.class, int.class, String.class, String.class, int.class);
setDeviceConnectionState.setAccessible(true);
setDeviceConnectionState.invoke(audioSystem, 0x8000, 0, "", "", 0);
} catch (ClassNotFoundException
| NoSuchMethodException
| InvocationTargetException
| IllegalAccessException e) {
LogUtils.e(TAG, "SET CONNECTION STATE Error3: " + e.getCause());
}
}
Android13的源码
在看一下Android13的android.media.AudioSystem
类的setDeviceConnectionState
方法
这里参数跟Android12的是有所改变的,因此原来的反射代码会有问题。
这里的setDeviceConnectionState
方法有两个方法重载,可以看到这两个重载方法里都没有音频输出设备的参数,因此我们可以先看看AudioDeviceAttributes
是啥?我们继续看AudioDeviceAttributes
的源码可以发现其构造函数有5个,其中有两个的参数就有音频输出设备和设备地址,那么我们完全可以使用这两个构造函数进行反射创建它的实例。
解决问题
知道原因了,就可以做一下适配:
public static void setDeviceConnectionStateTablet() {
LogUtils.i(TAG, "set Connection State to Tablet");
try {
Class<?> audioSystem = Class.forName("android.media.AudioSystem");
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
Class<?> audioDeviceAttributesClass = Class.forName("android.media.AudioDeviceAttributes");
Constructor<?> constructor = audioDeviceAttributesClass.getDeclaredConstructor(int.class, String.class);
constructor.setAccessible(true);
Object audioDeviceAttributes = constructor.newInstance(0x8000,"");
Method setDeviceConnectionState = audioSystem.getDeclaredMethod("setDeviceConnectionState",
audioDeviceAttributesClass, int.class, int.class);
setDeviceConnectionState.setAccessible(true);
setDeviceConnectionState.invoke(audioSystem, audioDeviceAttributes, 1, 0);
}else {
Method setDeviceConnectionState = audioSystem.getDeclaredMethod("setDeviceConnectionState",
int.class, int.class, String.class, String.class, int.class);
setDeviceConnectionState.setAccessible(true);
setDeviceConnectionState.invoke(audioSystem, 0x8000, 1, "", "", 0);
}
} catch (ClassNotFoundException
| NoSuchMethodException
| InvocationTargetException
| InstantiationException
| IllegalAccessException e) {
LogUtils.e(TAG, "SET CONNECTION STATE Error1: " + e.getCause());
}
}
public static void setDeviceConnectionStatePhone() {
LogUtils.i(TAG, "set Connection State to Phone" );
try {
Class<?> audioSystem = Class.forName("android.media.AudioSystem");
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.S) {
Class<?> audioDeviceAttributesClass = Class.forName("android.media.AudioDeviceAttributes");
Constructor<?> constructor = audioDeviceAttributesClass.getDeclaredConstructor(int.class, String.class);
constructor.setAccessible(true);
Object audioDeviceAttributes = constructor.newInstance(0x8000,"");
Method setDeviceConnectionState = audioSystem.getDeclaredMethod("setDeviceConnectionState",
audioDeviceAttributesClass, int.class, int.class);
setDeviceConnectionState.setAccessible(true);
setDeviceConnectionState.invoke(audioSystem, audioDeviceAttributes, 0, 0);
}else {
Method setDeviceConnectionState = audioSystem.getDeclaredMethod("setDeviceConnectionState",
int.class, int.class, String.class, String.class, int.class);
setDeviceConnectionState.setAccessible(true);
setDeviceConnectionState.invoke(audioSystem, 0x8000, 0, "", "", 0);
}
} catch (ClassNotFoundException
| NoSuchMethodException
| InvocationTargetException
| InstantiationException
| IllegalAccessException e) {
LogUtils.e(TAG, "SET CONNECTION STATE Error1: " + e.getCause());
}
}