Loading media/java/android/media/AudioSystem.java +5 −0 Original line number Diff line number Diff line Loading @@ -1341,6 +1341,11 @@ public class AudioSystem || isBluetoothLeInDevice(deviceType); } /** @hide */ public static boolean isRemoteSubmixDevice(int deviceType) { return deviceType == DEVICE_IN_REMOTE_SUBMIX || deviceType == DEVICE_OUT_REMOTE_SUBMIX; } /** @hide */ public static final String LEGACY_REMOTE_SUBMIX_ADDRESS = "0"; Loading media/java/android/media/audiopolicy/AudioMix.java +39 −23 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.media.audiopolicy; import static android.media.AudioSystem.getDeviceName; import static android.media.AudioSystem.isRemoteSubmixDevice; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; Loading Loading @@ -445,36 +448,36 @@ public class AudioMix { // CHANNEL_IN_FRONT_BACK is hidden, should not appear. } } if ((mDeviceSystemType != AudioSystem.DEVICE_NONE) && (mDeviceSystemType != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) && (mDeviceSystemType != AudioSystem.DEVICE_IN_REMOTE_SUBMIX)) { if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) { throw new IllegalArgumentException( "Can't have audio device without flag ROUTE_FLAG_RENDER"); } if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { throw new IllegalArgumentException("Unsupported device on non-playback mix"); if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) { if (mDeviceSystemType == AudioSystem.DEVICE_NONE) { // If there was no device type explicitly set, configure it based on mix type. mDeviceSystemType = getLoopbackDeviceSystemTypeForAudioMixingRule(mRule); } else if (!isRemoteSubmixDevice(mDeviceSystemType)) { // Loopback mode only supports remote submix devices. throw new IllegalArgumentException("Device " + getDeviceName(mDeviceSystemType) + "is not supported for loopback mix."); } } else if (mDeviceSystemType == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) { if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { throw new IllegalArgumentException( "DEVICE_OUT_REMOTE_SUBMIX device is not supported on non-playback mix"); } } else { if ((mRouteFlags & ROUTE_FLAG_SUPPORTED) == ROUTE_FLAG_RENDER) { if ((mRouteFlags & ROUTE_FLAG_RENDER) == ROUTE_FLAG_RENDER) { if (mDeviceSystemType == AudioSystem.DEVICE_NONE) { throw new IllegalArgumentException( "Can't have flag ROUTE_FLAG_RENDER without an audio device"); } if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) { if (mRule.getTargetMixType() == MIX_TYPE_PLAYERS) { mDeviceSystemType = AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; } else if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) { mDeviceSystemType = AudioSystem.DEVICE_IN_REMOTE_SUBMIX; } else { throw new IllegalArgumentException("Unknown mixing rule type"); if (AudioSystem.DEVICE_IN_ALL_SET.contains(mDeviceSystemType)) { throw new IllegalArgumentException( "Input device is not supported with ROUTE_FLAG_RENDER"); } if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) { throw new IllegalArgumentException( "ROUTE_FLAG_RENDER/ROUTE_FLAG_LOOP_BACK_RENDER is not supported for " + "non-playback mix rule"); } } if (mRule.allowPrivilegedMediaPlaybackCapture()) { String error = AudioMix.canBeUsedForPrivilegedMediaCapture(mFormat); if (error != null) { Loading @@ -484,5 +487,18 @@ public class AudioMix { return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType, mDeviceAddress); } private int getLoopbackDeviceSystemTypeForAudioMixingRule(AudioMixingRule rule) { switch (mRule.getTargetMixType()) { case MIX_TYPE_PLAYERS: return AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; case MIX_TYPE_RECORDERS: return AudioSystem.DEVICE_IN_REMOTE_SUBMIX; default: throw new IllegalArgumentException( "Unknown mixing rule type - 0x" + Integer.toHexString( rule.getTargetMixType())); } } } } media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java +36 −2 Original line number Diff line number Diff line Loading @@ -117,7 +117,7 @@ public class AudioMixUnitTests { // --- Equality group 3 final AudioMix recordingAudioMixWithSessionId42AndUid123Render = new AudioMix.Builder(new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_INJECTOR) .setTargetMixRole(MIX_ROLE_PLAYERS) .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) .addMixRule(RULE_MATCH_UID, 123).build()) .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) Loading Loading @@ -205,7 +205,19 @@ public class AudioMixUnitTests { } @Test public void buildLoopbackWithDevice_throws() { public void buildLoopbackForInjectorMix_success() { final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_INJECTOR) .addMixRule(RULE_MATCH_UID, 42).build()) .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat()); assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags()); } @Test public void buildLoopbackWithIncompatibleDevice_throws() { assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_PLAYERS) Loading @@ -225,6 +237,28 @@ public class AudioMixUnitTests { .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER).build()); } @Test public void buildRenderWithInputDevice_throws() { assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_PLAYERS) .addMixRule(RULE_MATCH_UID, 42).build()) .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) .setDevice(AudioSystem.DEVICE_IN_BUILTIN_MIC, /*address=*/"").build()); } @Test public void buildRenderWithInjectorMix_throws() { assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_INJECTOR) .addMixRule(RULE_MATCH_UID, 42).build()) .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build()); } private static AudioMix writeToAndFromParcel(AudioMix audioMix) { Loading Loading
media/java/android/media/AudioSystem.java +5 −0 Original line number Diff line number Diff line Loading @@ -1341,6 +1341,11 @@ public class AudioSystem || isBluetoothLeInDevice(deviceType); } /** @hide */ public static boolean isRemoteSubmixDevice(int deviceType) { return deviceType == DEVICE_IN_REMOTE_SUBMIX || deviceType == DEVICE_OUT_REMOTE_SUBMIX; } /** @hide */ public static final String LEGACY_REMOTE_SUBMIX_ADDRESS = "0"; Loading
media/java/android/media/audiopolicy/AudioMix.java +39 −23 Original line number Diff line number Diff line Loading @@ -16,6 +16,9 @@ package android.media.audiopolicy; import static android.media.AudioSystem.getDeviceName; import static android.media.AudioSystem.isRemoteSubmixDevice; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.SystemApi; Loading Loading @@ -445,36 +448,36 @@ public class AudioMix { // CHANNEL_IN_FRONT_BACK is hidden, should not appear. } } if ((mDeviceSystemType != AudioSystem.DEVICE_NONE) && (mDeviceSystemType != AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) && (mDeviceSystemType != AudioSystem.DEVICE_IN_REMOTE_SUBMIX)) { if ((mRouteFlags & ROUTE_FLAG_RENDER) == 0) { throw new IllegalArgumentException( "Can't have audio device without flag ROUTE_FLAG_RENDER"); } if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { throw new IllegalArgumentException("Unsupported device on non-playback mix"); if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) { if (mDeviceSystemType == AudioSystem.DEVICE_NONE) { // If there was no device type explicitly set, configure it based on mix type. mDeviceSystemType = getLoopbackDeviceSystemTypeForAudioMixingRule(mRule); } else if (!isRemoteSubmixDevice(mDeviceSystemType)) { // Loopback mode only supports remote submix devices. throw new IllegalArgumentException("Device " + getDeviceName(mDeviceSystemType) + "is not supported for loopback mix."); } } else if (mDeviceSystemType == AudioSystem.DEVICE_OUT_REMOTE_SUBMIX) { if (mRule.getTargetMixType() != AudioMix.MIX_TYPE_PLAYERS) { throw new IllegalArgumentException( "DEVICE_OUT_REMOTE_SUBMIX device is not supported on non-playback mix"); } } else { if ((mRouteFlags & ROUTE_FLAG_SUPPORTED) == ROUTE_FLAG_RENDER) { if ((mRouteFlags & ROUTE_FLAG_RENDER) == ROUTE_FLAG_RENDER) { if (mDeviceSystemType == AudioSystem.DEVICE_NONE) { throw new IllegalArgumentException( "Can't have flag ROUTE_FLAG_RENDER without an audio device"); } if ((mRouteFlags & ROUTE_FLAG_LOOP_BACK) == ROUTE_FLAG_LOOP_BACK) { if (mRule.getTargetMixType() == MIX_TYPE_PLAYERS) { mDeviceSystemType = AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; } else if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) { mDeviceSystemType = AudioSystem.DEVICE_IN_REMOTE_SUBMIX; } else { throw new IllegalArgumentException("Unknown mixing rule type"); if (AudioSystem.DEVICE_IN_ALL_SET.contains(mDeviceSystemType)) { throw new IllegalArgumentException( "Input device is not supported with ROUTE_FLAG_RENDER"); } if (mRule.getTargetMixType() == MIX_TYPE_RECORDERS) { throw new IllegalArgumentException( "ROUTE_FLAG_RENDER/ROUTE_FLAG_LOOP_BACK_RENDER is not supported for " + "non-playback mix rule"); } } if (mRule.allowPrivilegedMediaPlaybackCapture()) { String error = AudioMix.canBeUsedForPrivilegedMediaCapture(mFormat); if (error != null) { Loading @@ -484,5 +487,18 @@ public class AudioMix { return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType, mDeviceAddress); } private int getLoopbackDeviceSystemTypeForAudioMixingRule(AudioMixingRule rule) { switch (mRule.getTargetMixType()) { case MIX_TYPE_PLAYERS: return AudioSystem.DEVICE_OUT_REMOTE_SUBMIX; case MIX_TYPE_RECORDERS: return AudioSystem.DEVICE_IN_REMOTE_SUBMIX; default: throw new IllegalArgumentException( "Unknown mixing rule type - 0x" + Integer.toHexString( rule.getTargetMixType())); } } } }
media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java +36 −2 Original line number Diff line number Diff line Loading @@ -117,7 +117,7 @@ public class AudioMixUnitTests { // --- Equality group 3 final AudioMix recordingAudioMixWithSessionId42AndUid123Render = new AudioMix.Builder(new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_INJECTOR) .setTargetMixRole(MIX_ROLE_PLAYERS) .addMixRule(RULE_MATCH_AUDIO_SESSION_ID, 42) .addMixRule(RULE_MATCH_UID, 123).build()) .setFormat(INPUT_FORMAT_MONO_16KHZ_PCM) Loading Loading @@ -205,7 +205,19 @@ public class AudioMixUnitTests { } @Test public void buildLoopbackWithDevice_throws() { public void buildLoopbackForInjectorMix_success() { final AudioMix audioMix = new AudioMix.Builder(new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_INJECTOR) .addMixRule(RULE_MATCH_UID, 42).build()) .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) .setRouteFlags(AudioMix.ROUTE_FLAG_LOOP_BACK).build(); assertEquals(OUTPUT_FORMAT_MONO_16KHZ_PCM, audioMix.getFormat()); assertEquals(AudioMix.ROUTE_FLAG_LOOP_BACK, audioMix.getRouteFlags()); } @Test public void buildLoopbackWithIncompatibleDevice_throws() { assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_PLAYERS) Loading @@ -225,6 +237,28 @@ public class AudioMixUnitTests { .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER).build()); } @Test public void buildRenderWithInputDevice_throws() { assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_PLAYERS) .addMixRule(RULE_MATCH_UID, 42).build()) .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) .setDevice(AudioSystem.DEVICE_IN_BUILTIN_MIC, /*address=*/"").build()); } @Test public void buildRenderWithInjectorMix_throws() { assertThrows(IllegalArgumentException.class, () -> new AudioMix.Builder( new AudioMixingRule.Builder() .setTargetMixRole(MIX_ROLE_INJECTOR) .addMixRule(RULE_MATCH_UID, 42).build()) .setFormat(OUTPUT_FORMAT_MONO_16KHZ_PCM) .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) .setDevice(AudioSystem.DEVICE_OUT_SPEAKER, /*address=*/"").build()); } private static AudioMix writeToAndFromParcel(AudioMix audioMix) { Loading