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