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