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