上一篇内容分析了音频配置文件中节点对应的具体的类。包括mixPort,devicePort,route。

2. mixPort与devicePort关系

  • 创建 AudioRoute 对象连接源(source)和汇(sink)

  • 处理路由类型:

    • mix:软件混音输出

    • device:物理设备直连

mixPort是stream,比如"primary_output", devicePort是具体的硬件设备,比如"speaker",通过route节点连接起来,我们可以理解为音频数据流最终输出到哪个设备(输出/播放),或从设备输入到哪种数据流(输入/录音)。

从代码的角度看,一个mixPort对应了一个IOProfile,在IOProfile里面有一个mSupportedDevices成员,它是一个DeviceDescriptor集合类型,意思也就是IOProfile支持的设备集合,这些设备集合可以把音频数据传递给IOProfile或IOProfile可以把数据传给device;那IOProfile是如何找到对应的DeviceDescriptor的?

void HwModule::refreshSupportedDevices()
{
    // Now updating the streams (aka IOProfile until now) supported devices
    for (const auto& stream : mInputProfiles) {
        DeviceVector sourceDevices;
        for (const auto& route : stream->getRoutes()) {
            sp<PolicyAudioPort> sink = route->getSink();
            if (sink == 0 || stream != sink) {
                ALOGE("%s: Invalid route attached to input stream", __FUNCTION__);
                continue;
            }
            DeviceVector sourceDevicesForRoute = getRouteSourceDevices(route);
            if (sourceDevicesForRoute.isEmpty()) {
                ALOGE("%s: invalid source devices for %s", __FUNCTION__, stream->getName().c_str());
                continue;
            }
            sourceDevices.add(sourceDevicesForRoute);
        }
        if (sourceDevices.isEmpty()) {
            ALOGE("%s: invalid source devices for %s", __FUNCTION__, stream->getName().c_str());
            continue;
        }
        stream->setSupportedDevices(sourceDevices);
    }
    for (const auto& stream : mOutputProfiles) {
        DeviceVector sinkDevices;
        for (const auto& route : stream->getRoutes()) {
            sp<PolicyAudioPort> source = findByTagName(route->getSources(), stream->getTagName());
            if (source == 0 || stream != source) {
                ALOGE("%s: Invalid route attached to output stream", __FUNCTION__);
                continue;
            }
            sp<DeviceDescriptor> sinkDevice = getRouteSinkDevice(route);
            if (sinkDevice == 0) {
                ALOGE("%s: invalid sink device for %s", __FUNCTION__, stream->getName().c_str());
                continue;
            }
            sinkDevices.add(sinkDevice);
        }
        stream->setSupportedDevices(sinkDevices);
    }
}
sp<DeviceDescriptor> HwModule::getRouteSinkDevice(const sp<AudioRoute> &route) const
{
    sp<DeviceDescriptor> sinkDevice = 0;
    if (route->getSink()->asAudioPort()->getType() == AUDIO_PORT_TYPE_DEVICE) {
        sinkDevice = mDeclaredDevices.getDeviceFromTagName(route->getSink()->getTagName());
    }
    return sinkDevice;
}

DeviceVector HwModule::getRouteSourceDevices(const sp<AudioRoute> &route) const
{
    DeviceVector sourceDevices;
    for (const auto& source : route->getSources()) {
        if (source->asAudioPort()->getType() == AUDIO_PORT_TYPE_DEVICE) {
            sourceDevices.add(mDeclaredDevices.getDeviceFromTagName(source->getTagName()));
        }
    }
    return sourceDevices;
}

源码的基本原理是:

遍历route,如果是输入流,则mixport作为sink,则需要查找对应有哪些source即设备输入到mixport,在source中过滤出device(source->asAudioPort()->getType() == AUDIO_PORT_TYPE_DEVICE), 即devicesPort标签对应的实体类DeviceDescriptor,并保存在mSupportedDevices。

如果是输出流,则mixport作为source,则需要查找对应有些可以输出的设备即sink,在sink中过滤出device(route->getSink()->asAudioPort()->getType() == AUDIO_PORT_TYPE_DEVICE)。

作为输出流source,mSupportedDevices保存此流可以输出到对应的device,stream -> device
作为输入流sink,mSupportedDevices保存了其他device能输出数据到此流, device -> stream

<routes>
    <route type="mix" sink="Earpiece"
                       sources="primary output,deep_buffer,BT SCO Headset Mic"/>
    <route type="mix" sink="Speaker"
                       sources="primary output,deep_buffer,compressed_offload,BT SCO Headset Mic,Telephony Rx"/>
    <route type="mix" sink="Wired Headset"
                       sources="primary output,deep_buffer,compressed_offload,BT SCO Headset Mic,Telephony Rx"/>
    <route type="mix" sink="Wired Headphones"
                       sources="primary output,deep_buffer,compressed_offload,BT SCO Headset Mic,Telephony Rx"/>
    <route type="mix" sink="primary input"
                       sources="Built-In Mic,Built-In Back Mic,Wired Headset Mic,BT SCO Headset Mic"/>
    <route type="mix" sink="Telephony Tx"
                       sources="Built-In Mic,Built-In Back Mic,Wired Headset Mic,BT SCO Headset Mic, voice_tx"/>
    <route type="mix" sink="voice_rx"
                       sources="Telephony Rx"/>
</routes>

例如:当sink为primary input时,对应sources即device有sources="Built-In Mic,Built-In Back Mic,Wired Headset Mic,BT SCO Headset Mic"。

当source为primary output时,对应的sink即device有Earpiece,speaker,Wired HeadSet, Wired Headphones。

3.多配置文件合并

(1)主配置通过 <xi:include> 引入子配置:

<xi:include href="a2dp_audio_policy_configuration.xml"

(2)解析顺序:

  1. 先加载 /vendor/etc/audio_policy_configuration.xml

  2. 再合并 /system/etc/audio_policy_volumes.xml(音量配置)

4.调试与验证技巧

(1)配置有效性检查

adb shell dumpsys media.audio_policy --files
# 输出:Loaded primary: /vendor/etc/audio_policy_configuration.xml

(2)运行时路由状态

adb shell dumpsys media.audio_policy | grep -A 10 "Devices"
# 输出设备状态:Speaker: available | Bluetooth: unavailable

(3)XSD 架构校验

<!-- 使用官方Schema校验 -->
<audioPolicyConfiguration xmlns:xi="http://www.w3.org/2001/XInclude"
  xsi:noNamespaceSchemaLocation="audio_policy_configuration.xsd">

5.解析流程图解

关于策略规则和音量配置,将在后续章节中说明。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