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.ac3; 18 19 import android.os.Handler; 20 import android.os.SystemClock; 21 import android.util.Log; 22 23 import com.google.android.exoplayer.CodecCounters; 24 import com.google.android.exoplayer.ExoPlaybackException; 25 import com.google.android.exoplayer.MediaClock; 26 import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; 27 import com.google.android.exoplayer.MediaFormat; 28 import com.google.android.exoplayer.MediaFormatHolder; 29 import com.google.android.exoplayer.MediaFormatUtil; 30 import com.google.android.exoplayer.SampleHolder; 31 import com.google.android.exoplayer.SampleSource; 32 import com.google.android.exoplayer.TrackRenderer; 33 import com.google.android.exoplayer.audio.AudioTrack; 34 import com.google.android.exoplayer.util.Assertions; 35 import com.google.android.exoplayer.util.MimeTypes; 36 import com.android.tv.tuner.tvinput.TunerDebug; 37 38 import java.io.IOException; 39 import java.nio.ByteBuffer; 40 import java.util.ArrayList; 41 42 /** 43 * Decodes and renders AC3 audio. 44 */ 45 public class Ac3PassthroughTrackRenderer 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 private static final String TAG = "Ac3PassthroughTrackRenderer"; 55 private static final boolean DEBUG = false; 56 57 /** 58 * Interface definition for a callback to be notified of 59 * {@link com.google.android.exoplayer.audio.AudioTrack} error. 60 */ 61 public interface EventListener { onAudioTrackInitializationError(AudioTrack.InitializationException e)62 void onAudioTrackInitializationError(AudioTrack.InitializationException e); onAudioTrackWriteError(AudioTrack.WriteException e)63 void onAudioTrackWriteError(AudioTrack.WriteException e); 64 } 65 66 private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2; 67 private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024*1024; 68 private static final int MONITOR_DURATION_MS = 1000; 69 private static final int AC3_HEADER_BITRATE_OFFSET = 4; 70 71 // Keep this as static in order to prevent new framework AudioTrack creation 72 // while old AudioTrack is being released. 73 private static final AudioTrackWrapper AUDIO_TRACK = new AudioTrackWrapper(); 74 private static final long KEEP_ALIVE_AFTER_EOS_DURATION_MS = 3000; 75 76 // Ignore AudioTrack backward movement if duration of movement is below the threshold. 77 private static final long BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US = 3000; 78 79 // AudioTrack position cannot go ahead beyond this limit. 80 private static final long CURRENT_POSITION_FROM_PTS_LIMIT_US = 1000000; 81 82 // Since MediaCodec processing and AudioTrack playing add delay, 83 // PTS interpolated time should be delayed reasonably when AudioTrack is not used. 84 private static final long ESTIMATED_TRACK_RENDERING_DELAY_US = 500000; 85 86 private final CodecCounters mCodecCounters; 87 private final SampleSource.SampleSourceReader mSource; 88 private final SampleHolder mSampleHolder; 89 private final MediaFormatHolder mFormatHolder; 90 private final EventListener mEventListener; 91 private final Handler mEventHandler; 92 private final AudioTrackMonitor mMonitor; 93 private final AudioClock mAudioClock; 94 95 private MediaFormat mFormat; 96 private final ByteBuffer mOutputBuffer; 97 private boolean mOutputReady; 98 private int mTrackIndex; 99 private boolean mSourceStateReady; 100 private boolean mInputStreamEnded; 101 private boolean mOutputStreamEnded; 102 private long mEndOfStreamMs; 103 private long mCurrentPositionUs; 104 private int mPresentationCount; 105 private long mPresentationTimeUs; 106 private long mInterpolatedTimeUs; 107 private long mPreviousPositionUs; 108 private boolean mIsStopped; 109 private ArrayList<Integer> mTracksIndex; 110 Ac3PassthroughTrackRenderer(SampleSource source, Handler eventHandler, EventListener listener)111 public Ac3PassthroughTrackRenderer(SampleSource source, Handler eventHandler, 112 EventListener listener) { 113 mSource = source.register(); 114 mEventHandler = eventHandler; 115 mEventListener = listener; 116 mTrackIndex = -1; 117 mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); 118 mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE); 119 mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE); 120 mFormatHolder = new MediaFormatHolder(); 121 AUDIO_TRACK.restart(); 122 mCodecCounters = new CodecCounters(); 123 mMonitor = new AudioTrackMonitor(); 124 mAudioClock = new AudioClock(); 125 mTracksIndex = new ArrayList<>(); 126 } 127 128 @Override getMediaClock()129 protected MediaClock getMediaClock() { 130 return this; 131 } 132 handlesMimeType(String mimeType)133 private static boolean handlesMimeType(String mimeType) { 134 return mimeType.equals(MimeTypes.AUDIO_AC3) || mimeType.equals(MimeTypes.AUDIO_E_AC3); 135 } 136 137 @Override doPrepare(long positionUs)138 protected boolean doPrepare(long positionUs) throws ExoPlaybackException { 139 boolean sourcePrepared = mSource.prepare(positionUs); 140 if (!sourcePrepared) { 141 return false; 142 } 143 for (int i = 0; i < mSource.getTrackCount(); i++) { 144 if (handlesMimeType(mSource.getFormat(i).mimeType)) { 145 if (mTrackIndex < 0) { 146 mTrackIndex = i; 147 } 148 mTracksIndex.add(i); 149 } 150 } 151 152 // TODO: Check this case. Source does not have the proper mime type. 153 return true; 154 } 155 156 @Override getTrackCount()157 protected int getTrackCount() { 158 return mTracksIndex.size(); 159 } 160 161 @Override getFormat(int track)162 protected MediaFormat getFormat(int track) { 163 Assertions.checkArgument(track >= 0 && track < mTracksIndex.size()); 164 return mSource.getFormat(mTracksIndex.get(track)); 165 } 166 167 @Override onEnabled(int track, long positionUs, boolean joining)168 protected void onEnabled(int track, long positionUs, boolean joining) { 169 Assertions.checkArgument(track >= 0 && track < mTracksIndex.size()); 170 mTrackIndex = mTracksIndex.get(track); 171 mSource.enable(mTrackIndex, positionUs); 172 seekToInternal(positionUs); 173 } 174 175 @Override onDisabled()176 protected void onDisabled() { 177 AUDIO_TRACK.resetSessionId(); 178 clearDecodeState(); 179 mFormat = null; 180 mSource.disable(mTrackIndex); 181 } 182 183 @Override onReleased()184 protected void onReleased() { 185 AUDIO_TRACK.release(); 186 mSource.release(); 187 } 188 189 @Override isEnded()190 protected boolean isEnded() { 191 return mOutputStreamEnded && AUDIO_TRACK.isEnded(); 192 } 193 194 @Override isReady()195 protected boolean isReady() { 196 return AUDIO_TRACK.isReady() || (mFormat != null && (mSourceStateReady || mOutputReady)); 197 } 198 seekToInternal(long positionUs)199 private void seekToInternal(long positionUs) { 200 mMonitor.reset(MONITOR_DURATION_MS); 201 mSourceStateReady = false; 202 mInputStreamEnded = false; 203 mOutputStreamEnded = false; 204 mPresentationTimeUs = positionUs; 205 mPresentationCount = 0; 206 mPreviousPositionUs = 0; 207 mCurrentPositionUs = Long.MIN_VALUE; 208 mInterpolatedTimeUs = Long.MIN_VALUE; 209 mAudioClock.setPositionUs(positionUs); 210 } 211 212 @Override seekTo(long positionUs)213 protected void seekTo(long positionUs) { 214 mSource.seekToUs(positionUs); 215 AUDIO_TRACK.reset(); 216 // resetSessionId() will create a new framework AudioTrack instead of reusing old one. 217 AUDIO_TRACK.resetSessionId(); 218 seekToInternal(positionUs); 219 } 220 221 @Override onStarted()222 protected void onStarted() { 223 AUDIO_TRACK.play(); 224 mAudioClock.start(); 225 mIsStopped = false; 226 } 227 228 @Override onStopped()229 protected void onStopped() { 230 AUDIO_TRACK.pause(); 231 mAudioClock.stop(); 232 mIsStopped = true; 233 } 234 235 @Override maybeThrowError()236 protected void maybeThrowError() throws ExoPlaybackException { 237 try { 238 mSource.maybeThrowError(); 239 } catch (IOException e) { 240 throw new ExoPlaybackException(e); 241 } 242 } 243 244 @Override doSomeWork(long positionUs, long elapsedRealtimeUs)245 protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { 246 mMonitor.maybeLog(); 247 try { 248 if (mEndOfStreamMs != 0) { 249 // Ensure playback stops, after EoS was notified. 250 // Sometimes MediaCodecTrackRenderer does not fetch EoS timely 251 // after EoS was notified here long before. 252 long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs; 253 if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS && !mIsStopped) { 254 throw new ExoPlaybackException("Much time has elapsed after EoS"); 255 } 256 } 257 boolean continueBuffering = mSource.continueBuffering(mTrackIndex, positionUs); 258 if (mSourceStateReady != continueBuffering) { 259 mSourceStateReady = continueBuffering; 260 if (DEBUG) { 261 Log.d(TAG, "mSourceStateReady: " + String.valueOf(mSourceStateReady)); 262 } 263 } 264 long discontinuity = mSource.readDiscontinuity(mTrackIndex); 265 if (discontinuity != SampleSource.NO_DISCONTINUITY) { 266 AUDIO_TRACK.handleDiscontinuity(); 267 mPresentationTimeUs = discontinuity; 268 mPresentationCount = 0; 269 clearDecodeState(); 270 return; 271 } 272 if (mFormat == null) { 273 readFormat(); 274 return; 275 } 276 277 // Process only one sample at a time for doSomeWork() 278 if (processOutput()) { 279 if (!mOutputReady) { 280 while (feedInputBuffer()) { 281 if (mOutputReady) break; 282 } 283 } 284 } 285 mCodecCounters.ensureUpdated(); 286 } catch (IOException e) { 287 throw new ExoPlaybackException(e); 288 } 289 } 290 ensureAudioTrackInitialized()291 private void ensureAudioTrackInitialized() { 292 if (!AUDIO_TRACK.isInitialized()) { 293 try { 294 if (DEBUG) { 295 Log.d(TAG, "AudioTrack initialized"); 296 } 297 AUDIO_TRACK.initialize(); 298 } catch (AudioTrack.InitializationException e) { 299 Log.e(TAG, "Error on AudioTrack initialization", e); 300 notifyAudioTrackInitializationError(e); 301 302 // Do not throw exception here but just disabling audioTrack to keep playing 303 // video without audio. 304 AUDIO_TRACK.setStatus(false); 305 } 306 if (getState() == TrackRenderer.STATE_STARTED) { 307 if (DEBUG) { 308 Log.d(TAG, "AudioTrack played"); 309 } 310 AUDIO_TRACK.play(); 311 } 312 } 313 } 314 clearDecodeState()315 private void clearDecodeState() { 316 mOutputReady = false; 317 AUDIO_TRACK.reset(); 318 } 319 readFormat()320 private void readFormat() throws IOException, ExoPlaybackException { 321 int result = mSource.readData(mTrackIndex, mCurrentPositionUs, 322 mFormatHolder, mSampleHolder); 323 if (result == SampleSource.FORMAT_READ) { 324 onInputFormatChanged(mFormatHolder); 325 } 326 } 327 onInputFormatChanged(MediaFormatHolder formatHolder)328 private void onInputFormatChanged(MediaFormatHolder formatHolder) 329 throws ExoPlaybackException { 330 mFormat = formatHolder.format; 331 if (DEBUG) { 332 Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString()); 333 } 334 clearDecodeState(); 335 AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16()); 336 } 337 feedInputBuffer()338 private boolean feedInputBuffer() throws IOException, ExoPlaybackException { 339 if (mInputStreamEnded) { 340 return false; 341 } 342 343 mSampleHolder.data.clear(); 344 mSampleHolder.size = 0; 345 int result = mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder, 346 mSampleHolder); 347 switch (result) { 348 case SampleSource.NOTHING_READ: { 349 return false; 350 } 351 case SampleSource.FORMAT_READ: { 352 Log.i(TAG, "Format was read again"); 353 onInputFormatChanged(mFormatHolder); 354 return true; 355 } 356 case SampleSource.END_OF_STREAM: { 357 Log.i(TAG, "End of stream from SampleSource"); 358 mInputStreamEnded = true; 359 return false; 360 } 361 default: { 362 mSampleHolder.data.flip(); 363 decodeDone(mSampleHolder.data, mSampleHolder.timeUs); 364 return true; 365 } 366 } 367 } 368 processOutput()369 private boolean processOutput() throws ExoPlaybackException { 370 if (mOutputStreamEnded) { 371 return false; 372 } 373 if (!mOutputReady) { 374 if (mInputStreamEnded) { 375 mOutputStreamEnded = true; 376 mEndOfStreamMs = SystemClock.elapsedRealtime(); 377 return false; 378 } 379 return true; 380 } 381 382 ensureAudioTrackInitialized(); 383 int handleBufferResult; 384 try { 385 // To reduce discontinuity, interpolate presentation time. 386 mInterpolatedTimeUs = mPresentationTimeUs 387 + mPresentationCount * AC3_SAMPLE_DURATION_US; 388 handleBufferResult = AUDIO_TRACK.handleBuffer(mOutputBuffer, 389 0, mOutputBuffer.limit(), mInterpolatedTimeUs); 390 } catch (AudioTrack.WriteException e) { 391 notifyAudioTrackWriteError(e); 392 throw new ExoPlaybackException(e); 393 } 394 395 if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { 396 Log.i(TAG, "Play discontinuity happened"); 397 mCurrentPositionUs = Long.MIN_VALUE; 398 } 399 if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) { 400 mCodecCounters.renderedOutputBufferCount++; 401 mOutputReady = false; 402 return true; 403 } 404 return false; 405 } 406 407 @Override getDurationUs()408 protected long getDurationUs() { 409 return mSource.getFormat(mTrackIndex).durationUs; 410 } 411 412 @Override getBufferedPositionUs()413 protected long getBufferedPositionUs() { 414 long pos = mSource.getBufferedPositionUs(); 415 return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US 416 ? pos : Math.max(pos, getPositionUs()); 417 } 418 419 @Override getPositionUs()420 public long getPositionUs() { 421 if (!AUDIO_TRACK.isInitialized()) { 422 return mAudioClock.getPositionUs(); 423 } else if (!AUDIO_TRACK.isEnabled()) { 424 if (mInterpolatedTimeUs > 0) { 425 return mInterpolatedTimeUs - ESTIMATED_TRACK_RENDERING_DELAY_US; 426 } 427 return mPresentationTimeUs; 428 } 429 long audioTrackCurrentPositionUs = AUDIO_TRACK.getCurrentPositionUs(isEnded()); 430 if (audioTrackCurrentPositionUs == AudioTrack.CURRENT_POSITION_NOT_SET) { 431 mPreviousPositionUs = 0L; 432 if (DEBUG) { 433 long oldPositionUs = Math.max(mCurrentPositionUs, 0); 434 long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); 435 Log.d(TAG, "Audio position is not set, diff in us: " 436 + String.valueOf(currentPositionUs - oldPositionUs)); 437 } 438 mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs); 439 } else { 440 if (mPreviousPositionUs 441 > audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) { 442 Log.e(TAG, "audio_position BACK JUMP: " 443 + (mPreviousPositionUs - audioTrackCurrentPositionUs)); 444 mCurrentPositionUs = audioTrackCurrentPositionUs; 445 } else { 446 mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs); 447 } 448 mPreviousPositionUs = audioTrackCurrentPositionUs; 449 } 450 long upperBound = mPresentationTimeUs + CURRENT_POSITION_FROM_PTS_LIMIT_US; 451 if (mCurrentPositionUs > upperBound) { 452 mCurrentPositionUs = upperBound; 453 } 454 return mCurrentPositionUs; 455 } 456 decodeDone(ByteBuffer outputBuffer, long presentationTimeUs)457 private void decodeDone(ByteBuffer outputBuffer, long presentationTimeUs) { 458 if (outputBuffer == null || mOutputBuffer == null) { 459 return; 460 } 461 if (presentationTimeUs < 0) { 462 Log.e(TAG, "decodeDone - invalid presentationTimeUs"); 463 return; 464 } 465 466 if (TunerDebug.ENABLED) { 467 TunerDebug.setAudioPtsUs(presentationTimeUs); 468 } 469 470 mOutputBuffer.clear(); 471 Assertions.checkState(mOutputBuffer.remaining() >= outputBuffer.limit()); 472 473 mOutputBuffer.put(outputBuffer); 474 mMonitor.addPts(presentationTimeUs, mOutputBuffer.position(), 475 mOutputBuffer.get(AC3_HEADER_BITRATE_OFFSET)); 476 if (presentationTimeUs == mPresentationTimeUs) { 477 mPresentationCount++; 478 } else { 479 mPresentationCount = 0; 480 mPresentationTimeUs = presentationTimeUs; 481 } 482 mOutputBuffer.flip(); 483 mOutputReady = true; 484 } 485 notifyAudioTrackInitializationError(final AudioTrack.InitializationException e)486 private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) { 487 if (mEventHandler == null || mEventListener == null) { 488 return; 489 } 490 mEventHandler.post(new Runnable() { 491 @Override 492 public void run() { 493 mEventListener.onAudioTrackInitializationError(e); 494 } 495 }); 496 } 497 notifyAudioTrackWriteError(final AudioTrack.WriteException e)498 private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) { 499 if (mEventHandler == null || mEventListener == null) { 500 return; 501 } 502 mEventHandler.post(new Runnable() { 503 @Override 504 public void run() { 505 mEventListener.onAudioTrackWriteError(e); 506 } 507 }); 508 } 509 510 @Override handleMessage(int messageType, Object message)511 public void handleMessage(int messageType, Object message) throws ExoPlaybackException { 512 switch (messageType) { 513 case MSG_SET_VOLUME: 514 AUDIO_TRACK.setVolume((Float) message); 515 break; 516 case MSG_SET_AUDIO_TRACK: 517 boolean enabled = (Integer) message == 1; 518 if (enabled == AUDIO_TRACK.isEnabled()) { 519 return; 520 } 521 if (!enabled) { 522 // mAudioClock can be different from getPositionUs. In order to sync them, 523 // we set mAudioClock. 524 mAudioClock.setPositionUs(getPositionUs()); 525 } 526 AUDIO_TRACK.setStatus(enabled); 527 if (enabled) { 528 // When AUDIO_TRACK is enabled, we need to clear AUDIO_TRACK and seek to 529 // the current position. If not, AUDIO_TRACK has the obsolete data. 530 seekTo(mAudioClock.getPositionUs()); 531 } 532 break; 533 case MSG_SET_PLAYBACK_SPEED: 534 mAudioClock.setPlaybackSpeed((Float) message); 535 break; 536 default: 537 super.handleMessage(messageType, message); 538 } 539 } 540 } 541