1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.audio;
17 
18 import android.hardware.automotive.audiocontrol.V1_0.ContextNumber;
19 import android.media.AudioAttributes;
20 import android.media.AudioFormat;
21 import android.media.AudioManager;
22 import android.media.audiopolicy.AudioMix;
23 import android.media.audiopolicy.AudioMixingRule;
24 import android.media.audiopolicy.AudioPolicy;
25 import android.util.Log;
26 import android.util.SparseIntArray;
27 
28 import com.android.car.CarLog;
29 
30 import java.util.ArrayList;
31 import java.util.Arrays;
32 import java.util.List;
33 
34 /**
35  * Builds dynamic audio routing in a car from audio zone configuration.
36  */
37 /* package */ class CarAudioDynamicRouting {
38 
39     static final int[] CONTEXT_NUMBERS = new int[] {
40             ContextNumber.MUSIC,
41             ContextNumber.NAVIGATION,
42             ContextNumber.VOICE_COMMAND,
43             ContextNumber.CALL_RING,
44             ContextNumber.CALL,
45             ContextNumber.ALARM,
46             ContextNumber.NOTIFICATION,
47             ContextNumber.SYSTEM_SOUND
48     };
49 
50     static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray();
51 
52     static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA;
53 
54     // For legacy stream type based volume control.
55     // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned.
56     static final int[] STREAM_TYPES = new int[] {
57             AudioManager.STREAM_MUSIC,
58             AudioManager.STREAM_ALARM,
59             AudioManager.STREAM_RING
60     };
61     static final int[] STREAM_TYPE_USAGES = new int[] {
62             AudioAttributes.USAGE_MEDIA,
63             AudioAttributes.USAGE_ALARM,
64             AudioAttributes.USAGE_NOTIFICATION_RINGTONE
65     };
66 
67     static {
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC)68         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC)69         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL)70         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING, ContextNumber.CALL)71         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
72                 ContextNumber.CALL);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM)73         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION)74         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING)75         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST, ContextNumber.NOTIFICATION)76         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST,
77                 ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT, ContextNumber.NOTIFICATION)78         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT,
79                 ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED, ContextNumber.NOTIFICATION)80         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED,
81                 ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION)82         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY, ContextNumber.VOICE_COMMAND)83         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
84                 ContextNumber.VOICE_COMMAND);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, ContextNumber.NAVIGATION)85         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
86                 ContextNumber.NAVIGATION);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION, ContextNumber.SYSTEM_SOUND)87         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
88                 ContextNumber.SYSTEM_SOUND);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC)89         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID)90         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID);
USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND)91         USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND);
92     }
93 
94     private final CarAudioZone[] mCarAudioZones;
95 
CarAudioDynamicRouting(CarAudioZone[] carAudioZones)96     CarAudioDynamicRouting(CarAudioZone[] carAudioZones) {
97         mCarAudioZones = carAudioZones;
98     }
99 
setupAudioDynamicRouting(AudioPolicy.Builder builder)100     void setupAudioDynamicRouting(AudioPolicy.Builder builder) {
101         for (CarAudioZone zone : mCarAudioZones) {
102             for (CarVolumeGroup group : zone.getVolumeGroups()) {
103                 setupAudioDynamicRoutingForGroup(group, builder);
104             }
105         }
106     }
107 
108     /**
109      * Enumerates all physical buses in a given volume group and attach the mixing rules.
110      * @param group {@link CarVolumeGroup} instance to enumerate the buses with
111      * @param builder {@link AudioPolicy.Builder} to attach the mixing rules
112      */
setupAudioDynamicRoutingForGroup(CarVolumeGroup group, AudioPolicy.Builder builder)113     private void setupAudioDynamicRoutingForGroup(CarVolumeGroup group,
114             AudioPolicy.Builder builder) {
115         // Note that one can not register audio mix for same bus more than once.
116         for (int busNumber : group.getBusNumbers()) {
117             boolean hasContext = false;
118             CarAudioDeviceInfo info = group.getCarAudioDeviceInfoForBus(busNumber);
119             AudioFormat mixFormat = new AudioFormat.Builder()
120                     .setSampleRate(info.getSampleRate())
121                     .setEncoding(info.getEncodingFormat())
122                     .setChannelMask(info.getChannelCount())
123                     .build();
124             AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
125             for (int contextNumber : group.getContextsForBus(busNumber)) {
126                 hasContext = true;
127                 int[] usages = getUsagesForContext(contextNumber);
128                 for (int usage : usages) {
129                     mixingRuleBuilder.addRule(
130                             new AudioAttributes.Builder().setUsage(usage).build(),
131                             AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
132                 }
133                 Log.d(CarLog.TAG_AUDIO, "Bus number: " + busNumber
134                         + " contextNumber: " + contextNumber
135                         + " sampleRate: " + info.getSampleRate()
136                         + " channels: " + info.getChannelCount()
137                         + " usages: " + Arrays.toString(usages));
138             }
139             if (hasContext) {
140                 // It's a valid case that an audio output bus is defined in
141                 // audio_policy_configuration and no context is assigned to it.
142                 // In such case, do not build a policy mix with zero rules.
143                 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
144                         .setFormat(mixFormat)
145                         .setDevice(info.getAudioDeviceInfo())
146                         .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
147                         .build();
148                 builder.addMix(audioMix);
149             }
150         }
151     }
152 
getUsagesForContext(int contextNumber)153     private int[] getUsagesForContext(int contextNumber) {
154         final List<Integer> usages = new ArrayList<>();
155         for (int i = 0; i < CarAudioDynamicRouting.USAGE_TO_CONTEXT.size(); i++) {
156             if (CarAudioDynamicRouting.USAGE_TO_CONTEXT.valueAt(i) == contextNumber) {
157                 usages.add(CarAudioDynamicRouting.USAGE_TO_CONTEXT.keyAt(i));
158             }
159         }
160         return usages.stream().mapToInt(i -> i).toArray();
161     }
162 }
163