Android Audio Policy 核心数据结构剖析:从 HwModule 到 AudioProfile 的关系链与运行时流程

0 阅读23分钟

1. 摘要

Android Audio Policy 子系统通过 HwModuleIOProfileAudioRouteDeviceDescriptorAudioProfile 五类核心数据结构,完成音频能力的描述、设备的路由匹配与运行时的动态管理。

本文以 USB 音频设备为线索,首先梳理 XML 配置文件到 C++ 对象的映射关系与类职责分工,然后深入两条关键运行时流程:

  1. 设备插入时的能力发现:从 HAL 层动态创建 DeviceDescriptor,到向 IOProfile 按需添加 AudioProfile 的完整过程;
  2. 录音请求的路由匹配:从应用音频属性出发,经过 IOProfile 兼容性判定,最终选定可用输入流的匹配算法。

最后,基于前述机制分析一个真实问题——USB 摄像头与 USB 耳麦共存时的录音失败场景,给出根因与配置级解决方案。

2. 配置文件与类的映射

先来看个配置文件usb_audio_policy_configuration.xml:

<module name="usb" halVersion="2.0" >
    <mixPorts>
        <mixPort name="usb_accessory output" role="source">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                     samplingRates="44100" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
        </mixPort>
        <mixPort name="usb_headset input" role="sink"/>
    </mixPorts>
    <devicePorts>
        <devicePort tagName="USB Host Out" type="AUDIO_DEVICE_OUT_USB_ACCESSORY" role="sink">
            <profile name="" format="AUDIO_FORMAT_PCM_16_BIT"
                     samplingRates="44100" channelMasks="AUDIO_CHANNEL_OUT_STEREO"/>
        </devicePort>
        <devicePort tagName="USB Headset In" type="AUDIO_DEVICE_IN_USB_HEADSET" role="source"/>
    </devicePorts>
    <routes>
        <route type="mix" sink="usb_headset input"
            sources="USB Headset In"/>
    </routes>
</module>

配置文件经过解析后,XML 元素C++ 类的对应关系如下:

XML 元素                           →  C++ 类
─────────────────────────────────────────────
<module>                          →  HwModule
  <mixPort>                       →  IOProfile (role=source → 播放, role=sink → 录音)
    <profile>                     →  IOProfile 内的 AudioProfile 列表
      format / samplingRates /    →  AudioProfile 的字段
      channelMasks

  <devicePort>                    →  DeviceDescriptor (也是 IOProfile 的子类)
    <profile>                     →  DeviceDescriptor 内的 AudioProfile 列表

  <route>                       →  AudioRoute (连接 mixPort 和 devicePort)

3. 类关系概览

AudioHALAudioPolicyConfigurationFile.png

在 Android Audio Policy 子系统中,这几个类的职责分工如下:

HwModule

硬件音频模块(如 primarya2dpusb)的抽象,持有该模块下所有输入/输出 IOProfile。对应 XML 中的 <module>标签。

IOProfile

继承自 AudioPortPolicyAudioPort,代表一条输入或输出流的能力描述(采样率、格式、声道数、flag 等),对应 XML 中的 <mixPort>标签。

HwModule::mInputProfilesHwModule::mOutputProfiles 都是 IOProfile 的集合,IOProfile也作为 HwModule::mPorts 中的元素被统一管理。

IOProfile.mSupportedDevices 的设备来源

IOProfile.mSupportedDevices包含DeviceDescriptor对象的指针:

静态来源(解析XML里的route时): 在HwModule::refreshSupportedDevices()中:

  • routesources对应的DeviceDescriptor添加到 HwModule.mInputProfiles.IOProfile.mSupportedDevices
  • routesink对应的DeviceDescriptor添加到 HwModule.mOutputProfiles.IOProfile.mSupportedDevices

动态来源(设备插入时): 在HwModuleCollection::createDevice()AudioPolicyManagerCustomWhaleImpl::getUsbDeviceDescriptor()中:

  • 创建一个新的DeviceDescriptor对象
  • 调用attach()把它绑定到usb HwModule
  • IOProfile::addSupportedDevice()把它添加到mSupportedDevices

DeviceDescriptor

继承自 DeviceDescriptorBase PolicyAudioPort,代表物理音频设备(如 USB Headset In、Speaker、Built-In Mic)的抽象。

  • 静态设备:定义来源于 XML 中的<devicePort>标签,在系统启动时解析配置文件即创建,作为"能力声明"始终存在。并作为 HwModule::mPorts 和 HwModule::mDeclaredDevices 中的元素被统一管理;
  • 动态设备:真正的物理设备插入时,会在 AudioPolicyManager::setDeviceConnectionState()流程中基于静态模板创建带具体地址的动态设备实例,通过attach()绑定到所属的HwModule,并被加入到 HwModule::mDynamicDevices中统一管理。也被IOProfile::mSupportedDevices引用,表示该IOProfile支持此设备。

AudioRoute

定义了 IOProfile(mixPort)与 DeviceDescriptor(devicePort)之间的连接关系,对应 XML 中的 <route>标签。

方向规则:连接方向与音频数据流向相反,遵循"sink 消费源,source 产生数据"的原则。

场景数据源数据目的AudioRoute 表现
输出 (Playback)IOProfile (role=source)DeviceDescriptor (role=sink)mSources → IOProfile, mSink → DeviceDescriptor
输入 (Recording)DeviceDescriptor (role=source)IOProfile (role=sink)mSources → DeviceDescriptor, mSink → IOProfile

其连接方向与音频数据流向相反,遵循“sink 消费源,source 产生数据”的原则:

输出音频场景(Playback):数据从应用输出至物理设备。此时 IOProfile(mixPort,role="source")作为数据源,DeviceDescriptor(devicePort,role="sink")作为数据目的。在 AudioRoute 中表现为:mSources 指向 IOProfile,mSink 指向 DeviceDescriptor。

<!-- 输出示例 -->
<route type="mix" sink="USB Device Out" sources="usb_device output"/>
<!-- 输入示例 -->
<route type="mix" sink="usb_device input" sources="USB Device In"/>

每个 HwModule::mRoutes 持有该模块下所有路由规则。在 refreshSupportedDevices() 中,系统遍历每条 AudioRoute,根据方向将 mSources 或 mSink 中的设备添加到对应 IOProfile::mSupportedDevices 中,从而建立IO能力与物理设备的绑定关系。

AudioPort(基类)

是所有端口(IOProfile 与 DeviceDescriptor)的基类,内部持有 mProfiles(类型 AudioProfileVector),描述该端口支持的音频能力。

