手机投屏处理音频流转问题
背景
公司项目中有手机投屏平板,声音默认是在手机端播放,也可以切换到平板上播放,这里使用的是反射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()); } }
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!