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