AudioProfileVector

是 std::vector<sp<AudioProfile>> 的封装,提供增删查和能力协商方法。

AudioProfile

是一条具体的能力记录:一种编码格式 + 一组采样率 + 一组声道掩码。对应 XML 中的 <profile> 标签。当mixPortdevicePort标签下没有声明profile时,在解析XML过程中,会自动生成一个全 dynamic 用于占位的 AudioProfile,三个维度(格式/声道/采样率)都标记为dynamic,表示不约束任何值。并将这个全 dynamic 的占位AudioProfile添加到IOProfileDeviceDescriptor

4. 音频设备插入时的能力动态发现

现在以USB 耳机 USB Headset In设备接入为例,探索DeviceDescriptorAudioProfile 的动态创建流程 。

4.1 DeviceDescriptor 的动态创建与绑定

UsbHostManager#usbDeviceAdded()
    ↓
UsbAlsaManager#usbDeviceAdded()
    ↓
UsbAlsaManager#selectAlsaDevice()
    ↓
UsbAlsaDevice#start()
    ↓
UsbAlsaDevice#startInput()
    ↓
UsbAlsaDevice#startDevice()
    ↓
UsbAlsaDevice#updateWiredDeviceConnectionState()
    ↓
AudioService#setWiredDeviceConnectionState()
    ↓
AudioDeviceBroker#setWiredDeviceConnectionState()
    ↓
AudioDeviceInventory#setWiredDeviceConnectionState()
    ↓
AudioDeviceBroker#postSetWiredDeviceConnectionState()
    ↓
sendLMsg(MSG_L_SET_WIRED_DEVICE_CONNECTION_STATE)
    ↓
AudioDeviceInventory#onSetWiredDeviceConnectionState
    ↓
AudioDeviceInventory:handleDeviceConnection()
    ↓
AudioSystem:setDeviceConnectionState()
    ↓
android_media_AudioSystem_setDeviceConnectionState()
    ↓
AudioSystem:setDeviceConnectionState()
    ↓
AudioPolicyService::setDeviceConnectionState()
    ↓
AudioPolicyManager::setDeviceConnectionState()
    ↓
AudioPolicyManagerCustomImplwhale::setDeviceConnectionStateInt()
    ↓
AudioPolicyManagerCustomWhaleImpl::getUsbDeviceDescriptor()

AudioPolicyManagerCustomWhaleImpl::getUsbDeviceDescriptor()的源码如下:

sp<DeviceDescriptor> AudioPolicyManagerCustomWhaleImpl::getUsbDeviceDescriptor(const audio_devices_t deviceType,
                                                             const char *address,
                                                             const char *name,
                                                             const audio_format_t encodedFormat,
                                                             bool allowToCreate,
                                                             bool matchAddress)
{
    //步骤1: 判断要用哪个HwModule
    String8 devAddress = (address == nullptr || !matchAddress) ? String8("") : String8(address);

    bool usbDeviceInPrimary = allowToCreate ? isUsbDeviceSupportOffload(deviceType, address, name)
                                :(mUsbOffloadDevices.valueFor(toString(deviceType) + std::string(address)) != 0);

    sp<HwModule> hwUsbModule = usbDeviceInPrimary ? mApm->mHwModules.getModuleFromName(AUDIO_HARDWARE_MODULE_ID_PRIMARY)
                                                            : mApm->mHwModules.getModuleFromName(AUDIO_HARDWARE_MODULE_ID_USB);

    ......
    //步骤2: 创建DeviceDescriptor并和HwModule绑定
    sp<DeviceDescriptor> device = new DeviceDescriptor(deviceType, name, address);
    device->setName(name);
    device->setEncodedFormat(encodedFormat);
    device->setDynamic();
    // Add the device to the list of dynamic devices
    hwUsbModule->addDynamicDevice(device);
    // Reciprocally attach the device to the module
    device->attach(hwUsbModule);

    ALOGD("%s: adding dynamic device %s to module %s", __FUNCTION__,
          device->toString().c_str(), hwUsbModule->getName());

    //步骤3: 将DeviceDescriptor加入到适配的IOProfile里
    const auto &profiles = (audio_is_output_device(deviceType) ? hwUsbModule->getOutputProfiles() :
                                                             hwUsbModule->getInputProfiles());
    for (const auto &profile : profiles) {
        // Add the device as supported to all profile supporting "weakly" or not the device
        // according to its type
        if (profile->supportsDevice(device, false /*matchAddress*/)) {

            // @todo quid of audio profile? import the profile from device of the same type?
            const auto &isoTypeDeviceForProfile =
                profile->getSupportedDevices().getDevice(deviceType, String8(), AUDIO_FORMAT_DEFAULT);
            device->importAudioPortAndPickAudioProfile(isoTypeDeviceForProfile, true /* force */);

            ALOGV("%s: adding device %s to profile %s", __FUNCTION__,
                  device->toString().c_str(), profile->getTagName().c_str());
            profile->addSupportedDevice(device);
        }
    }
    return device;
}

AudioPolicyManagerCustomWhaleImpl::getUsbDeviceDescriptor()做了这3件事:

  • 判断是要将新的DeviceDescriptor attach到哪个HwModule : 展锐平台的USB offload模式下,USB音频数据由 primary HALDSP直接处理,不经过 USB HAL,所以必须归 primary HwModule。而普通USB 录音走USB HAL,归usb HwModule。这里不支持 USB offload模式所以走USB HAL,归usb HwModule

  • 创建代表USB Headset InDeviceDescriptor,加入到usb HwModule里的mDynamicDevices, 并attachusb HwModule

  • 遍历usb HwModule里的IOProfile, 从IOProfile.mSupportedDevices中查找是否有跟USB Headset In一样type="AUDIO_DEVICE_IN_USB_HEADSET"DeviceDescriptor,有的话表示该IOProfile支持"AUDIO_DEVICE_IN_USB_HEADSET"类型的设备,USB Headset InDeviceDescriptor可以加入到IOProfile.mSupportedDevices

因此,在XML里配置一个静态的type="AUDIO_DEVICE_IN_USB_HEADSET"devicePort和对应的route就可以让系统支持"AUDIO_DEVICE_IN_USB_HEADSET"类型的设备。如:

<mixPorts>
    <mixPort name="usb_headset input" role="sink"/>
</mixPorts>
<devicePorts>
    <devicePort tagName="USB Headset In" type="AUDIO_DEVICE_IN_USB_HEADSET" role="source"/>
