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