介绍
在汽车领域,围绕多个用户同时与平台互动并且每个用户都希望使用单独媒体的需求,衍生出了音频分区的概念。比如后座上的乘客在后座显示屏上观看 YouTube 视频时,司机可以在驾驶舱中播放音乐。多可用区音频允许不同的音频源通过车辆的不同可用区同时进行播放。
音频分区的配置
1.启用多分区配置,首先就要在config.xml文件中将audioUseDynamicRouting属性设置为true。
2.在car_audio_configuration.xml文件中配置多分区,如下所示,配置了一个主分区,一个休息区。
?xml version="1.0" encoding="utf-8"?>
<carAudioConfiguration version="2">
<zones>
<zone name="primary zone" isPrimary="true" audioZoneId="0" occupantZoneId="1"> //主分区 分区id为0
<volumeGroups>
<group>
<device address="bus0_media_out">
<context context="music"/>
</device>
<device address="bus3_call_ring_out">
<context context="call_ring"/>
</device>
</group>
<group>
<device address="bus1_navigation_out">
<context context="navigation"/>
<context context="emergency"/>
<context context="safety"/>
<context context="vehicle_status"/>
<context context="announcement"/>
</device>
</group>
</volumeGroups>
</zone>
<zone name="rear seat zone" audioZoneId="2"> 休息区 分区id为2
<volumeGroups>
<group>
<device address="bus100_rear_seat">
<context context="music"/>
<context context="navigation"/>
<context context="voice_command"/>
<context context="call_ring"/>
<context context="call"/>
<context context="alarm"/>
<context context="system_sound"/>
<context context="notification"/>
<context context="emergency"/>
<context context="safety"/>
<context context="vehicle_status"/>
<context context="announcement"/>
</device>
</group>
</volumeGroups>
</zone>
</zones>
</carAudioConfiguration>
-
audioZoneId
:表示分区id -
device
:代表音频设置的物理地址,其必须是在音频路由中配置过的地址才能生效 -
volumeGroups
:音量组,音量大小调节时是一起进行调节的。 -
context
:代表改设备可用于进行音频播放的类型,用来进行焦点分配。对应CarAudioContext.java中定义的类型。如下所示
//packages/services/Car/service/src/com/android/car/audio/CarAudioContext.java
static {
CONTEXT_NAMES.append(INVALID, "INVALID");
CONTEXT_NAMES.append(MUSIC, "MUSIC");
CONTEXT_NAMES.append(NAVIGATION, "NAVIGATION");
CONTEXT_NAMES.append(VOICE_COMMAND, "VOICE_COMMAND");
CONTEXT_NAMES.append(CALL_RING, "CALL_RING");
CONTEXT_NAMES.append(CALL, "CALL");
CONTEXT_NAMES.append(ALARM, "ALARM");
CONTEXT_NAMES.append(NOTIFICATION, "NOTIFICATION");
CONTEXT_NAMES.append(SYSTEM_SOUND, "SYSTEM_SOUND");
CONTEXT_NAMES.append(EMERGENCY, "EMERGENCY");
CONTEXT_NAMES.append(SAFETY, "SAFETY");
CONTEXT_NAMES.append(VEHICLE_STATUS, "VEHICLE_STATUS");
CONTEXT_NAMES.append(ANNOUNCEMENT, "ANNOUNCEMENT");
}
音频分区的加载
1.在CarAudioService初始化中加载音频分区,主要方法为loadCarAudioZonesLocked
.
///packages/services/Car/service/src/com/android/car/audio/CarAudioService.java
private void setupDynamicRoutingLocked() {
final AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
builder.setLooper(Looper.getMainLooper());
loadCarAudioZonesLocked();
}
2.在loadCarAudioZonesLocked方法中,获取音频输出设备,判断是否有配置文件car_audio_configuration.xml。
private void loadCarAudioZonesLocked() {
//2 从AudioManager中获取音频输出设备列表
List<CarAudioDeviceInfo> carAudioDeviceInfos = generateCarAudioDeviceInfos();
//3.获取分区的配置文件。配置文件一般在vendor/etc和system/etc目录下。
mCarAudioConfigurationPath = getAudioConfigurationPath();
if (mCarAudioConfigurationPath != null) {
//4 分区文件存在,则直接加载解析xml文件中的分区配置
mCarAudioZones = loadCarAudioConfigurationLocked(carAudioDeviceInfos);
} else {
//分区文件不存在,直接加载音量组的配置文件car_volume_groups,生成一个主分区。
mCarAudioZones = loadVolumeGroupConfigurationWithAudioControlLocked(
carAudioDeviceInfos);
}
CarAudioZonesValidator.validate(mCarAudioZones);
}
3.loadCarAudioConfigurationLocked方法中,创建CarAudioZonesHelper,传入文件输入流,所有输入设备等,在loadAudioZones方法区加载分区。
private SparseArray<CarAudioZone> loadCarAudioConfigurationLocked(
List<CarAudioDeviceInfo> carAudioDeviceInfos) {
//1.从AudioManager中获取所有输入设备列表
AudioDeviceInfo[] inputDevices = getAllInputDevices();
try (InputStream inputStream = new FileInputStream(mCarAudioConfigurationPath)) {
//创建CarAudioZonesHelper,在loadAudioZones方法区加载分区。
CarAudioZonesHelper zonesHelper = new CarAudioZonesHelper(mCarAudioSettings,
inputStream, carAudioDeviceInfos, inputDevices, mUseCarVolumeGroupMuting);
mAudioZoneIdToOccupantZoneIdMapping =
zonesHelper.getCarAudioZoneIdToOccupantZoneIdMapping();
return zonesHelper.loadAudioZones();
} catch (IOException | XmlPullParserException e) {
throw new RuntimeException("Failed to parse audio zone configuration", e);
}
}
4.最终xml的解析,是在CarAudioZonesHelper的parseCarAudioZones方法中执行的。
//packages/services/Car/service/src/com/android/car/audio/CarAudioZonesHelper.java
SparseArray<CarAudioZone> loadAudioZones() throws IOException, XmlPullParserException {
return parseCarAudioZones(mInputStream);
}
private SparseArray<CarAudioZone> parseCarAudioZones(InputStream stream)
throws XmlPullParserException, IOException {
XmlPullParser parser = Xml.newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, NAMESPACE != null);
parser.setInput(stream, null);
// Ensure <carAudioConfiguration> is the root
parser.nextTag();
parser.require(XmlPullParser.START_TAG, NAMESPACE, TAG_ROOT);
// Version check
final int versionNumber = Integer.parseInt(
parser.getAttributeValue(NAMESPACE, ATTR_VERSION));
if (SUPPORTED_VERSIONS.get(versionNumber, INVALID_VERSION) == INVALID_VERSION) {
throw new IllegalArgumentException("Latest Supported version:"
+ SUPPORTED_VERSION_2 + " , got version:" + versionNumber);
}
mCurrentVersion = versionNumber;
// Get all zones configured under <zones> tag
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (TAG_AUDIO_ZONES.equals(parser.getName())) {
return parseAudioZones(parser);
} else {
skip(parser);
}
}
throw new RuntimeException(TAG_AUDIO_ZONES + " is missing from configuration");
}
解析出来的CarAudioZone包含以下这些属性。
class CarAudioZone {
private final int mId; //对应配置文件中的audioZoneId
private final String mName;//对应name
private final List<CarVolumeGroup> mVolumeGroups;//音量组列表
private final Set<String> mDeviceAddresses;//device节点下的所有设备合集
private List<AudioDeviceAttributes> mInputAudioDevice;//音频输入设备