</devicePorts>
<routes>
    <route type="mix" sink="usb_headset input"
        sources="USB Headset In"/>
</routes>

此时执行dumpsys media.audio_policy命令可以看到:

 Hardware modules (5):
  4. Handle: 34; "usb"
   - Input MixPorts (2):
      2. "usb_headset input"; 0x0000 (AUDIO_INPUT_FLAG_NONE)
       - Profiles (3):
          1. ""; [dynamic format][dynamic channels][dynamic rates]; AUDIO_FORMAT_DEFAULT (0x0)
             AUDIO_ENCAPSULATION_TYPE_NONE
          2. ""; [dynamic format][dynamic channels][dynamic rates]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             AUDIO_ENCAPSULATION_TYPE_NONE
          3. ""; [dynamic format]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             sampling rates: 48000
             channel masks: 0x000c, 0x80000003
             AUDIO_ENCAPSULATION_TYPE_NONE
       - Supported devices (2):
        1. Port ID: 1; "USB-Audio - USB Audio Device"; {AUDIO_DEVICE_IN_USB_HEADSET, @:card=1;device=0;}
           Encapsulation modes: 0, metadata types: 0
"USB-Audio - USB Audio Device"
        2. "USB Headset In"; {AUDIO_DEVICE_IN_USB_HEADSET, @:}
           Encapsulation modes: 0, metadata types: 0
       - maxOpenCount: 1; curOpenCount: 0
       - maxActiveCount: 1; curActiveCount: 0
       - recommendedMuteDurationMs: 0 ms

其中:

1. Port ID: 1; "USB-Audio - USB Audio Device"
   address: @:card=1;device=0;        ← 运行时动态创建

2. "USB Headset In"
   address: @:                          ← XML 静态定义

静态设备 — 来自 XML 配置:

<devicePort tagName="USB Headset In" type="AUDIO_DEVICE_IN_USB_HEADSET" role="source"/>
<route type="mix" sink="usb_headset input" sources="USB Headset In"/>

解析时通过 <route>加入 mSupportedDevices。地址为空 @:,因为还没有真实设备。

动态设备USB 设备插入时,Audio HAL 扫描到 card=2, device=0,动态创建一个同类型但带具体地址的 DeviceDescriptor,并关联到该IOProfile

静态 "USB Headset In"动态 "USB-Audio - USB Audio Device"
存在时机始终存在仅设备插入时
address@:(空)@:card=2;device=0;(具体)
用途占位模板,保证 profile 始终有支持设备实际录音时使用的真实设备
Port ID无(未 attach)2(已 attach 到 HAL)

静态的那个是"占位符",确保在没插 USB 设备时 IOProfile 结构完整;动态的那个才是真正打开录音流时用的设备。选设备时优先选有Port ID的(已连接的),静态的仅作 fallback

4.2 AudioProfile 的动态发现与添加

AudioPolicyManagerCustomImplwhale::setDeviceConnectionStateInt()里创建完DeviceDescriptor后,代码来到AudioPolicyManager::updateAudioProfiles():

AudioPolicyManager::setDeviceConnectionState()
    ↓
AudioPolicyManagerCustomImplwhale::setDeviceConnectionStateInt()
    ↓
AudioPolicyManager::setDeviceConnectionStateInt()
    ↓
AudioPolicyManager::checkInputsForDevice()
    ↓
AudioPolicyManager::updateAudioProfiles()

frameworks/av/services/audiopolicy/managerdefault/AudioPolicyManager.cpp

void AudioPolicyManager::updateAudioProfiles(const sp<DeviceDescriptor>& devDesc,
                                             audio_io_handle_t ioHandle,
                                             AudioProfileVector &profiles)
{
    String8 reply;
    audio_devices_t device = devDesc->type();

    // Format MUST be checked first to update the list of AudioProfile
    if (profiles.hasDynamicFormat()) {
        reply = mpClientInterface->getParameters(
                ioHandle, String8(AudioParameter::keyStreamSupportedFormats));
        ALOGV("%s: supported formats %d, %s", __FUNCTION__, ioHandle, reply.string());
        AudioParameter repliedParameters(reply);
        if (repliedParameters.get(
                String8(AudioParameter::keyStreamSupportedFormats), reply) != NO_ERROR) {
            ALOGE("%s: failed to retrieve format, bailing out", __FUNCTION__);
            return;
        }
        FormatVector formats = formatsFromString(reply.string());
        mReportedFormatsMap[devDesc] = formats;
        if (device == AUDIO_DEVICE_OUT_HDMI
                || isDeviceOfModule(devDesc, AUDIO_HARDWARE_MODULE_ID_MSD)) {
            modifySurroundFormats(devDesc, &formats);
        }
        addProfilesForFormats(profiles, formats);
    }

    for (audio_format_t format : profiles.getSupportedFormats()) {
        ChannelMaskSet channelMasks;
        SampleRateSet samplingRates;
        AudioParameter requestedParameters;
        requestedParameters.addInt(String8(AudioParameter::keyFormat), format);

        if (profiles.hasDynamicRateFor(format)) {
            reply = mpClientInterface->getParameters(
                    ioHandle,
                    requestedParameters.toString() + ";" +
                    AudioParameter::keyStreamSupportedSamplingRates);
            ALOGV("%s: supported sampling rates %s", __FUNCTION__, reply.string());
            AudioParameter repliedParameters(reply);
            if (repliedParameters.get(
                    String8(AudioParameter::keyStreamSupportedSamplingRates), reply) == NO_ERROR) {
                samplingRates = samplingRatesFromString(reply.string());
            }
        }
        if (profiles.hasDynamicChannelsFor(format)) {
            reply = mpClientInterface->getParameters(ioHandle,
                                                     requestedParameters.toString() + ";" +
                                                     AudioParameter::keyStreamSupportedChannels);
            ALOGV("%s: supported channel masks %s", __FUNCTION__, reply.string());
            AudioParameter repliedParameters(reply);
            if (repliedParameters.get(
                    String8(AudioParameter::keyStreamSupportedChannels), reply) == NO_ERROR) {
                channelMasks = channelMasksFromString(reply.string());
                if (device == AUDIO_DEVICE_OUT_HDMI
                        || isDeviceOfModule(devDesc, AUDIO_HARDWARE_MODULE_ID_MSD)) {
                    modifySurroundChannelMasks(&channelMasks);
                }
            }
        }
        addDynamicAudioProfileAndSort(
                profiles, new AudioProfile(format, channelMasks, samplingRates));
    }
}

