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 android.media.audio.cts; 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_DEFAULT; 22 import static android.media.AudioFormat.ENCODING_PCM_16BIT; 23 import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_INJECTOR; 24 import static android.media.audiopolicy.AudioMixingRule.MIX_ROLE_PLAYERS; 25 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_AUDIO_SESSION_ID; 26 import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_UID; 27 28 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertThrows; 30 31 import android.media.AudioAttributes; 32 import android.media.AudioFormat; 33 import android.media.AudioSystem; 34 import android.media.audiopolicy.AudioMix; 35 import android.media.audiopolicy.AudioMixingRule; 36 import android.media.audiopolicy.AudioPolicyConfig; 37 import android.os.Parcel; 38 39 import androidx.test.ext.junit.runners.AndroidJUnit4; 40 41 import com.android.compatibility.common.util.FrameworkSpecificTest; 42 43 import com.google.common.testing.EqualsTester; 44 45 import org.junit.Test; 46 import org.junit.runner.RunWith; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 51 @FrameworkSpecificTest 52 @RunWith(AndroidJUnit4.class) 53 public class AudioMixTest { 54 55 private static final AudioFormat OUTPUT_FORMAT_STEREO_44KHZ_PCM = 56 new AudioFormat.Builder() 57 .setSampleRate(44000) 58 .setChannelMask(CHANNEL_OUT_STEREO) 59 .setEncoding(ENCODING_PCM_16BIT).build(); 60 61 private static final AudioFormat OUTPUT_FORMAT_STEREO_48KHZ_DEFAULT = 62 new AudioFormat.Builder() 63 .setSampleRate(48000) 64 .setChannelMask(CHANNEL_OUT_STEREO) 65 .setEncoding(ENCODING_DEFAULT).build(); 66 private static final AudioFormat OUTPUT_FORMAT_MONO_16KHZ_PCM = 67 new AudioFormat.Builder() 68 .setSampleRate(16000) 69 .setChannelMask(CHANNEL_OUT_MONO) 70 .setEncoding(ENCODING_PCM_16BIT).build(); 71 private static final AudioFormat INPUT_FORMAT_MONO_16KHZ_PCM = 72 new AudioFormat.Builder() 73 .setSampleRate(16000) 74 .setChannelMask(AudioFormat.CHANNEL_IN_MONO) 75 .setEncoding(ENCODING_PCM_16BIT).build(); 76 77 private static final AudioAttributes AUDIO_ATTRIBUTES_MEDIA_MUSIC = 78 new AudioAttributes.Builder() 79 .setUsage(AudioAttributes.USAGE_MEDIA) 80 .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) 81 .build(); 82 83 @Test testEquals()84 public void testEquals() { 85 final EqualsTester equalsTester = new EqualsTester(); 86 87 // --- Equality group 1 88 final AudioMix playbackAudioMixWithSessionId42AndUid123 = 89 new AudioMix.Builder(new AudioMixingRule.Builder() 90 .setTargetMixRole(MIX_ROLE_PLAYERS) 91 .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) 92 .addMixRule(RULE_MATCH_UID, 123).build()) 93 .setFormat(OUTPUT_FORMAT_STEREO_44KHZ_PCM) 94 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); 95 equalsTester.addEqualityGroup( 96 playbackAudioMixWithSessionId42AndUid123, 97 writeToAndFromParcel(playbackAudioMixWithSessionId42AndUid123)); 98 99 // --- Equality group 2 100 final AudioMix recordingAudioMixWithSessionId42AndUid123 = 101 new AudioMix.Builder(new AudioMixingRule.Builder() 102 .setTargetMixRole(MIX_ROLE_INJECTOR) 103 .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) 104 .addMixRule(RULE_MATCH_UID, 123).build()) 105 .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) 106 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); 107 equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123, 108 writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123)); 109 110 // --- Equality group 3 111 final AudioMix recordingAudioMixWithSessionId42AndUid123Render = 112 new AudioMix.Builder(new AudioMixingRule.Builder() 113 .setTargetMixRole(MIX_ROLE_PLAYERS) 114 .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) 115 .addMixRule(RULE_MATCH_UID, 123).build()) 116 .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) 117 .setRouteFlags( 118 AudioMix.ROUTE_FLAG_LOOP_BACK | AudioMix.ROUTE_FLAG_RENDER).build(); 119 equalsTester.addEqualityGroup(recordingAudioMixWithSessionId42AndUid123Render, 120 writeToAndFromParcel(recordingAudioMixWithSessionId42AndUid123Render)); 121 122 // --- Equality group 4 123 final AudioMix playbackAudioMixWithUid123 = 124 new AudioMix.Builder(new AudioMixingRule.Builder() 125 .setTargetMixRole(MIX_ROLE_PLAYERS) 126 .addMixRule(RULE_MATCH_UID, 123).build()) 127 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 128 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); 129 equalsTester.addEqualityGroup(playbackAudioMixWithUid123, 130 writeToAndFromParcel(playbackAudioMixWithUid123)); 131 132 // --- Equality group 5 133 final AudioMix playbackAudioMixWithUid42 = 134 new AudioMix.Builder(new AudioMixingRule.Builder() 135 .setTargetMixRole(MIX_ROLE_PLAYERS) 136 .addMixRule(RULE_MATCH_UID, 42).build()) 137 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 138 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); 139 equalsTester.addEqualityGroup(playbackAudioMixWithUid42, 140 writeToAndFromParcel(playbackAudioMixWithUid42)); 141 142 // -- Equality group 6 143 final AudioMix playbackAudioMixWithMediaUsage = new AudioMix.Builder( 144 new AudioMixingRule.Builder() 145 .excludeRule(AUDIO_ATTRIBUTES_MEDIA_MUSIC, 146 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE) 147 .build()) 148 .setDevice(AudioSystem.DEVICE_BIT_DEFAULT, "device-address") 149 .setFormat(OUTPUT_FORMAT_STEREO_48KHZ_DEFAULT) 150 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 151 .build(); 152 equalsTester.addEqualityGroup(playbackAudioMixWithMediaUsage, 153 writeToAndFromParcel(playbackAudioMixWithMediaUsage)); 154 155 equalsTester.testEquals(); 156 } 157 158 @Test buildRenderToRemoteSubmix_success()159 public void buildRenderToRemoteSubmix_success() { 160 final String deviceAddress = "address"; 161 final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder() 162 .setTargetMixRole(MIX_ROLE_PLAYERS) 163 .addMixRule(RULE_MATCH_UID, 42).build()) 164 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 165 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 166 .setDevice(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, /*address=*/deviceAddress).build(); 167 168 assertEquals(deviceAddress, audioMix.getRegistration()); 169 assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat()); 170 assertEquals(AudioMix.ROUTE_FLAG_RENDER, audioMix.getRouteFlags()); 171 } 172 173 @Test buildLoopbackAndRenderToRemoteSubmix_success()174 public void buildLoopbackAndRenderToRemoteSubmix_success() { 175 final String deviceAddress = "address"; 176 final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder() 177 .setTargetMixRole(MIX_ROLE_PLAYERS) 178 .addMixRule(RULE_MATCH_UID, 42).build()) 179 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 180 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER) 181 .setDevice(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, /*address=*/deviceAddress).build(); 182 183 assertEquals(deviceAddress, audioMix.getRegistration()); 184 assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat()); 185 assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK_RENDER, audioMix.getRouteFlags()); 186 } 187 188 @Test buildRenderToSpeaker_success()189 public void buildRenderToSpeaker_success() { 190 final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder() 191 .setTargetMixRole(MIX_ROLE_PLAYERS) 192 .addMixRule(RULE_MATCH_UID, 42).build()) 193 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 194 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 195 .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build(); 196 197 assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat()); 198 assertEquals(AudioMix.ROUTE_FLAG_RENDER, audioMix.getRouteFlags()); 199 } 200 201 @Test buildLoopbackForPlayerMix_success()202 public void buildLoopbackForPlayerMix_success() { 203 final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder() 204 .setTargetMixRole(MIX_ROLE_PLAYERS) 205 .addMixRule(RULE_MATCH_UID, 42).build()) 206 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 207 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); 208 209 assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat()); 210 assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags()); 211 } 212 213 @Test buildLoopbackForInjectorMix_success()214 public void buildLoopbackForInjectorMix_success() { 215 final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder() 216 .setTargetMixRole(MIX_ROLE_INJECTOR) 217 .addMixRule(RULE_MATCH_UID, 42).build()) 218 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 219 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); 220 221 assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat()); 222 assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags()); 223 } 224 225 @Test buildLoopbackWithIncompatibleDevice_throws()226 public void buildLoopbackWithIncompatibleDevice_throws() { 227 assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( 228 new AudioMixingRule.Builder() 229 .setTargetMixRole(MIX_ROLE_PLAYERS) 230 .addMixRule(RULE_MATCH_UID, 42).build()) 231 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 232 .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK) 233 .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build()); 234 } 235 236 @Test buildRenderWithoutDevice_throws()237 public void buildRenderWithoutDevice_throws() { 238 assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( 239 new AudioMixingRule.Builder() 240 .setTargetMixRole(MIX_ROLE_PLAYERS) 241 .addMixRule(RULE_MATCH_UID, 42).build()) 242 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 243 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER).build()); 244 } 245 246 @Test buildRenderWithInputDevice_throws()247 public void buildRenderWithInputDevice_throws() { 248 assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( 249 new AudioMixingRule.Builder() 250 .setTargetMixRole(MIX_ROLE_PLAYERS) 251 .addMixRule(RULE_MATCH_UID, 42).build()) 252 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 253 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 254 .setDevice(AudioSystem.DEVICE_IN_BUILTIN_MIC, /*address=*/"").build()); 255 } 256 257 @Test buildRenderWithInjectorMix_throws()258 public void buildRenderWithInjectorMix_throws() { 259 assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( 260 new AudioMixingRule.Builder() 261 .setTargetMixRole(MIX_ROLE_INJECTOR) 262 .addMixRule(RULE_MATCH_UID, 42).build()) 263 .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) 264 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 265 .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build()); 266 } 267 268 269 writeToAndFromParcel(AudioMix audioMix)270 private static AudioMix writeToAndFromParcel(AudioMix audioMix) { 271 AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix))); 272 Parcel parcel = Parcel.obtain(); 273 apc.writeToParcel(parcel, /*flags=*/0); 274 parcel.setDataPosition(0); 275 AudioMix unmarshalledMix = 276 AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0); 277 parcel.recycle(); 278 return unmarshalledMix; 279 } 280 } 281