• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tv.tuner.exoplayer.audio;
18 
19 import android.media.MediaCodec;
20 import android.os.Build;
21 import android.os.Handler;
22 import android.os.SystemClock;
23 import android.util.Log;
24 import com.android.tv.tuner.tvinput.debug.TunerDebug;
25 import com.google.android.exoplayer.CodecCounters;
26 import com.google.android.exoplayer.ExoPlaybackException;
27 import com.google.android.exoplayer.MediaClock;
28 import com.google.android.exoplayer.MediaCodecSelector;
29 import com.google.android.exoplayer.MediaFormat;
30 import com.google.android.exoplayer.MediaFormatHolder;
31 import com.google.android.exoplayer.SampleHolder;
32 import com.google.android.exoplayer.SampleSource;
33 import com.google.android.exoplayer.TrackRenderer;
34 import com.google.android.exoplayer.audio.AudioTrack;
35 import com.google.android.exoplayer.util.Assertions;
36 import com.google.android.exoplayer.util.MimeTypes;
37 import java.io.IOException;
38 import java.nio.ByteBuffer;
39 import java.util.ArrayList;
40 
41 /**
42  * Decodes and renders DTV audio. Supports MediaCodec based decoding, passthrough playback and
43  * ffmpeg based software decoding (AC3, MP2).
44  */
45 public class MpegTsDefaultAudioTrackRenderer extends TrackRenderer implements MediaClock {
46     public static final int MSG_SET_VOLUME = 10000;
47     public static final int MSG_SET_AUDIO_TRACK = MSG_SET_VOLUME + 1;
48     public static final int MSG_SET_PLAYBACK_SPEED = MSG_SET_VOLUME + 2;
49 
50     // ATSC/53 allows sample rate to be only 48Khz.
51     // One AC3 sample has 1536 frames, and its duration is 32ms.
52     public static final long AC3_SAMPLE_DURATION_US = 32000;
53 
54     // TODO: Check whether DVB broadcasting uses sample rate other than 48Khz.
55     // MPEG-1 audio Layer II and III has 1152 frames per sample.
56     // 1152 frames duration is 24ms when sample rate is 48Khz.
57     static final long MP2_SAMPLE_DURATION_US = 24000;
58 
59     // This is around 150ms, 150ms is big enough not to under-run AudioTrack,
60     // and  150ms is also small enough to fill the buffer rapidly.
61     static int BUFFERED_SAMPLES_IN_AUDIOTRACK = 5;
62     public static final long INITIAL_AUDIO_BUFFERING_TIME_US =
63             BUFFERED_SAMPLES_IN_AUDIOTRACK * AC3_SAMPLE_DURATION_US;
64 
65     private static final String TAG = "MpegTsDefaultAudioTrac";
66     private static final boolean DEBUG = false;
67 
68     /**
69      * Interface definition for a callback to be notified of {@link
70      * com.google.android.exoplayer.audio.AudioTrack} error.
71      */
72     public interface EventListener {
onAudioTrackInitializationError(AudioTrack.InitializationException e)73         void onAudioTrackInitializationError(AudioTrack.InitializationException e);
74 
onAudioTrackWriteError(AudioTrack.WriteException e)75         void onAudioTrackWriteError(AudioTrack.WriteException e);
76     }
77 
78     private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2;
79     private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024 * 1024;
80     private static final int MONITOR_DURATION_MS = 1000;
81     private static final int AC3_HEADER_BITRATE_OFFSET = 4;
82     private static final int MP2_HEADER_BITRATE_OFFSET = 2;
83     private static final int MP2_HEADER_BITRATE_MASK = 0xfc;
84 
85     // Keep this as static in order to prevent new framework AudioTrack creation
86     // while old AudioTrack is being released.
87     private static final AudioTrackWrapper AUDIO_TRACK = new AudioTrackWrapper();
88     private static final long KEEP_ALIVE_AFTER_EOS_DURATION_MS = 3000;
89 
90     // Ignore AudioTrack backward movement if duration of movement is below the threshold.
91     private static final long BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US = 3000;
92 
93     // AudioTrack position cannot go ahead beyond this limit.
94     private static final long CURRENT_POSITION_FROM_PTS_LIMIT_US = 1000000;
95 
96     // Since MediaCodec processing and AudioTrack playing add delay,
97     // PTS interpolated time should be delayed reasonably when AudioTrack is not used.
98     private static final long ESTIMATED_TRACK_RENDERING_DELAY_US = 500000;
99 
100     private final MediaCodecSelector mSelector;
101 
102     private final CodecCounters mCodecCounters;
103     private final SampleSource.SampleSourceReader mSource;
104     private final MediaFormatHolder mFormatHolder;
105     private final EventListener mEventListener;
106     private final Handler mEventHandler;
107     private final AudioTrackMonitor mMonitor;
108     private final AudioClock mAudioClock;
109 
110     private MediaFormat mFormat;
111     private SampleHolder mSampleHolder;
112     private String mDecodingMime;
113     private boolean mFormatConfigured;
114     private int mSampleSize;
115     private final ByteBuffer mOutputBuffer;
116     private AudioDecoder mAudioDecoder;
117     private boolean mOutputReady;
118     private int mTrackIndex;
119     private boolean mSourceStateReady;
120     private boolean mInputStreamEnded;
121     private boolean mOutputStreamEnded;
122     private long mEndOfStreamMs;
123     private long mCurrentPositionUs;
124     private int mPresentationCount;
125     private long mPresentationTimeUs;
126     private long mInterpolatedTimeUs;
127     private long mPreviousPositionUs;
128     private boolean mIsStopped;
129     private boolean mEnabled = true;
130     private boolean mIsMuted;
131     private ArrayList<Integer> mTracksIndex;
132     private boolean mUseFrameworkDecoder;
133 
MpegTsDefaultAudioTrackRenderer( SampleSource source, MediaCodecSelector selector, Handler eventHandler, EventListener listener)134     public MpegTsDefaultAudioTrackRenderer(
135             SampleSource source,
136             MediaCodecSelector selector,
137             Handler eventHandler,
138             EventListener listener) {
139         mSource = source.register();
140         mSelector = selector;
141         mEventHandler = eventHandler;
142         mEventListener = listener;
143         mTrackIndex = -1;
144         mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE);
145         mFormatHolder = new MediaFormatHolder();
146         AUDIO_TRACK.restart();
147         mCodecCounters = new CodecCounters();
148         mMonitor = new AudioTrackMonitor();
149         mAudioClock = new AudioClock();
150         mTracksIndex = new ArrayList<>();
151     }
152 
153     @Override
getMediaClock()154     protected MediaClock getMediaClock() {
155         return this;
156     }
157 
handlesMimeType(String mimeType)158     private boolean handlesMimeType(String mimeType) {
159         return mimeType.equals(MimeTypes.AUDIO_AC3)
160                 || mimeType.equals(MimeTypes.AUDIO_E_AC3)
161                 || mimeType.equals(MimeTypes.AUDIO_MPEG_L2)
162                 || MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType);
163     }
164 
165     @Override
doPrepare(long positionUs)166     protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
167         boolean sourcePrepared = mSource.prepare(positionUs);
168         if (!sourcePrepared) {
169             return false;
170         }
171         for (int i = 0; i < mSource.getTrackCount(); i++) {
172             String mimeType = mSource.getFormat(i).mimeType;
173             if (MimeTypes.isAudio(mimeType) && handlesMimeType(mimeType)) {
174                 if (mTrackIndex < 0) {
175                     mTrackIndex = i;
176                 }
177                 mTracksIndex.add(i);
178             }
179         }
180 
181         // TODO: Check this case. Source does not have the proper mime type.
182         return true;
183     }
184 
185     @Override
getTrackCount()186     protected int getTrackCount() {
187         return mTracksIndex.size();
188     }
189 
190     @Override
getFormat(int track)191     protected MediaFormat getFormat(int track) {
192         Assertions.checkArgument(track >= 0 && track < mTracksIndex.size());
193         return mSource.getFormat(mTracksIndex.get(track));
194     }
195 
196     @Override
onEnabled(int track, long positionUs, boolean joining)197     protected void onEnabled(int track, long positionUs, boolean joining) {
198         Assertions.checkArgument(track >= 0 && track < mTracksIndex.size());
199         mTrackIndex = mTracksIndex.get(track);
200         mSource.enable(mTrackIndex, positionUs);
201         seekToInternal(positionUs);
202     }
203 
204     @Override
onDisabled()205     protected void onDisabled() {
206         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
207             AUDIO_TRACK.resetSessionId();
208         }
209         clearDecodeState();
210         mFormat = null;
211         mSource.disable(mTrackIndex);
212     }
213 
214     @Override
onReleased()215     protected void onReleased() {
216         releaseDecoder();
217         AUDIO_TRACK.release();
218         mSource.release();
219     }
220 
221     @Override
isEnded()222     protected boolean isEnded() {
223         return mOutputStreamEnded && AUDIO_TRACK.isEnded();
224     }
225 
226     @Override
isReady()227     protected boolean isReady() {
228         return AUDIO_TRACK.isReady() || (mFormat != null && (mSourceStateReady || mOutputReady));
229     }
230 
seekToInternal(long positionUs)231     private void seekToInternal(long positionUs) {
232         mMonitor.reset(MONITOR_DURATION_MS);
233         mSourceStateReady = false;
234         mInputStreamEnded = false;
235         mOutputStreamEnded = false;
236         mPresentationTimeUs = positionUs;
237         mPresentationCount = 0;
238         mPreviousPositionUs = 0;
239         mCurrentPositionUs = Long.MIN_VALUE;
240         mInterpolatedTimeUs = Long.MIN_VALUE;
241         mAudioClock.setPositionUs(positionUs);
242     }
243 
244     @Override
seekTo(long positionUs)245     protected void seekTo(long positionUs) {
246         mSource.seekToUs(positionUs);
247         AUDIO_TRACK.reset();
248         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
249             // b/21824483 workaround
250             // resetSessionId() will create a new framework AudioTrack instead of reusing old one.
251             AUDIO_TRACK.resetSessionId();
252         }
253         seekToInternal(positionUs);
254         clearDecodeState();
255     }
256 
257     @Override
onStarted()258     protected void onStarted() {
259         AUDIO_TRACK.play();
260         mAudioClock.start();
261         mIsStopped = false;
262     }
263 
264     @Override
onStopped()265     protected void onStopped() {
266         AUDIO_TRACK.pause();
267         mAudioClock.stop();
268         mIsStopped = true;
269     }
270 
271     @Override
maybeThrowError()272     protected void maybeThrowError() throws ExoPlaybackException {
273         try {
274             mSource.maybeThrowError();
275         } catch (IOException e) {
276             throw new ExoPlaybackException(e);
277         }
278     }
279 
280     @Override
doSomeWork(long positionUs, long elapsedRealtimeUs)281     protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
282         mMonitor.maybeLog();
283         try {
284             if (mEndOfStreamMs != 0) {
285                 // Ensure playback stops, after EoS was notified.
286                 // Sometimes MediaCodecTrackRenderer does not fetch EoS timely
287                 // after EoS was notified here long before.
288                 // see b/21909113
289                 long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs;
290                 if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS && !mIsStopped) {
291                     throw new ExoPlaybackException("Much time has elapsed after EoS");
292                 }
293             }
294             boolean continueBuffering = mSource.continueBuffering(mTrackIndex, positionUs);
295             if (mSourceStateReady != continueBuffering) {
296                 mSourceStateReady = continueBuffering;
297                 if (DEBUG) {
298                     Log.d(TAG, "mSourceStateReady: " + String.valueOf(mSourceStateReady));
299                 }
300             }
301             long discontinuity = mSource.readDiscontinuity(mTrackIndex);
302             if (discontinuity != SampleSource.NO_DISCONTINUITY) {
303                 AUDIO_TRACK.handleDiscontinuity();
304                 mPresentationTimeUs = discontinuity;
305                 mPresentationCount = 0;
306                 clearDecodeState();
307                 return;
308             }
309             if (mFormat == null) {
310                 readFormat();
311                 return;
312             }
313 
314             if (mAudioDecoder != null) {
315                 mAudioDecoder.maybeInitDecoder(mFormat);
316             }
317             // Process only one sample at a time for doSomeWork() when using FFmpeg decoder.
318             if (processOutput()) {
319                 if (!mOutputReady) {
320                     while (feedInputBuffer()) {
321                         if (mOutputReady) break;
322                     }
323                 }
324             }
325             mCodecCounters.ensureUpdated();
326         } catch (IOException e) {
327             throw new ExoPlaybackException(e);
328         }
329     }
330 
ensureAudioTrackInitialized()331     private void ensureAudioTrackInitialized() {
332         if (!AUDIO_TRACK.isInitialized()) {
333             try {
334                 if (DEBUG) {
335                     Log.d(TAG, "AudioTrack initialized");
336                 }
337                 AUDIO_TRACK.initialize();
338             } catch (AudioTrack.InitializationException e) {
339                 Log.e(TAG, "Error on AudioTrack initialization", e);
340                 notifyAudioTrackInitializationError(e);
341 
342                 // Do not throw exception here but just disabling audioTrack to keep playing
343                 // video without audio.
344                 AUDIO_TRACK.setStatus(false);
345             }
346             if (getState() == TrackRenderer.STATE_STARTED) {
347                 if (DEBUG) {
348                     Log.d(TAG, "AudioTrack played");
349                 }
350                 AUDIO_TRACK.play();
351             }
352         }
353     }
354 
clearDecodeState()355     private void clearDecodeState() {
356         mOutputReady = false;
357         if (mAudioDecoder != null) {
358             mAudioDecoder.resetDecoderState(mDecodingMime);
359         }
360         AUDIO_TRACK.reset();
361     }
362 
releaseDecoder()363     private void releaseDecoder() {
364         if (mAudioDecoder != null) {
365             mAudioDecoder.release();
366         }
367     }
368 
readFormat()369     private void readFormat() throws IOException, ExoPlaybackException {
370         int result =
371                 mSource.readData(mTrackIndex, mCurrentPositionUs, mFormatHolder, mSampleHolder);
372         if (result == SampleSource.FORMAT_READ) {
373             onInputFormatChanged(mFormatHolder);
374         }
375     }
376 
onInputFormatChanged(MediaFormatHolder formatHolder)377     private void onInputFormatChanged(MediaFormatHolder formatHolder) throws ExoPlaybackException {
378         String mimeType = formatHolder.format.mimeType;
379         mUseFrameworkDecoder = MediaCodecAudioDecoder.supportMimeType(mSelector, mimeType);
380         if (mUseFrameworkDecoder) {
381             mAudioDecoder = new MediaCodecAudioDecoder(mSelector);
382             mFormat = formatHolder.format;
383             mAudioDecoder.maybeInitDecoder(mFormat);
384             mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DISABLED);
385             // TODO reimplement ffmeg for google3
386             // Here use else if
387             // (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mimeType)
388             //            || MimeTypes.AUDIO_AC3.equalsIgnoreCase(mimeType) && !mAc3Passthrough
389             // then set the audio decoder to ffmpeg
390         } else {
391             mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
392             mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
393             mFormat = formatHolder.format;
394             releaseDecoder();
395         }
396         mFormatConfigured = true;
397         mMonitor.setEncoding(mimeType);
398         if (DEBUG && !mUseFrameworkDecoder) {
399             Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString());
400         }
401         clearDecodeState();
402         if (!mUseFrameworkDecoder) {
403             AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), 0);
404         }
405     }
406 
onSampleSizeChanged(int sampleSize)407     private void onSampleSizeChanged(int sampleSize) {
408         if (DEBUG) {
409             Log.d(TAG, "Sample size was changed to : " + sampleSize);
410         }
411         clearDecodeState();
412         int audioBufferSize = sampleSize * BUFFERED_SAMPLES_IN_AUDIOTRACK;
413         mSampleSize = sampleSize;
414         AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16(), audioBufferSize);
415     }
416 
onOutputFormatChanged(android.media.MediaFormat format)417     private void onOutputFormatChanged(android.media.MediaFormat format) {
418         if (DEBUG) {
419             Log.d(TAG, "AudioTrack was configured to FORMAT: " + format.toString());
420         }
421         AUDIO_TRACK.reconfigure(format, 0);
422     }
423 
feedInputBuffer()424     private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
425         if (mInputStreamEnded) {
426             return false;
427         }
428 
429         if (mUseFrameworkDecoder) {
430             boolean indexChanged =
431                     ((MediaCodecAudioDecoder) mAudioDecoder).getInputIndex()
432                             == MediaCodecAudioDecoder.INDEX_INVALID;
433             if (indexChanged) {
434                 mSampleHolder.data = mAudioDecoder.getInputBuffer();
435                 if (mSampleHolder.data != null) {
436                     mSampleHolder.clearData();
437                 } else {
438                     return false;
439                 }
440             }
441         } else {
442             mSampleHolder.data.clear();
443             mSampleHolder.size = 0;
444         }
445         int result =
446                 mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, mSampleHolder);
447         switch (result) {
448             case SampleSource.NOTHING_READ:
449                 {
450                     return false;
451                 }
452             case SampleSource.FORMAT_READ:
453                 {
454                     Log.i(TAG, "Format was read again");
455                     onInputFormatChanged(mFormatHolder);
456                     return true;
457                 }
458             case SampleSource.END_OF_STREAM:
459                 {
460                     Log.i(TAG, "End of stream from SampleSource");
461                     mInputStreamEnded = true;
462                     return false;
463                 }
464             default:
465                 {
466                     if (mSampleHolder.size != mSampleSize
467                             && mFormatConfigured
468                             && !mUseFrameworkDecoder) {
469                         onSampleSizeChanged(mSampleHolder.size);
470                     }
471                     mSampleHolder.data.flip();
472                     if (!mUseFrameworkDecoder) {
473                         if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) {
474                             mMonitor.addPts(
475                                     mSampleHolder.timeUs,
476                                     mOutputBuffer.position(),
477                                     mSampleHolder.data.get(MP2_HEADER_BITRATE_OFFSET)
478                                             & MP2_HEADER_BITRATE_MASK);
479                         } else {
480                             mMonitor.addPts(
481                                     mSampleHolder.timeUs,
482                                     mOutputBuffer.position(),
483                                     mSampleHolder.data.get(AC3_HEADER_BITRATE_OFFSET) & 0xff);
484                         }
485                     }
486                     if (mAudioDecoder != null) {
487                         mAudioDecoder.decode(mSampleHolder);
488                         if (mUseFrameworkDecoder) {
489                             int outputIndex =
490                                     ((MediaCodecAudioDecoder) mAudioDecoder).getOutputIndex();
491                             if (outputIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
492                                 onOutputFormatChanged(mAudioDecoder.getOutputFormat());
493                                 return true;
494                             } else if (outputIndex < 0) {
495                                 return true;
496                             }
497                             if (((MediaCodecAudioDecoder) mAudioDecoder).maybeDecodeOnlyIndex()) {
498                                 AUDIO_TRACK.handleDiscontinuity();
499                                 return true;
500                             }
501                         }
502                         ByteBuffer outputBuffer = mAudioDecoder.getDecodedSample();
503                         long presentationTimeUs = mAudioDecoder.getDecodedTimeUs();
504                         decodeDone(outputBuffer, presentationTimeUs);
505                     } else {
506                         decodeDone(mSampleHolder.data, mSampleHolder.timeUs);
507                     }
508                     return true;
509                 }
510         }
511     }
512 
processOutput()513     private boolean processOutput() throws ExoPlaybackException {
514         if (mOutputStreamEnded) {
515             return false;
516         }
517         if (!mOutputReady) {
518             if (mInputStreamEnded) {
519                 mOutputStreamEnded = true;
520                 mEndOfStreamMs = SystemClock.elapsedRealtime();
521                 return false;
522             }
523             return true;
524         }
525 
526         ensureAudioTrackInitialized();
527         int handleBufferResult;
528         try {
529             // To reduce discontinuity, interpolate presentation time.
530             if (MimeTypes.AUDIO_MPEG_L2.equalsIgnoreCase(mDecodingMime)) {
531                 mInterpolatedTimeUs =
532                         mPresentationTimeUs + mPresentationCount * MP2_SAMPLE_DURATION_US;
533             } else if (!mUseFrameworkDecoder) {
534                 mInterpolatedTimeUs =
535                         mPresentationTimeUs + mPresentationCount * AC3_SAMPLE_DURATION_US;
536             } else {
537                 mInterpolatedTimeUs = mPresentationTimeUs;
538             }
539             handleBufferResult =
540                     AUDIO_TRACK.handleBuffer(
541                             mOutputBuffer, 0, mOutputBuffer.limit(), mInterpolatedTimeUs);
542         } catch (AudioTrack.WriteException e) {
543             notifyAudioTrackWriteError(e);
544             throw new ExoPlaybackException(e);
545         }
546         if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
547             Log.i(TAG, "Play discontinuity happened");
548             mCurrentPositionUs = Long.MIN_VALUE;
549         }
550         if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
551             mCodecCounters.renderedOutputBufferCount++;
552             mOutputReady = false;
553             if (mUseFrameworkDecoder) {
554                 ((MediaCodecAudioDecoder) mAudioDecoder).releaseOutputBuffer();
555             }
556             return true;
557         }
558         return false;
559     }
560 
561     @Override
getDurationUs()562     protected long getDurationUs() {
563         return mSource.getFormat(mTrackIndex).durationUs;
564     }
565 
566     @Override
getBufferedPositionUs()567     protected long getBufferedPositionUs() {
568         long pos = mSource.getBufferedPositionUs();
569         return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US
570                 ? pos
571                 : Math.max(pos, getPositionUs());
572     }
573 
574     @Override
getPositionUs()575     public long getPositionUs() {
576         if (!AUDIO_TRACK.isInitialized()) {
577             return mAudioClock.getPositionUs();
578         } else if (!AUDIO_TRACK.isEnabled()) {
579             if (mInterpolatedTimeUs > 0 && !mUseFrameworkDecoder) {
580                 return mInterpolatedTimeUs - ESTIMATED_TRACK_RENDERING_DELAY_US;
581             }
582             return mPresentationTimeUs;
583         }
584         long audioTrackCurrentPositionUs = AUDIO_TRACK.getCurrentPositionUs(isEnded());
585         if (audioTrackCurrentPositionUs == AudioTrack.CURRENT_POSITION_NOT_SET) {
586             mPreviousPositionUs = 0L;
587             if (DEBUG) {
588                 long oldPositionUs = Math.max(mCurrentPositionUs, 0);
589                 long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
590                 Log.d(
591                         TAG,
592                         "Audio position is not set, diff in us: "
593                                 + String.valueOf(currentPositionUs - oldPositionUs));
594             }
595             mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
596         } else {
597             // TODO: Remove this workaround when b/22023809 is resolved.
598             if (mPreviousPositionUs
599                     > audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) {
600                 Log.e(
601                         TAG,
602                         "audio_position BACK JUMP: "
603                                 + (mPreviousPositionUs - audioTrackCurrentPositionUs));
604                 mCurrentPositionUs = audioTrackCurrentPositionUs;
605             } else {
606                 mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs);
607             }
608             mPreviousPositionUs = audioTrackCurrentPositionUs;
609         }
610         long upperBound = mPresentationTimeUs + CURRENT_POSITION_FROM_PTS_LIMIT_US;
611         if (mCurrentPositionUs > upperBound) {
612             mCurrentPositionUs = upperBound;
613         }
614         return mCurrentPositionUs;
615     }
616 
decodeDone(ByteBuffer outputBuffer, long presentationTimeUs)617     private void decodeDone(ByteBuffer outputBuffer, long presentationTimeUs) {
618         if (outputBuffer == null || mOutputBuffer == null) {
619             return;
620         }
621         if (presentationTimeUs < 0) {
622             Log.e(TAG, "decodeDone - invalid presentationTimeUs");
623             return;
624         }
625 
626         if (TunerDebug.ENABLED) {
627             TunerDebug.setAudioPtsUs(presentationTimeUs);
628         }
629 
630         mOutputBuffer.clear();
631         Assertions.checkState(mOutputBuffer.remaining() >= outputBuffer.limit());
632 
633         mOutputBuffer.put(outputBuffer);
634         if (presentationTimeUs == mPresentationTimeUs) {
635             mPresentationCount++;
636         } else {
637             mPresentationCount = 0;
638             mPresentationTimeUs = presentationTimeUs;
639         }
640         mOutputBuffer.flip();
641         mOutputReady = true;
642     }
643 
notifyAudioTrackInitializationError(final AudioTrack.InitializationException e)644     private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
645         if (mEventHandler == null || mEventListener == null) {
646             return;
647         }
648         mEventHandler.post(() -> mEventListener.onAudioTrackInitializationError(e));
649     }
650 
notifyAudioTrackWriteError(final AudioTrack.WriteException e)651     private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
652         if (mEventHandler == null || mEventListener == null) {
653             return;
654         }
655         mEventHandler.post(() -> mEventListener.onAudioTrackWriteError(e));
656     }
657 
658     @Override
handleMessage(int messageType, Object message)659     public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
660         switch (messageType) {
661             case MSG_SET_VOLUME:
662                 float volume = (Float) message;
663                 // Workaround: we cannot mute the audio track by setting the volume to 0, we need to
664                 // disable the AUDIO_TRACK for this intent. However, enabling/disabling audio track
665                 // whenever volume is being set might cause side effects, therefore we only handle
666                 // "explicit mute operations", i.e., only after certain non-zero volume has been
667                 // set, the subsequent volume setting operations will be consider as mute/un-mute
668                 // operations and thus enable/disable the audio track.
669                 if (mIsMuted && volume > 0) {
670                     mIsMuted = false;
671                     if (mEnabled) {
672                         setStatus(true);
673                     }
674                 } else if (!mIsMuted && volume == 0) {
675                     mIsMuted = true;
676                     if (mEnabled) {
677                         setStatus(false);
678                     }
679                 }
680                 AUDIO_TRACK.setVolume(volume);
681                 break;
682             case MSG_SET_AUDIO_TRACK:
683                 mEnabled = (Integer) message == 1;
684                 setStatus(mEnabled);
685                 break;
686             case MSG_SET_PLAYBACK_SPEED:
687                 mAudioClock.setPlaybackSpeed((Float) message);
688                 break;
689             default:
690                 super.handleMessage(messageType, message);
691         }
692     }
693 
setStatus(boolean enabled)694     private void setStatus(boolean enabled) {
695         if (enabled == AUDIO_TRACK.isEnabled()) {
696             return;
697         }
698         if (!enabled) {
699             // mAudioClock can be different from getPositionUs. In order to sync them,
700             // we set mAudioClock.
701             mAudioClock.setPositionUs(getPositionUs());
702         }
703         AUDIO_TRACK.setStatus(enabled);
704         if (enabled) {
705             // When AUDIO_TRACK is enabled, we need to clear AUDIO_TRACK and seek to
706             // the current position. If not, AUDIO_TRACK has the obsolete data.
707             seekTo(mAudioClock.getPositionUs());
708         }
709     }
710 }
711