相关log:

APM_AudioPolicyManager: checkInputsForDevice(): adding profile 0 from module usb
modules.usbaudio.audio_hal: in_set_parameters() keys:card=1;device=0
modules.usbaudio.audio_hal: device_get_parameters = sup_formats=AUDIO_FORMAT_PCM_16_BIT
modules.usbaudio.audio_hal: device_get_parameters = sup_sampling_rates=48000
modules.usbaudio.audio_hal: device_get_parameters = sup_channels=AUDIO_CHANNEL_IN_STEREO|AUDIO_CHANNEL_INDEX_MASK_2
APM_AudioPolicyManager: checkInputsForDevice(): adding input 54

在接入USB Headset In设备之前, 名为"usb_headset input"MixPort只有一个AudioProfile:

$ dumpsys media.audio_policy
      2. "usb_headset input"; 0x0000 (AUDIO_INPUT_FLAG_NONE)
       - Profiles (1):
          1. ""; [dynamic format][dynamic channels][dynamic rates]; AUDIO_FORMAT_DEFAULT (0x0)
             AUDIO_ENCAPSULATION_TYPE_NONE

接入USB Headset In设备之后, 名为"usb_headset input"MixPort就会多出2个AudioProfile:

$ dumpsys media.audio_policy
      2. "usb_headset input"; 0x0000 (AUDIO_INPUT_FLAG_NONE)
       - Profiles (3):
          1. ""; [dynamic format][dynamic channels][dynamic rates]; AUDIO_FORMAT_DEFAULT (0x0)
             AUDIO_ENCAPSULATION_TYPE_NONE
          2. ""; [dynamic format][dynamic channels][dynamic rates]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             AUDIO_ENCAPSULATION_TYPE_NONE
          3. ""; [dynamic format]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             sampling rates: 48000
             channel masks: 0x000c, 0x80000003
             AUDIO_ENCAPSULATION_TYPE_NONE

结合AudioPolicyManager::updateAudioProfiles()的逻辑和以上信息,后面的2个AudioProfile的添加流程如下:

初始状态 只有一个profile

Profile 1: format=DEFAULT, dynamic format, dynamic channels, dynamic rates

Step 1: 查格式

if (profiles.hasDynamicFormat()) {  // Profile 1 是 dynamic format → true
    reply = mpClientInterface->getParameters(ioHandle, "streamSupportedFormats");
    // HAL 返回: "AUDIO_FORMAT_PCM_16_BIT"
    FormatVector formats = {PCM_16_BIT};
    addProfilesForFormats(profiles, formats);  // ← 关键
}

addProfilesForFormats() 为每个格式创建一个新profile(dynamic channels + dynamic rates),加入列表:

Profile 1: DEFAULT, dynamic format, dynamic channels, dynamic rates  (原始)
Profile 2: PCM_16_BIT, dynamic channels, dynamic rates              (新增) ← 这就是 dump 里的 Profile 2

Step 2: 遍历已支持的格式,查采样率和声道

