在这里插入图片描述

HarmonyOS 7 音频切换没生效?应用级输出与单条 AudioRenderer 的优先级

给鸿蒙电脑应用接上外置音箱后,调用“切换输出设备”没有报错,音乐却仍从耳机响。这时先别改播放器 UI:同一个应用可能同时有应用级输出选择和某条 AudioRenderer 的专属输出选择。后者只覆盖那一条播放流,不能靠再次设置应用级设备把它冲掉。

本文只讨论 HarmonyOS 7 / API 26 的 PC、二合一设备,Stage 模型。这里的“增强路由”不是所有 7.0 设备都能用:调用前必须检查 isEnhancedRoutingSupported();不同机型即使同属 PC,也可能因硬件而返回不同结果。官方接口文档更新于 2026-09-04。

先把优先级说清楚

当前流的设置应用级设置这条流使用的选择
指定了专属输出 B选择了 AB;其他没有专属设置的流仍走 A
没有专属输出选择了 AA
没有专属输出没有应用级选择系统默认输出
设备不支持增强路由任何选择系统默认输出;不要把调用返回当作切换成功

这里的“优先级”来自接口的作用范围,并不是承诺任意时刻都有一个可持久保存的目标设备。应用退出、所选设备离线后,选择会失效;恢复连接后需要重新查设备并重新设置。

应用级与单流输出路由示意图

图中蓝线是应用级选择;橙线只改变一条播放流。下半部分表示耳机离线后,先重新获取设备列表,再决定是否重新选路;不能继续用旧的设备对象。

案例一:选了音箱,只有提示音还在耳机里

准备一台支持增强路由的 API 26 鸿蒙电脑,同时接音箱 A 和耳机 B。应用里有音乐流和提示音流,先对应用选择 A,再对提示音对应的 AudioRenderer 单独选择 B。音乐从 A、提示音从 B 才符合接口的作用范围。随后再次调用应用级选择 A,提示音仍可能保持 B;这不是“应用级调用没执行”,而是单流选择在起作用。

最容易犯的错是把 AudioRenderer 当成应用的唯一音频出口。排查时要记录 renderer 实例、选择层级、目标设备、调用结果 四件事。仅记录“用户点了音箱”不够,因为这条日志并不能说明哪一条流最终被指定。

下面的 ArkTS 示例只展示选路边界。musicRenderer 和 noticeRenderer 应由现有播放流程创建并保持有效;创建、启动和写入 PCM 的过程要按各自播放器代码完成,不能用一个已释放的 renderer 调用选路。

import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';

const audioManager = audio.getAudioManager();
const enhance = audioManager.getDeviceEnhanceManager();
const routing = audioManager.getRoutingManager();

function outputs(): audio.AudioDeviceDescriptors {
  return routing.getAvailableDevices(audio.DeviceUsage.MEDIA_OUTPUT_DEVICES);
}

async function selectForApp(device: audio.AudioDeviceDescriptor): Promise<boolean> {
  if (!enhance.isEnhancedRoutingSupported()) {
    console.info('[route] unsupported; keep system default');
    return false;
  }
  try {
    await enhance.selectOutputDevice(device);
    console.info('[route] application selection accepted');
    return true;
  } catch (error) {
    const err = error as BusinessError;
    console.error(`[route] app selection failed code=${err.code}`);
    return false;
  }
}

async function selectForStream(renderer: audio.AudioRenderer,
                               device: audio.AudioDeviceDescriptor): Promise<boolean> {
  if (!enhance.isEnhancedRoutingSupported()) return false;
  try {
    await enhance.selectOutputDeviceForAudioRenderer(renderer, device);
    console.info('[route] one renderer selection accepted');
    return true;
  } catch (error) {
    const err = error as BusinessError;
    console.error(`[route] renderer selection failed code=${err.code}`);
    return false;
  }
}

// 由播放模块传入仍有效的提示音 renderer。
async function routeTwoStreams(noticeRenderer: audio.AudioRenderer): Promise<void> {
  const available = outputs();
  if (available.length < 2) return;
  await selectForApp(available[0]);
  await selectForStream(noticeRenderer, available[1]);
}

available[0]、available[1] 只是演示“不同设备”的占位选择,不代表第一个一定是音箱、第二个一定是耳机。实际 UI 应显示当前可用列表,让用户确认目标;只有一个设备时,不要强行演示“双路输出”。不要打印设备的可识别信息到公开日志。

案例二:拔掉耳机后还用旧设备对象重试

先让提示音流走耳机 B,播放期间拔掉 B。应用退出或设备离线后,官方明确说明选路设置失效。如果界面仍保留旧的 AudioDeviceDescriptor,下一次调用可能报 6800101,典型原因是目标设备已经不存在;6800301 则是音频服务错误,不能一律解释为设备离线。

正确的恢复路径是:收到设备变化事件后重新查询可用输出设备,按用户意图检查目标是否仍存在;不存在时先展示当前实际可选项,或允许系统默认路径继续播放。不要在离线事件回调里对同一个旧对象无限重试。

// 把设备变化视为“旧选择待核实”,而不是“立刻重放旧 descriptor”。
routing.on('availableDeviceChange', audio.DeviceUsage.MEDIA_OUTPUT_DEVICES,
  () => {
    const current = outputs();
    console.info(`[route] available output count=${current.length}`);
    // 此处刷新设备选择 UI;用户确认后再用 current 中的新对象选路。
    // 若当前列表为空,保留系统默认路径并提示稍后重试。
  });

// 页面或服务结束时,使用同一个回调引用调用 off,避免重复订阅。

注意这段回调没有偷偷“恢复原设备”。设备回来不等于用户仍想选它;具体产品可以保存“用户偏好”,但每次恢复都必须从新列表里找到当前可用的设备对象,再决定是否重新应用。

怎么验证,怎么判断确实修好了

  1. 在 API 26 且 isEnhancedRoutingSupported() 为 true 的 PC/二合一设备上准备两种输出设备、两条有效播放流;记录每次应用级和单流级选路调用及错误码。
  2. 先只设置应用级 A,确认两条流都在 A;然后只给提示音流指定 B,确认音乐仍在 A、提示音在 B。
  3. 再次设置应用级 A,确认提示音不会被误判为“应该跟着切回 A”。
  4. 拔掉 B,核对设备变化通知和新的可用设备列表;旧 B 不应被直接重试。重连后重新选择,并再次试听。
  5. 在 isEnhancedRoutingSupported() 为 false 的设备上,确认界面不宣称“已切换成功”,仍以系统默认输出为准。

这五步里,调用 Promise 成功不是“人耳听到目标设备”的充分证据。最终验收仍要听两条真实播放流,并确认设备离线、重连、应用重启三个时刻的行为。

我目前能够核对官方接口作用范围和把选路决策写成可检查的代码路径;手头没有 API 26 SDK 和支持增强路由的鸿蒙电脑,因此 没有完成 API 26 编译和真实设备试听。上述步骤是待在目标设备执行的验收方案,不是已经拿到的实测结果。

什么时候用哪种方案

  • 整个应用都要走一个设备:应用级 selectOutputDevice,逻辑简单。
  • 仅一条播放流要单独输出:selectOutputDeviceForAudioRenderer,避免影响其他流。
  • 不支持增强路由或设备离线:不伪造成功状态,保留系统默认输出并刷新可用列表。

这类问题以后最值得先看的是“到底哪一层选了设备”,其次才是“设备有没有在线”。把两层选择和设备变化写进日志,比在 UI 上反复点切换按钮更快定位原因。

参考:

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