1 /*
2  * Copyright (C) 2023 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 
17 package com.android.audiopolicytest;
18 
19 import static android.media.AudioFormat.CHANNEL_OUT_MONO;
20 import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
21 import static android.media.AudioFormat.ENCODING_PCM_16BIT;
22 import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR;
23 import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS;
24 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID;
25 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID;
26 
27 import static org.junit.Assert.assertEquals;
28 import static org.junit.Assert.assertThrows;
29 
30 import android.media.AudioFormat;
31 import android.media.AudioSystem;
32 import android.media.audiopolicy.AudioMix;
33 import android.media.audiopolicy.AudioMixingRule;
34 import android.media.audiopolicy.AudioPolicyConfig;
35 import android.os.Parcel;
36 import android.platform.test.annotations.Presubmit;
37 
38 import androidx.test.ext.junit.runners.AndroidJUnit4;
39 
40 import com.google.common.testing.EqualsTester;
41 
42 import org.junit.Test;
43 import org.junit.runner.RunWith;
44 
45 import java.util.ArrayList;
46 import java.util.List;
47 
48 /**
49  * Unit tests for AudioMix.
50  *
51  * Run with "atest AudioMixUnitTests".
52  */
53 @Presubmit
54 @RunWith(AndroidJUnit4.class)
55 public class AudioMixUnitTests {
56     private static final AudioFormat OUTPUT_FORMAT_STEREO_44KHZ_PCM =
57             new AudioFormat.Builder()
58                     .setSampleRate(44000)
59                     .setChannelMask(CHANNEL_OUT_STEREO)
60                     .setEncoding(ENCODING_PCM_16BIT).build();
61     private static final AudioFormat OUTPUT_FORMAT_MONO_16KHZ_PCM =
62             new AudioFormat.Builder()
63                     .setSampleRate(16000)
64                     .setChannelMask(CHANNEL_OUT_MONO)
65                     .setEncoding(ENCODING_PCM_16BIT).build();
66     private static final AudioFormat INPUT_FORMAT_MONO_16KHZ_PCM =
67             new AudioFormat.Builder()
68                     .setSampleRate(16000)
69                     .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
70                     .setEncoding(ENCODING_PCM_16BIT).build();
71 
72     @Test
testEquals()73     public void testEquals() {
74         final EqualsTester equalsTester = new EqualsTester();
75 
76         // --- Equality group 1
77         final AudioMix playbackAudioMixWithSessionId42AndUid123 =
78                 new AudioMix.Builder(new AudioMixingRule.Builder()
79                         .setTargetMixRole(MIX_ROLE_PLAYERS)
80                         .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
81                         .addMixRule(RULE_MATCH_UID, 123).build())
82                         .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
83                         .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
84         final AudioMix playbackAudioMixWithUid123AndSessionId42 =
85                 new AudioMix.Builder(new AudioMixingRule.Builder()
86                         .setTargetMixRole(MIX_ROLE_PLAYERS)
87                         .addMixRule(RULE_MATCH_UID, 123)
88                         .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42).build())
89                         .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM)
90                         .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
91         equalsTester.addEqualityGroup(
92                 playbackAudioMixWithSessionId42AndUid123,
93                 playbackAudioMixWithUid123AndSessionId42,
94                 writeToAndFromParcel(playbackAudioMixWithSessionId42AndUid123),
95                 writeToAndFromParcel(playbackAudioMixWithUid123AndSessionId42));
96 
97         // --- Equality group 2
98         final AudioMix recordingAudioMixWithSessionId42AndUid123 =
99                 new AudioMix.Builder(new AudioMixingRule.Builder()
100                         .setTargetMixRole(MIX_ROLE_INJECTOR)
101                         .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
102                         .addMixRule(RULE_MATCH_UID, 123).build())
103                         .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
104                         .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
105         final AudioMix recordingAudioMixWithUid123AndSessionId42 =
106                 new AudioMix.Builder(new AudioMixingRule.Builder()
107                         .setTargetMixRole(MIX_ROLE_INJECTOR)
108                         .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
109                         .addMixRule(RULE_MATCH_UID, 123).build())
110                         .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
111                         .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
112         equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123,
113                 recordingAudioMixWithUid123AndSessionId42,
114                 writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123),
115                 writeToAndFromParcel(recordingAudioMixWithUid123AndSessionId42));
116 
117         // --- Equality group 3
118         final AudioMix recordingAudioMixWithSessionId42AndUid123Render =
119                 new AudioMix.Builder(new AudioMixingRule.Builder()
120                         .setTargetMixRole(MIX_ROLE_INJECTOR)
121                         .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42)
122                         .addMixRule(RULE_MATCH_UID, 123).build())
123                         .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM)
124                         .setRouteFlags(
125                                 AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER).build();
126         equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123Render,
127                 writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123Render));
128 
129         // --- Equality group 4
130         final AudioMix playbackAudioMixWithUid123 =
131                 new AudioMix.Builder(new AudioMixingRule.Builder()
132                         .setTargetMixRole(MIX_ROLE_PLAYERS)
133                         .addMixRule(RULE_MATCH_UID, 123).build())
134                         .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
135                         .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
136         equalsTester.addEqualityGroup(playbackAudioMixWithUid123,
137                 writeToAndFromParcel(playbackAudioMixWithUid123));
138 
139         // --- Equality group 5
140         final AudioMix playbackAudioMixWithUid42 =
141                 new AudioMix.Builder(new AudioMixingRule.Builder()
142                         .setTargetMixRole(MIX_ROLE_PLAYERS)
143                         .addMixRule(RULE_MATCH_UID, 42).build())
144                         .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
145                         .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
146         equalsTester.addEqualityGroup(playbackAudioMixWithUid42,
147                 writeToAndFromParcel(playbackAudioMixWithUid42));
148 
149         equalsTester.testEquals();
150     }
151 
152     @Test
buildRenderToRemoteSubmix_success()153     public void buildRenderToRemoteSubmix_success() {
154         final String deviceAddress = "address";
155         final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
156                 .setTargetMixRole(MIX_ROLE_PLAYERS)
157                 .addMixRule(RULE_MATCH_UID, 42).build())
158                 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
159                 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
160                 .setDevice(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, /*address=*/deviceAddress).build();
161 
162         assertEquals(deviceAddress, audioMix.getRegistration());
163         assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
164         assertEquals(AudioMix.ROUTE_FLAG_RENDER, audioMix.getRouteFlags());
165     }
166 
167     @Test
buildLoopbackAndRenderToRemoteSubmix_success()168     public void buildLoopbackAndRenderToRemoteSubmix_success() {
169         final String deviceAddress = "address";
170         final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
171                 .setTargetMixRole(MIX_ROLE_PLAYERS)
172                 .addMixRule(RULE_MATCH_UID, 42).build())
173                 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
174                 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER)
175                 .setDevice(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, /*address=*/deviceAddress).build();
176 
177         assertEquals(deviceAddress, audioMix.getRegistration());
178         assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
179         assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER, audioMix.getRouteFlags());
180     }
181 
182     @Test
buildRenderToSpeaker_success()183     public void buildRenderToSpeaker_success() {
184         final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
185                 .setTargetMixRole(MIX_ROLE_PLAYERS)
186                 .addMixRule(RULE_MATCH_UID, 42).build())
187                 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
188                 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
189                 .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build();
190 
191         assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
192         assertEquals(AudioMix.ROUTE_FLAG_RENDER, audioMix.getRouteFlags());
193     }
194 
195     @Test
buildLoopbackForPlayerMix_success()196     public void buildLoopbackForPlayerMix_success() {
197         final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder()
198                 .setTargetMixRole(MIX_ROLE_PLAYERS)
199                 .addMixRule(RULE_MATCH_UID, 42).build())
200                 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
201                 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build();
202 
203         assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat());
204         assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags());
205     }
206 
207     @Test
buildLoopbackWithDevice_throws()208     public void buildLoopbackWithDevice_throws() {
209         assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
210                 new AudioMixingRule.Builder()
211                         .setTargetMixRole(MIX_ROLE_PLAYERS)
212                         .addMixRule(RULE_MATCH_UID, 42).build())
213                 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
214                 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK)
215                 .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build());
216     }
217 
218     @Test
buildRenderWithoutDevice_throws()219     public void buildRenderWithoutDevice_throws() {
220         assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder(
221                 new AudioMixingRule.Builder()
222                         .setTargetMixRole(MIX_ROLE_PLAYERS)
223                         .addMixRule(RULE_MATCH_UID, 42).build())
224                 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM)
225                 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER).build());
226     }
227 
228 
229 
writeToAndFromParcel(AudioMix audioMix)230     private static AudioMix writeToAndFromParcel(AudioMix audioMix) {
231         AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix)));
232         Parcel parcel = Parcel.obtain();
233         apc.writeToParcel(parcel, /*flags=*/0);
234         parcel.setDataPosition(0);
235         AudioMix unmarshalledMix =
236                 AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0);
237         parcel.recycle();
238         return unmarshalledMix;
239     }
240 }
241