1 /*
2  * Copyright (C) 2015 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 com.android.cts.verifier.audio;
18 
19 import android.content.Context;
20 import android.media.AudioAttributes;
21 import android.media.AudioFormat;
22 import android.media.AudioManager;
23 import android.media.AudioTrack;
24 import android.media.MediaCodec;
25 import android.media.MediaCodecList;
26 import android.media.MediaExtractor;
27 import android.media.MediaFormat;
28 import android.media.MediaPlayer;
29 import android.net.Uri;
30 import android.util.Log;
31 
32 import com.android.cts.verifier.audio.wavelib.PipeShort;
33 
34 import java.io.IOException;
35 import java.nio.ByteBuffer;
36 
37 public class SoundPlayerObject implements Runnable, AudioTrack.OnPlaybackPositionUpdateListener {
38     private static final String LOGTAG = "SoundPlayerObject";
39 
40     private static final int TIME_OUT_US = 5000;
41     private static final int DEFAULT_BLOCK_SIZE = 4096;
42 
43     private MediaPlayer mMediaPlayer;
44     private boolean isInitialized = false;
45     private boolean isRunning = false;
46 
47     private int bufferSize = 65536;
48     private PipeShort mDecoderPipe = new PipeShort(bufferSize);
49     public PipeShort mPipe = new PipeShort(65536);
50     private short[] mAudioShortArray;
51 
52     private AudioTrack mAudioTrack;
53     private int mSamplingRate = 48000;
54     private int mChannelConfigOut = AudioFormat.CHANNEL_OUT_MONO;
55     private int mAudioFormat = AudioFormat.ENCODING_PCM_16BIT;
56     private int mMinPlayBufferSizeInBytes = 0;
57     private int mMinBufferSizeInSamples = 0;
58 
59     private int mStreamType = AudioManager.STREAM_MUSIC;
60     private int mResId = -1;
61     private final boolean mUseMediaPlayer; //true: MediaPlayer, false: AudioTrack
62     private float mBalance = 0.5f; //0 left, 1 right
63 
64     private Object mLock = new Object();
65     private MediaCodec mCodec = null;
66     private MediaExtractor mExtractor = null;
67     private int mInputAudioFormat;
68     private int mInputSampleRate;
69     private int mInputChannelCount;
70 
71     private boolean mLooping = true;
72     private Context mContext = null;
73 
74     private final int mBlockSizeSamples;
75 
76     private Thread mPlaybackThread;
77 
SoundPlayerObject()78     public SoundPlayerObject() {
79         mUseMediaPlayer = true;
80         mBlockSizeSamples = DEFAULT_BLOCK_SIZE;
81     }
82 
SoundPlayerObject(boolean useMediaPlayer, int blockSize)83     public SoundPlayerObject(boolean useMediaPlayer, int blockSize) {
84         mUseMediaPlayer = useMediaPlayer;
85         mBlockSizeSamples = blockSize;
86     }
87 
getCurrentResId()88     public int getCurrentResId() {
89         return mResId;
90     }
91 
getStreamType()92     public int getStreamType () {
93         return mStreamType;
94     }
95 
getChannelCount()96     public int getChannelCount() {
97         return mInputChannelCount;
98     }
99 
run()100     public void run() {
101         isRunning = true;
102         int decodeRounds = 0;
103         MediaCodec.BufferInfo buffInfo = new MediaCodec.BufferInfo();
104 
105         while (isRunning) {
106             if (Thread.interrupted()) {
107                 log("got thread interrupted!");
108                 isRunning = false;
109                 return;
110             }
111 
112             if (mUseMediaPlayer) {
113                 log("run . using media player");
114                 try {
115                     Thread.sleep(10);
116                 } catch (InterruptedException e) {
117                     e.printStackTrace();
118                 }
119             } else {
120                 if (isInitialized && !outputEosSignalled) {
121 
122                     synchronized (mLock) {
123 
124                         int bytesPerSample = getBytesPerSample(mInputAudioFormat);
125 
126                         int valuesAvailable = mDecoderPipe.availableToRead();
127                         if (valuesAvailable > 0 && mAudioTrack != null) {
128                             int valuesOfInterest = valuesAvailable;
129                             if (mMinBufferSizeInSamples < valuesOfInterest) {
130                                 valuesOfInterest = mMinBufferSizeInSamples;
131                             }
132                             mDecoderPipe.read(mAudioShortArray, 0, valuesOfInterest);
133                             //inject into output.
134                             mAudioTrack.write(mAudioShortArray, 0, valuesOfInterest);
135 
136                             //delay
137                             int delayMs = (int)(1000.0f * valuesOfInterest /
138                                     (float)(mInputSampleRate * bytesPerSample *
139                                             mInputChannelCount));
140                             delayMs = Math.max(2, delayMs);
141 
142                             try {
143                                 Thread.sleep(delayMs);
144                             } catch (InterruptedException e) {
145                                 e.printStackTrace();
146                             }
147                         } else {
148                             //read another block if possible
149 
150                             decodeRounds++;
151                             if (!inputEosSignalled) {
152                                 final int inputBufIndex = mCodec.dequeueInputBuffer(TIME_OUT_US);
153                                 if (inputBufIndex >= 0) {
154                                     ByteBuffer encodedBuf = mCodec.getInputBuffer(inputBufIndex);
155                                     final int sampleSize =
156                                             mExtractor.readSampleData(encodedBuf, 0 /* offset */);
157                                     long presentationTimeUs = 0;
158                                     if (sampleSize < 0) {
159                                         inputEosSignalled = true;
160                                         log("[v] input EOS at decode round " + decodeRounds);
161 
162                                     } else {
163                                         presentationTimeUs = mExtractor.getSampleTime();
164                                     }
165                                     mCodec.queueInputBuffer(inputBufIndex, 0/*offset*/,
166                                             inputEosSignalled ? 0 : sampleSize, presentationTimeUs,
167                                             (inputEosSignalled && !mLooping) ?
168                                                     MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
169                                     if (!inputEosSignalled) {
170                                         mExtractor.advance();
171                                     }
172 
173                                     if (inputEosSignalled && mLooping) {
174                                         log("looping is enabled. Rewinding");
175                                         mExtractor.seekTo(0, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
176                                         inputEosSignalled = false;
177                                     }
178                                 } else {
179                                     log("[v] no input buffer available at decode round "
180                                             + decodeRounds);
181                                 }
182                             } //!inputEosSignalled
183 
184                             final int outputRes = mCodec.dequeueOutputBuffer(buffInfo, TIME_OUT_US);
185 
186                             if (outputRes >= 0) {
187                                 if (buffInfo.size > 0) {
188                                     final int outputBufIndex = outputRes;
189                                     final ByteBuffer decodedBuf =
190                                             mCodec.getOutputBuffer(outputBufIndex);
191 
192                                     short sValue = 0; //scaled to 16 bits
193                                     int index = 0;
194                                     for (int i = 0; i < buffInfo.size && index <
195                                             mAudioShortArray.length; i += bytesPerSample) {
196                                         switch (mInputAudioFormat) {
197                                             case AudioFormat.ENCODING_PCM_FLOAT:
198                                                 sValue = (short) (decodedBuf.getFloat(i) * 32768.0);
199                                                 break;
200                                             case AudioFormat.ENCODING_PCM_16BIT:
201                                                 sValue = (short) decodedBuf.getShort(i);
202                                                 break;
203                                             case AudioFormat.ENCODING_PCM_8BIT:
204                                                 sValue = (short) ((decodedBuf.getChar(i) - 128) *
205                                                         128);
206                                                 break;
207                                         }
208                                         mAudioShortArray[index] = sValue;
209                                         index++;
210                                     }
211                                     mDecoderPipe.write(mAudioShortArray, 0, index);
212                                     mPipe.write(mAudioShortArray, 0, index);
213 
214                                     mCodec.getOutputBuffer(outputBufIndex).position(0);
215                                     mCodec.releaseOutputBuffer(outputBufIndex,
216                                             false/*render to surface*/);
217                                     if ((buffInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) !=
218                                             0) {
219                                         outputEosSignalled = true;
220                                     }
221                                 }
222                             } else if (outputRes == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
223                                 log("[w] INFO_OUTPUT_FORMAT_CHANGED at decode round " +
224                                         decodeRounds);
225                                 decodeRounds = 0;
226                             } else if (outputRes == MediaCodec.INFO_TRY_AGAIN_LATER) {
227                                 log("[w] INFO_TRY_AGAIN_LATER at decode round " + decodeRounds);
228                                 if (!mLooping) {
229                                     outputEosSignalled = true; //quit!
230                                 }
231                             }
232                         }
233                     }
234 
235                     if (outputEosSignalled) {
236                         log ("note: outputEosSignalled");
237                     }
238                 }
239                 else {
240                     try {
241                         Thread.sleep(10);
242                     } catch (InterruptedException e) {
243                         e.printStackTrace();
244                     }
245                 }
246             }
247         }
248         log("done running thread");
249     }
250 
setBalance(float balance)251     public void setBalance(float balance) {
252         mBalance = balance;
253         if (mUseMediaPlayer) {
254             if (mMediaPlayer != null) {
255                 float left = Math.min(2.0f * (1.0f - mBalance), 1.0f);
256                 float right = Math.min(2.0f * mBalance, 1.0f);
257                 mMediaPlayer.setVolume(left, right);
258                 log(String.format("Setting balance to %f", mBalance));
259             }
260         }
261     }
262 
setStreamType(int streamType)263     public void setStreamType(int streamType) {
264         mStreamType = streamType;
265     }
266 
rewind()267     public void rewind() {
268         if (mUseMediaPlayer) {
269             if (mMediaPlayer != null) {
270                 mMediaPlayer.seekTo(0);
271             }
272         }
273     }
274 
275     boolean inputEosSignalled = false;
276     boolean outputEosSignalled = false;
277 
setSoundWithResId(Context context, int resId)278     public void setSoundWithResId(Context context, int resId) {
279 
280         setSoundWithResId(context, resId, true);
281     }
282 
setSoundWithResId(Context context, int resId, boolean looping)283     public void setSoundWithResId(Context context, int resId, boolean looping) {
284         log("setSoundWithResId " + resId + ", looping: " + looping);
285         mLooping = looping;
286         mContext = context;
287         if (mContext == null) {
288             log("Context can't be null");
289             return;
290         }
291 
292         boolean playing = isPlaying();
293         if (playing) {
294             play(false);
295         }
296         //release player
297         releasePlayer();
298         isInitialized = false;
299 
300         log("loading uri: " + resId);
301         mResId = resId;
302         Uri uri = Uri.parse("android.resource://com.android.cts.verifier/" + resId);
303         if (mUseMediaPlayer) {
304             mMediaPlayer = new MediaPlayer();
305             try {
306                 log("opening resource with stream type: " + mStreamType);
307                 mMediaPlayer.setAudioStreamType(mStreamType);
308                 mMediaPlayer.setDataSource(mContext.getApplicationContext(),
309                         uri);
310                 mMediaPlayer.prepare();
311             } catch (IOException e) {
312                 e.printStackTrace();
313                 log("Error: " + e.toString());
314             }
315             mMediaPlayer.setLooping(mLooping);
316             setBalance(mBalance);
317             isInitialized = true;
318 
319         } else {
320             synchronized (mLock) {
321                 //TODO: encapsulate MediaPlayer and AudioTrack related code into separate classes
322                 // with common interface. Simplify locking code.
323                 mDecoderPipe.flush();
324                 mPipe.flush();
325 
326                 if (mCodec != null)
327                     mCodec = null;
328                 try {
329                     mExtractor = new MediaExtractor();
330                     mExtractor.setDataSource(mContext.getApplicationContext(), uri, null);
331                     final int trackCount = mExtractor.getTrackCount();
332 
333 //                    log("Track count: " + trackCount);
334                     // find first audio track
335                     MediaFormat format = null;
336                     String mime = null;
337                     for (int i = 0; i < trackCount; i++) {
338                         format = mExtractor.getTrackFormat(i);
339                         mime = format.getString(MediaFormat.KEY_MIME);
340                         if (mime.startsWith("audio/")) {
341                             mExtractor.selectTrack(i);
342 //                            log("found track " + i + " with MIME = " + mime);
343                             break;
344                         }
345                     }
346                     if (format == null) {
347                         log("found 0 audio tracks in " + uri);
348                     }
349                     MediaCodecList mclist = new MediaCodecList(MediaCodecList.ALL_CODECS);
350 
351                     String myDecoderName = mclist.findDecoderForFormat(format);
352 //                    log("my decoder name = " + myDecoderName);
353 
354                     mCodec = MediaCodec.createByCodecName(myDecoderName);
355 
356 //                    log("[ok] about to configure codec with " + format);
357                     mCodec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
358 
359                     // prepare decoding
360                     mCodec.start();
361 
362                     inputEosSignalled = false;
363                     outputEosSignalled = false;
364 
365                     MediaFormat outputFormat = format;
366 
367                     printAudioFormat(outputFormat);
368 
369                     //int sampleRate
370                     mInputSampleRate = getMediaFormatInteger(outputFormat,
371                             MediaFormat.KEY_SAMPLE_RATE, 48000);
372                     //int channelCount
373                     mInputChannelCount = getMediaFormatInteger(outputFormat,
374                             MediaFormat.KEY_CHANNEL_COUNT, 1);
375 
376                     mInputAudioFormat = getMediaFormatInteger(outputFormat,
377                             MediaFormat.KEY_PCM_ENCODING,
378                             AudioFormat.ENCODING_PCM_16BIT);
379 
380                     int channelMask = channelMaskFromCount(mInputChannelCount);
381                     int buffSize = AudioTrack.getMinBufferSize(mInputSampleRate, channelMask,
382                             mInputAudioFormat);
383 
384                     AudioAttributes.Builder aab = new AudioAttributes.Builder()
385                             .setLegacyStreamType(mStreamType);
386 
387                     AudioFormat.Builder afb = new AudioFormat.Builder()
388                             .setEncoding(mInputAudioFormat)
389                             .setSampleRate(mInputSampleRate)
390                             .setChannelMask(channelMask);
391 
392                     AudioTrack.Builder atb = new AudioTrack.Builder()
393                             .setAudioAttributes(aab.build())
394                             .setAudioFormat(afb.build())
395                             .setBufferSizeInBytes(buffSize)
396                             .setTransferMode(AudioTrack.MODE_STREAM);
397 
398                     mAudioTrack = atb.build();
399 
400                     mMinPlayBufferSizeInBytes = AudioTrack.getMinBufferSize(mInputSampleRate,
401                             mChannelConfigOut, mInputAudioFormat);
402 
403                     mMinBufferSizeInSamples = mMinPlayBufferSizeInBytes / 2;
404                     mAudioShortArray = new short[mMinBufferSizeInSamples * 100];
405                     mAudioTrack.setPlaybackPositionUpdateListener(this);
406                     mAudioTrack.setPositionNotificationPeriod(mBlockSizeSamples);
407                     isInitialized = true;
408                 } catch (IOException e) {
409                     e.printStackTrace();
410                     log("Error creating codec or extractor: " + e.toString());
411                 }
412             }
413         } //mLock
414 
415 //        log("done preparing media player");
416         if (playing)
417             play(true); //start playing if it was playing before
418     }
419 
isPlaying()420     public boolean isPlaying() {
421         boolean result = false;
422         if (mUseMediaPlayer) {
423             if (mMediaPlayer != null) {
424                 result = mMediaPlayer.isPlaying();
425             }
426         } else {
427             synchronized (mLock) {
428                 if (mAudioTrack != null) {
429                     result = mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING;
430                 }
431             }
432         }
433         return result;
434     }
435 
isAlive()436     public boolean isAlive() {
437         if (mUseMediaPlayer) {
438             return true;
439         }
440 
441         synchronized (mLock) {
442             if (mPlaybackThread != null) {
443                 return mPlaybackThread.isAlive();
444             }
445         }
446         return false;
447     }
448 
start()449     public void start() {
450         if (!mUseMediaPlayer) {
451             synchronized (mLock) {
452                 if (mPlaybackThread == null) {
453                     mPlaybackThread = new Thread(this);
454                     mPlaybackThread.setName("playbackThread");
455                     log("Created playback thread " + mPlaybackThread);
456                 }
457 
458                 if (!mPlaybackThread.isAlive()) {
459                     mPlaybackThread.start();
460                     mPlaybackThread.setPriority(Thread.MAX_PRIORITY);
461                     log("Started playback thread " + mPlaybackThread);
462                 }
463             }
464         }
465     }
466 
play(boolean play)467     public void play(boolean play) {
468         if (mUseMediaPlayer) {
469             if (mMediaPlayer != null) {
470                 if (play) {
471                     mMediaPlayer.start();
472                 } else {
473                     mMediaPlayer.pause();
474                 }
475             }
476         } else {
477             synchronized (mLock) {
478                 log(" called Play : " + play);
479                 if (mAudioTrack != null && isInitialized) {
480                     if (play) {
481                         log("Play");
482                         mDecoderPipe.flush();
483                         mPipe.flush();
484                         mAudioTrack.play();
485                     } else {
486                         log("pause");
487                         mAudioTrack.pause();
488                         isRunning = false;
489                     }
490                 }
491             }
492             start();
493         }
494     }
495 
finish()496     public void finish() {
497         play(false);
498         releasePlayer();
499     }
500 
releasePlayer()501     private void releasePlayer() {
502         if (mMediaPlayer != null) {
503             mMediaPlayer.stop();
504             mMediaPlayer.release();
505             mMediaPlayer = null;
506         }
507 
508         isRunning = false;
509         synchronized (mLock) {
510             if (mAudioTrack != null) {
511                 mAudioTrack.stop();
512                 mAudioTrack.setPlaybackPositionUpdateListener(null);
513                 mAudioTrack.release();
514                 mAudioTrack = null;
515             }
516         }
517 
518         log("Deleting playback thread " + mPlaybackThread);
519         Thread zeThread = mPlaybackThread;
520         mPlaybackThread = null;
521 
522         if (zeThread != null) {
523             log("terminating zeThread...");
524             zeThread.interrupt();
525             try {
526                 log("zeThread join...");
527                 zeThread.join();
528             } catch (InterruptedException e) {
529                 log("issue deleting playback thread " + e.toString());
530                 zeThread.interrupt();
531             }
532         }
533         isInitialized = false;
534         log("Done deleting thread");
535 
536     }
537 
538     /*
539        Misc
540     */
log(String msg)541     private static void log(String msg) {
542         Log.v(LOGTAG, msg);
543     }
544 
getMediaFormatInteger(MediaFormat mf, String name, int defaultValue)545     private final int getMediaFormatInteger(MediaFormat mf, String name, int defaultValue) {
546         try {
547             return mf.getInteger(name);
548         } catch (NullPointerException e) {
549             log("Warning: MediaFormat " + name +
550                 " field does not exist. Using default " + defaultValue); /* no such field */
551         } catch (ClassCastException e) {
552             log("Warning: MediaFormat " + name +
553                     " field unexpected type"); /* field of different type */
554         }
555         return defaultValue;
556     }
557 
getBytesPerSample(int audioFormat)558     public static int getBytesPerSample(int audioFormat) {
559         switch(audioFormat) {
560             case AudioFormat.ENCODING_PCM_16BIT:
561                 return 2;
562             case AudioFormat.ENCODING_PCM_8BIT:
563                 return 1;
564             case AudioFormat.ENCODING_PCM_FLOAT:
565                 return 4;
566         }
567         return 0;
568     }
569 
printAudioFormat(MediaFormat format)570     private void printAudioFormat(MediaFormat format) {
571         try {
572             log("channel mask = " + format.getInteger(MediaFormat.KEY_CHANNEL_MASK));
573         } catch (NullPointerException npe) {
574             log("channel mask unknown");
575         }
576         try {
577             log("channel count = " + format.getInteger(MediaFormat.KEY_CHANNEL_COUNT));
578         } catch (NullPointerException npe) {
579             log("channel count unknown");
580         }
581         try {
582             log("sample rate = " + format.getInteger(MediaFormat.KEY_SAMPLE_RATE));
583         } catch (NullPointerException npe) {
584             log("sample rate unknown");
585         }
586         try {
587             log("sample format = " + format.getInteger(MediaFormat.KEY_PCM_ENCODING));
588         } catch (NullPointerException npe) {
589             log("sample format unknown");
590         }
591     }
592 
channelMaskFromCount(int channelCount)593     public static int channelMaskFromCount(int channelCount) {
594         switch(channelCount) {
595             case 1:
596                 return AudioFormat.CHANNEL_OUT_MONO;
597             case 2:
598                 return AudioFormat.CHANNEL_OUT_STEREO;
599             case 3:
600                 return AudioFormat.CHANNEL_OUT_STEREO | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
601             case 4:
602                 return AudioFormat.CHANNEL_OUT_FRONT_LEFT |
603                         AudioFormat.CHANNEL_OUT_FRONT_RIGHT | AudioFormat.CHANNEL_OUT_BACK_LEFT |
604                         AudioFormat.CHANNEL_OUT_BACK_RIGHT;
605             case 5:
606                 return AudioFormat.CHANNEL_OUT_FRONT_LEFT |
607                         AudioFormat.CHANNEL_OUT_FRONT_RIGHT | AudioFormat.CHANNEL_OUT_BACK_LEFT |
608                         AudioFormat.CHANNEL_OUT_BACK_RIGHT
609                         | AudioFormat.CHANNEL_OUT_FRONT_CENTER;
610             case 6:
611                 return AudioFormat.CHANNEL_OUT_5POINT1;
612             default:
613                 return 0;
614         }
615     }
616 
periodicNotification(AudioTrack track)617     public void periodicNotification(AudioTrack track) {
618     }
619 
markerReached(AudioTrack track)620     public void markerReached(AudioTrack track) {
621     }
622 
623     @Override
onMarkerReached(AudioTrack track)624     public void onMarkerReached(AudioTrack track) {
625         markerReached(track);
626     }
627 
628     @Override
onPeriodicNotification(AudioTrack track)629     public void onPeriodicNotification(AudioTrack track) {
630         periodicNotification(track);
631     }
632 }
633