| /* |
| * Copyright (C) 2016 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 android.media.cts; |
| |
| import android.content.pm.PackageManager; |
| import android.cts.util.CtsAndroidTestCase; |
| import android.media.AudioDeviceInfo; |
| import android.media.AudioFormat; |
| import android.media.AudioManager; |
| import android.media.AudioRecord; |
| import android.media.AudioRecordingConfiguration; |
| import android.media.MediaRecorder; |
| import android.os.Looper; |
| import android.util.Log; |
| |
| public class AudioRecordingConfigurationTest extends CtsAndroidTestCase { |
| private final static String TAG = "AudioRecordingConfigurationTest"; |
| |
| private final static int TEST_SAMPLE_RATE = 16000; |
| private final static int TEST_AUDIO_SOURCE = MediaRecorder.AudioSource.VOICE_RECOGNITION; |
| |
| private final static int TEST_TIMING_TOLERANCE_MS = 70; |
| |
| private AudioRecord mAudioRecord; |
| private Looper mLooper; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| if (!hasMicrophone()) { |
| return; |
| } |
| |
| /* |
| * InstrumentationTestRunner.onStart() calls Looper.prepare(), which creates a looper |
| * for the current thread. However, since we don't actually call loop() in the test, |
| * any messages queued with that looper will never be consumed. Therefore, we must |
| * create the instance in another thread, either without a looper, so the main looper is |
| * used, or with an active looper. |
| */ |
| Thread t = new Thread() { |
| @Override |
| public void run() { |
| Looper.prepare(); |
| mLooper = Looper.myLooper(); |
| synchronized(this) { |
| mAudioRecord = new AudioRecord.Builder() |
| .setAudioSource(TEST_AUDIO_SOURCE) |
| .setAudioFormat(new AudioFormat.Builder() |
| .setEncoding(AudioFormat.ENCODING_PCM_16BIT) |
| .setSampleRate(TEST_SAMPLE_RATE) |
| .setChannelMask(AudioFormat.CHANNEL_IN_MONO) |
| .build()) |
| .build(); |
| this.notify(); |
| } |
| Looper.loop(); |
| } |
| }; |
| synchronized(t) { |
| t.start(); // will block until we wait |
| t.wait(); |
| } |
| assertNotNull(mAudioRecord); |
| assertNotNull(mLooper); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| if (hasMicrophone()) { |
| mAudioRecord.stop(); |
| mAudioRecord.release(); |
| mLooper.quit(); |
| } |
| super.tearDown(); |
| } |
| |
| // start a recording and verify it is seen as an active recording |
| public void testAudioManagerGetActiveRecordConfigurations() throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| AudioManager am = new AudioManager(getContext()); |
| assertNotNull("Could not create AudioManager", am); |
| |
| AudioRecordingConfiguration[] configs = am.getActiveRecordingConfigurations(); |
| assertNotNull("Invalid null array of record configurations before recording", configs); |
| |
| assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState()); |
| mAudioRecord.startRecording(); |
| assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState()); |
| Thread.sleep(TEST_TIMING_TOLERANCE_MS); |
| |
| // recording is active, verify there is an active record configuration |
| configs = am.getActiveRecordingConfigurations(); |
| assertNotNull("Invalid null array of record configurations during recording", configs); |
| assertTrue("no active record configurations (empty array) during recording", |
| configs.length > 0); |
| final int nbConfigsDuringRecording = configs.length; |
| |
| // verify our recording shows as one of the recording configs |
| assertTrue("Test source/session not amongst active record configurations", |
| verifyAudioConfig(TEST_AUDIO_SOURCE, mAudioRecord.getAudioSessionId(), |
| mAudioRecord.getFormat(), mAudioRecord.getRoutedDevice(), configs)); |
| |
| // stopping recording: verify there are less active record configurations |
| mAudioRecord.stop(); |
| Thread.sleep(TEST_TIMING_TOLERANCE_MS); |
| configs = am.getActiveRecordingConfigurations(); |
| assertTrue("end of recording not reported in record configs", |
| configs.length < nbConfigsDuringRecording); |
| } |
| |
| public void testCallback() throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| AudioManager am = new AudioManager(getContext()); |
| assertNotNull("Could not create AudioManager", am); |
| |
| MyAudioRecordingCallback callback = new MyAudioRecordingCallback( |
| mAudioRecord.getAudioSessionId(), TEST_AUDIO_SOURCE); |
| am.registerAudioRecordingCallback(callback, null /*handler*/); |
| |
| assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState()); |
| mAudioRecord.startRecording(); |
| assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState()); |
| Thread.sleep(TEST_TIMING_TOLERANCE_MS); |
| |
| assertTrue("AudioRecordingCallback not called", callback.mCalled); |
| assertTrue("Expected record configuration was not found", callback.mParamMatch); |
| |
| // stopping recording: callback is called with no match |
| callback.reset(); |
| mAudioRecord.stop(); |
| Thread.sleep(TEST_TIMING_TOLERANCE_MS); |
| assertTrue("AudioRecordingCallback not called", callback.mCalled); |
| assertFalse("Should not have found test record configuration", callback.mParamMatch); |
| |
| // unregister callback and start recording again |
| am.unregisterAudioRecordingCallback(callback); |
| callback.reset(); |
| mAudioRecord.startRecording(); |
| Thread.sleep(TEST_TIMING_TOLERANCE_MS); |
| assertFalse("Unregistered callback was called", callback.mCalled); |
| } |
| |
| class MyAudioRecordingCallback extends AudioManager.AudioRecordingCallback { |
| boolean mCalled = false; |
| boolean mParamMatch = false; |
| final AudioManager mAM; |
| final int mTestSource; |
| final int mTestSession; |
| |
| void reset() { |
| mCalled = false; |
| mParamMatch = false; |
| } |
| |
| MyAudioRecordingCallback(int session, int source) { |
| mAM = new AudioManager(getContext()); |
| mTestSource = source; |
| mTestSession = session; |
| } |
| |
| @Override |
| public void onRecordConfigChanged(AudioRecordingConfiguration[] configs) { |
| mCalled = true; |
| mParamMatch = verifyAudioConfig(mTestSource, mTestSession, mAudioRecord.getFormat(), |
| mAudioRecord.getRoutedDevice(), configs); |
| } |
| } |
| |
| private static boolean deviceMatch(AudioDeviceInfo devJoe, AudioDeviceInfo devJeff) { |
| return ((devJoe.getId() == devJeff.getId() |
| && (devJoe.getAddress() == devJeff.getAddress()) |
| && (devJoe.getType() == devJeff.getType()))); |
| } |
| |
| private static boolean verifyAudioConfig(int source, int session, AudioFormat format, |
| AudioDeviceInfo device, AudioRecordingConfiguration[] configs) { |
| for (int i = 0 ; i < configs.length ; i++) { |
| if ((configs[i].getClientAudioSource() == source) |
| && (configs[i].getClientAudioSessionId() == session) |
| // test the client format matches that requested (same as the AudioRecord's) |
| && (configs[i].getClientFormat().getEncoding() == format.getEncoding()) |
| && (configs[i].getClientFormat().getSampleRate() == format.getSampleRate()) |
| && (configs[i].getClientFormat().getChannelMask() == format.getChannelMask()) |
| && (configs[i].getClientFormat().getChannelIndexMask() == |
| format.getChannelIndexMask()) |
| // test the device format is configured |
| && (configs[i].getFormat().getEncoding() != AudioFormat.ENCODING_INVALID) |
| && (configs[i].getFormat().getSampleRate() > 0) |
| // for the channel mask, either the position or index-based value must be valid |
| && ((configs[i].getFormat().getChannelMask() != AudioFormat.CHANNEL_INVALID) |
| || (configs[i].getFormat().getChannelIndexMask() != |
| AudioFormat.CHANNEL_INVALID)) |
| && deviceMatch(device, configs[i].getAudioDevice())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean hasMicrophone() { |
| return getContext().getPackageManager().hasSystemFeature( |
| PackageManager.FEATURE_MICROPHONE); |
| } |
| } |