背景
在手机投屏处理音频流转问题 中介绍了反射android.media.AudioSystem
类的setDeviceConnectionState
方法来达到音频流转方案,此方案是基于系统权限的,也就是说具有系统签名并且拥有android:sharedUserId="android.uid.system"
,如果没有这个权限咋整?
因公司需要把之前的投屏项目脱离系统权限并准备上Google Play,所以不能在拥有系统权限了,因此之前投屏项目的好的功能需要能适配就适配不能的只能去掉,所以投屏分成了两个app:一个app是没有任何系统权限的,另外一个app则是拥有签名权限的,没有系统权限的app将来是要上Google Play的,拥有签名权限的内置在自己手机里,因此把上面实现流转的功能放在了拥有签名权限app里,两个app之间通过AIDL进程间通信。
替代的API
当我把相关音频流转API放入到拥有签名权限app里时去执行时,虽然反射执行方法没啥问题,但结果就是没有效果,此时细心的可以发现日志有一句打印:
经过分析源码可以发现android.media.AudioSystem
类的setDeviceConnectionState
方法成功反射执行了,但是在调用C++层就出现了问题:
看来没有system uid
权限还是不行的,于是我就在系统源码里各种寻找其他方案。
最终在我不懈努力终于被我找到了AudioManager
类的setWiredDeviceConnectionState
哇,感觉发现了新大陆,这个api比android.media.AudioSystem
类的setDeviceConnectionState
方法好用多了,不用费那么大的劲进行反射,而且Android13及以下的api参数基本上是一致的,直接调用就行了。但是这里我们通过源码的api方法上面可以发现:此方法是需要android.Manifest.permission.MODIFY_AUDIO_ROUTING
权限的,而且这个权限只能是系统签名的app能使用,所以还是得把这个api得调用放入到拥有签名权限的app中。
public class AudioUtils {
private static final String TAG = AudioUtils.class.getSimpleName();
public static void setWiredDeviceConnectionState(Context context,int state){
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setWiredDeviceConnectionState(AudioManager.DEVICE_OUT_REMOTE_SUBMIX,state,"","");
}
}
音频卡顿问题
当实现了后音频可以正常进程流转,但是又一个新问题,就是流转后得音频播放出来是卡顿的,一开始我以为是我的Sink端有问题,直到我在Source端把录音保存本地后,发现是音频录取有问题。
于是又是排查一番发现是调用setWiredDeviceConnectionState
方法传入的AudioManager.DEVICE_OUT_REMOTE_SUBMIX
参数问题,最终发现可以使用AudioManager.DEVICE_OUT_WIRED_HEADPHONE
来替代,表示有线耳机输出