Android平台USB外接MIDI控制器音频端口检测及多输出疑问
我正在开发一款流媒体音乐应用,通过USB连接了一台带有2个3.5mm音频接口(一个音箱接口、一个耳机接口)的MIDI控制器,已完成连接与通信。Android自动将音箱接口设为默认音频端口,当音箱或耳机插入该默认接口时,应用运行正常可播放音乐,但插入第二个耳机接口时无声音输出。
在三星Galaxy Tab S8设备上,使用AudioManager.getDevices()检测到连接控制器后存在3个设备:
- Type 2(
AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) - Type 11(
AudioDeviceInfo.TYPE_USB_DEVICE) - Type 18(
AudioDeviceInfo.TYPE_TELEPHONY)
连接后选中的设备为Type 11,音频输出至Android自动选择的主端口。
已在清单文件中添加权限:
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
尝试用以下代码检测第二个耳机接口插入事件,但未捕获到相关信息:
AudioManager audioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); AudioDeviceInfo[] audioDevices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); for(AudioDeviceInfo deviceInfo : audioDevices){ if(deviceInfo.getType()==AudioDeviceInfo.TYPE_WIRED_HEADPHONES){ Log.d(TAG, "AudioDeviceInfo.TYPE_WIRED_HEADPHONES"); } else if(deviceInfo.getType()==AudioDeviceInfo.TYPE_WIRED_HEADSET) { Log.d(TAG, "AudioDeviceInfo.TYPE_WIRED_HEADSET"); } else { Log.d(TAG, "something else - " + deviceInfo.getType()); } }
Logcat日志显示:I/AudioPathManager: loadAudioPath() : loaded audio path is usb (6),推测Android自动将第一个音频端口设为默认。
疑问
- 如何检测耳机插入第二个接口的事件?
- 是否有可用的辅助库?
- 是否可获取USB外接设备的所有音频端口并实现同时输出音频?
1. 检测第二个耳机接口插入事件
你当前的代码只检测了独立的有线耳机设备类型,但第二个接口属于USB设备的子端口,不会被识别为TYPE_WIRED_HEADPHONES或TYPE_WIRED_HEADSET。要检测这类子端口的状态变化,推荐以下方案:
- 使用
AudioDeviceCallback监听设备变化(Android 7.0+):这是最可靠的方式,通过注册回调监听音频设备的连接/断开事件。当USB设备的子端口状态变化时,会触发onAudioDevicesAdded()或onAudioDevicesRemoved(),你可以在回调中解析AudioDeviceInfo的详细参数,比如getPortNames()(Android 10+)或getPortInfo()来区分不同的子端口。AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); AudioDeviceCallback callback = new AudioDeviceCallback() { @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { for (AudioDeviceInfo device : addedDevices) { if (device.getType() == AudioDeviceInfo.TYPE_USB_DEVICE) { // 解析USB设备的端口信息,判断第二个接口是否插入 Log.d(TAG, "USB设备端口更新: " + Arrays.toString(device.getPortNames())); } } } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] removedDevices) { // 处理端口移除逻辑 } }; audioManager.registerAudioDeviceCallback(callback, null); - 轮询USB设备端口属性:定时调用
getDevices(),对比前后AudioDeviceInfo的getChannels()、getProductName等参数变化,判断第二个接口是否插入。
2. 可用的辅助库
Android官方没有专门针对多USB音频端口的辅助库,但可以借助以下工具实现更精准的控制:
- Android USB Host API:直接与USB设备通信,读取设备的UAC(USB Audio Class)协议描述符,手动识别和控制各个音频端口,需要你熟悉USB音频协议细节。
- 底层音频框架:使用
AAudio(Android 8.0+)或OpenSL ES,这些API提供了更细粒度的音频设备控制能力,能直接指定输出到USB设备的特定端口。
3. 获取USB外接设备的所有音频端口并实现同时输出
可以实现,但依赖Android版本和硬件支持:
获取所有端口
在Android 10及以上,通过AudioDeviceInfo.getPortInfo()可以获取USB设备的所有音频端口。每个AudioPortInfo对象包含端口方向、通道数、类型等信息,可用于区分两个3.5mm接口。
同时输出音频
需要为每个端口创建独立的音频播放轨道,并指定对应输出端口:
- 枚举USB设备的所有输出端口,通过
AudioPortInfo记录每个端口的标识。 - 为每个端口创建
AudioTrack实例,在构建时通过setAudioDevice()指定输出端口。 - 同时启动多个
AudioTrack播放音频数据。
注意:部分设备或USB音频硬件可能不支持多端口同时输出,可通过AudioManager.isDeviceSupported()提前检查兼容性。
示例代码片段:
// 找到连接的USB音频设备 AudioDeviceInfo usbDevice = null; AudioDeviceInfo[] devices = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); for (AudioDeviceInfo device : devices) { if (device.getType() == AudioDeviceInfo.TYPE_USB_DEVICE) { usbDevice = device; break; } } if (usbDevice != null) { AudioPortInfo[] portInfos = usbDevice.getPortInfo(); for (AudioPortInfo portInfo : portInfos) { if (portInfo.getDirection() == AudioPortInfo.DIRECTION_OUTPUT) { // 为当前端口创建AudioTrack AudioAttributes attributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(); AudioFormat format = new AudioFormat.Builder() .setSampleRate(44100) .setEncoding(AudioFormat.ENCODING_PCM_16BIT) .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) .build(); AudioTrack track = new AudioTrack.Builder() .setAudioAttributes(attributes) .setAudioFormat(format) .setBufferSizeInBytes(AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT)) .setAudioDevice(portInfo) // 指定输出到当前端口 .build(); // 加载音频数据并启动播放 // track.write(audioData, 0, audioData.length); // track.play(); } } }
内容的提问来源于stack exchange,提问作者Vlad Petreaca

