1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.media.cts; 18 19 import android.cts.util.CtsAndroidTestCase; 20 import android.media.AudioFormat; 21 import android.media.AudioManager; 22 import android.media.AudioTrack; 23 import android.media.AudioTrack.OnPlaybackPositionUpdateListener; 24 import android.os.Handler; 25 import android.os.Looper; 26 import android.os.Message; 27 28 import com.android.compatibility.common.util.DeviceReportLog; 29 import com.android.compatibility.common.util.ResultType; 30 import com.android.compatibility.common.util.ResultUnit; 31 32 import java.util.ArrayList; 33 34 public class AudioTrack_ListenerTest extends CtsAndroidTestCase { 35 private final static String TAG = "AudioTrack_ListenerTest"; 36 private static final String REPORT_LOG_NAME = "CtsMediaTestCases"; 37 private final static int TEST_SR = 11025; 38 private final static int TEST_CONF = AudioFormat.CHANNEL_OUT_MONO; 39 private final static int TEST_FORMAT = AudioFormat.ENCODING_PCM_8BIT; 40 private final static int TEST_STREAM_TYPE = AudioManager.STREAM_MUSIC; 41 private final static int TEST_LOOP_FACTOR = 2; // # loops (>= 1) for static tracks 42 // simulated for streaming. 43 private final static int TEST_BUFFER_FACTOR = 25; 44 private boolean mIsHandleMessageCalled; 45 private int mMarkerPeriodInFrames; 46 private int mMarkerPosition; 47 private int mFrameCount; 48 private Handler mHandler = new Handler(Looper.getMainLooper()) { 49 @Override 50 public void handleMessage(Message msg) { 51 mIsHandleMessageCalled = true; 52 super.handleMessage(msg); 53 } 54 }; 55 testAudioTrackCallback()56 public void testAudioTrackCallback() throws Exception { 57 doTest("streaming_local_looper", true /*localTrack*/, false /*customHandler*/, 58 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STREAM); 59 } 60 testAudioTrackCallbackWithHandler()61 public void testAudioTrackCallbackWithHandler() throws Exception { 62 // with 100 periods per second, trigger back-to-back notifications. 63 doTest("streaming_private_handler", false /*localTrack*/, true /*customHandler*/, 64 100 /*periodsPerSecond*/, 10 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STREAM); 65 // verify mHandler is used only for accessing its associated Looper 66 assertFalse(mIsHandleMessageCalled); 67 } 68 testStaticAudioTrackCallback()69 public void testStaticAudioTrackCallback() throws Exception { 70 doTest("static", false /*localTrack*/, false /*customHandler*/, 71 100 /*periodsPerSecond*/, 10 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STATIC); 72 } 73 testStaticAudioTrackCallbackWithHandler()74 public void testStaticAudioTrackCallbackWithHandler() throws Exception { 75 String streamName = "test_static_audio_track_callback_handler"; 76 doTest("static_private_handler", false /*localTrack*/, true /*customHandler*/, 77 30 /*periodsPerSecond*/, 2 /*markerPeriodsPerSecond*/, AudioTrack.MODE_STATIC); 78 // verify mHandler is used only for accessing its associated Looper 79 assertFalse(mIsHandleMessageCalled); 80 } 81 doTest(String reportName, boolean localTrack, boolean customHandler, int periodsPerSecond, int markerPeriodsPerSecond, final int mode)82 private void doTest(String reportName, boolean localTrack, boolean customHandler, 83 int periodsPerSecond, int markerPeriodsPerSecond, final int mode) throws Exception { 84 mIsHandleMessageCalled = false; 85 final int minBuffSize = AudioTrack.getMinBufferSize(TEST_SR, TEST_CONF, TEST_FORMAT); 86 final int bufferSizeInBytes; 87 if (mode == AudioTrack.MODE_STATIC && TEST_LOOP_FACTOR > 1) { 88 // use setLoopPoints for static mode 89 bufferSizeInBytes = minBuffSize * TEST_BUFFER_FACTOR; 90 mFrameCount = bufferSizeInBytes * TEST_LOOP_FACTOR; 91 } else { 92 bufferSizeInBytes = minBuffSize * TEST_BUFFER_FACTOR * TEST_LOOP_FACTOR; 93 mFrameCount = bufferSizeInBytes; 94 } 95 96 final AudioTrack track; 97 final AudioHelper.MakeSomethingAsynchronouslyAndLoop<AudioTrack> makeSomething; 98 if (localTrack) { 99 makeSomething = null; 100 track = new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, 101 TEST_FORMAT, bufferSizeInBytes, mode); 102 } else { 103 makeSomething = 104 new AudioHelper.MakeSomethingAsynchronouslyAndLoop<AudioTrack>( 105 new AudioHelper.MakesSomething<AudioTrack>() { 106 @Override 107 public AudioTrack makeSomething() { 108 return new AudioTrack(TEST_STREAM_TYPE, TEST_SR, TEST_CONF, 109 TEST_FORMAT, bufferSizeInBytes, mode); 110 } 111 } 112 ); 113 // create audiotrack on different thread's looper. 114 track = makeSomething.make(); 115 } 116 final MockOnPlaybackPositionUpdateListener listener; 117 if (customHandler) { 118 listener = new MockOnPlaybackPositionUpdateListener(track, mHandler); 119 } else { 120 listener = new MockOnPlaybackPositionUpdateListener(track); 121 } 122 123 byte[] vai = AudioHelper.createSoundDataInByteArray( 124 bufferSizeInBytes, TEST_SR, 1024 /* frequency */, 0 /* sweep */); 125 int markerPeriods = Math.max(3, mFrameCount * markerPeriodsPerSecond / TEST_SR); 126 mMarkerPeriodInFrames = mFrameCount / markerPeriods; 127 markerPeriods = mFrameCount / mMarkerPeriodInFrames; // recalculate due to round-down 128 mMarkerPosition = mMarkerPeriodInFrames; 129 130 // check that we can get and set notification marker position 131 assertEquals(0, track.getNotificationMarkerPosition()); 132 assertEquals(AudioTrack.SUCCESS, 133 track.setNotificationMarkerPosition(mMarkerPosition)); 134 assertEquals(mMarkerPosition, track.getNotificationMarkerPosition()); 135 136 int updatePeriods = Math.max(3, mFrameCount * periodsPerSecond / TEST_SR); 137 final int updatePeriodInFrames = mFrameCount / updatePeriods; 138 updatePeriods = mFrameCount / updatePeriodInFrames; // recalculate due to round-down 139 140 // we set the notification period before running for better period positional accuracy. 141 // check that we can get and set notification periods 142 assertEquals(0, track.getPositionNotificationPeriod()); 143 assertEquals(AudioTrack.SUCCESS, 144 track.setPositionNotificationPeriod(updatePeriodInFrames)); 145 assertEquals(updatePeriodInFrames, track.getPositionNotificationPeriod()); 146 147 if (mode == AudioTrack.MODE_STATIC && TEST_LOOP_FACTOR > 1) { 148 track.setLoopPoints(0, vai.length, TEST_LOOP_FACTOR - 1); 149 } 150 // write data with single blocking write, then play. 151 assertEquals(vai.length, track.write(vai, 0 /* offsetInBytes */, vai.length)); 152 track.play(); 153 154 // sleep until track completes playback - it must complete within 1 second 155 // of the expected length otherwise the periodic test should fail. 156 final int numChannels = AudioFormat.channelCountFromOutChannelMask(TEST_CONF); 157 final int bytesPerSample = AudioFormat.getBytesPerSample(TEST_FORMAT); 158 final int bytesPerFrame = numChannels * bytesPerSample; 159 final int trackLengthMs = (int)((double)mFrameCount * 1000 / TEST_SR / bytesPerFrame); 160 Thread.sleep(trackLengthMs + 1000); 161 162 // stop listening - we should be done. 163 listener.stop(); 164 165 // Beware: stop() resets the playback head position for both static and streaming 166 // audio tracks, so stop() cannot be called while we're still logging playback 167 // head positions. We could recycle the track after stop(), which isn't done here. 168 track.stop(); 169 170 // clean up 171 if (makeSomething != null) { 172 makeSomething.join(); 173 } 174 listener.release(); 175 track.release(); 176 177 // collect statistics 178 final ArrayList<Integer> markerList = listener.getMarkerList(); 179 final ArrayList<Integer> periodicList = listener.getPeriodicList(); 180 // verify count of markers and periodic notifications. 181 assertEquals(markerPeriods, markerList.size()); 182 assertEquals(updatePeriods, periodicList.size()); 183 // verify actual playback head positions returned. 184 // the max diff should really be around 24 ms, 185 // but system load and stability will affect this test; 186 // we use 80ms limit here for failure. 187 final int tolerance80MsInFrames = TEST_SR * 80 / 1000; 188 189 AudioHelper.Statistics markerStat = new AudioHelper.Statistics(); 190 for (int i = 0; i < markerPeriods; ++i) { 191 final int expected = mMarkerPeriodInFrames * (i + 1); 192 final int actual = markerList.get(i); 193 // Log.d(TAG, "Marker: expected(" + expected + ") actual(" + actual 194 // + ") diff(" + (actual - expected) + ")"); 195 assertEquals(expected, actual, tolerance80MsInFrames); 196 markerStat.add((double)(actual - expected) * 1000 / TEST_SR); 197 } 198 199 AudioHelper.Statistics periodicStat = new AudioHelper.Statistics(); 200 for (int i = 0; i < updatePeriods; ++i) { 201 final int expected = updatePeriodInFrames * (i + 1); 202 final int actual = periodicList.get(i); 203 // Log.d(TAG, "Update: expected(" + expected + ") actual(" + actual 204 // + ") diff(" + (actual - expected) + ")"); 205 assertEquals(expected, actual, tolerance80MsInFrames); 206 periodicStat.add((double)(actual - expected) * 1000 / TEST_SR); 207 } 208 209 // report this 210 DeviceReportLog log = new DeviceReportLog(REPORT_LOG_NAME, reportName); 211 log.addValue("average_marker_diff", markerStat.getAvg(), ResultType.LOWER_BETTER, 212 ResultUnit.MS); 213 log.addValue("maximum_marker_abs_diff", markerStat.getMaxAbs(), ResultType.LOWER_BETTER, 214 ResultUnit.MS); 215 log.addValue("average_marker_abs_diff", markerStat.getAvgAbs(), ResultType.LOWER_BETTER, 216 ResultUnit.MS); 217 log.addValue("average_periodic_diff", periodicStat.getAvg(), ResultType.LOWER_BETTER, 218 ResultUnit.MS); 219 log.addValue("maximum_periodic_abs_diff", periodicStat.getMaxAbs(), ResultType.LOWER_BETTER, 220 ResultUnit.MS); 221 log.addValue("average_periodic_abs_diff", periodicStat.getAvgAbs(), ResultType.LOWER_BETTER, 222 ResultUnit.MS); 223 log.setSummary("unified_abs_diff", (periodicStat.getAvgAbs() + markerStat.getAvgAbs()) / 2, 224 ResultType.LOWER_BETTER, ResultUnit.MS); 225 log.submit(getInstrumentation()); 226 } 227 228 private class MockOnPlaybackPositionUpdateListener 229 implements OnPlaybackPositionUpdateListener { MockOnPlaybackPositionUpdateListener(AudioTrack track)230 public MockOnPlaybackPositionUpdateListener(AudioTrack track) { 231 mAudioTrack = track; 232 track.setPlaybackPositionUpdateListener(this); 233 } 234 MockOnPlaybackPositionUpdateListener(AudioTrack track, Handler handler)235 public MockOnPlaybackPositionUpdateListener(AudioTrack track, Handler handler) { 236 mAudioTrack = track; 237 track.setPlaybackPositionUpdateListener(this, handler); 238 } 239 onMarkerReached(AudioTrack track)240 public synchronized void onMarkerReached(AudioTrack track) { 241 if (mIsTestActive) { 242 int position = mAudioTrack.getPlaybackHeadPosition(); 243 mOnMarkerReachedCalled.add(position); 244 mMarkerPosition += mMarkerPeriodInFrames; 245 if (mMarkerPosition <= mFrameCount) { 246 assertEquals(AudioTrack.SUCCESS, 247 mAudioTrack.setNotificationMarkerPosition(mMarkerPosition)); 248 } 249 } else { 250 fail("onMarkerReached called when not active"); 251 } 252 } 253 onPeriodicNotification(AudioTrack track)254 public synchronized void onPeriodicNotification(AudioTrack track) { 255 if (mIsTestActive) { 256 mOnPeriodicNotificationCalled.add(mAudioTrack.getPlaybackHeadPosition()); 257 } else { 258 fail("onPeriodicNotification called when not active"); 259 } 260 } 261 stop()262 public synchronized void stop() { 263 mIsTestActive = false; 264 } 265 getMarkerList()266 public ArrayList<Integer> getMarkerList() { 267 return mOnMarkerReachedCalled; 268 } 269 getPeriodicList()270 public ArrayList<Integer> getPeriodicList() { 271 return mOnPeriodicNotificationCalled; 272 } 273 release()274 public synchronized void release() { 275 mAudioTrack.setPlaybackPositionUpdateListener(null); 276 mAudioTrack = null; 277 } 278 279 private boolean mIsTestActive = true; 280 private AudioTrack mAudioTrack; 281 private ArrayList<Integer> mOnMarkerReachedCalled = new ArrayList<Integer>(); 282 private ArrayList<Integer> mOnPeriodicNotificationCalled = new ArrayList<Integer>(); 283 } 284 } 285