for (audio_format_t format : profiles.getSupportedFormats()) {
    // format = PCM_16_BIT (唯一非 dynamic 的格式)

查采样率:

if (profiles.hasDynamicRateFor(PCM_16_BIT)) {  // Profile 2 是 dynamic rate → true
    reply = mpClientInterface->getParameters(ioHandle, "format=PCM_16_BIT;streamSupportedSamplingRates");
    // HAL 返回: "48000"
    samplingRates = {48000};
}

查声道:

if (profiles.hasDynamicChannelsFor(PCM_16_BIT)) {  // Profile 2 是 dynamic channels → true
    reply = mpClientInterface->getParameters(ioHandle, "format=PCM_16_BIT;streamSupportedChannels");
    // HAL 返回: "0x000c,0x80000003"
    channelMasks = {0x000c, 0x80000003};
}

创建精确 profile

addDynamicAudioProfileAndSort(profiles, 
    new AudioProfile(PCM_16_BIT, {0x000c, 0x80000003}, {48000}));

这创建了 Profile 3

最终结果

Profile 1: DEFAULT, dynamic format, dynamic channels, dynamic rates   (原始兜底)
Profile 2: PCM_16_BIT, dynamic channels, dynamic rates               (格式已确定,其余待查)
Profile 3: PCM_16_BIT, 48000Hz, {0x000c, 0x80000003}                (完全确定)

流程图

初始: [Profile 1: 全 dynamic]

  ↓ addProfilesForFormats(PCM_16_BIT)
  
中间: [Profile 1: 全 dynamic]
      [Profile 2: PCM_16_BIT, dynamic rate, dynamic channels]

  ↓ addDynamicAudioProfileAndSort(PCM_16_BIT, 48000, {0x000c, 0x80000003})

最终: [Profile 1: 全 dynamic]          ← 兜底
      [Profile 2: PCM_16_BIT, dynamic] ← 格式固定,其余通配
      [Profile 3: PCM_16_BIT, 48000, {0x000c, 0x80000003}]  ← 精确值

三个 profile 形成从宽到窄的梯度:全通配 → 格式固定 → 完全确定,兼容匹配时从前往后找,精确匹配时从后往前找。

5. App 录音请求时的路由匹配路径

相关log:

I APM_AudioPolicyManager: getInputForAttr() source 7, sampling rate 48000, format 0x1, channel mask 0x10, session 177, flags 0 attributes={ Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_UNKNOWN Source: AUDIO_SOURCE_VOICE_COMMUNICATION Flags: 0x2800 Tags:  } requested device ID 0
V APM::Devices: DeviceVector::refreshTypes() mDeviceTypes 0x1
V APM::Devices: DeviceVector::refreshTypes() mDeviceTypes 0x1, 0x2
V APM::Devices: DeviceVector::refreshTypes() mDeviceTypes 0x1, 0x2, 0x10000
V APM::Devices: DeviceVector::refreshTypes() mDeviceTypes 0x1, 0x2, 0x10000, 0x4000000
V APM::Devices: DeviceVector::refreshTypes() mDeviceTypes 0x80000004
V APM::Devices: DeviceVector::refreshTypes() mDeviceTypes 0x80000004, 0x80000040
V APM::Devices: DeviceVector::refreshTypes() mDeviceTypes 0x80000004, 0x80000040, 0x80000080
V APM::Devices: DeviceVector::refreshTypes() mDeviceTypes 0x80000004, 0x80000040, 0x80000080, 0x80002000
D APM::AudioPolicyEngine/InputSource: get: 0x82000000 for inputSource AUDIO_SOURCE_VOICE_COMMUNICATION
D APM::AudioPolicyEngine: getInputDeviceForAttributes deviceType = 0x82000000, address = 
D APM::Devices: DeviceVector::getDevice() for type 82000000 address "" found 0xb4000077d3d59de0 format 00000000
D APM_AudioPolicyManager: getInputForAttr found device type is 0x82000000
D APM_AudioPolicyManager: getInputForDevice attributes.source = 7
D APM_AudioPolicyManager: getInputProfile, flags = 00000020, device = AUDIO_DEVICE_IN_USB_HEADSET, @:card=1;device=0;
D APM_AudioPolicyManager: getInputProfile: hwModule: primary channelMask = 00000010
I AudioPolicyManagerCustomImplwhale: skip <primary input> profile or output whose module is different from <AUDIO_DEVICE_IN_USB_HEADSET, @:card=1;device=0;>

App发出录音请求时,代码会走AudioPolicyManager里的:

getInputForAttr(source, flags)
  │
  ├─ Engine::getInputDeviceForAttributes(source)
  │    → 查 PFW 配置,返回设备 (如 USB_HEADSET)
  │
  └─ getInputForDevice(device, ..., attributes, flags)
       │
       └─ getInputProfile(device, samplingRate, format, channelMask, flags)
            → 遍历模块找 isCompatibleProfile 的 IOProfile
            → 返回 profile
       │
       → 打开输入流,返回 input handle
函数作用
getInputForAttr()根据音频属性(source, flags)选输入流,返回 input handle
Engine::getInputDeviceForAttributes()策略决策。根据 source 查 parameter-framework,决定用哪个输入设备
getInputForDevice()设备→流。给定设备,找能路由到该设备的 IOProfile 并打开输入流
getInputProfile()设备→profile。给定设备+参数,遍历所有模块找兼容的 IOProfile

至于Engine::getInputDeviceForAttributes()怎么使用 parameter-framework选择输入设备的,后续再研究。

bool IOProfile::isCompatibleProfile(const DeviceVector &devices,
                                    uint32_t samplingRate,
                                    uint32_t *updatedSamplingRate,
                                    audio_format_t format,
                                    audio_format_t *updatedFormat,
                                    audio_channel_mask_t channelMask,
                                    audio_channel_mask_t *updatedChannelMask,
                                    // FIXME type punning here
                                    uint32_t flags,
                                    bool exactMatchRequiredForInputFlags) const
{
    const bool isPlaybackThread =
            getType() == AUDIO_PORT_TYPE_MIX && getRole() == AUDIO_PORT_ROLE_SOURCE;
    const bool isRecordThread =
            getType() == AUDIO_PORT_TYPE_MIX && getRole() == AUDIO_PORT_ROLE_SINK;
    ALOG_ASSERT(isPlaybackThread != isRecordThread);

    if (!devices.isEmpty()) {
        if (!mSupportedDevices.containsAllDevices(devices)) {
            return false;
        }
    }

    if (!audio_is_valid_format(format) ||
            (isPlaybackThread && (samplingRate == 0 || !audio_is_output_channel(channelMask))) ||
            (isRecordThread && (!audio_is_input_channel(channelMask)))) {
         return false;
    }

    audio_format_t myUpdatedFormat = format;
    audio_channel_mask_t myUpdatedChannelMask = channelMask;
    uint32_t myUpdatedSamplingRate = samplingRate;
    const struct audio_port_config config = {
        .config_mask = AUDIO_PORT_CONFIG_ALL & ~AUDIO_PORT_CONFIG_GAIN,
        .sample_rate = samplingRate,
        .channel_mask = channelMask,
        .format = format,
    };
    if (isRecordThread)
    {
        if ((flags & AUDIO_INPUT_FLAG_MMAP_NOIRQ) != 0) {
            if (checkExactAudioProfile(&config) != NO_ERROR) {
                return false;
            }
        } else if (checkCompatibleAudioProfile(
                myUpdatedSamplingRate, myUpdatedChannelMask, myUpdatedFormat) != NO_ERROR) {
            return false;
        }
    } else {
        if (checkExactAudioProfile(&config) != NO_ERROR) {
            return false;
        }
    }

    const uint32_t mustMatchOutputFlags =
            AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_HW_AV_SYNC|AUDIO_OUTPUT_FLAG_MMAP_NOIRQ;
    if (isPlaybackThread && (((getFlags() ^ flags) & mustMatchOutputFlags)
                    || (getFlags() & flags) != flags)) {
        return false;
    }
    // The only input flag that is allowed to be different is the fast flag.
    // An existing fast stream is compatible with a normal track request.
    // An existing normal stream is compatible with a fast track request,
    // but the fast request will be denied by AudioFlinger and converted to normal track.
    if (isRecordThread && ((getFlags() ^ flags) &
            ~(exactMatchRequiredForInputFlags ? AUDIO_INPUT_FLAG_NONE : AUDIO_INPUT_FLAG_FAST))) {
        return false;
    }

    if (updatedSamplingRate != NULL) {
        *updatedSamplingRate = myUpdatedSamplingRate;
    }
    if (updatedFormat != NULL) {
        *updatedFormat = myUpdatedFormat;
    }
    if (updatedChannelMask != NULL) {
        *updatedChannelMask = myUpdatedChannelMask;
    }
    return true;
}

frameworks/av/services/audiopolicy/common/managerdefinitions/include/PolicyAudioPort.h

status_t checkCompatibleAudioProfile(uint32_t &samplingRate,
									 audio_channel_mask_t &channelMask,
									 audio_format_t &format) const
{
	return checkCompatibleProfile(
			asAudioPort()->getAudioProfiles(), samplingRate, channelMask, format,
			asAudioPort()->getType(), asAudioPort()->getRole());
}

IOProfile转成AudioPort,然后取其mProfiles,也就是IOProfile所管理的AudioProfile列表进入checkCompatibleProfile()做匹配。

frameworks/av/services/audiopolicy/common/managerdefinitions/src/AudioProfileVectorHelper.cpp

status_t checkCompatibleProfile(const AudioProfileVector &audioProfileVector,
                                uint32_t &samplingRate,
                                audio_channel_mask_t &channelMask,
                                audio_format_t &format,
                                audio_port_type_t portType,
                                audio_port_role_t portRole)
{
    if (audioProfileVector.empty()) {
        return NO_ERROR;
    }

    const bool checkInexact = // when port is input and format is linear pcm
            portType == AUDIO_PORT_TYPE_MIX && portRole == AUDIO_PORT_ROLE_SINK
            && audio_is_linear_pcm(format);

    // iterate from best format to worst format (reverse order)
    for (ssize_t i = audioProfileVector.size() - 1; i >= 0 ; --i) {
        const sp<AudioProfile> profile = audioProfileVector.at(i);
        audio_format_t formatToCompare = profile->getFormat();
        if (formatToCompare == format ||
                (checkInexact
                        && formatToCompare != AUDIO_FORMAT_DEFAULT
                        && audio_is_linear_pcm(formatToCompare))) {
            // Compatible profile has been found, checks if this profile has compatible
            // rate and channels as well
            audio_channel_mask_t updatedChannels;
            uint32_t updatedRate;
            if (checkCompatibleChannelMask(profile, channelMask, updatedChannels,
                                           portType, portRole) == NO_ERROR &&
                    checkCompatibleSamplingRate(profile, samplingRate, updatedRate) == NO_ERROR) {
                // for inexact checks we take the first linear pcm format due to sorting.
                format = formatToCompare;
                channelMask = updatedChannels;
                samplingRate = updatedRate;
                return NO_ERROR;
            }
        }
    }
    return BAD_VALUE;
}

status_t checkCompatibleChannelMask(const sp<AudioProfile> &audioProfile,
                                    audio_channel_mask_t channelMask,
                                    audio_channel_mask_t &updatedChannelMask,
                                    audio_port_type_t portType,
                                    audio_port_role_t portRole)
{
    const ChannelMaskSet channelMasks = audioProfile->getChannels();
    if (channelMasks.empty()) {
        updatedChannelMask = channelMask;
        return NO_ERROR;
    }
    const bool isRecordThread = portType == AUDIO_PORT_TYPE_MIX && portRole == AUDIO_PORT_ROLE_SINK;
    const bool isIndex = audio_channel_mask_get_representation(channelMask)
            == AUDIO_CHANNEL_REPRESENTATION_INDEX;
    const uint32_t channelCount = audio_channel_count_from_in_mask(channelMask);
    int bestMatch = 0;
    for (const auto &supported : channelMasks) {
        if (supported == channelMask) {
            // Exact matches always taken.
            updatedChannelMask = channelMask;
            return NO_ERROR;
        }

        // AUDIO_CHANNEL_NONE (value: 0) is used for dynamic channel support
        if (isRecordThread && supported != AUDIO_CHANNEL_NONE) {
            // Approximate (best) match:
            // The match score measures how well the supported channel mask matches the
            // desired mask, where increasing-is-better.
            //
            // TODO: Some tweaks may be needed.
            // Should be a static function of the data processing library.
            //
            // In priority:
            // match score = 1000 if legacy channel conversion equivalent (always prefer this)
            // OR
            // match score += 100 if the channel mask representations match
            // match score += number of channels matched.
            // match score += 100 if the channel mask representations DO NOT match
            //   but the profile has positional channel mask and less than 2 channels.
            //   This is for audio HAL convention to not list index masks for less than 2 channels
            //
            // If there are no matched channels, the mask may still be accepted
            // but the playback or record will be silent.
            const bool isSupportedIndex = (audio_channel_mask_get_representation(supported)
                    == AUDIO_CHANNEL_REPRESENTATION_INDEX);
            const uint32_t supportedChannelCount = audio_channel_count_from_in_mask(supported);
            int match;
            if (isIndex && isSupportedIndex) {
                // index equivalence
                match = 100 + __builtin_popcount(
                        audio_channel_mask_get_bits(channelMask)
                            & audio_channel_mask_get_bits(supported));
            } else if (isIndex && !isSupportedIndex) {
                const uint32_t equivalentBits = (1 << supportedChannelCount) - 1 ;
                match = __builtin_popcount(
                        audio_channel_mask_get_bits(channelMask) & equivalentBits);
                if (supportedChannelCount <= FCC_2) {
                    match += 100;
                }
            } else if (!isIndex && isSupportedIndex) {
                const uint32_t equivalentBits = (1 << channelCount) - 1;
                match = __builtin_popcount(
                        equivalentBits & audio_channel_mask_get_bits(supported));
            } else {
                // positional equivalence
                match = 100 + __builtin_popcount(
                        audio_channel_mask_get_bits(channelMask)
                            & audio_channel_mask_get_bits(supported));
                switch (supported) {
                case AUDIO_CHANNEL_IN_FRONT_BACK:
                case AUDIO_CHANNEL_IN_STEREO:
                    if (channelMask == AUDIO_CHANNEL_IN_MONO) {
                        match = 1000;
                    }
                    break;
                case AUDIO_CHANNEL_IN_MONO:
                    if (channelMask == AUDIO_CHANNEL_IN_FRONT_BACK
                            || channelMask == AUDIO_CHANNEL_IN_STEREO) {
                        match = 1000;
                    }
                    break;
                default:
                    break;
                }
            }
            if (match > bestMatch) {
                bestMatch = match;
                updatedChannelMask = supported;
            }
        }
    }
    return bestMatch > 0 ? NO_ERROR : BAD_VALUE;
}

status_t checkCompatibleSamplingRate(const sp<AudioProfile> &audioProfile,
                                     uint32_t samplingRate,
                                     uint32_t &updatedSamplingRate)
{
    ALOG_ASSERT(samplingRate > 0);

    const SampleRateSet sampleRates = audioProfile->getSampleRates();
    if (sampleRates.empty()) {
        updatedSamplingRate = samplingRate;
        return NO_ERROR;
    }

    // Search for the closest supported sampling rate that is above (preferred)
    // or below (acceptable) the desired sampling rate, within a permitted ratio.
    // The sampling rates are sorted in ascending order.
    auto desiredRate = sampleRates.lower_bound(samplingRate);

    // Prefer to down-sample from a higher sampling rate, as we get the desired frequency spectrum.
    if (desiredRate != sampleRates.end()) {
        if (*desiredRate / AUDIO_RESAMPLER_DOWN_RATIO_MAX <= samplingRate) {
            updatedSamplingRate = *desiredRate;
            return NO_ERROR;
        }
    }
    // But if we have to up-sample from a lower sampling rate, that's OK.
    if (desiredRate != sampleRates.begin()) {
        uint32_t candidate = *(--desiredRate);
        if (candidate * AUDIO_RESAMPLER_UP_RATIO_MAX >= samplingRate) {
            updatedSamplingRate = candidate;
            return NO_ERROR;
        }
    }
    // leave updatedSamplingRate unmodified
    return BAD_VALUE;
}
IOProfile::isCompatibleProfile()
PolicyAudioPort:
	checkCompatibleProfile()
AudioProfileVectorHelper:
	checkCompatibleProfile()
	checkCompatibleChannelMask()
	checkCompatibleSamplingRate()

以如下录音请求参数:

APM_AudioPolicyManager: getInputForAttr() source 7, sampling rate 48000, format 0x1, channel mask 0x10, session 177, flags 0 attributes={ Content type: AUDIO_CONTENT_TYPE_UNKNOWN Usage: AUDIO_USAGE_UNKNOWN Source: AUDIO_SOURCE_VOICE_COMMUNICATION Flags: 0x2800 Tags:  } requested device ID 0

为例,IOProfile::isCompatibleProfile()匹配流程如下:

Step 1. 确定方向

getType() = MIX, getRole() = SINK
isPlaybackThread = false
isRecordThread = true

Step 2. 设备兼容

请求设备: AUDIO_DEVICE_IN_USB_HEADSET
mSupportedDevices: {USB_HEADSET(card=1;dev=0), USB_HEADSET(空地址)}
→ containsAllDevices ✓

Step 3. 参数合法性

format = PCM_16_BIT (0x1) → audio_is_valid_format ✓
isRecordThread → audio_is_input_channel(0x10) ✓ (FRONT 是合法输入声道)

Step 4. AudioProfile 匹配(核心)

flags = 0, 不是 MMAP_NOIRQ
→ 走 checkCompatibleAudioProfile

checkCompatibleProfile()里倒序遍历 3 个AudioProfile

i=2, Profile 3 (PCM_16_BIT, {48000}, {0x000c, 0x80000003})::

format: 请求 PCM_16_BIT, profile 是 PCM_16_BIT → ✓ 精确匹配

checkCompatibleChannelMask(0x10):
  0x000c (AUDIO_CHANNEL_IN_STEREO, positional/AUDIO_CHANNEL_REPRESENTATION_POSITION):
    都是 positional → 情况4
    交集 popcount = 0, match = 100
    特殊分支: STEREO + 请求是 MONO → match = 1000 ★
  0x80000003 (index 2ch):
    情况3, match = 1
  bestMatch = 1000, updatedChannelMask = 0x000c ✓

checkCompatibleSamplingRate(48000):
  {48000} 包含 48000 → 精确匹配 ✓

→ Profile 3 匹配成功! 不再继续遍历

匹配到此就结束了,不会继续看 Profile 2 和 1

Step 5. 标志位匹配

getFlags() = 0x0000 (NONE)
请求 flags = 0 (NONE)

(getFlags() ^ flags) = 0
0 & ~(AUDIO_INPUT_FLAG_FAST) = 0 → 无差异 ✓

Step 6. 回填结果

updatedSamplingRate = 48000  (保持原值,dynamic 不改)
updatedFormat = PCM_16_BIT   (保持原值)
updatedChannelMask = 0x000c  (从 0x10 MONO 升级为 STEREO)

匹配流程总结

检查项结果
方向record ✓
设备USB_HEADSET 在支持列表 ✓
参数合法性format/channelMask 有效 ✓
音频匹配Profile 3 (最精确) ✓
标志位NONE vs NONE ✓

全部通过,返回 true。匹配到 Profile 3,声道从 MONO(0x10) 升级为 STEREO(0x000c),因为 STEREO↔MONO 是经典声道转换(得分 1000)。

6. USB 摄像头与 USB 耳麦共存问题分析与解决方案

6.1 问题描述

Android音频系统中,当同时连接USB摄像头(仅含麦克风)和USB耳麦(含麦克风和扬声器)时,音频功能正常。但拔掉USB摄像头后,USB耳麦的麦克风无法使用,系统无法找到合适的输入IOProfile,导致录音失败。

关键错误日志:

W APM_AudioPolicyManager: getInputForDevice could not find profile for device AUDIO_DEVICE_IN_USB_HEADSET, @:card=1;device=0;, sampling rate 48000, format 0x1, channel mask 0x10, flags 0x20
E AudioFlinger: createRecord() getInputForAttr return error -38

6.2 根本原因分析

原始音频策略配置

在原有的usb_audio_policy_configuration.xml中,USB 模块的配置如下:

<mixPorts>
	<mixPort name="usb_device output" role="source"/>
	<mixPort name="usb_device input" role="sink"/>
</mixPorts>

<devicePorts>
	<devicePort tagName="USB Device In" type="AUDIO_DEVICE_IN_USB_DEVICE" role="source"/>
	
	<devicePort tagName="USB Headset In" type="AUDIO_DEVICE_IN_USB_HEADSET" role="source"/>
	
</devicePorts>

<routes>
	<route type="mix" sink="USB Headset Out" sources="usb_device output"/>
	<route type="mix" sink="usb_device input" sources="USB Device In,USB Headset In"/>
</routes>

USB 摄像头(AUDIO_DEVICE_IN_USB_DEVICE)和USB 耳麦(AUDIO_DEVICE_IN_USB_HEADSET)共享同一个mixPort -"usb_device input"

dumpsys media.audio_policy 命令的结果对比

  • 同时接了 USB 摄像头和 USB 耳麦的:
  4. Handle: 34; "usb"
   - Input MixPorts (1):
      1. "usb_device input"; 0x0000 (AUDIO_INPUT_FLAG_NONE)
       - Profiles (7):
          1. ""; [dynamic format][dynamic channels][dynamic rates]; AUDIO_FORMAT_DEFAULT (0x0)
             AUDIO_ENCAPSULATION_TYPE_NONE
          2. ""; [dynamic format][dynamic channels][dynamic rates]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             AUDIO_ENCAPSULATION_TYPE_NONE
          3. ""; [dynamic format]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             sampling rates: 48000
             channel masks: 0x000c, 0x80000003
             AUDIO_ENCAPSULATION_TYPE_NONE
          4. ""; [dynamic format][dynamic channels][dynamic rates]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             AUDIO_ENCAPSULATION_TYPE_NONE
          5. ""; [dynamic format]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             sampling rates: 16000
             channel masks: 0x000c, 0x0010, 0x80000001
             AUDIO_ENCAPSULATION_TYPE_NONE
          6. ""; [dynamic format]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             sampling rates: 16000
             channel masks: 0x000c, 0x0010, 0x80000001
             AUDIO_ENCAPSULATION_TYPE_NONE
          7. ""; [dynamic format]; AUDIO_FORMAT_PCM_16_BIT (0x1)
             sampling rates: 16000
             channel masks: 0x000c, 0x0010, 0x80000001
             AUDIO_ENCAPSULATION_TYPE_NONE
       - Supported devices (4):
        1. Port ID: 8; "USB-Audio - 1080P USB Camera"; {AUDIO_DEVICE_IN_USB_DEVICE, @:card=2;device=0;}
           Encapsulation modes: 0, metadata types: 0
"USB-Audio - 1080P USB Camera"
        2. "USB Device In"; {AUDIO_DEVICE_IN_USB_DEVICE, @:}
           Encapsulation modes: 0, metadata types: 0
        3. Port ID: 6; "USB-Audio - USB Audio Device"; {AUDIO_DEVICE_IN_USB_HEADSET, @:card=1;device=0;}
           Encapsulation modes: 0, metadata types: 0
"USB-Audio - USB Audio Device"
        4. "USB Headset In"; {AUDIO_DEVICE_IN_USB_HEADSET, @:}
           Encapsulation modes: 0, metadata types: 0
       - maxOpenCount: 1; curOpenCount: 0
       - maxActiveCount: 1; curActiveCount: 0
       - recommendedMuteDurationMs: 0 ms
  • 同时接了 USB 摄像头和 USB 耳麦再拔掉 USB 摄像头后的:
  4. Handle: 34; "usb"
   - Input MixPorts (1):
      1. "usb_device input"; 0x0000 (AUDIO_INPUT_FLAG_NONE)
       - Profiles (1):
          1. ""; [dynamic format][dynamic channels][dynamic rates]; AUDIO_FORMAT_DEFAULT (0x0)
             AUDIO_ENCAPSULATION_TYPE_NONE
       - Supported devices (3):
        1. "USB Device In"; {AUDIO_DEVICE_IN_USB_DEVICE, @:}
           Encapsulation modes: 0, metadata types: 0
        2. Port ID: 6; "USB-Audio - USB Audio Device"; {AUDIO_DEVICE_IN_USB_HEADSET, @:card=1;device=0;}
           Encapsulation modes: 0, metadata types: 0
"USB-Audio - USB Audio Device"
        3. "USB Headset In"; {AUDIO_DEVICE_IN_USB_HEADSET, @:}
           Encapsulation modes: 0, metadata types: 0
       - maxOpenCount: 1; curOpenCount: 0
       - maxActiveCount: 1; curActiveCount: 0
       - recommendedMuteDurationMs: 0 ms

通过dumpsys media.audio_policy命令的结果对比发现:

  • 同时连接摄像头和耳麦时usb_device inputProfile列表有7个AudioProfile,支持两种设备
  • 拔掉摄像头后Profile列表只剩下1个全 dynamic 的占位AudioProfile

checkCompatibleProfile 中的格式匹配条件:

if (formatToCompare == format ||                          // 精确匹配
    (checkInexact                                          // true
     && formatToCompare != AUDIO_FORMAT_DEFAULT            // ← 这里排除了 DEFAULT!
     && audio_is_linear_pcm(formatToCompare)))             // 线性 PCM

当唯一的AudioProfileAUDIO_FORMAT_DEFAULT 时:

formatToCompare = AUDIO_FORMAT_DEFAULT

条件1: DEFAULT == PCM_16_BIT → false
条件2: checkInexact=true && DEFAULT != DEFAULT → false  ← 短路!
       整个条件为 false

→ 跳过该 profile,循环结束
→ return BAD_VALUE  匹配失败!

DEFAULT 格式被显式排除在非精确匹配之外。这是设计意图——DEFAULT 是占位符,表示"格式待定",不应作为兼容匹配的候选。

因为AudioProfile匹配失败,所以"usb_device input"IOProfile无法被使用,导致录音失败。

为什么会只剩一个占位 AudioProfile

拔掉 USB 摄像头后,系统会触发AudioPolicyManager::checkInputsForDevice() -> IOProfile::clearAudioProfiles()清理动态创建的AudioProfile,只留一个占位AudioProfile

6.3 解决方案

核心思路

将 USB 耳麦(USB Headset)和 USB 摄像头(USB Device)的音频路径分离,使它们使用独立的mixPort,避免Profile互相影响。

具体修改

usb_audio_policy_configuration.xml进行以下修改:

Step 1. 添加专用mixPort

<!-- 新增专用于USB耳麦的mixPort -->
<mixPort name="usb_headset output" role="source"/>
<mixPort name="usb_headset input" role="sink"/>

Step 2. 分离路由配置

<!-- 修改前:USB耳麦和USB摄像头共享同一个输出mixPort和输入mixPort -->
<route type="mix" sink="USB Headset Out"
       sources="usb_device output"/>
<route type="mix" sink="usb_device input"
       sources="USB Device In,USB Headset In"/>

<!-- 修改后:USB耳麦使用专用mixPort -->
<route type="mix" sink="USB Headset Out"
       sources="usb_headset output"/>
<!-- 摄像头走原来的 mixPort -->
<route type="mix" sink="usb_device input"
    sources="USB Device In"/>
<!-- 耳麦走新的 mixPort,profile 独立管理 -->
<route type="mix" sink="usb_headset input"
    sources="USB Headset In"/>

技术原理

Profile 独立性 通过为 USB 耳麦创建独立的usb_headset input mixPort,USB 耳麦的Profile 管理完全独立于USB摄像头:

  • USB 摄像头的 Profile 清理不会影响 USB 耳麦
  • 两种设备可以同时维护各自的动态 Profile 集合
  • 设备插拔事件只影响对应的 mixPort

路由分离 修改后的路由配置确保:

  • USB Headset Inusb_headset input
  • USB Device Inusb_device input

这样即使 USB 摄像头被移除,触发usb_device input的 Profile 清理,usb_headset input中的 Profile 仍然保持不变,USB 耳麦可以继续正常工作。

6.4 小结

问题本质

Android 音频策略的动态 Profile 管理机制在处理共享 mixPort 的多设备场景时存在缺陷。当不同设备共享同一个 mixPort 时,一个设备的 Profile 清理操作会影响其他设备。

解决方案价值

  • 架构清晰:为不同类型的 USB 音频设备提供独立的音频路径
  • 稳定性提升:避免设备间的相互干扰
  • 易于维护:配置清晰,便于后续扩展
  • 低风险:通过配置文件修改,无需修改核心代码

1E980148-7A86-4BB7-B504-1C79887E85A5.jpeg