Merge "Configure session ids of AudioRecord for device-specific context."

This commit is contained in:
Ján Sebechlebský
2022-12-19 16:54:37 +00:00
committed by Android (Google) Code Review
2 changed files with 202 additions and 5 deletions

View File

@@ -16,6 +16,11 @@
package android.media;
import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
import android.annotation.CallbackExecutor;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -26,6 +31,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.companion.virtual.VirtualDeviceManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.AttributionSource;
import android.content.AttributionSource.ScopedParcelState;
@@ -455,7 +461,7 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
int[] sampleRate = new int[] {mSampleRate};
int[] session = new int[1];
session[0] = sessionId;
session[0] = resolveSessionId(context, sessionId);
//TODO: update native initialization when information about hardware init failure
// due to capture device already open is available.
@@ -624,15 +630,15 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
/**
* Sets the context the record belongs to. This context will be used to pull information,
* such as {@link android.content.AttributionSource}, which will be associated with
* the AudioRecord. However, the context itself will not be retained by the AudioRecord.
* such as {@link android.content.AttributionSource} and device specific session ids,
* which will be associated with the {@link AudioRecord} the AudioRecord.
* However, the context itself will not be retained by the AudioRecord.
* @param context a non-null {@link Context} instance
* @return the same Builder instance.
*/
public @NonNull Builder setContext(@NonNull Context context) {
Objects.requireNonNull(context);
// keep reference, we only copy the data when building
mContext = context;
mContext = Objects.requireNonNull(context);
return this;
}
@@ -746,6 +752,9 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
/**
* @hide
* To be only used by system components.
*
* Note, that if there's a device specific session id asociated with the context, explicitly
* setting a session id using this method will override it.
* @param sessionId ID of audio session the AudioRecord must be attached to, or
* {@link AudioManager#AUDIO_SESSION_ID_GENERATE} if the session isn't known at
* construction time.
@@ -983,6 +992,45 @@ public class AudioRecord implements AudioRouting, MicrophoneDirection,
}
}
/**
* Helper method to resolve session id to be used for AudioRecord initialization.
*
* This method will assign session id in following way:
* 1. Explicitly requested session id has the highest priority, if there is one,
* it will be used.
* 2. If there's device-specific session id asociated with the provided context,
* it will be used.
* 3. Otherwise {@link AUDIO_SESSION_ID_GENERATE} is returned.
*
* @param context {@link Context} to use for extraction of device specific session id.
* @param requestedSessionId explicitly requested session id or AUDIO_SESSION_ID_GENERATE.
* @return session id to be passed to AudioService for the {@link AudioRecord} instance given
* provided {@link Context} instance and explicitly requested session id.
*/
private static int resolveSessionId(@Nullable Context context, int requestedSessionId) {
if (requestedSessionId != AUDIO_SESSION_ID_GENERATE) {
// Use explicitly requested session id.
return requestedSessionId;
}
if (context == null) {
return AUDIO_SESSION_ID_GENERATE;
}
int deviceId = context.getDeviceId();
if (deviceId == DEVICE_ID_DEFAULT) {
return AUDIO_SESSION_ID_GENERATE;
}
VirtualDeviceManager vdm = context.getSystemService(VirtualDeviceManager.class);
if (vdm == null || vdm.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)
== DEVICE_POLICY_DEFAULT) {
return AUDIO_SESSION_ID_GENERATE;
}
return vdm.getAudioRecordingSessionId(deviceId);
}
// Convenience method for the constructor's parameter checks.
// This, getChannelMaskFromLegacyConfig and audioBuffSizeCheck are where constructor
// IllegalArgumentException-s are thrown

View File

