• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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