| /* |
| * 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 static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| import static org.testng.Assert.assertThrows; |
| |
| import android.app.ActivityManager; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.media.AudioAttributes; |
| import android.media.AudioFormat; |
| import android.media.AudioManager; |
| import android.media.AudioMetadata; |
| import android.media.AudioMetadataReadMap; |
| import android.media.AudioPresentation; |
| import android.media.AudioSystem; |
| import android.media.AudioTimestamp; |
| import android.media.AudioTrack; |
| import android.media.PlaybackParams; |
| import android.media.metrics.LogSessionId; |
| import android.media.metrics.MediaMetricsManager; |
| import android.media.metrics.PlaybackSession; |
| import android.os.PersistableBundle; |
| import android.os.SystemClock; |
| import android.platform.test.annotations.Presubmit; |
| import android.util.Log; |
| |
| import androidx.test.InstrumentationRegistry; |
| import androidx.test.filters.LargeTest; |
| import androidx.test.runner.AndroidJUnit4; |
| |
| import com.android.compatibility.common.util.DeviceReportLog; |
| import com.android.compatibility.common.util.ResultType; |
| import com.android.compatibility.common.util.ResultUnit; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.FloatBuffer; |
| import java.nio.ShortBuffer; |
| import java.util.concurrent.Executor; |
| |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| @NonMediaMainlineTest |
| @RunWith(AndroidJUnit4.class) |
| public class AudioTrackTest { |
| private String TAG = "AudioTrackTest"; |
| private final long WAIT_MSEC = 200; |
| private final int OFFSET_DEFAULT = 0; |
| private final int OFFSET_NEGATIVE = -10; |
| |
| private void log(String testName, String message) { |
| Log.v(TAG, "[" + testName + "] " + message); |
| } |
| |
| private void loge(String testName, String message) { |
| Log.e(TAG, "[" + testName + "] " + message); |
| } |
| |
| // ----------------------------------------------------------------- |
| // private class to hold test results |
| private static class TestResults { |
| public boolean mResult = false; |
| public String mResultLog = ""; |
| |
| public TestResults(boolean b, String s) { |
| mResult = b; |
| mResultLog = s; |
| } |
| } |
| |
| // ----------------------------------------------------------------- |
| // generic test methods |
| public TestResults constructorTestMultiSampleRate( |
| // parameters tested by this method |
| int _inTest_streamType, int _inTest_mode, int _inTest_config, int _inTest_format, |
| // parameter-dependent expected results |
| int _expected_stateForMode) { |
| |
| int[] testSampleRates = { 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000 }; |
| String failedRates = "Failure for rate(s): "; |
| boolean localRes, finalRes = true; |
| |
| for (int i = 0; i < testSampleRates.length; i++) { |
| AudioTrack track = null; |
| try { |
| track = new AudioTrack(_inTest_streamType, testSampleRates[i], _inTest_config, |
| _inTest_format, AudioTrack.getMinBufferSize(testSampleRates[i], |
| _inTest_config, _inTest_format), _inTest_mode); |
| } catch (IllegalArgumentException iae) { |
| Log.e("MediaAudioTrackTest", "[ constructorTestMultiSampleRate ] exception at SR " |
| + testSampleRates[i] + ": \n" + iae); |
| localRes = false; |
| } |
| if (track != null) { |
| localRes = (track.getState() == _expected_stateForMode); |
| track.release(); |
| } else { |
| localRes = false; |
| } |
| |
| if (!localRes) { |
| // log the error for the test runner |
| failedRates += Integer.toString(testSampleRates[i]) + "Hz "; |
| // log the error for logcat |
| log("constructorTestMultiSampleRate", "failed to construct " |
| + "AudioTrack(streamType=" |
| + _inTest_streamType |
| + ", sampleRateInHz=" |
| + testSampleRates[i] |
| + ", channelConfig=" |
| + _inTest_config |
| + ", audioFormat=" |
| + _inTest_format |
| + ", bufferSizeInBytes=" |
| + AudioTrack.getMinBufferSize(testSampleRates[i], _inTest_config, |
| AudioFormat.ENCODING_PCM_16BIT) + ", mode=" + _inTest_mode); |
| // mark test as failed |
| finalRes = false; |
| } |
| } |
| return new TestResults(finalRes, failedRates); |
| } |
| |
| // ----------------------------------------------------------------- |
| // AUDIOTRACK TESTS: |
| // ---------------------------------- |
| |
| // ----------------------------------------------------------------- |
| // AudioTrack constructor and AudioTrack.getMinBufferSize(...) for 16bit PCM |
| // ---------------------------------- |
| |
| // Test case 1: constructor for streaming AudioTrack, mono, 16bit at misc |
| // valid sample rates |
| @Test |
| public void testConstructorMono16MusicStream() throws Exception { |
| |
| TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC, |
| AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_MONO, |
| AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_INITIALIZED); |
| |
| assertTrue("testConstructorMono16MusicStream: " + res.mResultLog, res.mResult); |
| } |
| |
| // Test case 2: constructor for streaming AudioTrack, stereo, 16bit at misc |
| // valid sample rates |
| @Test |
| public void testConstructorStereo16MusicStream() throws Exception { |
| |
| TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC, |
| AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_STEREO, |
| AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_INITIALIZED); |
| |
| assertTrue("testConstructorStereo16MusicStream: " + res.mResultLog, res.mResult); |
| } |
| |
| // Test case 3: constructor for static AudioTrack, mono, 16bit at misc valid |
| // sample rates |
| @Test |
| public void testConstructorMono16MusicStatic() throws Exception { |
| |
| TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC, |
| AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_MONO, |
| AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_NO_STATIC_DATA); |
| |
| assertTrue("testConstructorMono16MusicStatic: " + res.mResultLog, res.mResult); |
| } |
| |
| // Test case 4: constructor for static AudioTrack, stereo, 16bit at misc |
| // valid sample rates |
| @Test |
| public void testConstructorStereo16MusicStatic() throws Exception { |
| |
| TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC, |
| AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_STEREO, |
| AudioFormat.ENCODING_PCM_16BIT, AudioTrack.STATE_NO_STATIC_DATA); |
| |
| assertTrue("testConstructorStereo16MusicStatic: " + res.mResultLog, res.mResult); |
| } |
| |
| // ----------------------------------------------------------------- |
| // AudioTrack constructor and AudioTrack.getMinBufferSize(...) for 8bit PCM |
| // ---------------------------------- |
| |
| // Test case 1: constructor for streaming AudioTrack, mono, 8bit at misc |
| // valid sample rates |
| @Test |
| public void testConstructorMono8MusicStream() throws Exception { |
| |
| TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC, |
| AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_MONO, |
| AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_INITIALIZED); |
| |
| assertTrue("testConstructorMono8MusicStream: " + res.mResultLog, res.mResult); |
| } |
| |
| // Test case 2: constructor for streaming AudioTrack, stereo, 8bit at misc |
| // valid sample rates |
| @Test |
| public void testConstructorStereo8MusicStream() throws Exception { |
| |
| TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC, |
| AudioTrack.MODE_STREAM, AudioFormat.CHANNEL_CONFIGURATION_STEREO, |
| AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_INITIALIZED); |
| |
| assertTrue("testConstructorStereo8MusicStream: " + res.mResultLog, res.mResult); |
| } |
| |
| // Test case 3: constructor for static AudioTrack, mono, 8bit at misc valid |
| // sample rates |
| @Test |
| public void testConstructorMono8MusicStatic() throws Exception { |
| |
| TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC, |
| AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_MONO, |
| AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_NO_STATIC_DATA); |
| |
| assertTrue("testConstructorMono8MusicStatic: " + res.mResultLog, res.mResult); |
| } |
| |
| // Test case 4: constructor for static AudioTrack, stereo, 8bit at misc |
| // valid sample rates |
| @Test |
| public void testConstructorStereo8MusicStatic() throws Exception { |
| |
| TestResults res = constructorTestMultiSampleRate(AudioManager.STREAM_MUSIC, |
| AudioTrack.MODE_STATIC, AudioFormat.CHANNEL_CONFIGURATION_STEREO, |
| AudioFormat.ENCODING_PCM_8BIT, AudioTrack.STATE_NO_STATIC_DATA); |
| |
| assertTrue("testConstructorStereo8MusicStatic: " + res.mResultLog, res.mResult); |
| } |
| |
| // ----------------------------------------------------------------- |
| // AudioTrack constructor for all stream types |
| // ---------------------------------- |
| |
| // Test case 1: constructor for all stream types |
| @Test |
| public void testConstructorStreamType() throws Exception { |
| // constants for test |
| final int TYPE_TEST_SR = 22050; |
| final int TYPE_TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TYPE_TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TYPE_TEST_MODE = AudioTrack.MODE_STREAM; |
| final int[] STREAM_TYPES = { AudioManager.STREAM_ALARM, AudioManager.STREAM_MUSIC, |
| AudioManager.STREAM_NOTIFICATION, AudioManager.STREAM_RING, |
| AudioManager.STREAM_SYSTEM, AudioManager.STREAM_VOICE_CALL }; |
| final String[] STREAM_NAMES = { "STREAM_ALARM", "STREAM_MUSIC", "STREAM_NOTIFICATION", |
| "STREAM_RING", "STREAM_SYSTEM", "STREAM_VOICE_CALL" }; |
| |
| boolean localTestRes = true; |
| AudioTrack track = null; |
| // test: loop constructor on all stream types |
| for (int i = 0; i < STREAM_TYPES.length; i++) { |
| try { |
| // -------- initialization -------------- |
| track = new AudioTrack(STREAM_TYPES[i], TYPE_TEST_SR, TYPE_TEST_CONF, |
| TYPE_TEST_FORMAT, AudioTrack.getMinBufferSize(TYPE_TEST_SR, TYPE_TEST_CONF, |
| TYPE_TEST_FORMAT), TYPE_TEST_MODE); |
| } catch (IllegalArgumentException iae) { |
| loge("testConstructorStreamType", "exception for stream type " + STREAM_NAMES[i] |
| + ": " + iae); |
| localTestRes = false; |
| } |
| // -------- test -------------- |
| if (track != null) { |
| if (track.getState() != AudioTrack.STATE_INITIALIZED) { |
| localTestRes = false; |
| Log.e("MediaAudioTrackTest", |
| "[ testConstructorStreamType ] failed for stream type " |
| + STREAM_NAMES[i]); |
| } |
| // -------- tear down -------------- |
| track.release(); |
| } else { |
| localTestRes = false; |
| } |
| } |
| |
| assertTrue("testConstructorStreamType", localTestRes); |
| } |
| |
| // ----------------------------------------------------------------- |
| // AudioTrack construction with Builder |
| // ---------------------------------- |
| |
| // Test case 1: build AudioTrack with default parameters, test documented default params |
| @Test |
| public void testBuilderDefault() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testBuilderDefault"; |
| final int expectedDefaultEncoding = AudioFormat.ENCODING_PCM_16BIT; |
| final int expectedDefaultRate = |
| AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); |
| final int expectedDefaultChannels = AudioFormat.CHANNEL_OUT_STEREO; |
| // use Builder |
| final int buffSizeInBytes = AudioTrack.getMinBufferSize( |
| expectedDefaultRate, expectedDefaultChannels, expectedDefaultEncoding); |
| final AudioTrack track = new AudioTrack.Builder() |
| .setBufferSizeInBytes(buffSizeInBytes) |
| .build(); |
| // save results |
| final int observedState = track.getState(); |
| final int observedFormat = track.getAudioFormat(); |
| final int observedChannelConf = track.getChannelConfiguration(); |
| final int observedRate = track.getSampleRate(); |
| // release track before the test exits (either successfully or with an exception) |
| track.release(); |
| // compare results |
| assertEquals(TEST_NAME + ": Track initialized", AudioTrack.STATE_INITIALIZED, |
| observedState); |
| assertEquals(TEST_NAME + ": Default track encoding", expectedDefaultEncoding, |
| observedFormat); |
| assertEquals(TEST_NAME + ": Default track channels", expectedDefaultChannels, |
| observedChannelConf); |
| } |
| |
| // Test case 2: build AudioTrack with AudioFormat, test it's used |
| @Test |
| public void testBuilderFormat() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testBuilderFormat"; |
| final int TEST_RATE = 32000; |
| final int TEST_CHANNELS = AudioFormat.CHANNEL_OUT_STEREO; |
| // use Builder |
| final int buffSizeInBytes = AudioTrack.getMinBufferSize( |
| TEST_RATE, TEST_CHANNELS, AudioFormat.ENCODING_PCM_16BIT); |
| final AudioTrack track = new AudioTrack.Builder() |
| .setAudioAttributes(new AudioAttributes.Builder().build()) |
| .setBufferSizeInBytes(buffSizeInBytes) |
| .setAudioFormat(new AudioFormat.Builder() |
| .setChannelMask(TEST_CHANNELS).setSampleRate(TEST_RATE).build()) |
| .build(); |
| // save results |
| final int observedState = track.getState(); |
| final int observedChannelConf = track.getChannelConfiguration(); |
| final int observedRate = track.getSampleRate(); |
| // release track before the test exits (either successfully or with an exception) |
| track.release(); |
| // compare results |
| assertEquals(TEST_NAME + ": Track initialized", AudioTrack.STATE_INITIALIZED, |
| observedState); |
| assertEquals(TEST_NAME + ": Track channels", TEST_CHANNELS, observedChannelConf); |
| assertEquals(TEST_NAME + ": Track sample rate", TEST_RATE, observedRate); |
| } |
| |
| // Test case 3: build AudioTrack with session ID, test it's used |
| @Test |
| public void testBuilderSession() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testBuilderSession"; |
| // generate a session ID |
| final int expectedSessionId = new AudioManager(getContext()).generateAudioSessionId(); |
| // use builder |
| final AudioTrack track = new AudioTrack.Builder() |
| .setSessionId(expectedSessionId) |
| .build(); |
| // save results |
| final int observedSessionId = track.getAudioSessionId(); |
| // release track before the test exits (either successfully or with an exception) |
| track.release(); |
| // compare results |
| assertEquals(TEST_NAME + ": Assigned track session ID", expectedSessionId, |
| observedSessionId); |
| } |
| |
| // Test case 4: build AudioTrack with AudioAttributes built from stream type, test it's used |
| @Test |
| public void testBuilderAttributesStream() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testBuilderAttributesStream"; |
| // use a stream type documented in AudioAttributes.Builder.setLegacyStreamType(int) |
| final int expectedStreamType = AudioManager.STREAM_ALARM; |
| final int expectedContentType = AudioAttributes.CONTENT_TYPE_SPEECH; |
| final AudioAttributes attributes = new AudioAttributes.Builder() |
| .setLegacyStreamType(expectedStreamType) |
| .setContentType(expectedContentType) |
| .build(); |
| // use builder |
| final AudioTrack track = new AudioTrack.Builder() |
| .setAudioAttributes(attributes) |
| .build(); |
| // save results |
| final int observedStreamType = track.getStreamType(); |
| final AudioAttributes observedAttributes = track.getAudioAttributes(); |
| |
| // release track before the test exits (either successfully or with an exception) |
| track.release(); |
| // compare results |
| assertEquals(TEST_NAME + ": track stream type", expectedStreamType, observedStreamType); |
| // attributes and observedAttributes should satisfy the overloaded equals. |
| assertEquals(TEST_NAME + ": observed attributes must match", |
| attributes, observedAttributes); |
| // also test content type was preserved in the attributes even though they |
| // were first configured with a legacy stream type |
| assertEquals(TEST_NAME + ": attributes content type", expectedContentType, |
| attributes.getContentType()); |
| } |
| |
| // Test case 5: build AudioTrack with attributes and performance mode |
| @Test |
| public void testBuilderAttributesPerformanceMode() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testBuilderAttributesPerformanceMode"; |
| final int testPerformanceModes[] = new int[] { |
| AudioTrack.PERFORMANCE_MODE_NONE, |
| AudioTrack.PERFORMANCE_MODE_LOW_LATENCY, |
| AudioTrack.PERFORMANCE_MODE_POWER_SAVING, |
| }; |
| // construct various attributes with different preset performance modes. |
| final AudioAttributes testAttributes[] = new AudioAttributes[] { |
| new AudioAttributes.Builder().build(), |
| new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_LOW_LATENCY).build(), |
| new AudioAttributes.Builder().setFlags(AudioAttributes.FLAG_DEEP_BUFFER).build(), |
| }; |
| for (int performanceMode : testPerformanceModes) { |
| for (AudioAttributes attributes : testAttributes) { |
| final AudioTrack track = new AudioTrack.Builder() |
| .setPerformanceMode(performanceMode) |
| .setAudioAttributes(attributes) |
| .build(); |
| // save results |
| final int actualPerformanceMode = track.getPerformanceMode(); |
| // release track before the test exits |
| track.release(); |
| final String result = "Attribute flags: " + attributes.getAllFlags() |
| + " set performance mode: " + performanceMode |
| + " actual performance mode: " + actualPerformanceMode; |
| Log.d(TEST_NAME, result); |
| assertTrue(TEST_NAME + ": " + result, |
| actualPerformanceMode == performanceMode // either successful |
| || actualPerformanceMode == AudioTrack.PERFORMANCE_MODE_NONE // or none |
| || performanceMode == AudioTrack.PERFORMANCE_MODE_NONE); |
| } |
| } |
| } |
| |
| // ----------------------------------------------------------------- |
| // Playback head position |
| // ---------------------------------- |
| |
| // Test case 1: getPlaybackHeadPosition() at 0 after initialization |
| @Test |
| public void testPlaybackHeadPositionAfterInit() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testPlaybackHeadPositionAfterInit"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT), TEST_MODE); |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 2: getPlaybackHeadPosition() increases after play() |
| @Test |
| public void testPlaybackHeadPositionIncrease() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testPlaybackHeadPositionIncrease"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.play(); |
| Thread.sleep(100); |
| log(TEST_NAME, "position =" + track.getPlaybackHeadPosition()); |
| assertTrue(TEST_NAME, track.getPlaybackHeadPosition() > 0); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 3: getPlaybackHeadPosition() is 0 after flush(); |
| @Test |
| public void testPlaybackHeadPositionAfterFlush() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testPlaybackHeadPositionAfterFlush"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.play(); |
| Thread.sleep(WAIT_MSEC); |
| track.stop(); |
| track.flush(); |
| log(TEST_NAME, "position =" + track.getPlaybackHeadPosition()); |
| assertTrue(TEST_NAME, track.getPlaybackHeadPosition() == 0); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 3: getPlaybackHeadPosition() is 0 after stop(); |
| @Test |
| public void testPlaybackHeadPositionAfterStop() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testPlaybackHeadPositionAfterStop"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| final int TEST_LOOP_CNT = 10; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.play(); |
| Thread.sleep(WAIT_MSEC); |
| track.stop(); |
| int count = 0; |
| int pos; |
| do { |
| Thread.sleep(WAIT_MSEC); |
| pos = track.getPlaybackHeadPosition(); |
| count++; |
| } while((pos != 0) && (count < TEST_LOOP_CNT)); |
| log(TEST_NAME, "position =" + pos + ", read count ="+count); |
| assertTrue(TEST_NAME, pos == 0); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 4: getPlaybackHeadPosition() is > 0 after play(); pause(); |
| @Test |
| public void testPlaybackHeadPositionAfterPause() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testPlaybackHeadPositionAfterPause"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.play(); |
| Thread.sleep(100); |
| track.pause(); |
| int pos = track.getPlaybackHeadPosition(); |
| log(TEST_NAME, "position =" + pos); |
| assertTrue(TEST_NAME, pos > 0); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 5: getPlaybackHeadPosition() remains 0 after pause(); flush(); play(); |
| @Test |
| public void testPlaybackHeadPositionAfterFlushAndPlay() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testPlaybackHeadPositionAfterFlushAndPlay"; |
| final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| final int TEST_SR = AudioTrack.getNativeOutputSampleRate(TEST_STREAM_TYPE); |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.play(); |
| Thread.sleep(100); |
| track.pause(); |
| |
| int pos = track.getPlaybackHeadPosition(); |
| log(TEST_NAME, "position after pause =" + pos); |
| assertTrue(TEST_NAME, pos > 0); |
| |
| track.flush(); |
| pos = track.getPlaybackHeadPosition(); |
| log(TEST_NAME, "position after flush =" + pos); |
| assertTrue(TEST_NAME, pos == 0); |
| |
| track.play(); |
| pos = track.getPlaybackHeadPosition(); |
| log(TEST_NAME, "position after play =" + pos); |
| assertTrue(TEST_NAME, pos == 0); |
| |
| Thread.sleep(100); |
| pos = track.getPlaybackHeadPosition(); |
| log(TEST_NAME, "position after 100 ms sleep =" + pos); |
| assertTrue(TEST_NAME, pos == 0); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // ----------------------------------------------------------------- |
| // Playback properties |
| // ---------------------------------- |
| |
| // Common code for the testSetStereoVolume* and testSetVolume* tests |
| private void testSetVolumeCommon(String testName, float vol, boolean isStereo) throws Exception { |
| // constants for test |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.play(); |
| if (isStereo) { |
| // TODO to really test this, do a pan instead of using same value for left and right |
| assertTrue(testName, track.setStereoVolume(vol, vol) == AudioTrack.SUCCESS); |
| } else { |
| assertTrue(testName, track.setVolume(vol) == AudioTrack.SUCCESS); |
| } |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 1: setStereoVolume() with max volume returns SUCCESS |
| @Test |
| public void testSetStereoVolumeMax() throws Exception { |
| final String TEST_NAME = "testSetStereoVolumeMax"; |
| float maxVol = AudioTrack.getMaxVolume(); |
| testSetVolumeCommon(TEST_NAME, maxVol, true /*isStereo*/); |
| } |
| |
| // Test case 2: setStereoVolume() with min volume returns SUCCESS |
| @Test |
| public void testSetStereoVolumeMin() throws Exception { |
| final String TEST_NAME = "testSetStereoVolumeMin"; |
| float minVol = AudioTrack.getMinVolume(); |
| testSetVolumeCommon(TEST_NAME, minVol, true /*isStereo*/); |
| } |
| |
| // Test case 3: setStereoVolume() with mid volume returns SUCCESS |
| @Test |
| public void testSetStereoVolumeMid() throws Exception { |
| final String TEST_NAME = "testSetStereoVolumeMid"; |
| float midVol = (AudioTrack.getMaxVolume() - AudioTrack.getMinVolume()) / 2; |
| testSetVolumeCommon(TEST_NAME, midVol, true /*isStereo*/); |
| } |
| |
| // Test case 4: setPlaybackRate() with half the content rate returns SUCCESS |
| @Test |
| public void testSetPlaybackRate() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetPlaybackRate"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.play(); |
| assertTrue(TEST_NAME, track.setPlaybackRate((int) (TEST_SR / 2)) == AudioTrack.SUCCESS); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 5: setPlaybackRate(0) returns bad value error |
| @Test |
| public void testSetPlaybackRateZero() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetPlaybackRateZero"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| minBuffSize, TEST_MODE); |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.setPlaybackRate(0) == AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 6: setPlaybackRate() accepts values twice the output sample |
| // rate |
| @Test |
| public void testSetPlaybackRateTwiceOutputSR() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetPlaybackRateTwiceOutputSR"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| int outputSR = AudioTrack.getNativeOutputSampleRate(TEST_STREAM_TYPE); |
| // -------- test -------------- |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.play(); |
| assertTrue(TEST_NAME, track.setPlaybackRate(2 * outputSR) == AudioTrack.SUCCESS); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 7: setPlaybackRate() and retrieve value, should be the same for |
| // half the content SR |
| @Test |
| public void testSetGetPlaybackRate() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetGetPlaybackRate"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_STEREO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.play(); |
| track.setPlaybackRate((int) (TEST_SR / 2)); |
| assertTrue(TEST_NAME, track.getPlaybackRate() == (int) (TEST_SR / 2)); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 8: setPlaybackRate() invalid operation if track not initialized |
| @Test |
| public void testSetPlaybackRateUninit() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetPlaybackRateUninit"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| minBuffSize, TEST_MODE); |
| // -------- test -------------- |
| assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState()); |
| assertEquals(TEST_NAME, AudioTrack.ERROR_INVALID_OPERATION, |
| track.setPlaybackRate(TEST_SR / 2)); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 9: setVolume() with max volume returns SUCCESS |
| @Test |
| public void testSetVolumeMax() throws Exception { |
| final String TEST_NAME = "testSetVolumeMax"; |
| float maxVol = AudioTrack.getMaxVolume(); |
| testSetVolumeCommon(TEST_NAME, maxVol, false /*isStereo*/); |
| } |
| |
| // Test case 10: setVolume() with min volume returns SUCCESS |
| @Test |
| public void testSetVolumeMin() throws Exception { |
| final String TEST_NAME = "testSetVolumeMin"; |
| float minVol = AudioTrack.getMinVolume(); |
| testSetVolumeCommon(TEST_NAME, minVol, false /*isStereo*/); |
| } |
| |
| // Test case 11: setVolume() with mid volume returns SUCCESS |
| @Test |
| public void testSetVolumeMid() throws Exception { |
| final String TEST_NAME = "testSetVolumeMid"; |
| float midVol = (AudioTrack.getMaxVolume() - AudioTrack.getMinVolume()) / 2; |
| testSetVolumeCommon(TEST_NAME, midVol, false /*isStereo*/); |
| } |
| |
| // ----------------------------------------------------------------- |
| // Playback progress |
| // ---------------------------------- |
| |
| // Test case 1: setPlaybackHeadPosition() on playing track |
| @Test |
| public void testSetPlaybackHeadPositionPlaying() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetPlaybackHeadPositionPlaying"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.play(); |
| assertTrue(TEST_NAME, |
| track.setPlaybackHeadPosition(10) == AudioTrack.ERROR_INVALID_OPERATION); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 2: setPlaybackHeadPosition() on stopped track |
| @Test |
| public void testSetPlaybackHeadPositionStopped() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetPlaybackHeadPositionStopped"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState()); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState()); |
| track.play(); |
| track.stop(); |
| assertEquals(TEST_NAME, AudioTrack.PLAYSTATE_STOPPED, track.getPlayState()); |
| assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackHeadPosition(10)); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 3: setPlaybackHeadPosition() on paused track |
| @Test |
| public void testSetPlaybackHeadPositionPaused() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetPlaybackHeadPositionPaused"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState()); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState()); |
| track.play(); |
| track.pause(); |
| assertEquals(TEST_NAME, AudioTrack.PLAYSTATE_PAUSED, track.getPlayState()); |
| assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackHeadPosition(10)); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 4: setPlaybackHeadPosition() beyond what has been written |
| @Test |
| public void testSetPlaybackHeadPositionTooFar() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetPlaybackHeadPositionTooFar"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // make up a frame index that's beyond what has been written: go from |
| // buffer size to frame |
| // count (given the audio track properties), and add 77. |
| int frameIndexTooFar = (2 * minBuffSize / 2) + 77; |
| // -------- test -------------- |
| assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState()); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState()); |
| track.play(); |
| track.stop(); |
| assertEquals(TEST_NAME, AudioTrack.PLAYSTATE_STOPPED, track.getPlayState()); |
| assertEquals(TEST_NAME, AudioTrack.ERROR_BAD_VALUE, |
| track.setPlaybackHeadPosition(frameIndexTooFar)); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 5: setLoopPoints() fails for MODE_STREAM |
| @Test |
| public void testSetLoopPointsStream() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetLoopPointsStream"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.setLoopPoints(2, 50, 2) == AudioTrack.ERROR_INVALID_OPERATION); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 6: setLoopPoints() fails start > end |
| @Test |
| public void testSetLoopPointsStartAfterEnd() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetLoopPointsStartAfterEnd"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.setLoopPoints(50, 0, 2) == AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 6: setLoopPoints() success |
| @Test |
| public void testSetLoopPointsSuccess() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetLoopPointsSuccess"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.setLoopPoints(0, 50, 2) == AudioTrack.SUCCESS); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 7: setLoopPoints() fails with loop length bigger than content |
| @Test |
| public void testSetLoopPointsLoopTooLong() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetLoopPointsLoopTooLong"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| int dataSizeInFrames = minBuffSize / 2; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.setLoopPoints(10, dataSizeInFrames + 20, 2) == |
| AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 8: setLoopPoints() fails with start beyond what can be written |
| // for the track |
| @Test |
| public void testSetLoopPointsStartTooFar() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetLoopPointsStartTooFar"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| int dataSizeInFrames = minBuffSize / 2;// 16bit data |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, |
| track.setLoopPoints(dataSizeInFrames + 20, dataSizeInFrames + 50, 2) == |
| AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 9: setLoopPoints() fails with end beyond what can be written |
| // for the track |
| @Test |
| public void testSetLoopPointsEndTooFar() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testSetLoopPointsEndTooFar"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| int dataSizeInFrames = minBuffSize / 2;// 16bit data |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_NO_STATIC_DATA); |
| track.write(data, OFFSET_DEFAULT, data.length); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| int loopCount = 2; |
| assertTrue(TEST_NAME, |
| track.setLoopPoints(dataSizeInFrames - 10, dataSizeInFrames + 50, loopCount) == |
| AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // ----------------------------------------------------------------- |
| // Audio data supply |
| // ---------------------------------- |
| |
| // Test case 1: write() fails when supplying less data (bytes) than declared |
| @Test |
| public void testWriteByteOffsetTooBig() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteByteOffsetTooBig"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| int offset = 10; |
| assertTrue(TEST_NAME, track.write(data, offset, data.length) == AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 2: write() fails when supplying less data (shorts) than |
| // declared |
| @Test |
| public void testWriteShortOffsetTooBig() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteShortOffsetTooBig"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| short data[] = new short[minBuffSize / 2]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| int offset = 10; |
| assertTrue(TEST_NAME, track.write(data, offset, data.length) |
| == AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 3: write() fails when supplying less data (bytes) than declared |
| @Test |
| public void testWriteByteSizeTooBig() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteByteSizeTooBig"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length + 10) |
| == AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 4: write() fails when supplying less data (shorts) than |
| // declared |
| @Test |
| public void testWriteShortSizeTooBig() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteShortSizeTooBig"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| short data[] = new short[minBuffSize / 2]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length + 10) |
| == AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 5: write() fails with negative offset |
| @Test |
| public void testWriteByteNegativeOffset() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteByteNegativeOffset"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.write(data, OFFSET_NEGATIVE, data.length - 10) == |
| AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 6: write() fails with negative offset |
| @Test |
| public void testWriteShortNegativeOffset() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteShortNegativeOffset"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| short data[] = new short[minBuffSize / 2]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, |
| track.write(data, OFFSET_NEGATIVE, data.length - 10) == AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 7: write() fails with negative size |
| @Test |
| public void testWriteByteNegativeSize() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteByteNegativeSize"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| int dataLength = -10; |
| assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, dataLength) |
| == AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 8: write() fails with negative size |
| @Test |
| public void testWriteShortNegativeSize() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteShortNegativeSize"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| short data[] = new short[minBuffSize / 2]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| int dataLength = -10; |
| assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, dataLength) |
| == AudioTrack.ERROR_BAD_VALUE); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 9: write() succeeds and returns the size that was written for |
| // 16bit |
| @Test |
| public void testWriteByte() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteByte"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length) == data.length); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 10: write() succeeds and returns the size that was written for |
| // 16bit |
| @Test |
| public void testWriteShort() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteShort"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| short data[] = new short[minBuffSize / 2]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertTrue(TEST_NAME, track.write(data, OFFSET_DEFAULT, data.length) == data.length); |
| track.flush(); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 11: write() succeeds and returns the size that was written for |
| // 8bit |
| @Test |
| public void testWriteByte8bit() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteByte8bit"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| byte data[] = new byte[minBuffSize]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertEquals(TEST_NAME, data.length, track.write(data, OFFSET_DEFAULT, data.length)); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // Test case 12: write() succeeds and returns the size that was written for |
| // 8bit |
| @Test |
| public void testWriteShort8bit() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testWriteShort8bit"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| 2 * minBuffSize, TEST_MODE); |
| short data[] = new short[minBuffSize / 2]; |
| // -------- test -------------- |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| assertEquals(TEST_NAME, data.length, track.write(data, OFFSET_DEFAULT, data.length)); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| // ----------------------------------------------------------------- |
| // Getters |
| // ---------------------------------- |
| |
| // Test case 1: getMinBufferSize() return ERROR_BAD_VALUE if SR < 4000 |
| @Test |
| public void testGetMinBufferSizeTooLowSR() throws Exception { |
| // constant for test |
| final String TEST_NAME = "testGetMinBufferSizeTooLowSR"; |
| final int TEST_SR = 3999; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; |
| |
| // -------- initialization & test -------------- |
| assertTrue(TEST_NAME, AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT) == |
| AudioTrack.ERROR_BAD_VALUE); |
| } |
| |
| // Test case 2: getMinBufferSize() return ERROR_BAD_VALUE if sample rate too high |
| @Test |
| public void testGetMinBufferSizeTooHighSR() throws Exception { |
| // constant for test |
| final String TEST_NAME = "testGetMinBufferSizeTooHighSR"; |
| // FIXME need an API to retrieve AudioTrack.SAMPLE_RATE_HZ_MAX |
| final int TEST_SR = AudioFormat.SAMPLE_RATE_HZ_MAX + 1; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; |
| |
| // -------- initialization & test -------------- |
| assertTrue(TEST_NAME, AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT) == |
| AudioTrack.ERROR_BAD_VALUE); |
| } |
| |
| @Test |
| public void testAudioTrackProperties() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testAudioTrackProperties"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| MockAudioTrack track = new MockAudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, |
| TEST_FORMAT, 2 * minBuffSize, TEST_MODE); |
| assertEquals(TEST_NAME, AudioTrack.STATE_INITIALIZED, track.getState()); |
| assertEquals(TEST_NAME, TEST_FORMAT, track.getAudioFormat()); |
| assertEquals(TEST_NAME, TEST_CONF, track.getChannelConfiguration()); |
| assertEquals(TEST_NAME, TEST_SR, track.getSampleRate()); |
| assertEquals(TEST_NAME, TEST_STREAM_TYPE, track.getStreamType()); |
| final int hannelCount = 1; |
| assertEquals(hannelCount, track.getChannelCount()); |
| final int notificationMarkerPosition = 0; |
| assertEquals(TEST_NAME, notificationMarkerPosition, track.getNotificationMarkerPosition()); |
| final int markerInFrames = 2; |
| assertEquals(TEST_NAME, AudioTrack.SUCCESS, |
| track.setNotificationMarkerPosition(markerInFrames)); |
| assertEquals(TEST_NAME, markerInFrames, track.getNotificationMarkerPosition()); |
| final int positionNotificationPeriod = 0; |
| assertEquals(TEST_NAME, positionNotificationPeriod, track.getPositionNotificationPeriod()); |
| final int periodInFrames = 2; |
| assertEquals(TEST_NAME, AudioTrack.SUCCESS, |
| track.setPositionNotificationPeriod(periodInFrames)); |
| assertEquals(TEST_NAME, periodInFrames, track.getPositionNotificationPeriod()); |
| track.setState(AudioTrack.STATE_NO_STATIC_DATA); |
| assertEquals(TEST_NAME, AudioTrack.STATE_NO_STATIC_DATA, track.getState()); |
| track.setState(AudioTrack.STATE_UNINITIALIZED); |
| assertEquals(TEST_NAME, AudioTrack.STATE_UNINITIALIZED, track.getState()); |
| int frameCount = 2 * minBuffSize; |
| if (TEST_CONF == AudioFormat.CHANNEL_CONFIGURATION_STEREO) { |
| frameCount /= 2; |
| } |
| if (TEST_FORMAT == AudioFormat.ENCODING_PCM_16BIT) { |
| frameCount /= 2; |
| } |
| assertTrue(TEST_NAME, track.getNativeFrameCount() >= frameCount); |
| assertEquals(TEST_NAME, track.getNativeFrameCount(), track.getBufferSizeInFrames()); |
| } |
| |
| @Test |
| public void testReloadStaticData() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testReloadStaticData"; |
| final int TEST_SR = 22050; |
| final int TEST_CONF = AudioFormat.CHANNEL_CONFIGURATION_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| // -------- initialization -------------- |
| int bufferSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| byte data[] = AudioHelper.createSoundDataInByteArray( |
| bufferSize, TEST_SR, 1024 /* frequency */, 0 /* sweep */); |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| bufferSize, TEST_MODE); |
| // -------- test -------------- |
| track.write(data, OFFSET_DEFAULT, bufferSize); |
| assertTrue(TEST_NAME, track.getState() == AudioTrack.STATE_INITIALIZED); |
| track.play(); |
| Thread.sleep(WAIT_MSEC); |
| track.stop(); |
| Thread.sleep(WAIT_MSEC); |
| assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.reloadStaticData()); |
| track.play(); |
| Thread.sleep(WAIT_MSEC); |
| track.stop(); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| @Presubmit |
| @Test |
| public void testPlayStaticDataShort() throws Exception { |
| if (!hasAudioOutput()) { |
| Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid " |
| + "audio output HAL"); |
| return; |
| } |
| // constants for test |
| final String TEST_NAME = "testPlayStaticDataShort"; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT; |
| final int TEST_SR = 48000; |
| final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| final double TEST_SWEEP = 100; |
| final int TEST_LOOPS = 1; |
| final double TEST_FREQUENCY = 400; |
| final long WAIT_TIME_MS = 150; // compensate for cold start when run in isolation. |
| final double TEST_LOOP_DURATION = 0.25; |
| final int TEST_ADDITIONAL_DRAIN_MS = 300; // as a presubmit test, 1% of the time the |
| // startup is slow by 200ms. |
| |
| playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP, |
| TEST_LOOPS, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF, |
| WAIT_TIME_MS, TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS); |
| |
| } |
| |
| @Test |
| public void testPlayStaticData() throws Exception { |
| if (!hasAudioOutput()) { |
| Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid " |
| + "audio output HAL"); |
| return; |
| } |
| // constants for test |
| final String TEST_NAME = "testPlayStaticData"; |
| final int TEST_FORMAT_ARRAY[] = { // 6 chirps repeated (TEST_LOOPS+1) times, 3 times |
| AudioFormat.ENCODING_PCM_8BIT, |
| AudioFormat.ENCODING_PCM_16BIT, |
| AudioFormat.ENCODING_PCM_FLOAT, |
| }; |
| final int TEST_SR_ARRAY[] = { |
| 12055, // Note multichannel tracks will sound very short at low sample rates |
| 48000, |
| }; |
| final int TEST_CONF_ARRAY[] = { |
| AudioFormat.CHANNEL_OUT_MONO, // 1.0 |
| AudioFormat.CHANNEL_OUT_STEREO, // 2.0 |
| AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, // 7.1 |
| }; |
| final int TEST_MODE = AudioTrack.MODE_STATIC; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| final double TEST_SWEEP = 100; |
| final int TEST_LOOPS = 1; |
| final double TEST_LOOP_DURATION = 1.; |
| final int TEST_ADDITIONAL_DRAIN_MS = 0; |
| // Compensates for cold start when run in isolation. |
| // The cold output latency must be 500 ms less or |
| // 200 ms less for low latency devices. |
| final long WAIT_TIME_MS = isLowLatencyDevice() ? WAIT_MSEC : 500; |
| |
| for (int TEST_FORMAT : TEST_FORMAT_ARRAY) { |
| double frequency = 400; // frequency changes for each test |
| for (int TEST_SR : TEST_SR_ARRAY) { |
| for (int TEST_CONF : TEST_CONF_ARRAY) { |
| playOnceStaticData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP, |
| TEST_LOOPS, TEST_FORMAT, frequency, TEST_SR, TEST_CONF, WAIT_TIME_MS, |
| TEST_LOOP_DURATION, TEST_ADDITIONAL_DRAIN_MS); |
| |
| frequency += 70; // increment test tone frequency |
| } |
| } |
| } |
| } |
| |
| private void playOnceStaticData(String testName, int testMode, int testStreamType, |
| double testSweep, int testLoops, int testFormat, double testFrequency, int testSr, |
| int testConf, long waitMsec, double testLoopDuration, int additionalDrainMs) |
| throws InterruptedException { |
| // -------- initialization -------------- |
| final int channelCount = Integer.bitCount(testConf); |
| final int bufferFrames = (int)(testLoopDuration * testSr); |
| final int bufferSamples = bufferFrames * channelCount; |
| final int bufferSize = bufferSamples |
| * AudioFormat.getBytesPerSample(testFormat); |
| final double frequency = testFrequency / channelCount; |
| final long MILLISECONDS_PER_SECOND = 1000; |
| AudioTrack track = new AudioTrack(testStreamType, testSr, |
| testConf, testFormat, bufferSize, testMode); |
| assertEquals(testName, AudioTrack.STATE_NO_STATIC_DATA, track.getState()); |
| |
| // -------- test -------------- |
| |
| // test setLoopPoints and setPosition can be called here. |
| assertEquals(testName, |
| android.media.AudioTrack.SUCCESS, |
| track.setPlaybackHeadPosition(bufferFrames/2)); |
| assertEquals(testName, |
| android.media.AudioTrack.SUCCESS, |
| track.setLoopPoints( |
| 0 /*startInFrames*/, bufferFrames, 10 /*loopCount*/)); |
| // only need to write once to the static track |
| switch (testFormat) { |
| case AudioFormat.ENCODING_PCM_8BIT: { |
| byte data[] = AudioHelper.createSoundDataInByteArray( |
| bufferSamples, testSr, |
| frequency, testSweep); |
| assertEquals(testName, |
| bufferSamples, |
| track.write(data, 0 /*offsetInBytes*/, data.length)); |
| } break; |
| case AudioFormat.ENCODING_PCM_16BIT: { |
| short data[] = AudioHelper.createSoundDataInShortArray( |
| bufferSamples, testSr, |
| frequency, testSweep); |
| assertEquals(testName, |
| bufferSamples, |
| track.write(data, 0 /*offsetInBytes*/, data.length)); |
| } break; |
| case AudioFormat.ENCODING_PCM_FLOAT: { |
| float data[] = AudioHelper.createSoundDataInFloatArray( |
| bufferSamples, testSr, |
| frequency, testSweep); |
| assertEquals(testName, |
| bufferSamples, |
| track.write(data, 0 /*offsetInBytes*/, data.length, |
| AudioTrack.WRITE_BLOCKING)); |
| } break; |
| } |
| assertEquals(testName, AudioTrack.STATE_INITIALIZED, track.getState()); |
| // test setLoopPoints and setPosition can be called here. |
| assertEquals(testName, |
| android.media.AudioTrack.SUCCESS, |
| track.setPlaybackHeadPosition(0 /*positionInFrames*/)); |
| assertEquals(testName, |
| android.media.AudioTrack.SUCCESS, |
| track.setLoopPoints(0 /*startInFrames*/, bufferFrames, testLoops)); |
| |
| track.play(); |
| Thread.sleep((int)(testLoopDuration * MILLISECONDS_PER_SECOND) * (testLoops + 1)); |
| Thread.sleep(waitMsec + additionalDrainMs); |
| |
| // Check position after looping. AudioTrack.getPlaybackHeadPosition() returns |
| // the running count of frames played, not the actual static buffer position. |
| int position = track.getPlaybackHeadPosition(); |
| assertEquals(testName, bufferFrames * (testLoops + 1), position); |
| |
| track.stop(); |
| Thread.sleep(waitMsec); |
| // -------- tear down -------------- |
| track.release(); |
| } |
| |
| @Presubmit |
| @Test |
| public void testPlayStreamDataShort() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testPlayStreamDataShort"; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_16BIT; |
| final int TEST_SR = 48000; |
| final int TEST_CONF = AudioFormat.CHANNEL_OUT_STEREO; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| final float TEST_SWEEP = 0; // sine wave only |
| final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice(); |
| final double TEST_FREQUENCY = 1000; |
| final long NO_WAIT = 0; |
| |
| playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP, |
| TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, TEST_FREQUENCY, TEST_SR, TEST_CONF, |
| NO_WAIT, 0 /* mask */); |
| } |
| |
| @Test |
| public void testPlayStreamData() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testPlayStreamData"; |
| final int TEST_FORMAT_ARRAY[] = { // should hear 40 increasing frequency tones, 3 times |
| AudioFormat.ENCODING_PCM_8BIT, |
| AudioFormat.ENCODING_PCM_16BIT, |
| AudioFormat.ENCODING_PCM_FLOAT, |
| }; |
| // due to downmixer algorithmic latency, source channels greater than 2 may |
| // sound shorter in duration at 4kHz sampling rate. |
| final int TEST_SR_ARRAY[] = { |
| 4000, |
| 44100, |
| 48000, |
| 96000, |
| 192000, |
| }; |
| final int TEST_CONF_ARRAY[] = { |
| AudioFormat.CHANNEL_OUT_MONO, // 1.0 |
| AudioFormat.CHANNEL_OUT_STEREO, // 2.0 |
| AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER, // 3.0 |
| AudioFormat.CHANNEL_OUT_QUAD, // 4.0 |
| AudioFormat.CHANNEL_OUT_QUAD | AudioFormat.CHANNEL_OUT_FRONT_CENTER, // 5.0 |
| AudioFormat.CHANNEL_OUT_5POINT1, // 5.1 |
| AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER, // 6.1 |
| AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, // 7.1 |
| }; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| final float TEST_SWEEP = 0; // sine wave only |
| final boolean TEST_IS_LOW_RAM_DEVICE = isLowRamDevice(); |
| |
| for (int TEST_FORMAT : TEST_FORMAT_ARRAY) { |
| double frequency = 400; // frequency changes for each test |
| for (int TEST_SR : TEST_SR_ARRAY) { |
| for (int TEST_CONF : TEST_CONF_ARRAY) { |
| playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP, |
| TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF, |
| WAIT_MSEC, 0 /* mask */); |
| frequency += 50; // increment test tone frequency |
| } |
| } |
| } |
| } |
| |
| private void playOnceStreamData(String testName, int testMode, int testStream, |
| float testSweep, boolean isLowRamDevice, int testFormat, double testFrequency, |
| int testSr, int testConf, long waitMsec, int mask) |
| throws InterruptedException { |
| final int channelCount = Integer.bitCount(testConf); |
| if (isLowRamDevice |
| && (testSr > 96000 || channelCount > 4)) { |
| return; // ignore. FIXME: reenable when AF memory allocation is updated. |
| } |
| // -------- initialization -------------- |
| final int minBufferSize = AudioTrack.getMinBufferSize(testSr, |
| testConf, testFormat); // in bytes |
| AudioTrack track = new AudioTrack(testStream, testSr, |
| testConf, testFormat, minBufferSize, testMode); |
| assertTrue(testName, track.getState() == AudioTrack.STATE_INITIALIZED); |
| |
| // compute parameters for the source signal data. |
| AudioFormat format = track.getFormat(); |
| assertEquals(testName, testSr, format.getSampleRate()); |
| assertEquals(testName, testConf, format.getChannelMask()); |
| assertEquals(testName, channelCount, format.getChannelCount()); |
| assertEquals(testName, testFormat, format.getEncoding()); |
| // duration of test tones |
| final int frames = AudioHelper.frameCountFromMsec(500 /* ms */, format); |
| final int sourceSamples = channelCount * frames; |
| final double frequency = testFrequency / channelCount; |
| |
| int written = 0; |
| // For streaming tracks, it's ok to issue the play() command |
| // before any audio is written. |
| track.play(); |
| // -------- test -------------- |
| |
| // samplesPerWrite can be any positive value. |
| // We prefer this to be a multiple of channelCount so write() |
| // does not return a short count. |
| // If samplesPerWrite is very large, it is limited to the data length |
| // and we simply write (blocking) the entire source data and not even loop. |
| // We choose a value here which simulates double buffer writes. |
| final int buffers = 2; // double buffering mode |
| final int samplesPerWrite = |
| (track.getBufferSizeInFrames() / buffers) * channelCount; |
| switch (testFormat) { |
| case AudioFormat.ENCODING_PCM_8BIT: { |
| byte data[] = AudioHelper.createSoundDataInByteArray( |
| sourceSamples, testSr, |
| frequency, testSweep); |
| if (mask != 0) { |
| AudioHelper.maskArray(data, testConf, mask); |
| } |
| while (written < data.length) { |
| int samples = Math.min(data.length - written, samplesPerWrite); |
| int ret = track.write(data, written, samples); |
| assertEquals(testName, samples, ret); |
| written += ret; |
| } |
| } |
| break; |
| case AudioFormat.ENCODING_PCM_16BIT: { |
| short data[] = AudioHelper.createSoundDataInShortArray( |
| sourceSamples, testSr, |
| frequency, testSweep); |
| if (mask != 0) { |
| AudioHelper.maskArray(data, testConf, mask); |
| } |
| while (written < data.length) { |
| int samples = Math.min(data.length - written, samplesPerWrite); |
| int ret = track.write(data, written, samples); |
| assertEquals(testName, samples, ret); |
| written += ret; |
| } |
| } |
| break; |
| case AudioFormat.ENCODING_PCM_FLOAT: { |
| float data[] = AudioHelper.createSoundDataInFloatArray( |
| sourceSamples, testSr, |
| frequency, testSweep); |
| if (mask != 0) { |
| AudioHelper.maskArray(data, testConf, mask); |
| } |
| while (written < data.length) { |
| int samples = Math.min(data.length - written, samplesPerWrite); |
| int ret = track.write(data, written, samples, |
| AudioTrack.WRITE_BLOCKING); |
| assertEquals(testName, samples, ret); |
| written += ret; |
| } |
| } |
| break; |
| } |
| |
| // For streaming tracks, AudioTrack.stop() doesn't immediately stop playback. |
| // Rather, it allows the remaining data in the internal buffer to drain. |
| track.stop(); |
| Thread.sleep(waitMsec); // wait for the data to drain. |
| // -------- tear down -------------- |
| track.release(); |
| Thread.sleep(waitMsec); // wait for release to complete |
| } |
| |
| private void playOnceStreamByteBuffer( |
| String testName, double testFrequency, double testSweep, |
| int testStreamType, int testSampleRate, int testChannelMask, int testEncoding, |
| int testTransferMode, int testWriteMode, |
| boolean useChannelIndex, boolean useDirect) throws Exception { |
| AudioTrack track = null; |
| try { |
| AudioFormat.Builder afb = new AudioFormat.Builder() |
| .setEncoding(testEncoding) |
| .setSampleRate(testSampleRate); |
| if (useChannelIndex) { |
| afb.setChannelIndexMask(testChannelMask); |
| } else { |
| afb.setChannelMask(testChannelMask); |
| } |
| final AudioFormat format = afb.build(); |
| final int frameSize = AudioHelper.frameSizeFromFormat(format); |
| final int frameCount = |
| AudioHelper.frameCountFromMsec(300 /* ms */, format); |
| final int bufferSize = frameCount * frameSize; |
| final int bufferSamples = frameCount * format.getChannelCount(); |
| |
| track = new AudioTrack.Builder() |
| .setAudioFormat(format) |
| .setTransferMode(testTransferMode) |
| .setBufferSizeInBytes(bufferSize) |
| .build(); |
| |
| assertEquals(testName + ": state", |
| AudioTrack.STATE_INITIALIZED, track.getState()); |
| assertEquals(testName + ": sample rate", |
| testSampleRate, track.getSampleRate()); |
| assertEquals(testName + ": encoding", |
| testEncoding, track.getAudioFormat()); |
| |
| ByteBuffer bb = useDirect |
| ? ByteBuffer.allocateDirect(bufferSize) |
| : ByteBuffer.allocate(bufferSize); |
| bb.order(java.nio.ByteOrder.nativeOrder()); |
| |
| final double sampleFrequency = testFrequency / format.getChannelCount(); |
| switch (testEncoding) { |
| case AudioFormat.ENCODING_PCM_8BIT: { |
| byte data[] = AudioHelper.createSoundDataInByteArray( |
| bufferSamples, testSampleRate, |
| sampleFrequency, testSweep); |
| bb.put(data); |
| bb.flip(); |
| } |
| break; |
| case AudioFormat.ENCODING_PCM_16BIT: { |
| short data[] = AudioHelper.createSoundDataInShortArray( |
| bufferSamples, testSampleRate, |
| sampleFrequency, testSweep); |
| ShortBuffer sb = bb.asShortBuffer(); |
| sb.put(data); |
| bb.limit(sb.limit() * 2); |
| } |
| break; |
| case AudioFormat.ENCODING_PCM_FLOAT: { |
| float data[] = AudioHelper.createSoundDataInFloatArray( |
| bufferSamples, testSampleRate, |
| sampleFrequency, testSweep); |
| FloatBuffer fb = bb.asFloatBuffer(); |
| fb.put(data); |
| bb.limit(fb.limit() * 4); |
| } |
| break; |
| } |
| // start the AudioTrack |
| // This can be done before or after the first write. |
| // Current behavior for streaming tracks is that |
| // actual playback does not begin before the internal |
| // data buffer is completely full. |
| track.play(); |
| |
| // write data |
| final long startTime = System.currentTimeMillis(); |
| final long maxDuration = frameCount * 1000 / testSampleRate + 1000; |
| for (int written = 0; written < bufferSize; ) { |
| // ret may return a short count if write |
| // is non blocking or even if write is blocking |
| // when a stop/pause/flush is issued from another thread. |
| final int kBatchFrames = 1000; |
| int ret = track.write(bb, |
| Math.min(bufferSize - written, frameSize * kBatchFrames), |
| testWriteMode); |
| // for non-blocking mode, this loop may spin quickly |
| assertTrue(testName + ": write error " + ret, ret >= 0); |
| assertTrue(testName + ": write timeout", |
| (System.currentTimeMillis() - startTime) <= maxDuration); |
| written += ret; |
| } |
| |
| // for streaming tracks, stop will allow the rest of the data to |
| // drain out, but we don't know how long to wait unless |
| // we check the position before stop. if we check position |
| // after we stop, we read 0. |
| final int position = track.getPlaybackHeadPosition(); |
| final int remainingTimeMs = (int)((double)(frameCount - position) |
| * 1000 / testSampleRate); |
| track.stop(); |
| Thread.sleep(remainingTimeMs); |
| Thread.sleep(WAIT_MSEC); |
| } finally { |
| if (track != null) { |
| track.release(); |
| } |
| } |
| } |
| |
| @Test |
| public void testPlayStreamByteBuffer() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testPlayStreamByteBuffer"; |
| final int TEST_FORMAT_ARRAY[] = { // should hear 4 tones played 3 times |
| AudioFormat.ENCODING_PCM_8BIT, |
| AudioFormat.ENCODING_PCM_16BIT, |
| AudioFormat.ENCODING_PCM_FLOAT, |
| }; |
| final int TEST_SR_ARRAY[] = { |
| 48000, |
| }; |
| final int TEST_CONF_ARRAY[] = { |
| AudioFormat.CHANNEL_OUT_STEREO, |
| }; |
| final int TEST_WRITE_MODE_ARRAY[] = { |
| AudioTrack.WRITE_BLOCKING, |
| AudioTrack.WRITE_NON_BLOCKING, |
| }; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| final double TEST_SWEEP = 0; // sine wave only |
| |
| for (int TEST_FORMAT : TEST_FORMAT_ARRAY) { |
| double frequency = 800; // frequency changes for each test |
| for (int TEST_SR : TEST_SR_ARRAY) { |
| for (int TEST_CONF : TEST_CONF_ARRAY) { |
| for (int TEST_WRITE_MODE : TEST_WRITE_MODE_ARRAY) { |
| for (int useDirect = 0; useDirect < 2; ++useDirect) { |
| playOnceStreamByteBuffer(TEST_NAME, frequency, TEST_SWEEP, |
| TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| TEST_MODE, TEST_WRITE_MODE, |
| false /* useChannelIndex */, useDirect != 0); |
| |
| // add a gap to make tones distinct |
| Thread.sleep(100 /* millis */); |
| frequency += 30; // increment test tone frequency |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testPlayChannelIndexStreamBuffer() throws Exception { |
| // should hear 4 tones played 3 or 4 times depending |
| // on the device output capabilities (e.g. stereo or 5.1 or otherwise) |
| final String TEST_NAME = "testPlayChannelIndexStreamBuffer"; |
| final int TEST_FORMAT_ARRAY[] = { |
| AudioFormat.ENCODING_PCM_8BIT, |
| //AudioFormat.ENCODING_PCM_16BIT, |
| //AudioFormat.ENCODING_PCM_FLOAT, |
| }; |
| final int TEST_SR_ARRAY[] = { |
| 48000, |
| }; |
| // The following channel index masks are iterated over and route |
| // the AudioTrack channels to the output sink channels based on |
| // the set bits in counting order (lsb to msb). |
| // |
| // For a stereo output sink, the sound may come from L and R, L only, none, or R only. |
| // For a 5.1 output sink, the sound may come from a variety of outputs |
| // as commented below. |
| final int TEST_CONF_ARRAY[] = { // matches output sink channels: |
| (1 << 0) | (1 << 1), // Stereo(L, R) 5.1(FL, FR) |
| (1 << 0) | (1 << 2), // Stereo(L) 5.1(FL, FC) |
| (1 << 4) | (1 << 5), // Stereo(None) 5.1(BL, BR) |
| (1 << 1) | (1 << 2), // Stereo(R) 5.1(FR, FC) |
| }; |
| final int TEST_WRITE_MODE_ARRAY[] = { |
| AudioTrack.WRITE_BLOCKING, |
| AudioTrack.WRITE_NON_BLOCKING, |
| }; |
| final double TEST_SWEEP = 0; |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| for (int TEST_FORMAT : TEST_FORMAT_ARRAY) { |
| for (int TEST_CONF : TEST_CONF_ARRAY) { |
| double frequency = 800; // frequency changes for each test |
| for (int TEST_SR : TEST_SR_ARRAY) { |
| for (int TEST_WRITE_MODE : TEST_WRITE_MODE_ARRAY) { |
| for (int useDirect = 0; useDirect < 2; ++useDirect) { |
| playOnceStreamByteBuffer(TEST_NAME, frequency, TEST_SWEEP, |
| TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| TEST_MODE, TEST_WRITE_MODE, |
| true /* useChannelIndex */, useDirect != 0); |
| |
| // add a gap to make tones distinct |
| Thread.sleep(100 /* millis */); |
| frequency += 30; // increment test tone frequency |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private boolean hasAudioOutput() { |
| return getContext().getPackageManager() |
| .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT); |
| } |
| |
| private boolean isLowLatencyDevice() { |
| return getContext().getPackageManager() |
| .hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY); |
| } |
| |
| private boolean isLowRamDevice() { |
| return ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE)) |
| .isLowRamDevice(); |
| } |
| |
| private boolean isProAudioDevice() { |
| return getContext().getPackageManager().hasSystemFeature( |
| PackageManager.FEATURE_AUDIO_PRO); |
| } |
| |
| @Test |
| public void testGetTimestamp() throws Exception { |
| if (!hasAudioOutput()) { |
| Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " |
| + "audio output HAL"); |
| return; |
| } |
| String streamName = "test_get_timestamp"; |
| doTestTimestamp( |
| 22050 /* sampleRate */, |
| AudioFormat.CHANNEL_OUT_MONO , |
| AudioFormat.ENCODING_PCM_16BIT, |
| AudioTrack.MODE_STREAM, |
| streamName); |
| } |
| |
| @Test |
| public void testFastTimestamp() throws Exception { |
| if (!hasAudioOutput()) { |
| Log.w(TAG, "AUDIO_OUTPUT feature not found. This system might not have a valid " |
| + "audio output HAL"); |
| return; |
| } |
| String streamName = "test_fast_timestamp"; |
| doTestTimestamp( |
| AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC), |
| AudioFormat.CHANNEL_OUT_MONO, |
| AudioFormat.ENCODING_PCM_16BIT, |
| AudioTrack.MODE_STREAM, |
| streamName); |
| } |
| |
| // Note: this test may fail if playing through a remote device such as Bluetooth. |
| private void doTestTimestamp(int sampleRate, int channelMask, int encoding, int transferMode, |
| String streamName) throws Exception { |
| // constants for test |
| final int TEST_LOOP_CNT = 10; |
| final int TEST_BUFFER_MS = 100; |
| final int TEST_USAGE = AudioAttributes.USAGE_MEDIA; |
| |
| final int MILLIS_PER_SECOND = 1000; |
| final int FRAME_TOLERANCE = sampleRate * TEST_BUFFER_MS / MILLIS_PER_SECOND; |
| |
| // -------- initialization -------------- |
| final int frameSize = |
| AudioFormat.getBytesPerSample(encoding) |
| * AudioFormat.channelCountFromOutChannelMask(channelMask); |
| // see whether we can use fast mode |
| final int nativeOutputSampleRate = |
| AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC); |
| Log.d(TAG, "Native output sample rate " + nativeOutputSampleRate); |
| final boolean fast = (sampleRate == nativeOutputSampleRate); |
| |
| AudioAttributes attributes = (fast ? new AudioAttributes.Builder() |
| .setFlags(AudioAttributes.FLAG_LOW_LATENCY) : new AudioAttributes.Builder()) |
| .setUsage(TEST_USAGE) |
| .build(); |
| AudioFormat format = new AudioFormat.Builder() |
| //.setChannelIndexMask((1 << AudioFormat.channelCountFromOutChannelMask(channelMask)) - 1) |
| .setChannelMask(channelMask) |
| .setEncoding(encoding) |
| .setSampleRate(sampleRate) |
| .build(); |
| // not specifying the buffer size in the builder should get us the minimum buffer size. |
| AudioTrack track = new AudioTrack.Builder() |
| .setAudioAttributes(attributes) |
| .setAudioFormat(format) |
| .setTransferMode(transferMode) |
| .build(); |
| assertEquals(AudioTrack.STATE_INITIALIZED, track.getState()); |
| |
| try { |
| // We generally use a transfer size of 100ms for testing, but in rare cases |
| // (e.g. Bluetooth) this needs to be larger to exceed the internal track buffer. |
| final int frameCount = |
| Math.max(track.getBufferCapacityInFrames(), |
| sampleRate * TEST_BUFFER_MS / MILLIS_PER_SECOND); |
| track.play(); |
| |
| // Android nanoTime implements MONOTONIC, same as our audio timestamps. |
| |
| final ByteBuffer data = ByteBuffer.allocate(frameCount * frameSize); |
| data.order(java.nio.ByteOrder.nativeOrder()).limit(frameCount * frameSize); |
| final AudioTimestamp timestamp = new AudioTimestamp(); |
| |
| long framesWritten = 0; |
| |
| // We start data delivery twice, the second start simulates restarting |
| // the track after a fully drained underrun (important case for Android TV). |
| for (int start = 0; start < 2; ++start) { |
| final long trackStartTimeNs = System.nanoTime(); |
| final AudioHelper.TimestampVerifier tsVerifier = |
| new AudioHelper.TimestampVerifier( |
| TAG + "(start " + start + ")", |
| sampleRate, framesWritten, isProAudioDevice()); |
| for (int i = 0; i < TEST_LOOP_CNT; ++i) { |
| final long trackWriteTimeNs = System.nanoTime(); |
| |
| data.position(0); |
| assertEquals("write did not complete", |
| data.limit(), track.write(data, data.limit(), |
| AudioTrack.WRITE_BLOCKING)); |
| assertEquals("write did not fill buffer", |
| data.position(), data.limit()); |
| framesWritten += data.limit() / frameSize; |
| |
| // track.getTimestamp may return false if there are no physical HAL outputs. |
| // This may occur on TV devices without connecting an HDMI monitor. |
| // It may also be true immediately after start-up, as the mixing thread could |
| // be idle, but since we've already pushed much more than the |
| // minimum buffer size, that is unlikely. |
| // Nevertheless, we don't want to have unnecessary failures, so we ignore the |
| // first iteration if we don't get a timestamp. |
| final boolean result = track.getTimestamp(timestamp); |
| assertTrue("timestamp could not be read", result || i == 0); |
| if (!result) { |
| continue; |
| } |
| |
| tsVerifier.add(timestamp); |
| |
| // Ensure that seen is greater than presented. |
| // This is an "on-the-fly" read without pausing because pausing may cause the |
| // timestamp to become stale and affect our jitter measurements. |
| final long framesPresented = timestamp.framePosition; |
| final int framesSeen = track.getPlaybackHeadPosition(); |
| assertTrue("server frames ahead of client frames", |
| framesWritten >= framesSeen); |
| assertTrue("presented frames ahead of server frames", |
| framesSeen >= framesPresented); |
| } |
| // Full drain. |
| Thread.sleep(1000 /* millis */); |
| // check that we are really at the end of playback. |
| assertTrue("timestamp should be valid while draining", |
| track.getTimestamp(timestamp)); |
| // Fast tracks and sw emulated tracks may not fully drain. |
| // We log the status here. |
| if (framesWritten != timestamp.framePosition) { |
| Log.d(TAG, "timestamp should fully drain. written: " |
| + framesWritten + " position: " + timestamp.framePosition); |
| } |
| final long framesLowerLimit = framesWritten - FRAME_TOLERANCE; |
| assertTrue("timestamp frame position needs to be close to written: " |
| + timestamp.framePosition + " >= " + framesLowerLimit, |
| timestamp.framePosition >= framesLowerLimit); |
| |
| assertTrue("timestamp should not advance during underrun: " |
| + timestamp.framePosition + " <= " + framesWritten, |
| timestamp.framePosition <= framesWritten); |
| |
| tsVerifier.verifyAndLog(trackStartTimeNs, streamName); |
| } |
| } finally { |
| track.release(); |
| } |
| } |
| |
| @Test |
| public void testVariableRatePlayback() throws Exception { |
| final String TEST_NAME = "testVariableRatePlayback"; |
| final int TEST_SR = 24000; |
| final int TEST_FINAL_SR = 96000; |
| final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; // required for test |
| final int TEST_MODE = AudioTrack.MODE_STATIC; // required for test |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| final int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); |
| final int bufferSizeInBytes = minBuffSize * 100; |
| final int numChannels = AudioFormat.channelCountFromOutChannelMask(TEST_CONF); |
| final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT); |
| final int bytesPerFrame = numChannels * bytesPerSample; |
| final int frameCount = bufferSizeInBytes / bytesPerFrame; |
| |
| AudioTrack track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, |
| TEST_FORMAT, bufferSizeInBytes, TEST_MODE); |
| |
| // create byte array and write it |
| byte[] vai = AudioHelper.createSoundDataInByteArray(bufferSizeInBytes, TEST_SR, |
| 600 /* frequency */, 0 /* sweep */); |
| assertEquals(vai.length, track.write(vai, 0 /* offsetInBytes */, vai.length)); |
| |
| // sweep up test and sweep down test |
| int[] sampleRates = {TEST_SR, TEST_FINAL_SR}; |
| int[] deltaMss = {10, 10}; |
| int[] deltaFreqs = {200, -200}; |
| |
| for (int i = 0; i < 2; ++i) { |
| int remainingTime; |
| int sampleRate = sampleRates[i]; |
| final int deltaMs = deltaMss[i]; |
| final int deltaFreq = deltaFreqs[i]; |
| final int lastCheckMs = 500; // check the last 500 ms |
| |
| assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackRate(sampleRate)); |
| track.play(); |
| do { |
| Thread.sleep(deltaMs); |
| final int position = track.getPlaybackHeadPosition(); |
| sampleRate += deltaFreq; |
| sampleRate = Math.min(TEST_FINAL_SR, Math.max(TEST_SR, sampleRate)); |
| assertEquals(TEST_NAME, AudioTrack.SUCCESS, track.setPlaybackRate(sampleRate)); |
| remainingTime = (int)((double)(frameCount - position) * 1000 |
| / sampleRate / bytesPerFrame); |
| } while (remainingTime >= lastCheckMs + deltaMs); |
| |
| // ensure the final frequency set is constant and plays frames as expected |
| final int position1 = track.getPlaybackHeadPosition(); |
| Thread.sleep(lastCheckMs); |
| final int position2 = track.getPlaybackHeadPosition(); |
| |
| final int tolerance60MsInFrames = sampleRate * 60 / 1000; |
| final int expected = lastCheckMs * sampleRate / 1000; |
| final int actual = position2 - position1; |
| |
| // Log.d(TAG, "Variable Playback: expected(" + expected + ") actual(" + actual |
| // + ") diff(" + (expected - actual) + ")"); |
| assertEquals(expected, actual, tolerance60MsInFrames); |
| track.stop(); |
| } |
| track.release(); |
| } |
| |
| // Test that AudioTrack stop limits drain to only those frames written at the time of stop. |
| // This ensures consistent stop behavior on Android P and beyond, where data written |
| // immediately after a stop doesn't get caught in the drain. |
| @LargeTest |
| @Test |
| public void testStopDrain() throws Exception { |
| final String TEST_NAME = "testStopDrain"; |
| final int TEST_SR = 8000; |
| final int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO; // required for test |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; // required for test |
| final int TEST_MODE = AudioTrack.MODE_STREAM; // required for test |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| final int channelCount = AudioFormat.channelCountFromOutChannelMask(TEST_CONF); |
| final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT); |
| final int bytesPerFrame = channelCount * bytesPerSample; |
| final int frameCount = TEST_SR * 3; // 3 seconds of buffer. |
| final int bufferSizeInBytes = frameCount * bytesPerFrame; |
| |
| final AudioTrack track = new AudioTrack( |
| TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, bufferSizeInBytes, TEST_MODE); |
| |
| try { |
| // Create 6 seconds of data, but send down only 3 seconds to fill buffer. |
| final byte[] soundData = AudioHelper.createSoundDataInByteArray( |
| bufferSizeInBytes * 2, TEST_SR, 600 /* frequency */, 0 /* sweep */); |
| assertEquals("cannot fill AudioTrack buffer", |
| bufferSizeInBytes, |
| track.write(soundData, 0 /* offsetInBytes */, bufferSizeInBytes)); |
| |
| // Set the track playing. |
| track.play(); |
| |
| // Note that the timings here are very generous for our test (really the |
| // granularity we need is on the order of a second). If we don't get scheduled |
| // to run within about a second or so - this should be extremely rare - |
| // the result should be a false pass (rather than a false fail). |
| |
| // After 1.5 seconds stop. |
| Thread.sleep(1500 /* millis */); // Assume device starts within 1.5 sec. |
| track.stop(); |
| |
| // We should drain 1.5 seconds and fill another 3 seconds of data. |
| // We shouldn't be able to write 6 seconds of data - that indicates stop continues |
| // to drain beyond the frames written at the time of stop. |
| int length = 0; |
| while (length < soundData.length) { |
| Thread.sleep(800 /* millis */); // assume larger than AF thread loop period |
| final int delta = track.write(soundData, length, soundData.length - length); |
| assertTrue("track write error: " + delta, delta >= 0); |
| if (delta == 0) break; |
| length += delta; |
| } |
| |
| // Check to see we limit the data drained (should be able to exactly fill the buffer). |
| assertEquals("stop drain must be limited " + bufferSizeInBytes + " != " + length, |
| bufferSizeInBytes, length); |
| } finally { |
| track.release(); |
| } |
| } |
| |
| @Test |
| public void testVariableSpeedPlayback() throws Exception { |
| if (!hasAudioOutput()) { |
| Log.w(TAG,"AUDIO_OUTPUT feature not found. This system might not have a valid " |
| + "audio output HAL"); |
| return; |
| } |
| |
| final String TEST_NAME = "testVariableSpeedPlayback"; |
| final int TEST_FORMAT = AudioFormat.ENCODING_PCM_FLOAT; // required for test |
| final int TEST_MODE = AudioTrack.MODE_STATIC; // required for test |
| final int TEST_SR = 48000; |
| |
| AudioFormat format = new AudioFormat.Builder() |
| //.setChannelIndexMask((1 << 0)) // output to first channel, FL |
| .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) |
| .setEncoding(TEST_FORMAT) |
| .setSampleRate(TEST_SR) |
| .build(); |
| |
| // create track |
| final int frameCount = AudioHelper.frameCountFromMsec(100 /*ms*/, format); |
| final int frameSize = AudioHelper.frameSizeFromFormat(format); |
| AudioTrack track = new AudioTrack.Builder() |
| .setAudioFormat(format) |
| .setBufferSizeInBytes(frameCount * frameSize) |
| .setTransferMode(TEST_MODE) |
| .build(); |
| |
| // create float array and write it |
| final int sampleCount = frameCount * format.getChannelCount(); |
| float[] vaf = AudioHelper.createSoundDataInFloatArray( |
| sampleCount, TEST_SR, 600 /* frequency */, 0 /* sweep */); |
| assertEquals(vaf.length, track.write(vaf, 0 /* offsetInFloats */, vaf.length, |
| AudioTrack.WRITE_NON_BLOCKING)); |
| |
| // sweep speed and pitch |
| final float[][][] speedAndPitch = { |
| // { {speedStart, pitchStart} {speedEnd, pitchEnd} } |
| { {0.5f, 0.5f}, {2.0f, 2.0f} }, // speed by SR conversion (chirp) |
| { {0.5f, 1.0f}, {2.0f, 1.0f} }, // speed by time stretch (constant pitch) |
| { {1.0f, 0.5f}, {1.0f, 2.0f} }, // pitch by SR conversion (chirp) |
| }; |
| |
| // test that playback params works as expected |
| PlaybackParams params = new PlaybackParams().allowDefaults(); |
| assertEquals("default speed not correct", 1.0f, params.getSpeed(), 0.f /* delta */); |
| assertEquals("default pitch not correct", 1.0f, params.getPitch(), 0.f /* delta */); |
| assertEquals(TEST_NAME, |
| params.AUDIO_FALLBACK_MODE_DEFAULT, |
| params.getAudioFallbackMode()); |
| track.setPlaybackParams(params); // OK |
| params.setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_FAIL); |
| assertEquals(TEST_NAME, |
| params.AUDIO_FALLBACK_MODE_FAIL, params.getAudioFallbackMode()); |
| params.setPitch(0.0f); |
| try { |
| track.setPlaybackParams(params); |
| fail("IllegalArgumentException should be thrown on out of range data"); |
| } catch (IllegalArgumentException e) { |
| ; // expect this is invalid |
| } |
| // on failure, the AudioTrack params should not change. |
| PlaybackParams paramCheck = track.getPlaybackParams(); |
| assertEquals(TEST_NAME, |
| paramCheck.AUDIO_FALLBACK_MODE_DEFAULT, paramCheck.getAudioFallbackMode()); |
| assertEquals("pitch should be unchanged on failure", |
| 1.0f, paramCheck.getPitch(), 0. /* delta */); |
| |
| // now try to see if we can do extreme pitch correction that should probably be muted. |
| params.setAudioFallbackMode(params.AUDIO_FALLBACK_MODE_MUTE); |
| assertEquals(TEST_NAME, |
| params.AUDIO_FALLBACK_MODE_MUTE, params.getAudioFallbackMode()); |
| params.setPitch(0.1f); |
| track.setPlaybackParams(params); // OK |
| |
| // now do our actual playback |
| final int TEST_TIME_MS = 2000; |
| final int TEST_DELTA_MS = 100; |
| final int testSteps = TEST_TIME_MS / TEST_DELTA_MS; |
| |
| for (int i = 0; i < speedAndPitch.length; ++i) { |
| final float speedStart = speedAndPitch[i][0][0]; |
| final float pitchStart = speedAndPitch[i][0][1]; |
| final float speedEnd = speedAndPitch[i][1][0]; |
| final float pitchEnd = speedAndPitch[i][1][1]; |
| final float speedInc = (speedEnd - speedStart) / testSteps; |
| final float pitchInc = (pitchEnd - pitchStart) / testSteps; |
| |
| PlaybackParams playbackParams = new PlaybackParams() |
| .setPitch(pitchStart) |
| .setSpeed(speedStart) |
| .allowDefaults(); |
| |
| // set track in infinite loop to be a sine generator |
| track.setLoopPoints(0, frameCount, -1 /* loopCount */); // cleared by stop() |
| track.play(); |
| |
| Thread.sleep(300 /* millis */); // warm up track |
| |
| int anticipatedPosition = track.getPlaybackHeadPosition(); |
| long timeMs = SystemClock.elapsedRealtime(); |
| final long startTimeMs = timeMs; |
| for (int j = 0; j < testSteps; ++j) { |
| // set playback settings |
| final float pitch = playbackParams.getPitch(); |
| final float speed = playbackParams.getSpeed(); |
| |
| track.setPlaybackParams(playbackParams); |
| |
| // verify that settings have changed |
| PlaybackParams checkParams = track.getPlaybackParams(); |
| assertEquals("pitch not changed correctly", |
| pitch, checkParams.getPitch(), 0. /* delta */); |
| assertEquals("speed not changed correctly", |
| speed, checkParams.getSpeed(), 0. /* delta */); |
| |
| // sleep for playback |
| Thread.sleep(TEST_DELTA_MS); |
| final long newTimeMs = SystemClock.elapsedRealtime(); |
| // Log.d(TAG, "position[" + j + "] " + track.getPlaybackHeadPosition()); |
| anticipatedPosition += |
| playbackParams.getSpeed() * (newTimeMs - timeMs) * TEST_SR / 1000; |
| timeMs = newTimeMs; |
| playbackParams.setPitch(playbackParams.getPitch() + pitchInc); |
| playbackParams.setSpeed(playbackParams.getSpeed() + speedInc); |
| } |
| final int endPosition = track.getPlaybackHeadPosition(); |
| final int tolerance100MsInFrames = 100 * TEST_SR / 1000; |
| Log.d(TAG, "Total playback time: " + (timeMs - startTimeMs)); |
| assertEquals(TAG, anticipatedPosition, endPosition, tolerance100MsInFrames); |
| track.stop(); |
| |
| Thread.sleep(100 /* millis */); // distinct pause between each test |
| } |
| track.release(); |
| } |
| |
| // Test AudioTrack to ensure we can build after a failure. |
| @Test |
| public void testAudioTrackBufferSize() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testAudioTrackBufferSize"; |
| |
| // use builder with parameters that should fail |
| final int superBigBufferSize = 1 << 28; |
| try { |
| final AudioTrack track = new AudioTrack.Builder() |
| .setBufferSizeInBytes(superBigBufferSize) |
| .build(); |
| track.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 AudioTrack track2 = new AudioTrack.Builder() |
| .setBufferSizeInBytes(verySmallBufferSize) |
| .build(); |
| |
| final int observedState2 = track2.getState(); |
| final int observedBufferSize2 = track2.getBufferSizeInFrames(); |
| track2.release(); |
| |
| // succeeds for minimum buffer size |
| assertEquals(TEST_NAME + ": state", AudioTrack.STATE_INITIALIZED, observedState2); |
| // should force the minimum size buffer which is > 0 |
| assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0); |
| } |
| |
| // Test AudioTrack to see if there are any problems with large frame counts. |
| @Test |
| public void testAudioTrackLargeFrameCount() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testAudioTrackLargeFrameCount"; |
| final int[] BUFFER_SIZES = { 4294968, 42949680, 429496800, Integer.MAX_VALUE }; |
| final int[] MODES = { AudioTrack.MODE_STATIC, AudioTrack.MODE_STREAM }; |
| |
| for (int mode : MODES) { |
| for (int bufferSizeInBytes : BUFFER_SIZES) { |
| try { |
| final AudioTrack track = new AudioTrack.Builder() |
| .setAudioFormat(new AudioFormat.Builder() |
| .setEncoding(AudioFormat.ENCODING_PCM_8BIT) |
| .setSampleRate(44100) |
| .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) |
| .build()) |
| .setTransferMode(mode) |
| .setBufferSizeInBytes(bufferSizeInBytes) // 1 byte == 1 frame |
| .build(); |
| track.release(); // OK to successfully complete |
| } catch (UnsupportedOperationException e) { |
| ; // OK to throw unsupported exception |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testSetNullPresentation() throws Exception { |
| final AudioTrack track = new AudioTrack.Builder().build(); |
| assertThrows(IllegalArgumentException.class, () -> { |
| track.setPresentation(null); |
| }); |
| } |
| |
| @Test |
| public void testAc3BuilderNoBufferSize() throws Exception { |
| AudioFormat format = new AudioFormat.Builder() |
| .setEncoding(AudioFormat.ENCODING_AC3) |
| .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) |
| .setSampleRate(48000) |
| .build(); |
| try { |
| AudioTrack audioTrack = new AudioTrack.Builder() |
| .setAudioFormat(format) |
| .setBufferSizeInBytes(100) |
| .build(); |
| audioTrack.release(); |
| Thread.sleep(200); |
| } catch (UnsupportedOperationException e) { |
| // Do nothing. It's OK for a device to not support ac3 audio tracks. |
| return; |
| } |
| // if ac3 audio tracks with set buffer size succeed, the builder should also succeed if the |
| // buffer size isn't set, allowing the framework to report the recommended buffer size. |
| try { |
| AudioTrack audioTrack = new AudioTrack.Builder() |
| .setAudioFormat(format) |
| .build(); |
| audioTrack.release(); |
| } catch (UnsupportedOperationException e) { |
| // This builder should not fail as the first builder succeeded when setting buffer size |
| fail("UnsupportedOperationException should not be thrown when setBufferSizeInBytes" |
| + " is excluded from builder"); |
| } |
| } |
| |
| @Test |
| public void testSetPresentationDefaultTrack() throws Exception { |
| final AudioTrack track = new AudioTrack.Builder().build(); |
| assertEquals(AudioTrack.ERROR, track.setPresentation(createAudioPresentation())); |
| } |
| |
| @Test |
| public void testIsDirectPlaybackSupported() throws Exception { |
| // constants for test |
| final String TEST_NAME = "testIsDirectPlaybackSupported"; |
| // Default format leaves everything unspecified |
| assertFalse(AudioTrack.isDirectPlaybackSupported( |
| new AudioFormat.Builder().build(), |
| new AudioAttributes.Builder().build())); |
| // There is no requirement to support direct playback for this format, |
| // so it's not possible to assert on the result, but at least the method |
| // must execute with no exceptions. |
| boolean isPcmStereo48kSupported = AudioTrack.isDirectPlaybackSupported( |
| new AudioFormat.Builder() |
| .setEncoding(AudioFormat.ENCODING_PCM_16BIT) |
| .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO) |
| .setSampleRate(48000) |
| .build(), |
| new AudioAttributes.Builder().build()); |
| log(TEST_NAME, "PCM Stereo 48 kHz: " + isPcmStereo48kSupported); |
| } |
| |
| @Test |
| public void testMediaMetrics() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| AudioTrack track = null; |
| try { |
| final int TEST_SAMPLE_RATE = 44100; |
| final int TEST_CHANNEL_MASK = AudioFormat.CHANNEL_OUT_STEREO; |
| final int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT; |
| final AudioFormat format = new AudioFormat.Builder() |
| .setSampleRate(TEST_SAMPLE_RATE) |
| .setChannelMask(TEST_CHANNEL_MASK) |
| .setEncoding(TEST_ENCODING) |
| .build(); |
| |
| final int TEST_USAGE = AudioAttributes.USAGE_MEDIA; |
| final int TEST_CONTENT_TYPE = AudioAttributes.CONTENT_TYPE_MUSIC; |
| final AudioAttributes attributes = new AudioAttributes.Builder() |
| .setUsage(TEST_USAGE) |
| .setContentType(TEST_CONTENT_TYPE) |
| .build(); |
| |
| // Setup a new audio track |
| track = new AudioTrack.Builder() |
| .setAudioFormat(format) |
| .setAudioAttributes(attributes) |
| .build(); |
| |
| final PersistableBundle metrics = track.getMetrics(); |
| assertNotNull("null metrics", metrics); |
| |
| // The STREAMTYPE constant was generally not present in P, and if so |
| // was incorrectly exposed as an integer. |
| AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.STREAMTYPE, |
| new String("AUDIO_STREAM_MUSIC")); |
| AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.CONTENTTYPE, |
| new String("AUDIO_CONTENT_TYPE_MUSIC")); |
| AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.USAGE, |
| new String("AUDIO_USAGE_MEDIA")); |
| |
| // AudioTrack.MetricsConstants.SAMPLERATE, metrics doesn't exit |
| // AudioTrack.MetricsConstants.CHANNELMASK, metrics doesn't exist |
| |
| // TestApi: |
| AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.SAMPLE_RATE, |
| new Integer(track.getSampleRate())); |
| AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.CHANNEL_MASK, |
| new Long(TEST_CHANNEL_MASK >> 2)); |
| AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.ENCODING, |
| new String("AUDIO_FORMAT_PCM_16_BIT")); |
| AudioHelper.assertMetricsKeyEquals(metrics, AudioTrack.MetricsConstants.FRAME_COUNT, |
| new Integer(track.getBufferSizeInFrames())); |
| |
| // TestApi: no particular value checking. |
| AudioHelper.assertMetricsKey(metrics, AudioTrack.MetricsConstants.PORT_ID); |
| AudioHelper.assertMetricsKey(metrics, AudioTrack.MetricsConstants.ATTRIBUTES); |
| } finally { |
| if (track != null) { |
| track.release(); |
| } |
| } |
| } |
| |
| @Test |
| public void testMaxAudioTracks() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| // The framework must not give more than MAX_TRACKS tracks per UID. |
| final int MAX_TRACKS = 512; // an arbitrary large number > 40 |
| final int FRAMES = 1024; |
| |
| final AudioTrack[] tracks = new AudioTrack[MAX_TRACKS]; |
| final AudioTrack.Builder builder = new AudioTrack.Builder() |
| .setAudioFormat(new AudioFormat.Builder() |
| .setEncoding(AudioFormat.ENCODING_PCM_8BIT) |
| .setSampleRate(8000) |
| .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) |
| .build()) |
| .setBufferSizeInBytes(FRAMES) |
| .setTransferMode(AudioTrack.MODE_STATIC); |
| |
| int n = 0; |
| try { |
| for (; n < MAX_TRACKS; ++n) { |
| tracks[n] = builder.build(); |
| } |
| } catch (UnsupportedOperationException e) { |
| ; // we expect this when we hit the uid track limit. |
| } |
| |
| // release all the tracks created. |
| for (int i = 0; i < n; ++i) { |
| tracks[i].release(); |
| tracks[i] = null; |
| } |
| Log.d(TAG, "" + n + " tracks were created"); |
| assertTrue("should be able to create at least one static track", n > 0); |
| assertTrue("was able to create " + MAX_TRACKS + " tracks - that's too many!", |
| n < MAX_TRACKS); |
| } |
| |
| @Test |
| public void testTunerConfiguration() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> { |
| final AudioTrack.TunerConfiguration badConfig = |
| new AudioTrack.TunerConfiguration(-1 /* contentId */, 1 /* syncId */); |
| }); |
| |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> { |
| final AudioTrack.TunerConfiguration badConfig = |
| new AudioTrack.TunerConfiguration(1 /* contentId*/, 0 /* syncId */); |
| }); |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> { |
| final AudioTrack track = new AudioTrack.Builder() |
| .setEncapsulationMode(-1) |
| .build(); |
| track.release(); |
| }); |
| |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> { |
| final AudioTrack track = new AudioTrack.Builder() |
| .setTunerConfiguration(null) |
| .build(); |
| track.release(); |
| }); |
| |
| // this should work. |
| int[][] contentSyncPairs = { |
| {1, 2}, |
| {AudioTrack.TunerConfiguration.CONTENT_ID_NONE, 42}, |
| }; |
| for (int[] pair : contentSyncPairs) { |
| final int contentId = pair[0]; |
| final int syncId = pair[1]; |
| final AudioTrack.TunerConfiguration tunerConfiguration = |
| new AudioTrack.TunerConfiguration(contentId, syncId); |
| |
| assertEquals("contentId must be set", contentId, tunerConfiguration.getContentId()); |
| assertEquals("syncId must be set", syncId, tunerConfiguration.getSyncId()); |
| |
| // this may fail on creation, not in any setters. |
| AudioTrack track = null; |
| try { |
| track = new AudioTrack.Builder() |
| .setEncapsulationMode(AudioTrack.ENCAPSULATION_MODE_NONE) |
| .setTunerConfiguration(tunerConfiguration) |
| .build(); |
| } catch (UnsupportedOperationException e) { |
| ; // creation failure is OK as TunerConfiguration requires HW support, |
| // however other exception failures are not OK. |
| } finally { |
| if (track != null) { |
| track.release(); |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testCodecFormatChangedListener() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| final AudioTrack audioTrack = new AudioTrack.Builder().build(); |
| |
| assertThrows( |
| NullPointerException.class, |
| () -> { audioTrack.addOnCodecFormatChangedListener( |
| null /* executor */, null /* listener */); }); |
| |
| assertThrows( |
| NullPointerException.class, |
| () -> { audioTrack.removeOnCodecFormatChangedListener(null /* listener */); }); |
| |
| |
| final AudioTrack.OnCodecFormatChangedListener listener = |
| (AudioTrack track, AudioMetadataReadMap readMap) -> {}; |
| |
| // add a synchronous executor. |
| audioTrack.addOnCodecFormatChangedListener(new Executor() { |
| @Override |
| public void execute(Runnable r) { |
| r.run(); |
| } |
| }, listener); |
| audioTrack.removeOnCodecFormatChangedListener(listener); |
| audioTrack.release(); |
| } |
| |
| @Test |
| public void testDualMonoMode() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| final AudioTrack audioTrack = new AudioTrack.Builder().build(); |
| |
| // Note that the output device may not support Dual Mono mode. |
| // The following path should always succeed. |
| audioTrack.setDualMonoMode(AudioTrack.DUAL_MONO_MODE_OFF); |
| assertEquals(AudioTrack.DUAL_MONO_MODE_OFF, audioTrack.getDualMonoMode()); |
| |
| // throws IAE on invalid argument. |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> { audioTrack.setDualMonoMode(-1); } |
| ); |
| |
| // check behavior after release. |
| audioTrack.release(); |
| assertThrows( |
| IllegalStateException.class, |
| () -> { audioTrack.setDualMonoMode(AudioTrack.DUAL_MONO_MODE_OFF); } |
| ); |
| assertEquals(AudioTrack.DUAL_MONO_MODE_OFF, audioTrack.getDualMonoMode()); |
| } |
| |
| @Test |
| public void testAudioDescriptionMixLevel() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| final AudioTrack audioTrack = new AudioTrack.Builder().build(); |
| |
| // Note that the output device may not support Audio Description Mix Level. |
| // The following path should always succeed. |
| audioTrack.setAudioDescriptionMixLeveldB(Float.NEGATIVE_INFINITY); |
| assertEquals(Float.NEGATIVE_INFINITY, |
| audioTrack.getAudioDescriptionMixLeveldB(), 0.f /*delta*/); |
| |
| // throws IAE on invalid argument. |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> { audioTrack.setAudioDescriptionMixLeveldB(1e6f); } |
| ); |
| |
| // check behavior after release. |
| audioTrack.release(); |
| assertThrows( |
| IllegalStateException.class, |
| () -> { audioTrack.setAudioDescriptionMixLeveldB(0.f); } |
| ); |
| assertEquals(Float.NEGATIVE_INFINITY, |
| audioTrack.getAudioDescriptionMixLeveldB(), 0.f /*delta*/); |
| } |
| |
| @Test |
| public void testSetLogSessionId() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| AudioTrack audioTrack = null; |
| try { |
| audioTrack = new AudioTrack.Builder() |
| .setAudioFormat(new AudioFormat.Builder() |
| .setEncoding(AudioFormat.ENCODING_PCM_16BIT) |
| .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) |
| .build()) |
| .build(); |
| audioTrack.setLogSessionId(LogSessionId.LOG_SESSION_ID_NONE); // should not throw. |
| assertEquals(LogSessionId.LOG_SESSION_ID_NONE, audioTrack.getLogSessionId()); |
| |
| final String ARBITRARY_MAGIC = "0123456789abcdef"; // 16 char Base64Url. |
| audioTrack.setLogSessionId(new LogSessionId(ARBITRARY_MAGIC)); |
| assertEquals(new LogSessionId(ARBITRARY_MAGIC), audioTrack.getLogSessionId()); |
| |
| final MediaMetricsManager mediaMetricsManager = |
| getContext().getSystemService(MediaMetricsManager.class); |
| final PlaybackSession playbackSession = mediaMetricsManager.createPlaybackSession(); |
| audioTrack.setLogSessionId(playbackSession.getSessionId()); |
| assertEquals(playbackSession.getSessionId(), audioTrack.getLogSessionId()); |
| |
| // write some data to generate a log entry. |
| short data[] = new short[audioTrack.getSampleRate() / 2]; |
| audioTrack.play(); |
| audioTrack.write(data, 0 /* offsetInShorts */, data.length); |
| audioTrack.stop(); |
| Thread.sleep(500 /* millis */); // drain |
| |
| // Also can check the mediametrics dumpsys to validate logs generated. |
| } finally { |
| if (audioTrack != null) { |
| audioTrack.release(); |
| } |
| } |
| } |
| |
| /* |
| * The following helpers and tests are used to test setting |
| * and getting the start threshold in frames. |
| * |
| * See Android CDD 5.6 [C-1-2] Cold output latency |
| */ |
| private static final int START_THRESHOLD_SLEEP_MILLIS = 500; |
| |
| /** |
| * Helper test that validates setting the start threshold. |
| * |
| * @param track |
| * @param startThresholdInFrames |
| * @throws Exception |
| */ |
| private static void validateSetStartThresholdInFrames( |
| AudioTrack track, int startThresholdInFrames) throws Exception { |
| assertEquals(startThresholdInFrames, |
| track.setStartThresholdInFrames(startThresholdInFrames)); |
| assertEquals(startThresholdInFrames, |
| track.getStartThresholdInFrames()); |
| } |
| |
| /** |
| * Helper that tests that the head position eventually equals expectedFrames. |
| * |
| * Exponential backoff to ~ 2 x START_THRESHOLD_SLEEP_MILLIS |
| * |
| * @param track |
| * @param expectedFrames |
| * @param message |
| * @throws Exception |
| */ |
| private static void validatePlaybackHeadPosition( |
| AudioTrack track, int expectedFrames, String message) throws Exception { |
| int cumulativeMillis = 0; |
| int playbackHeadPosition = 0; |
| for (double testMillis = START_THRESHOLD_SLEEP_MILLIS * 0.125; |
| testMillis <= START_THRESHOLD_SLEEP_MILLIS; // this is exact for IEEE binary double |
| testMillis *= 2.) { |
| Thread.sleep((int)testMillis); |
| playbackHeadPosition = track.getPlaybackHeadPosition(); |
| if (playbackHeadPosition == expectedFrames) return; |
| cumulativeMillis += (int)testMillis; |
| } |
| fail(message + ": expected track playbackHeadPosition: " + expectedFrames |
| + " actual playbackHeadPosition: " + playbackHeadPosition |
| + " wait time: " + cumulativeMillis + "ms"); |
| } |
| |
| /** |
| * Helper test that sets the start threshold to frames, and validates |
| * writing exactly frames amount of data is needed to start the |
| * track streaming. |
| * |
| * @param track |
| * @param frames |
| * @throws Exception |
| */ |
| private static void validateWriteStartsStream( |
| AudioTrack track, int frames) throws Exception { |
| assertEquals(1, track.getChannelCount()); // must be MONO |
| final short[] data = new short[frames]; |
| |
| // The track must be idle/underrun or the test will fail. |
| int expectedFrames = track.getPlaybackHeadPosition(); |
| |
| // Set our threshold to frames. |
| validateSetStartThresholdInFrames(track, frames); |
| |
| Thread.sleep(START_THRESHOLD_SLEEP_MILLIS); |
| assertEquals("Changing start threshold doesn't start if it is larger than buffer data", |
| expectedFrames, track.getPlaybackHeadPosition()); |
| |
| // Write a small amount of data, this isn't enough to start the track. |
| final int PARTIAL_WRITE_IN_FRAMES = frames - 1; |
| track.write(data, 0 /* offsetInShorts */, PARTIAL_WRITE_IN_FRAMES); |
| |
| // Ensure the track hasn't started. |
| Thread.sleep(START_THRESHOLD_SLEEP_MILLIS); |
| assertEquals("Track needs enough frames to start", |
| expectedFrames, track.getPlaybackHeadPosition()); |
| |
| // Write exactly threshold frames out, this should kick the playback off. |
| track.write(data, 0 /* offsetInShorts */, data.length - PARTIAL_WRITE_IN_FRAMES); |
| |
| // Verify that we have processed the data now. |
| expectedFrames += frames; |
| Thread.sleep(frames * 1000L / track.getSampleRate()); // accommodate for #frames. |
| validatePlaybackHeadPosition(track, expectedFrames, |
| "Writing buffer data to start threshold should start streaming"); |
| } |
| |
| /** |
| * Helper that tests reducing the start threshold to frames will start track |
| * streaming when frames of data are written to it. (Presumes the |
| * previous start threshold was greater than frames). |
| * |
| * @param track |
| * @param frames |
| * @throws Exception |
| */ |
| private static void validateSetStartThresholdStartsStream( |
| AudioTrack track, int frames) throws Exception { |
| assertTrue(track.getStartThresholdInFrames() > frames); |
| assertEquals(1, track.getChannelCount()); // must be MONO |
| final short[] data = new short[frames]; |
| |
| // The track must be idle/underrun or the test will fail. |
| int expectedFrames = track.getPlaybackHeadPosition(); |
| |
| // This write is too small for now. |
| track.write(data, 0 /* offsetInShorts */, data.length); |
| |
| Thread.sleep(START_THRESHOLD_SLEEP_MILLIS); |
| assertEquals("Track needs enough frames to start", |
| expectedFrames, track.getPlaybackHeadPosition()); |
| |
| // Reduce our start threshold. This should start streaming. |
| validateSetStartThresholdInFrames(track, frames); |
| |
| // Verify that we have processed the data now. |
| expectedFrames += frames; |
| Thread.sleep(frames * 1000L / track.getSampleRate()); // accommodate for #frames. |
| validatePlaybackHeadPosition(track, expectedFrames, |
| "Changing start threshold to buffer data level should start streaming"); |
| } |
| |
| // Start threshold levels that we check. |
| private enum ThresholdLevel { LOW, MEDIUM, HIGH }; |
| @Test |
| public void testStartThresholdInFrames() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| for (ThresholdLevel level : new ThresholdLevel[] { |
| ThresholdLevel.LOW, ThresholdLevel.MEDIUM, ThresholdLevel.HIGH}) { |
| AudioTrack audioTrack = null; |
| try { |
| // Build our audiotrack |
| audioTrack = new AudioTrack.Builder() |
| .setAudioFormat(new AudioFormat.Builder() |
| .setEncoding(AudioFormat.ENCODING_PCM_16BIT) |
| .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) |
| .build()) |
| .build(); |
| |
| // Initially the start threshold must be the same as the buffer size in frames. |
| final int bufferSizeInFrames = audioTrack.getBufferSizeInFrames(); |
| assertEquals("At start, getBufferSizeInFrames should equal getStartThresholdInFrames", |
| bufferSizeInFrames, |
| audioTrack.getStartThresholdInFrames()); |
| |
| final int TARGET_THRESHOLD_IN_FRAMES; // threshold level to verify |
| switch (level) { |
| default: |
| case LOW: |
| TARGET_THRESHOLD_IN_FRAMES = 2; |
| break; |
| case MEDIUM: |
| TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames / 2; |
| break; |
| case HIGH: |
| TARGET_THRESHOLD_IN_FRAMES = bufferSizeInFrames - 1; |
| break; |
| } |
| |
| // Skip extreme cases that don't need testing. |
| if (TARGET_THRESHOLD_IN_FRAMES < 2 |
| || TARGET_THRESHOLD_IN_FRAMES >= bufferSizeInFrames) continue; |
| |
| // Start the AudioTrack. Now the track is waiting for data. |
| audioTrack.play(); |
| |
| validateWriteStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES); |
| |
| // Try a condition that requires buffers to be filled again. |
| if (false) { |
| // Only a deep underrun when the track becomes inactive requires a refill. |
| // Disabled as this is dependent on underlying MixerThread timeouts. |
| Thread.sleep(5000 /* millis */); |
| } else { |
| // Flushing will require a refill (this does not require timing). |
| audioTrack.pause(); |
| audioTrack.flush(); |
| audioTrack.play(); |
| } |
| |
| // Check that reducing to a smaller threshold will start the track streaming. |
| validateSetStartThresholdStartsStream(audioTrack, TARGET_THRESHOLD_IN_FRAMES - 1); |
| } finally { |
| if (audioTrack != null) { |
| audioTrack.release(); |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testStartThresholdInFramesExceptions() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| AudioTrack audioTrack = null; |
| try { |
| // Build our audiotrack |
| audioTrack = new AudioTrack.Builder() |
| .setAudioFormat(new AudioFormat.Builder() |
| .setEncoding(AudioFormat.ENCODING_PCM_16BIT) |
| .setChannelMask(AudioFormat.CHANNEL_OUT_MONO) |
| .build()) |
| .build(); |
| |
| // Test setting invalid start threshold. |
| final AudioTrack track = audioTrack; // make final for lambda |
| assertThrows(IllegalArgumentException.class, () -> { |
| track.setStartThresholdInFrames(-1 /* startThresholdInFrames */); |
| }); |
| } finally { |
| if (audioTrack != null) { |
| audioTrack.release(); |
| } |
| } |
| // If we're here audioTrack should be non-null but released, |
| // so calls should return an IllegalStateException. |
| final AudioTrack track = audioTrack; // make final for lambda |
| assertThrows(IllegalStateException.class, () -> { |
| track.getStartThresholdInFrames(); |
| }); |
| assertThrows(IllegalStateException.class, () -> { |
| track.setStartThresholdInFrames(1 /* setStartThresholdInFrames */); |
| }); |
| } |
| |
| /** |
| * Tests height channel masks and higher channel counts |
| * used in immersive AudioTrack streaming. |
| * |
| * @throws Exception |
| */ |
| @Test |
| public void testImmersiveStreaming() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| final String TEST_NAME = "testImmersiveStreaming"; |
| final int TEST_FORMAT_ARRAY[] = { |
| AudioFormat.ENCODING_PCM_16BIT, |
| AudioFormat.ENCODING_PCM_FLOAT, |
| }; |
| final int TEST_SR_ARRAY[] = { |
| 48000, // do not set too high - costly in memory. |
| }; |
| final int TEST_CONF_ARRAY[] = { |
| AudioFormat.CHANNEL_OUT_5POINT1POINT2, // 8 ch (includes height channels vs 7.1). |
| AudioFormat.CHANNEL_OUT_7POINT1POINT2, // 10ch |
| AudioFormat.CHANNEL_OUT_7POINT1POINT4, // 12 ch |
| AudioFormat.CHANNEL_OUT_9POINT1POINT4, // 14 ch |
| AudioFormat.CHANNEL_OUT_9POINT1POINT6, // 16 ch |
| AudioFormat.CHANNEL_OUT_22POINT2, // 24 ch |
| }; |
| |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| final float TEST_SWEEP = 0; // sine wave only |
| final boolean TEST_IS_LOW_RAM_DEVICE = false; |
| for (int TEST_FORMAT : TEST_FORMAT_ARRAY) { |
| double frequency = 400; // Note: frequency changes for each test |
| for (int TEST_SR : TEST_SR_ARRAY) { |
| for (int TEST_CONF : TEST_CONF_ARRAY) { |
| if (AudioFormat.channelCountFromOutChannelMask(TEST_CONF) |
| > AudioSystem.OUT_CHANNEL_COUNT_MAX) { |
| continue; // Skip if the channel count exceeds framework capabilities. |
| } |
| playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP, |
| TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, TEST_CONF, |
| WAIT_MSEC, 0 /* mask */); |
| frequency += 50; // increment test tone frequency |
| } |
| } |
| } |
| } |
| |
| @Test |
| public void testImmersiveChannelIndex() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| final String TEST_NAME = "testImmersiveChannelIndex"; |
| final int TEST_FORMAT_ARRAY[] = { |
| AudioFormat.ENCODING_PCM_FLOAT, |
| }; |
| final int TEST_SR_ARRAY[] = { |
| 48000, // do not set too high - costly in memory. |
| }; |
| final int MAX_CHANNEL_BIT = 1 << (AudioSystem.FCC_24 - 1); // highest allowed channel. |
| final int TEST_CONF_ARRAY[] = { |
| MAX_CHANNEL_BIT, // likely silent - no physical device on top channel. |
| MAX_CHANNEL_BIT | 1, // first channel will likely have physical device. |
| (1 << AudioSystem.OUT_CHANNEL_COUNT_MAX) - 1, |
| }; |
| final int TEST_WRITE_MODE_ARRAY[] = { |
| AudioTrack.WRITE_BLOCKING, |
| AudioTrack.WRITE_NON_BLOCKING, |
| }; |
| final double TEST_SWEEP = 0; |
| final int TEST_TRANSFER_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| |
| double frequency = 200; // frequency changes for each test |
| for (int TEST_FORMAT : TEST_FORMAT_ARRAY) { |
| for (int TEST_SR : TEST_SR_ARRAY) { |
| for (int TEST_WRITE_MODE : TEST_WRITE_MODE_ARRAY) { |
| for (int useDirect = 0; useDirect < 2; ++useDirect) { |
| for (int TEST_CONF : TEST_CONF_ARRAY) { |
| // put TEST_CONF in the inner loop to avoid |
| // back-to-back creation of large tracks. |
| playOnceStreamByteBuffer( |
| TEST_NAME, frequency, TEST_SWEEP, |
| TEST_STREAM_TYPE, TEST_SR, TEST_CONF, TEST_FORMAT, |
| TEST_TRANSFER_MODE, TEST_WRITE_MODE, |
| true /* useChannelIndex */, useDirect != 0); |
| frequency += 30; // increment test tone frequency |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Verifies downmixer works with different AudioTrack surround channel masks. |
| * |
| * Also a listening test: on a stereo output device, you should hear sine wave tones |
| * instead of silence if the downmixer is working. |
| * |
| * @throws Exception |
| */ |
| @Test |
| public void testDownmix() throws Exception { |
| if (!hasAudioOutput()) { |
| return; |
| } |
| |
| final String TEST_NAME = "testDownmix"; |
| final int TEST_FORMAT_ARRAY[] = { |
| // AudioFormat.ENCODING_PCM_8BIT, // sounds a bit tinny |
| AudioFormat.ENCODING_PCM_16BIT, |
| AudioFormat.ENCODING_PCM_FLOAT, |
| }; |
| final int TEST_SR_ARRAY[] = { |
| 48000, |
| }; |
| final int TEST_CONF_ARRAY[] = { |
| // This test will play back FRONT_WIDE_LEFT, then FRONT_WIDE_RIGHT. |
| AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT | |
| AudioFormat.CHANNEL_OUT_FRONT_WIDE_LEFT | AudioFormat.CHANNEL_OUT_FRONT_WIDE_RIGHT, |
| }; |
| |
| final int TEST_MODE = AudioTrack.MODE_STREAM; |
| final int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; |
| final float TEST_SWEEP = 0; // sine wave only |
| final boolean TEST_IS_LOW_RAM_DEVICE = false; |
| for (int TEST_FORMAT : TEST_FORMAT_ARRAY) { |
| double frequency = 400; // Note: frequency changes for each test |
| for (int TEST_SR : TEST_SR_ARRAY) { |
| for (int TEST_CONF : TEST_CONF_ARRAY) { |
| // Remove the front left and front right channels. |
| int signalMask = TEST_CONF & ~(AudioFormat.CHANNEL_OUT_FRONT_LEFT |
| | AudioFormat.CHANNEL_OUT_FRONT_RIGHT); |
| // Play all the "surround channels" in the mask individually |
| // at different frequencies. |
| while (signalMask != 0) { |
| final int lowbit = signalMask & -signalMask; |
| playOnceStreamData(TEST_NAME, TEST_MODE, TEST_STREAM_TYPE, TEST_SWEEP, |
| TEST_IS_LOW_RAM_DEVICE, TEST_FORMAT, frequency, TEST_SR, |
| TEST_CONF, WAIT_MSEC, lowbit); |
| signalMask -= lowbit; |
| frequency += 50; // increment test tone frequency |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Ensure AudioTrack.getMinBufferSize invalid arguments return BAD_VALUE instead |
| * of throwing exception. |
| * |
| * @throws Exception |
| */ |
| @Test |
| public void testInvalidMinBufferSize() throws Exception { |
| int TEST_SAMPLE_RATE = 24000; |
| int TEST_CHANNEL_CONFIGURATION = AudioFormat.CHANNEL_OUT_STEREO; |
| int TEST_ENCODING = AudioFormat.ENCODING_PCM_16BIT; |
| |
| for (int i = 1; i < 8; ++i) { |
| int minBuffSize = AudioTrack.getMinBufferSize( |
| (i & 1) != 0 ? 0 : TEST_SAMPLE_RATE, |
| (i & 2) != 0 ? AudioFormat.CHANNEL_INVALID : TEST_CHANNEL_CONFIGURATION, |
| (i & 4) != 0 ? AudioFormat.ENCODING_INVALID :TEST_ENCODING); |
| assertEquals("Invalid configuration " + i + " should return ERROR_BAD_VALUE", |
| AudioTrack.ERROR_BAD_VALUE, minBuffSize); |
| } |
| } |
| |
| /* Do not run in JB-MR1. will be re-opened in the next platform release. |
| public void testResourceLeakage() throws Exception { |
| final int BUFFER_SIZE = 600 * 1024; |
| ByteBuffer data = ByteBuffer.allocate(BUFFER_SIZE); |
| for (int i = 0; i < 10; i++) { |
| Log.i(TAG, "testResourceLeakage round " + i); |
| data.rewind(); |
| AudioTrack track = new AudioTrack(AudioManager.STREAM_VOICE_CALL, |
| 44100, |
| AudioFormat.CHANNEL_OUT_STEREO, |
| AudioFormat.ENCODING_PCM_16BIT, |
| data.capacity(), |
| AudioTrack.MODE_STREAM); |
| assertTrue(track != null); |
| track.write(data.array(), 0, data.capacity()); |
| track.play(); |
| Thread.sleep(100); |
| track.stop(); |
| track.release(); |
| } |
| } |
| */ |
| |
| /* MockAudioTrack allows testing of protected getNativeFrameCount() and setState(). */ |
| private class MockAudioTrack extends AudioTrack { |
| |
| public MockAudioTrack(int streamType, int sampleRateInHz, int channelConfig, |
| int audioFormat, int bufferSizeInBytes, int mode) throws IllegalArgumentException { |
| super(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode); |
| } |
| |
| public void setState(int state) { |
| super.setState(state); |
| } |
| |
| public int getNativeFrameCount() { |
| return super.getNativeFrameCount(); |
| } |
| } |
| |
| private static AudioPresentation createAudioPresentation() { |
| return (new AudioPresentation.Builder(42 /*presentationId*/)).build(); |
| } |
| |
| private static Context getContext() { |
| return InstrumentationRegistry.getInstrumentation().getTargetContext(); |
| } |
| } |