| /* |
| * Copyright (C) 2009 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 java.nio.ByteBuffer; |
| import java.util.ArrayList; |
| |
| import android.content.pm.PackageManager; |
| import android.cts.util.CtsAndroidTestCase; |
| import android.media.AudioFormat; |
| import android.media.AudioManager; |
| import android.media.AudioRecord; |
| import android.media.AudioRecord.OnRecordPositionUpdateListener; |
| import android.media.AudioTrack; |
| import android.media.MediaRecorder; |
| import android.media.MediaSyncEvent; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.util.Log; |
| |
| import com.android.cts.util.ReportLog; |
| import com.android.cts.util.ResultType; |
| import com.android.cts.util.ResultUnit; |
| |
| public class AudioRecordTest extends CtsAndroidTestCase { |
| private final static String TAG = "AudioRecordTest"; |
| private AudioRecord mAudioRecord; |
| private int mHz = 44100; |
| private boolean mIsOnMarkerReachedCalled; |
| private boolean mIsOnPeriodicNotificationCalled; |
| private boolean mIsHandleMessageCalled; |
| private Looper mLooper; |
| // For doTest |
| private int mMarkerPeriodInFrames; |
| private int mMarkerPosition; |
| private Handler mHandler = new Handler(Looper.getMainLooper()) { |
| @Override |
| public void handleMessage(Message msg) { |
| mIsHandleMessageCalled = true; |
| super.handleMessage(msg); |
| } |
| }; |
| |
| @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(MediaRecorder.AudioSource.DEFAULT, mHz, |
| AudioFormat.CHANNEL_CONFIGURATION_MONO, |
| AudioFormat.ENCODING_PCM_16BIT, |
| AudioRecord.getMinBufferSize(mHz, |
| AudioFormat.CHANNEL_CONFIGURATION_MONO, |
| AudioFormat.ENCODING_PCM_16BIT) * 10); |
| this.notify(); |
| } |
| Looper.loop(); |
| } |
| }; |
| synchronized(t) { |
| t.start(); // will block until we wait |
| t.wait(); |
| } |
| assertNotNull(mAudioRecord); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| if (hasMicrophone()) { |
| mAudioRecord.release(); |
| mLooper.quit(); |
| } |
| super.tearDown(); |
| } |
| |
| private void reset() { |
| mIsOnMarkerReachedCalled = false; |
| mIsOnPeriodicNotificationCalled = false; |
| mIsHandleMessageCalled = false; |
| } |
| |
| public void testAudioRecordProperties() throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| assertEquals(AudioFormat.ENCODING_PCM_16BIT, mAudioRecord.getAudioFormat()); |
| assertEquals(MediaRecorder.AudioSource.DEFAULT, mAudioRecord.getAudioSource()); |
| assertEquals(1, mAudioRecord.getChannelCount()); |
| assertEquals(AudioFormat.CHANNEL_IN_MONO, |
| mAudioRecord.getChannelConfiguration()); |
| assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState()); |
| assertEquals(mHz, mAudioRecord.getSampleRate()); |
| assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState()); |
| |
| int bufferSize = AudioRecord.getMinBufferSize(mHz, |
| AudioFormat.CHANNEL_CONFIGURATION_DEFAULT, AudioFormat.ENCODING_PCM_16BIT); |
| assertTrue(bufferSize > 0); |
| } |
| |
| public void testAudioRecordOP() throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| final int SLEEP_TIME = 10; |
| final int RECORD_TIME = 10000; |
| assertEquals(AudioRecord.STATE_INITIALIZED, mAudioRecord.getState()); |
| |
| int markerInFrames = mAudioRecord.getSampleRate() / 2; |
| assertEquals(AudioRecord.SUCCESS, |
| mAudioRecord.setNotificationMarkerPosition(markerInFrames)); |
| assertEquals(markerInFrames, mAudioRecord.getNotificationMarkerPosition()); |
| int periodInFrames = mAudioRecord.getSampleRate(); |
| assertEquals(AudioRecord.SUCCESS, |
| mAudioRecord.setPositionNotificationPeriod(periodInFrames)); |
| assertEquals(periodInFrames, mAudioRecord.getPositionNotificationPeriod()); |
| OnRecordPositionUpdateListener listener = new OnRecordPositionUpdateListener() { |
| |
| public void onMarkerReached(AudioRecord recorder) { |
| mIsOnMarkerReachedCalled = true; |
| } |
| |
| public void onPeriodicNotification(AudioRecord recorder) { |
| mIsOnPeriodicNotificationCalled = true; |
| } |
| }; |
| mAudioRecord.setRecordPositionUpdateListener(listener); |
| |
| // use byte array as buffer |
| final int BUFFER_SIZE = 102400; |
| byte[] byteData = new byte[BUFFER_SIZE]; |
| long time = System.currentTimeMillis(); |
| mAudioRecord.startRecording(); |
| assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState()); |
| while (System.currentTimeMillis() - time < RECORD_TIME) { |
| Thread.sleep(SLEEP_TIME); |
| mAudioRecord.read(byteData, 0, BUFFER_SIZE); |
| } |
| mAudioRecord.stop(); |
| assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState()); |
| assertTrue(mIsOnMarkerReachedCalled); |
| assertTrue(mIsOnPeriodicNotificationCalled); |
| reset(); |
| |
| // use short array as buffer |
| short[] shortData = new short[BUFFER_SIZE]; |
| time = System.currentTimeMillis(); |
| mAudioRecord.startRecording(); |
| assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState()); |
| while (System.currentTimeMillis() - time < RECORD_TIME) { |
| Thread.sleep(SLEEP_TIME); |
| mAudioRecord.read(shortData, 0, BUFFER_SIZE); |
| } |
| mAudioRecord.stop(); |
| assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState()); |
| assertTrue(mIsOnMarkerReachedCalled); |
| assertTrue(mIsOnPeriodicNotificationCalled); |
| reset(); |
| |
| // use ByteBuffer as buffer |
| ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER_SIZE); |
| time = System.currentTimeMillis(); |
| mAudioRecord.startRecording(); |
| assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState()); |
| while (System.currentTimeMillis() - time < RECORD_TIME) { |
| Thread.sleep(SLEEP_TIME); |
| mAudioRecord.read(byteBuffer, BUFFER_SIZE); |
| } |
| mAudioRecord.stop(); |
| assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState()); |
| assertTrue(mIsOnMarkerReachedCalled); |
| assertTrue(mIsOnPeriodicNotificationCalled); |
| reset(); |
| |
| // use handler |
| final Handler handler = new Handler(Looper.getMainLooper()) { |
| @Override |
| public void handleMessage(Message msg) { |
| mIsHandleMessageCalled = true; |
| super.handleMessage(msg); |
| } |
| }; |
| |
| mAudioRecord.setRecordPositionUpdateListener(listener, handler); |
| time = System.currentTimeMillis(); |
| mAudioRecord.startRecording(); |
| assertEquals(AudioRecord.RECORDSTATE_RECORDING, mAudioRecord.getRecordingState()); |
| while (System.currentTimeMillis() - time < RECORD_TIME) { |
| Thread.sleep(SLEEP_TIME); |
| mAudioRecord.read(byteData, 0, BUFFER_SIZE); |
| } |
| mAudioRecord.stop(); |
| assertEquals(AudioRecord.RECORDSTATE_STOPPED, mAudioRecord.getRecordingState()); |
| assertTrue(mIsOnMarkerReachedCalled); |
| assertTrue(mIsOnPeriodicNotificationCalled); |
| // The handler argument is only ever used for getting the associated Looper |
| assertFalse(mIsHandleMessageCalled); |
| |
| mAudioRecord.release(); |
| assertEquals(AudioRecord.STATE_UNINITIALIZED, mAudioRecord.getState()); |
| } |
| |
| public void testAudioRecordResamplerMono8Bit() throws Exception { |
| doTest("ResamplerResamplerMono8Bit", true /*localRecord*/, false /*customHandler*/, |
| 1 /*periodsPerSecond*/, 1 /*markerPeriodsPerSecond*/, |
| false /*useByteBuffer*/, false /*blocking*/, |
| false /*auditRecording*/, false /*isChannelIndex*/, 88200 /*TEST_SR*/, |
| AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_8BIT); |
| } |
| |
| public void testAudioRecordResamplerStereo8Bit() throws Exception { |
| doTest("ResamplerStereo8Bit", true /*localRecord*/, false /*customHandler*/, |
| 0 /*periodsPerSecond*/, 3 /*markerPeriodsPerSecond*/, |
| true /*useByteBuffer*/, true /*blocking*/, |
| false /*auditRecording*/, false /*isChannelIndex*/, 45000 /*TEST_SR*/, |
| AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_8BIT); |
| } |
| |
| public void testAudioRecordLocalMono16Bit() throws Exception { |
| doTest("LocalMono16Bit", true /*localRecord*/, false /*customHandler*/, |
| 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, |
| false /*useByteBuffer*/, true /*blocking*/, |
| false /*auditRecording*/, false /*isChannelIndex*/, 8000 /*TEST_SR*/, |
| AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT); |
| } |
| |
| public void testAudioRecordStereo16Bit() throws Exception { |
| doTest("Stereo16Bit", false /*localRecord*/, false /*customHandler*/, |
| 2 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, |
| false /*useByteBuffer*/, false /*blocking*/, |
| false /*auditRecording*/, false /*isChannelIndex*/, 17000 /*TEST_SR*/, |
| AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_16BIT); |
| } |
| |
| public void testAudioRecordMonoFloat() throws Exception { |
| doTest("MonoFloat", false /*localRecord*/, true /*customHandler*/, |
| 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, |
| false /*useByteBuffer*/, true /*blocking*/, |
| false /*auditRecording*/, false /*isChannelIndex*/, 32000 /*TEST_SR*/, |
| AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_FLOAT); |
| } |
| |
| public void testAudioRecordLocalNonblockingStereoFloat() throws Exception { |
| doTest("LocalNonblockingStereoFloat", true /*localRecord*/, true /*customHandler*/, |
| 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/, |
| false /*useByteBuffer*/, false /*blocking*/, |
| false /*auditRecording*/, false /*isChannelIndex*/, 48000 /*TEST_SR*/, |
| AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_FLOAT); |
| } |
| |
| // Audit modes work best with non-blocking mode |
| public void testAudioRecordAuditByteBufferResamplerStereoFloat() throws Exception { |
| doTest("AuditByteBufferResamplerStereoFloat", |
| false /*localRecord*/, true /*customHandler*/, |
| 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/, |
| true /*useByteBuffer*/, false /*blocking*/, |
| true /*auditRecording*/, false /*isChannelIndex*/, 96000 /*TEST_SR*/, |
| AudioFormat.CHANNEL_IN_STEREO, AudioFormat.ENCODING_PCM_FLOAT); |
| } |
| |
| public void testAudioRecordAuditChannelIndexMonoFloat() throws Exception { |
| doTest("AuditChannelIndexMonoFloat", true /*localRecord*/, true /*customHandler*/, |
| 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/, |
| false /*useByteBuffer*/, false /*blocking*/, |
| true /*auditRecording*/, true /*isChannelIndex*/, 47000 /*TEST_SR*/, |
| (1 << 0) /* 1 channel */, AudioFormat.ENCODING_PCM_FLOAT); |
| } |
| |
| // Audit buffers can run out of space with high sample rate, |
| // so keep the channels and pcm encoding low |
| public void testAudioRecordAuditChannelIndex2() throws Exception { |
| doTest("AuditChannelIndex2", true /*localRecord*/, true /*customHandler*/, |
| 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/, |
| false /*useByteBuffer*/, false /*blocking*/, |
| true /*auditRecording*/, true /*isChannelIndex*/, 192000 /*TEST_SR*/, |
| (1 << 0) | (1 << 2) /* 2 channels, gap in middle */, |
| AudioFormat.ENCODING_PCM_8BIT); |
| } |
| |
| // Audit buffers can run out of space with high numbers of channels, |
| // so keep the sample rate low. |
| public void testAudioRecordAuditChannelIndex5() throws Exception { |
| doTest("AuditChannelIndex5", true /*localRecord*/, true /*customHandler*/, |
| 2 /*periodsPerSecond*/, 0 /*markerPeriodsPerSecond*/, |
| false /*useByteBuffer*/, false /*blocking*/, |
| true /*auditRecording*/, true /*isChannelIndex*/, 16000 /*TEST_SR*/, |
| (1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4) /* 5 channels */, |
| AudioFormat.ENCODING_PCM_16BIT); |
| } |
| |
| // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord built with |
| // an empty Builder matches the documentation / expected values |
| public void testAudioRecordBuilderDefault() throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| // constants for test |
| final String TEST_NAME = "testAudioRecordBuilderDefault"; |
| // expected values below match the AudioRecord.Builder documentation |
| final int expectedCapturePreset = MediaRecorder.AudioSource.DEFAULT; |
| final String rateStr = new AudioManager(getContext()) |
| .getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE); |
| final int expectedRate = Integer.valueOf(rateStr).intValue(); |
| final int expectedChannel = AudioFormat.CHANNEL_IN_MONO; |
| final int expectedEncoding = AudioFormat.ENCODING_PCM_16BIT; |
| final int expectedState = AudioRecord.STATE_INITIALIZED; |
| // use builder with default values |
| final AudioRecord rec = new AudioRecord.Builder().build(); |
| // save results |
| final int observedRate = rec.getSampleRate(); |
| final int observedSource = rec.getAudioSource(); |
| final int observedChannel = rec.getChannelConfiguration(); |
| final int observedEncoding = rec.getAudioFormat(); |
| final int observedState = rec.getState(); |
| // release recorder before the test exits (either successfully or with an exception) |
| rec.release(); |
| // compare results |
| assertEquals(TEST_NAME + ": default capture preset", expectedCapturePreset, observedSource); |
| assertEquals(TEST_NAME + ": default rate", expectedRate, observedRate); |
| assertEquals(TEST_NAME + ": default channel config", expectedChannel, observedChannel); |
| assertEquals(TEST_NAME + ": default encoding", expectedEncoding, observedEncoding); |
| assertEquals(TEST_NAME + ": state", expectedState, observedState); |
| } |
| |
| // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord built with |
| // an incomplete AudioFormat matches the documentation / expected values |
| public void testAudioRecordBuilderPartialFormat() throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| // constants for test |
| final String TEST_NAME = "testAudioRecordBuilderPartialFormat"; |
| final int expectedRate = 16000; |
| final int expectedState = AudioRecord.STATE_INITIALIZED; |
| // expected values below match the AudioRecord.Builder documentation |
| final int expectedChannel = AudioFormat.CHANNEL_IN_MONO; |
| final int expectedEncoding = AudioFormat.ENCODING_PCM_16BIT; |
| // use builder with a partial audio format |
| final AudioRecord rec = new AudioRecord.Builder() |
| .setAudioFormat(new AudioFormat.Builder().setSampleRate(expectedRate).build()) |
| .build(); |
| // save results |
| final int observedRate = rec.getSampleRate(); |
| final int observedChannel = rec.getChannelConfiguration(); |
| final int observedEncoding = rec.getAudioFormat(); |
| final int observedState = rec.getState(); |
| // release recorder before the test exits (either successfully or with an exception) |
| rec.release(); |
| // compare results |
| assertEquals(TEST_NAME + ": configured rate", expectedRate, observedRate); |
| assertEquals(TEST_NAME + ": default channel config", expectedChannel, observedChannel); |
| assertEquals(TEST_NAME + ": default encoding", expectedEncoding, observedEncoding); |
| assertEquals(TEST_NAME + ": state", expectedState, observedState); |
| } |
| |
| // Test AudioRecord.Builder to verify the observed configuration of an AudioRecord matches |
| // the parameters used in the builder |
| public void testAudioRecordBuilderParams() throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| // constants for test |
| final String TEST_NAME = "testAudioRecordBuilderParams"; |
| final int expectedRate = 8000; |
| final int expectedChannel = AudioFormat.CHANNEL_IN_MONO; |
| final int expectedChannelCount = 1; |
| final int expectedEncoding = AudioFormat.ENCODING_PCM_16BIT; |
| final int expectedSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION; |
| final int expectedState = AudioRecord.STATE_INITIALIZED; |
| // use builder with expected parameters |
| final AudioRecord rec = new AudioRecord.Builder() |
| .setAudioFormat(new AudioFormat.Builder() |
| .setSampleRate(expectedRate) |
| .setChannelMask(expectedChannel) |
| .setEncoding(expectedEncoding) |
| .build()) |
| .setAudioSource(expectedSource) |
| .build(); |
| // save results |
| final int observedRate = rec.getSampleRate(); |
| final int observedChannel = rec.getChannelConfiguration(); |
| final int observedChannelCount = rec.getChannelCount(); |
| final int observedEncoding = rec.getAudioFormat(); |
| final int observedSource = rec.getAudioSource(); |
| final int observedState = rec.getState(); |
| // release recorder before the test exits (either successfully or with an exception) |
| rec.release(); |
| // compare results |
| assertEquals(TEST_NAME + ": configured rate", expectedRate, observedRate); |
| assertEquals(TEST_NAME + ": configured channel config", expectedChannel, observedChannel); |
| assertEquals(TEST_NAME + ": configured encoding", expectedEncoding, observedEncoding); |
| assertEquals(TEST_NAME + ": implicit channel count", expectedChannelCount, |
| observedChannelCount); |
| assertEquals(TEST_NAME + ": configured source", expectedSource, observedSource); |
| assertEquals(TEST_NAME + ": state", expectedState, observedState); |
| } |
| |
| // Test AudioRecord to ensure we can build after a failure. |
| public void testAudioRecordBufferSize() throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| // constants for test |
| final String TEST_NAME = "testAudioRecordBufferSize"; |
| |
| // use builder with parameters that should fail |
| final int superBigBufferSize = 1 << 28; |
| try { |
| final AudioRecord record = new AudioRecord.Builder() |
| .setBufferSizeInBytes(superBigBufferSize) |
| .build(); |
| record.release(); |
| fail(TEST_NAME + ": should throw exception on failure"); |
| } catch (UnsupportedOperationException e) { |
| ; |
| } |
| |
| // we should be able to create again with minimum buffer size |
| final int verySmallBufferSize = 2 * 3 * 4; // frame size multiples |
| final AudioRecord record2 = new AudioRecord.Builder() |
| .setBufferSizeInBytes(verySmallBufferSize) |
| .build(); |
| |
| final int observedState2 = record2.getState(); |
| final int observedBufferSize2 = record2.getBufferSizeInFrames(); |
| record2.release(); |
| |
| // succeeds for minimum buffer size |
| assertEquals(TEST_NAME + ": state", AudioRecord.STATE_INITIALIZED, observedState2); |
| // should force the minimum size buffer which is > 0 |
| assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0); |
| } |
| |
| public void testSynchronizedRecord() throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| final String TEST_NAME = "testSynchronizedRecord"; |
| AudioTrack track = null; |
| AudioRecord record = null; |
| |
| try { |
| // 1. create a static AudioTrack. |
| final int PLAYBACK_TIME_IN_MS = 2000; /* ms duration. */ |
| final int PLAYBACK_SAMPLE_RATE = 8000; /* in hz */ |
| AudioFormat format = new AudioFormat.Builder() |
| .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) |
| .setEncoding(AudioFormat.ENCODING_PCM_8BIT) |
| .setSampleRate(PLAYBACK_SAMPLE_RATE) |
| .build(); |
| final int frameCount = AudioHelper.frameCountFromMsec(PLAYBACK_TIME_IN_MS, format); |
| final int frameSize = AudioHelper.frameSizeFromFormat(format); |
| track = new AudioTrack.Builder() |
| .setAudioFormat(format) |
| .setBufferSizeInBytes(frameCount * frameSize) |
| .setTransferMode(AudioTrack.MODE_STATIC) |
| .build(); |
| // create float array and write it |
| final int sampleCount = frameCount * format.getChannelCount(); |
| byte[] vab = AudioHelper.createSoundDataInByteArray( |
| sampleCount, PLAYBACK_SAMPLE_RATE, 600 /* frequency */, 0 /* sweep */); |
| assertEquals(TEST_NAME, vab.length, |
| track.write(vab, 0 /* offsetInBytes */, vab.length, |
| AudioTrack.WRITE_NON_BLOCKING)); |
| final int trackSessionId = track.getAudioSessionId(); |
| |
| // 2. create an AudioRecord to sync off of AudioTrack completion. |
| final int RECORD_TIME_IN_MS = 2000; |
| final int RECORD_ENCODING = AudioFormat.ENCODING_PCM_16BIT; |
| final int RECORD_CHANNEL_MASK = AudioFormat.CHANNEL_IN_STEREO; |
| final int RECORD_SAMPLE_RATE = 44100; |
| record = new AudioRecord.Builder() |
| .setAudioFormat(new AudioFormat.Builder() |
| .setSampleRate(RECORD_SAMPLE_RATE) |
| .setChannelMask(RECORD_CHANNEL_MASK) |
| .setEncoding(RECORD_ENCODING) |
| .build()) |
| .build(); |
| // AudioRecord creation may have silently failed, check state now |
| assertEquals(TEST_NAME, AudioRecord.STATE_INITIALIZED, record.getState()); |
| |
| // 3. create a MediaSyncEvent |
| // This MediaSyncEvent checks playback completion of an AudioTrack |
| // (or MediaPlayer, or ToneGenerator) based on its audio session id. |
| // |
| // Note: when synchronizing record from a MediaSyncEvent |
| // (1) You need to be "close" to the end of the associated AudioTrack. |
| // If the track does not complete in 30 seconds, recording begins regardless. |
| // (actual delay limit may vary). |
| // |
| // (2) Track completion may be triggered by pause() as well as stop() |
| // or when a static AudioTrack completes playback. |
| // |
| final int eventType = MediaSyncEvent.SYNC_EVENT_PRESENTATION_COMPLETE; |
| MediaSyncEvent event = MediaSyncEvent.createEvent(eventType) |
| .setAudioSessionId(trackSessionId); |
| assertEquals(TEST_NAME, trackSessionId, event.getAudioSessionId()); |
| assertEquals(TEST_NAME, eventType, event.getType()); |
| |
| // 4. now set the AudioTrack playing and start the recording synchronized |
| track.play(); |
| // start recording. Recording state turns to RECORDSTATE_RECORDING immediately |
| // but the data read() only occurs after the AudioTrack completes. |
| record.startRecording(event); |
| assertEquals(TEST_NAME, |
| AudioRecord.RECORDSTATE_RECORDING, record.getRecordingState()); |
| long startTime = System.currentTimeMillis(); |
| |
| // 5. get record data. |
| // For our tests, we could set test duration by timed sleep or by # frames received. |
| // Since we don't know *exactly* when AudioRecord actually begins recording, |
| // we end the test by # frames read. |
| final int numChannels = |
| AudioFormat.channelCountFromInChannelMask(RECORD_CHANNEL_MASK); |
| final int bytesPerSample = AudioFormat.getBytesPerSample(RECORD_ENCODING); |
| final int bytesPerFrame = numChannels * bytesPerSample; |
| // careful about integer overflow in the formula below: |
| final int targetSamples = |
| (int)((long)RECORD_TIME_IN_MS * RECORD_SAMPLE_RATE * numChannels / 1000); |
| final int BUFFER_FRAMES = 512; |
| final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels; |
| |
| // After starting, there is no guarantee when the first frame of data is read. |
| long firstSampleTime = 0; |
| int samplesRead = 0; |
| |
| // For 16 bit data, use shorts |
| short[] shortData = new short[BUFFER_SAMPLES]; |
| while (samplesRead < targetSamples) { |
| // the first time through, we read a single frame. |
| // this sets the recording anchor position. |
| int amount = samplesRead == 0 ? numChannels : |
| Math.min(BUFFER_SAMPLES, targetSamples - samplesRead); |
| int ret = record.read(shortData, 0, amount); |
| assertEquals(TEST_NAME, amount, ret); |
| if (samplesRead == 0 && ret > 0) { |
| firstSampleTime = System.currentTimeMillis(); |
| } |
| samplesRead += ret; |
| // sanity check: elapsed time cannot be more than a second |
| // than what we expect. |
| assertTrue(System.currentTimeMillis() - startTime <= |
| PLAYBACK_TIME_IN_MS + RECORD_TIME_IN_MS + 1000); |
| } |
| |
| // 6. We've read all the frames, now check the timing. |
| final long endTime = System.currentTimeMillis(); |
| //Log.d(TEST_NAME, "first sample time " + (firstSampleTime - startTime) |
| // + " test time " + (endTime - firstSampleTime)); |
| // |
| // Verify recording starts within 400 ms of AudioTrack completion (typical 180ms) |
| // Verify recording completes within 50 ms of expected test time (typical 20ms) |
| assertEquals(TEST_NAME, PLAYBACK_TIME_IN_MS, firstSampleTime - startTime, 400); |
| assertEquals(TEST_NAME, RECORD_TIME_IN_MS, endTime - firstSampleTime, 50); |
| |
| record.stop(); |
| assertEquals(TEST_NAME, AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState()); |
| } finally { |
| if (record != null) { |
| record.release(); |
| record = null; |
| } |
| if (track != null) { |
| track.release(); |
| track = null; |
| } |
| } |
| } |
| |
| private AudioRecord createAudioRecord( |
| int audioSource, int sampleRateInHz, |
| int channelConfig, int audioFormat, int bufferSizeInBytes, |
| boolean auditRecording, boolean isChannelIndex) { |
| final AudioRecord record; |
| if (auditRecording) { |
| record = new AudioHelper.AudioRecordAudit( |
| audioSource, sampleRateInHz, channelConfig, |
| audioFormat, bufferSizeInBytes, isChannelIndex); |
| } else if (isChannelIndex) { |
| record = new AudioRecord.Builder() |
| .setAudioFormat(new AudioFormat.Builder() |
| .setChannelIndexMask(channelConfig) |
| .setEncoding(audioFormat) |
| .setSampleRate(sampleRateInHz) |
| .build()) |
| .setBufferSizeInBytes(bufferSizeInBytes) |
| .build(); |
| } else { |
| record = new AudioRecord(audioSource, sampleRateInHz, channelConfig, |
| audioFormat, bufferSizeInBytes); |
| } |
| |
| // did we get the AudioRecord we expected? |
| final AudioFormat format = record.getFormat(); |
| assertEquals(isChannelIndex ? channelConfig : AudioFormat.CHANNEL_INVALID, |
| format.getChannelIndexMask()); |
| assertEquals(isChannelIndex ? AudioFormat.CHANNEL_INVALID : channelConfig, |
| format.getChannelMask()); |
| assertEquals(audioFormat, format.getEncoding()); |
| assertEquals(sampleRateInHz, format.getSampleRate()); |
| final int frameSize = |
| format.getChannelCount() * AudioFormat.getBytesPerSample(audioFormat); |
| // our native frame count cannot be smaller than our minimum buffer size request. |
| assertTrue(record.getBufferSizeInFrames() * frameSize >= bufferSizeInBytes); |
| return record; |
| } |
| |
| private void doTest(String reportName, boolean localRecord, boolean customHandler, |
| int periodsPerSecond, int markerPeriodsPerSecond, |
| boolean useByteBuffer, boolean blocking, |
| final boolean auditRecording, final boolean isChannelIndex, |
| final int TEST_SR, final int TEST_CONF, final int TEST_FORMAT) throws Exception { |
| if (!hasMicrophone()) { |
| return; |
| } |
| // audit recording plays back recorded audio, so use longer test timing |
| final int TEST_TIME_MS = auditRecording ? 10000 : 2000; |
| final int TEST_SOURCE = MediaRecorder.AudioSource.DEFAULT; |
| mIsHandleMessageCalled = false; |
| |
| // For channelIndex use one frame in bytes for buffer size. |
| // This is adjusted to the minimum buffer size by native code. |
| final int bufferSizeInBytes = isChannelIndex ? |
| (AudioFormat.getBytesPerSample(TEST_FORMAT) |
| * AudioFormat.channelCountFromInChannelMask(TEST_CONF)) : |
| AudioRecord.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| assertTrue(bufferSizeInBytes > 0); |
| |
| final AudioRecord record; |
| final AudioHelper |
| .MakeSomethingAsynchronouslyAndLoop<AudioRecord> makeSomething; |
| |
| if (localRecord) { |
| makeSomething = null; |
| record = createAudioRecord(TEST_SOURCE, TEST_SR, TEST_CONF, |
| TEST_FORMAT, bufferSizeInBytes, auditRecording, isChannelIndex); |
| } else { |
| makeSomething = |
| new AudioHelper.MakeSomethingAsynchronouslyAndLoop<AudioRecord>( |
| new AudioHelper.MakesSomething<AudioRecord>() { |
| @Override |
| public AudioRecord makeSomething() { |
| return createAudioRecord(TEST_SOURCE, TEST_SR, TEST_CONF, |
| TEST_FORMAT, bufferSizeInBytes, auditRecording, |
| isChannelIndex); |
| } |
| } |
| ); |
| // create AudioRecord on different thread's looper. |
| record = makeSomething.make(); |
| } |
| |
| // AudioRecord creation may have silently failed, check state now |
| assertEquals(AudioRecord.STATE_INITIALIZED, record.getState()); |
| |
| final MockOnRecordPositionUpdateListener listener; |
| if (customHandler) { |
| listener = new MockOnRecordPositionUpdateListener(record, mHandler); |
| } else { |
| listener = new MockOnRecordPositionUpdateListener(record); |
| } |
| |
| final int updatePeriodInFrames = (periodsPerSecond == 0) |
| ? 0 : TEST_SR / periodsPerSecond; |
| // After starting, there is no guarantee when the first frame of data is read. |
| long firstSampleTime = 0; |
| |
| // blank final variables: all successful paths will initialize the times. |
| // this must be declared here for visibility as they are set within the try block. |
| final long endTime; |
| final long startTime; |
| final long stopRequestTime; |
| final long stopTime; |
| final long coldInputStartTime; |
| |
| try { |
| if (markerPeriodsPerSecond != 0) { |
| mMarkerPeriodInFrames = TEST_SR / markerPeriodsPerSecond; |
| mMarkerPosition = mMarkerPeriodInFrames; |
| assertEquals(AudioRecord.SUCCESS, |
| record.setNotificationMarkerPosition(mMarkerPosition)); |
| } else { |
| mMarkerPeriodInFrames = 0; |
| } |
| |
| assertEquals(AudioRecord.SUCCESS, |
| record.setPositionNotificationPeriod(updatePeriodInFrames)); |
| |
| listener.start(TEST_SR); |
| record.startRecording(); |
| assertEquals(AudioRecord.RECORDSTATE_RECORDING, record.getRecordingState()); |
| startTime = System.currentTimeMillis(); |
| |
| // For our tests, we could set test duration by timed sleep or by # frames received. |
| // Since we don't know *exactly* when AudioRecord actually begins recording, |
| // we end the test by # frames read. |
| final int numChannels = AudioFormat.channelCountFromInChannelMask(TEST_CONF); |
| final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT); |
| final int bytesPerFrame = numChannels * bytesPerSample; |
| // careful about integer overflow in the formula below: |
| final int targetSamples = (int)((long)TEST_TIME_MS * TEST_SR * numChannels / 1000); |
| final int BUFFER_FRAMES = 512; |
| final int BUFFER_SAMPLES = BUFFER_FRAMES * numChannels; |
| // TODO: verify behavior when buffer size is not a multiple of frame size. |
| |
| int samplesRead = 0; |
| if (useByteBuffer) { |
| ByteBuffer byteBuffer = |
| ByteBuffer.allocateDirect(BUFFER_SAMPLES * bytesPerSample); |
| while (samplesRead < targetSamples) { |
| // the first time through, we read a single frame. |
| // this sets the recording anchor position. |
| int amount = samplesRead == 0 ? numChannels : |
| Math.min(BUFFER_SAMPLES, targetSamples - samplesRead); |
| amount *= bytesPerSample; // in bytes |
| // read always places data at the start of the byte buffer with |
| // position and limit are ignored. test this by setting |
| // position and limit to arbitrary values here. |
| final int lastPosition = 7; |
| final int lastLimit = 13; |
| byteBuffer.position(lastPosition); |
| byteBuffer.limit(lastLimit); |
| int ret = blocking ? record.read(byteBuffer, amount) : |
| record.read(byteBuffer, amount, AudioRecord.READ_NON_BLOCKING); |
| // so long as amount requested in bytes is a multiple of the frame size |
| // we expect the byte buffer request to be filled. Caution: the |
| // byte buffer data will be in native endian order, not Java order. |
| if (blocking) { |
| assertEquals(amount, ret); |
| } else { |
| assertTrue("0 <= " + ret + " <= " + amount, |
| 0 <= ret && ret <= amount); |
| } |
| // position, limit are not changed by read(). |
| assertEquals(lastPosition, byteBuffer.position()); |
| assertEquals(lastLimit, byteBuffer.limit()); |
| if (samplesRead == 0 && ret > 0) { |
| firstSampleTime = System.currentTimeMillis(); |
| } |
| samplesRead += ret / bytesPerSample; |
| } |
| } else { |
| switch (TEST_FORMAT) { |
| case AudioFormat.ENCODING_PCM_8BIT: { |
| // For 8 bit data, use bytes |
| byte[] byteData = new byte[BUFFER_SAMPLES]; |
| while (samplesRead < targetSamples) { |
| // the first time through, we read a single frame. |
| // this sets the recording anchor position. |
| int amount = samplesRead == 0 ? numChannels : |
| Math.min(BUFFER_SAMPLES, targetSamples - samplesRead); |
| int ret = blocking ? record.read(byteData, 0, amount) : |
| record.read(byteData, 0, amount, AudioRecord.READ_NON_BLOCKING); |
| if (blocking) { |
| assertEquals(amount, ret); |
| } else { |
| assertTrue("0 <= " + ret + " <= " + amount, |
| 0 <= ret && ret <= amount); |
| } |
| if (samplesRead == 0 && ret > 0) { |
| firstSampleTime = System.currentTimeMillis(); |
| } |
| samplesRead += ret; |
| } |
| } break; |
| case AudioFormat.ENCODING_PCM_16BIT: { |
| // For 16 bit data, use shorts |
| short[] shortData = new short[BUFFER_SAMPLES]; |
| while (samplesRead < targetSamples) { |
| // the first time through, we read a single frame. |
| // this sets the recording anchor position. |
| int amount = samplesRead == 0 ? numChannels : |
| Math.min(BUFFER_SAMPLES, targetSamples - samplesRead); |
| int ret = blocking ? record.read(shortData, 0, amount) : |
| record.read(shortData, 0, amount, AudioRecord.READ_NON_BLOCKING); |
| if (blocking) { |
| assertEquals(amount, ret); |
| } else { |
| assertTrue("0 <= " + ret + " <= " + amount, |
| 0 <= ret && ret <= amount); |
| } |
| if (samplesRead == 0 && ret > 0) { |
| firstSampleTime = System.currentTimeMillis(); |
| } |
| samplesRead += ret; |
| } |
| } break; |
| case AudioFormat.ENCODING_PCM_FLOAT: { |
| float[] floatData = new float[BUFFER_SAMPLES]; |
| while (samplesRead < targetSamples) { |
| // the first time through, we read a single frame. |
| // this sets the recording anchor position. |
| int amount = samplesRead == 0 ? numChannels : |
| Math.min(BUFFER_SAMPLES, targetSamples - samplesRead); |
| int ret = record.read(floatData, 0, amount, blocking ? |
| AudioRecord.READ_BLOCKING : AudioRecord.READ_NON_BLOCKING); |
| if (blocking) { |
| assertEquals(amount, ret); |
| } else { |
| assertTrue("0 <= " + ret + " <= " + amount, |
| 0 <= ret && ret <= amount); |
| } |
| if (samplesRead == 0 && ret > 0) { |
| firstSampleTime = System.currentTimeMillis(); |
| } |
| samplesRead += ret; |
| } |
| } break; |
| } |
| } |
| |
| // We've read all the frames, now check the record timing. |
| endTime = System.currentTimeMillis(); |
| |
| coldInputStartTime = firstSampleTime - startTime; |
| //Log.d(TAG, "first sample time " + coldInputStartTime |
| // + " test time " + (endTime - firstSampleTime)); |
| |
| if (coldInputStartTime > 200) { |
| Log.w(TAG, "cold input start time way too long " |
| + coldInputStartTime + " > 200ms"); |
| } else if (coldInputStartTime > 100) { |
| Log.w(TAG, "cold input start time too long " |
| + coldInputStartTime + " > 100ms"); |
| } |
| assertTrue(coldInputStartTime < 5000); // must start within 5 seconds. |
| |
| // Verify recording completes within 50 ms of expected test time (typical 20ms) |
| assertEquals(TEST_TIME_MS, endTime - firstSampleTime, auditRecording ? 1000 : 50); |
| |
| // Even though we've read all the frames we want, the events may not be sent to |
| // the listeners (events are handled through a separate internal callback thread). |
| // One must sleep to make sure the last event(s) come in. |
| Thread.sleep(30); |
| |
| stopRequestTime = System.currentTimeMillis(); |
| record.stop(); |
| assertEquals(AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState()); |
| |
| stopTime = System.currentTimeMillis(); |
| |
| // stop listening - we should be done. |
| // Caution M behavior and likely much earlier: |
| // we assume no events can happen after stop(), but this may not |
| // always be true as stop can take 100ms to complete (as it may disable |
| // input recording on the hal); thus the event handler may be block with |
| // valid events, issuing right after stop completes. Except for those events, |
| // no other events should show up after stop. |
| // This behavior may change in the future but we account for it here in testing. |
| listener.stop(); |
| |
| // clean up |
| if (makeSomething != null) { |
| makeSomething.join(); |
| } |
| |
| } finally { |
| listener.release(); |
| // we must release the record immediately as it is a system-wide |
| // resource needed for other tests. |
| record.release(); |
| } |
| if (auditRecording) { // don't check timing if auditing (messes up timing) |
| return; |
| } |
| final int markerPeriods = markerPeriodsPerSecond * TEST_TIME_MS / 1000; |
| final int updatePeriods = periodsPerSecond * TEST_TIME_MS / 1000; |
| final int markerPeriodsMax = |
| markerPeriodsPerSecond * (int)(stopTime - firstSampleTime) / 1000 + 1; |
| final int updatePeriodsMax = |
| periodsPerSecond * (int)(stopTime - firstSampleTime) / 1000 + 1; |
| |
| // collect statistics |
| final ArrayList<Integer> markerList = listener.getMarkerList(); |
| final ArrayList<Integer> periodicList = listener.getPeriodicList(); |
| // verify count of markers and periodic notifications. |
| // there could be an extra notification since we don't stop() immediately |
| // rather wait for potential events to come in. |
| //Log.d(TAG, "markerPeriods " + markerPeriods + |
| // " markerPeriodsReceived " + markerList.size()); |
| //Log.d(TAG, "updatePeriods " + updatePeriods + |
| // " updatePeriodsReceived " + periodicList.size()); |
| assertTrue(TAG + ": markerPeriods " + markerPeriods + |
| " <= markerPeriodsReceived " + markerList.size() + |
| " <= markerPeriodsMax " + markerPeriodsMax, |
| markerPeriods <= markerList.size() |
| && markerList.size() <= markerPeriodsMax); |
| assertTrue(TAG + ": updatePeriods " + updatePeriods + |
| " <= updatePeriodsReceived " + periodicList.size() + |
| " <= updatePeriodsMax " + updatePeriodsMax, |
| updatePeriods <= periodicList.size() |
| && periodicList.size() <= updatePeriodsMax); |
| |
| // Since we don't have accurate positioning of the start time of the recorder, |
| // and there is no record.getPosition(), we consider only differential timing |
| // from the first marker or periodic event. |
| final int toleranceInFrames = TEST_SR * 80 / 1000; // 80 ms |
| final int testTimeInFrames = (int)((long)TEST_TIME_MS * TEST_SR / 1000); |
| |
| AudioHelper.Statistics markerStat = new AudioHelper.Statistics(); |
| for (int i = 1; i < markerList.size(); ++i) { |
| final int expected = mMarkerPeriodInFrames * i; |
| if (markerList.get(i) > testTimeInFrames) { |
| break; // don't consider any notifications when we might be stopping. |
| } |
| final int actual = markerList.get(i) - markerList.get(0); |
| //Log.d(TAG, "Marker: " + i + " expected(" + expected + ") actual(" + actual |
| // + ") diff(" + (actual - expected) + ")" |
| // + " tolerance " + toleranceInFrames); |
| assertEquals(expected, actual, toleranceInFrames); |
| markerStat.add((double)(actual - expected) * 1000 / TEST_SR); |
| } |
| |
| AudioHelper.Statistics periodicStat = new AudioHelper.Statistics(); |
| for (int i = 1; i < periodicList.size(); ++i) { |
| final int expected = updatePeriodInFrames * i; |
| if (periodicList.get(i) > testTimeInFrames) { |
| break; // don't consider any notifications when we might be stopping. |
| } |
| final int actual = periodicList.get(i) - periodicList.get(0); |
| //Log.d(TAG, "Update: " + i + " expected(" + expected + ") actual(" + actual |
| // + ") diff(" + (actual - expected) + ")" |
| // + " tolerance " + toleranceInFrames); |
| assertEquals(expected, actual, toleranceInFrames); |
| periodicStat.add((double)(actual - expected) * 1000 / TEST_SR); |
| } |
| |
| // report this |
| ReportLog log = getReportLog(); |
| log.printValue(reportName + ": startRecording lag", coldInputStartTime, |
| ResultType.LOWER_BETTER, ResultUnit.MS); |
| log.printValue(reportName + ": stop execution time", stopTime - stopRequestTime, |
| ResultType.LOWER_BETTER, ResultUnit.MS); |
| log.printValue(reportName + ": Total record time expected", TEST_TIME_MS, |
| ResultType.NEUTRAL, ResultUnit.MS); |
| log.printValue(reportName + ": Total record time actual", endTime - firstSampleTime, |
| ResultType.NEUTRAL, ResultUnit.MS); |
| log.printValue(reportName + ": Total markers expected", markerPeriods, |
| ResultType.NEUTRAL, ResultUnit.COUNT); |
| log.printValue(reportName + ": Total markers actual", markerList.size(), |
| ResultType.NEUTRAL, ResultUnit.COUNT); |
| log.printValue(reportName + ": Total periods expected", updatePeriods, |
| ResultType.NEUTRAL, ResultUnit.COUNT); |
| log.printValue(reportName + ": Total periods actual", periodicList.size(), |
| ResultType.NEUTRAL, ResultUnit.COUNT); |
| log.printValue(reportName + ": Average Marker diff", markerStat.getAvg(), |
| ResultType.LOWER_BETTER, ResultUnit.MS); |
| log.printValue(reportName + ": Maximum Marker abs diff", markerStat.getMaxAbs(), |
| ResultType.LOWER_BETTER, ResultUnit.MS); |
| log.printValue(reportName + ": Average Marker abs diff", markerStat.getAvgAbs(), |
| ResultType.LOWER_BETTER, ResultUnit.MS); |
| log.printValue(reportName + ": Average Periodic diff", periodicStat.getAvg(), |
| ResultType.LOWER_BETTER, ResultUnit.MS); |
| log.printValue(reportName + ": Maximum Periodic abs diff", periodicStat.getMaxAbs(), |
| ResultType.LOWER_BETTER, ResultUnit.MS); |
| log.printValue(reportName + ": Average Periodic abs diff", periodicStat.getAvgAbs(), |
| ResultType.LOWER_BETTER, ResultUnit.MS); |
| log.printSummary(reportName + ": Unified abs diff", |
| (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2, |
| ResultType.LOWER_BETTER, ResultUnit.MS); |
| } |
| |
| private class MockOnRecordPositionUpdateListener |
| implements OnRecordPositionUpdateListener { |
| public MockOnRecordPositionUpdateListener(AudioRecord record) { |
| mAudioRecord = record; |
| record.setRecordPositionUpdateListener(this); |
| } |
| |
| public MockOnRecordPositionUpdateListener(AudioRecord record, Handler handler) { |
| mAudioRecord = record; |
| record.setRecordPositionUpdateListener(this, handler); |
| } |
| |
| public synchronized void onMarkerReached(AudioRecord record) { |
| if (mIsTestActive) { |
| int position = getPosition(); |
| mOnMarkerReachedCalled.add(position); |
| mMarkerPosition += mMarkerPeriodInFrames; |
| assertEquals(AudioRecord.SUCCESS, |
| mAudioRecord.setNotificationMarkerPosition(mMarkerPosition)); |
| } else { |
| // stop() is not sufficient to end all notifications |
| // as is not synchronous with the event handling thread |
| // so we comment out the line below. |
| // fail("onMarkerReached called when not active"); |
| } |
| } |
| |
| public synchronized void onPeriodicNotification(AudioRecord record) { |
| if (mIsTestActive) { |
| int position = getPosition(); |
| mOnPeriodicNotificationCalled.add(position); |
| } else { |
| // see above comments about stop |
| // fail("onPeriodicNotification called when not active"); |
| } |
| } |
| |
| public synchronized void start(int sampleRate) { |
| mIsTestActive = true; |
| mSampleRate = sampleRate; |
| mStartTime = System.currentTimeMillis(); |
| } |
| |
| public synchronized void stop() { |
| mIsTestActive = false; |
| } |
| |
| public ArrayList<Integer> getMarkerList() { |
| return mOnMarkerReachedCalled; |
| } |
| |
| public ArrayList<Integer> getPeriodicList() { |
| return mOnPeriodicNotificationCalled; |
| } |
| |
| public synchronized void release() { |
| stop(); |
| mAudioRecord.setRecordPositionUpdateListener(null); |
| mAudioRecord = null; |
| } |
| |
| private int getPosition() { |
| // we don't have mAudioRecord.getRecordPosition(); |
| // so we fake this by timing. |
| long delta = System.currentTimeMillis() - mStartTime; |
| return (int)(delta * mSampleRate / 1000); |
| } |
| |
| private long mStartTime; |
| private int mSampleRate; |
| private boolean mIsTestActive = true; |
| private AudioRecord mAudioRecord; |
| private ArrayList<Integer> mOnMarkerReachedCalled = new ArrayList<Integer>(); |
| private ArrayList<Integer> mOnPeriodicNotificationCalled = new ArrayList<Integer>(); |
| } |
| |
| private boolean hasMicrophone() { |
| return getContext().getPackageManager().hasSystemFeature( |
| PackageManager.FEATURE_MICROPHONE); |
| } |
| } |