@@ -0,0 +1,149 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mediaframeworktest.unit;
import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_CUSTOM;
import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_AUDIO;
import static android.media.AudioManager.AUDIO_SESSION_ID_GENERATE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.companion.virtual.VirtualDeviceManager;
import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.test.mock.MockContext;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class AudioRecordUnitTest {
private static final int TEST_SAMPLE_RATE = 44000;
private static final int TEST_VIRTUAL_DEVICE_ID = 42;
private static final AudioFormat TEST_AUDIO_FORMAT = new AudioFormat.Builder().setSampleRate(
TEST_SAMPLE_RATE).setEncoding(AudioFormat.ENCODING_PCM_16BIT).setChannelMask(
AudioFormat.CHANNEL_IN_MONO).build();
private static final int TEST_BUFFER_SIZE = AudioRecord.getMinBufferSize(
TEST_AUDIO_FORMAT.getSampleRate(),
TEST_AUDIO_FORMAT.getChannelMask(), TEST_AUDIO_FORMAT.getEncoding());
@Test
public void testBuilderConstructionWithContext_defaultDeviceExplicitSessionId() {
Context mockDefaultDeviceContext = getVirtualDeviceMockContext(
DEVICE_ID_DEFAULT, /*vdm=*/null);
int sessionId = getContext().getSystemService(AudioManager.class).generateAudioSessionId();
AudioRecord audioRecord = new AudioRecord.Builder().setContext(
mockDefaultDeviceContext).setAudioSource(
MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).setSessionId(
sessionId).build();
assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
assertEquals(sessionId, audioRecord.getAudioSessionId());
}
@Test
public void testBuilderConstructionWithContext_virtualDeviceDefaultAudioPolicy() {
int vdmPlaybackSessionId = getContext().getSystemService(
AudioManager.class).generateAudioSessionId();
VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
vdmPlaybackSessionId, DEVICE_POLICY_DEFAULT);
Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
AudioRecord audioRecord = new AudioRecord.Builder().setContext(
virtualDeviceContext).setAudioSource(
MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
assertNotEquals(vdmPlaybackSessionId, audioRecord.getAudioSessionId());
}
@Test
public void testBuilderConstructionWithContext_virtualDeviceCustomAudioPolicy() {
int vdmRecordingSessionId = getContext().getSystemService(
AudioManager.class).generateAudioSessionId();
VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
vdmRecordingSessionId, DEVICE_POLICY_CUSTOM);
Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
AudioRecord audioRecord = new AudioRecord.Builder().setContext(
virtualDeviceContext).setAudioSource(
MediaRecorder.AudioSource.DEFAULT).setAudioFormat(
TEST_AUDIO_FORMAT).setBufferSizeInBytes(TEST_BUFFER_SIZE).build();
assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
assertEquals(vdmRecordingSessionId, audioRecord.getAudioSessionId());
}
@Test
public void testBuilderConstructionWithContext_virtualDeviceSetSessionIdOverridesContext() {
int vdmRecordingSessionId = getContext().getSystemService(
AudioManager.class).generateAudioSessionId();
int anotherSessionId = getContext().getSystemService(
AudioManager.class).generateAudioSessionId();
VirtualDeviceManager mockVdm = getMockVirtualDeviceManager(TEST_VIRTUAL_DEVICE_ID,
vdmRecordingSessionId, DEVICE_POLICY_CUSTOM);
Context virtualDeviceContext = getVirtualDeviceMockContext(TEST_VIRTUAL_DEVICE_ID, mockVdm);
AudioRecord audioRecord = new AudioRecord.Builder().setContext(
virtualDeviceContext).setAudioSource(
MediaRecorder.AudioSource.DEFAULT).setAudioSource(
MediaRecorder.AudioSource.DEFAULT).setBufferSizeInBytes(
TEST_BUFFER_SIZE).setSessionId(
anotherSessionId).build();
assertEquals(AudioRecord.STATE_INITIALIZED, audioRecord.getState());
assertEquals(anotherSessionId, audioRecord.getAudioSessionId());
}
private Context getContext() {
return InstrumentationRegistry.getInstrumentation().getContext();
}
private Context getVirtualDeviceMockContext(int deviceId, VirtualDeviceManager vdm) {
MockContext mockContext = mock(MockContext.class);
when(mockContext.getDeviceId()).thenReturn(deviceId);
when(mockContext.getSystemService(VirtualDeviceManager.class)).thenReturn(vdm);
when(mockContext.getAttributionSource()).thenReturn(getContext().getAttributionSource());
return mockContext;
}
private static VirtualDeviceManager getMockVirtualDeviceManager(int deviceId,
int recordingSessionId, int audioDevicePolicy) {
VirtualDeviceManager vdmMock = mock(VirtualDeviceManager.class);
when(vdmMock.getAudioRecordingSessionId(anyInt())).thenReturn(AUDIO_SESSION_ID_GENERATE);
when(vdmMock.getAudioRecordingSessionId(deviceId)).thenReturn(recordingSessionId);
when(vdmMock.getDevicePolicy(anyInt(), anyInt())).thenReturn(DEVICE_POLICY_DEFAULT);
when(vdmMock.getDevicePolicy(deviceId, POLICY_TYPE_AUDIO)).thenReturn(audioDevicePolicy);
return vdmMock;
}
}