aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java')
-rw-r--r--src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java540
1 files changed, 0 insertions, 540 deletions
diff --git a/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java b/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java
deleted file mode 100644
index 9dae2e34..00000000
--- a/src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java
+++ /dev/null
@@ -1,540 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.tv.tuner.exoplayer.ac3;
-
-import android.os.Handler;
-import android.os.SystemClock;
-import android.util.Log;
-
-import com.google.android.exoplayer.CodecCounters;
-import com.google.android.exoplayer.ExoPlaybackException;
-import com.google.android.exoplayer.MediaClock;
-import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
-import com.google.android.exoplayer.MediaFormat;
-import com.google.android.exoplayer.MediaFormatHolder;
-import com.google.android.exoplayer.MediaFormatUtil;
-import com.google.android.exoplayer.SampleHolder;
-import com.google.android.exoplayer.SampleSource;
-import com.google.android.exoplayer.TrackRenderer;
-import com.google.android.exoplayer.audio.AudioTrack;
-import com.google.android.exoplayer.util.Assertions;
-import com.google.android.exoplayer.util.MimeTypes;
-import com.android.tv.tuner.tvinput.TunerDebug;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.util.ArrayList;
-
-/**
- * Decodes and renders AC3 audio.
- */
-public class Ac3PassthroughTrackRenderer extends TrackRenderer implements MediaClock {
- public static final int MSG_SET_VOLUME = 10000;
- public static final int MSG_SET_AUDIO_TRACK = MSG_SET_VOLUME + 1;
- public static final int MSG_SET_PLAYBACK_SPEED = MSG_SET_VOLUME + 2;
-
- // ATSC/53 allows sample rate to be only 48Khz.
- // One AC3 sample has 1536 frames, and its duration is 32ms.
- public static final long AC3_SAMPLE_DURATION_US = 32000;
-
- private static final String TAG = "Ac3PassthroughTrackRenderer";
- private static final boolean DEBUG = false;
-
- /**
- * Interface definition for a callback to be notified of
- * {@link com.google.android.exoplayer.audio.AudioTrack} error.
- */
- public interface EventListener {
- void onAudioTrackInitializationError(AudioTrack.InitializationException e);
- void onAudioTrackWriteError(AudioTrack.WriteException e);
- }
-
- private static final int DEFAULT_INPUT_BUFFER_SIZE = 16384 * 2;
- private static final int DEFAULT_OUTPUT_BUFFER_SIZE = 1024*1024;
- private static final int MONITOR_DURATION_MS = 1000;
- private static final int AC3_HEADER_BITRATE_OFFSET = 4;
-
- // Keep this as static in order to prevent new framework AudioTrack creation
- // while old AudioTrack is being released.
- private static final AudioTrackWrapper AUDIO_TRACK = new AudioTrackWrapper();
- private static final long KEEP_ALIVE_AFTER_EOS_DURATION_MS = 3000;
-
- // Ignore AudioTrack backward movement if duration of movement is below the threshold.
- private static final long BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US = 3000;
-
- // AudioTrack position cannot go ahead beyond this limit.
- private static final long CURRENT_POSITION_FROM_PTS_LIMIT_US = 1000000;
-
- // Since MediaCodec processing and AudioTrack playing add delay,
- // PTS interpolated time should be delayed reasonably when AudioTrack is not used.
- private static final long ESTIMATED_TRACK_RENDERING_DELAY_US = 500000;
-
- private final CodecCounters mCodecCounters;
- private final SampleSource.SampleSourceReader mSource;
- private final SampleHolder mSampleHolder;
- private final MediaFormatHolder mFormatHolder;
- private final EventListener mEventListener;
- private final Handler mEventHandler;
- private final AudioTrackMonitor mMonitor;
- private final AudioClock mAudioClock;
-
- private MediaFormat mFormat;
- private final ByteBuffer mOutputBuffer;
- private boolean mOutputReady;
- private int mTrackIndex;
- private boolean mSourceStateReady;
- private boolean mInputStreamEnded;
- private boolean mOutputStreamEnded;
- private long mEndOfStreamMs;
- private long mCurrentPositionUs;
- private int mPresentationCount;
- private long mPresentationTimeUs;
- private long mInterpolatedTimeUs;
- private long mPreviousPositionUs;
- private boolean mIsStopped;
- private ArrayList<Integer> mTracksIndex;
-
- public Ac3PassthroughTrackRenderer(SampleSource source, Handler eventHandler,
- EventListener listener) {
- mSource = source.register();
- mEventHandler = eventHandler;
- mEventListener = listener;
- mTrackIndex = -1;
- mSampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT);
- mSampleHolder.ensureSpaceForWrite(DEFAULT_INPUT_BUFFER_SIZE);
- mOutputBuffer = ByteBuffer.allocate(DEFAULT_OUTPUT_BUFFER_SIZE);
- mFormatHolder = new MediaFormatHolder();
- AUDIO_TRACK.restart();
- mCodecCounters = new CodecCounters();
- mMonitor = new AudioTrackMonitor();
- mAudioClock = new AudioClock();
- mTracksIndex = new ArrayList<>();
- }
-
- @Override
- protected MediaClock getMediaClock() {
- return this;
- }
-
- private static boolean handlesMimeType(String mimeType) {
- return mimeType.equals(MimeTypes.AUDIO_AC3) || mimeType.equals(MimeTypes.AUDIO_E_AC3);
- }
-
- @Override
- protected boolean doPrepare(long positionUs) throws ExoPlaybackException {
- boolean sourcePrepared = mSource.prepare(positionUs);
- if (!sourcePrepared) {
- return false;
- }
- for (int i = 0; i < mSource.getTrackCount(); i++) {
- if (handlesMimeType(mSource.getFormat(i).mimeType)) {
- if (mTrackIndex < 0) {
- mTrackIndex = i;
- }
- mTracksIndex.add(i);
- }
- }
-
- // TODO: Check this case. Source does not have the proper mime type.
- return true;
- }
-
- @Override
- protected int getTrackCount() {
- return mTracksIndex.size();
- }
-
- @Override
- protected MediaFormat getFormat(int track) {
- Assertions.checkArgument(track >= 0 && track < mTracksIndex.size());
- return mSource.getFormat(mTracksIndex.get(track));
- }
-
- @Override
- protected void onEnabled(int track, long positionUs, boolean joining) {
- Assertions.checkArgument(track >= 0 && track < mTracksIndex.size());
- mTrackIndex = mTracksIndex.get(track);
- mSource.enable(mTrackIndex, positionUs);
- seekToInternal(positionUs);
- }
-
- @Override
- protected void onDisabled() {
- AUDIO_TRACK.resetSessionId();
- clearDecodeState();
- mFormat = null;
- mSource.disable(mTrackIndex);
- }
-
- @Override
- protected void onReleased() {
- AUDIO_TRACK.release();
- mSource.release();
- }
-
- @Override
- protected boolean isEnded() {
- return mOutputStreamEnded && AUDIO_TRACK.isEnded();
- }
-
- @Override
- protected boolean isReady() {
- return AUDIO_TRACK.isReady() || (mFormat != null && (mSourceStateReady || mOutputReady));
- }
-
- private void seekToInternal(long positionUs) {
- mMonitor.reset(MONITOR_DURATION_MS);
- mSourceStateReady = false;
- mInputStreamEnded = false;
- mOutputStreamEnded = false;
- mPresentationTimeUs = positionUs;
- mPresentationCount = 0;
- mPreviousPositionUs = 0;
- mCurrentPositionUs = Long.MIN_VALUE;
- mInterpolatedTimeUs = Long.MIN_VALUE;
- mAudioClock.setPositionUs(positionUs);
- }
-
- @Override
- protected void seekTo(long positionUs) {
- mSource.seekToUs(positionUs);
- AUDIO_TRACK.reset();
- // resetSessionId() will create a new framework AudioTrack instead of reusing old one.
- AUDIO_TRACK.resetSessionId();
- seekToInternal(positionUs);
- }
-
- @Override
- protected void onStarted() {
- AUDIO_TRACK.play();
- mAudioClock.start();
- mIsStopped = false;
- }
-
- @Override
- protected void onStopped() {
- AUDIO_TRACK.pause();
- mAudioClock.stop();
- mIsStopped = true;
- }
-
- @Override
- protected void maybeThrowError() throws ExoPlaybackException {
- try {
- mSource.maybeThrowError();
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- @Override
- protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
- mMonitor.maybeLog();
- try {
- if (mEndOfStreamMs != 0) {
- // Ensure playback stops, after EoS was notified.
- // Sometimes MediaCodecTrackRenderer does not fetch EoS timely
- // after EoS was notified here long before.
- long diff = SystemClock.elapsedRealtime() - mEndOfStreamMs;
- if (diff >= KEEP_ALIVE_AFTER_EOS_DURATION_MS && !mIsStopped) {
- throw new ExoPlaybackException("Much time has elapsed after EoS");
- }
- }
- boolean continueBuffering = mSource.continueBuffering(mTrackIndex, positionUs);
- if (mSourceStateReady != continueBuffering) {
- mSourceStateReady = continueBuffering;
- if (DEBUG) {
- Log.d(TAG, "mSourceStateReady: " + String.valueOf(mSourceStateReady));
- }
- }
- long discontinuity = mSource.readDiscontinuity(mTrackIndex);
- if (discontinuity != SampleSource.NO_DISCONTINUITY) {
- AUDIO_TRACK.handleDiscontinuity();
- mPresentationTimeUs = discontinuity;
- mPresentationCount = 0;
- clearDecodeState();
- return;
- }
- if (mFormat == null) {
- readFormat();
- return;
- }
-
- // Process only one sample at a time for doSomeWork()
- if (processOutput()) {
- if (!mOutputReady) {
- while (feedInputBuffer()) {
- if (mOutputReady) break;
- }
- }
- }
- mCodecCounters.ensureUpdated();
- } catch (IOException e) {
- throw new ExoPlaybackException(e);
- }
- }
-
- private void ensureAudioTrackInitialized() {
- if (!AUDIO_TRACK.isInitialized()) {
- try {
- if (DEBUG) {
- Log.d(TAG, "AudioTrack initialized");
- }
- AUDIO_TRACK.initialize();
- } catch (AudioTrack.InitializationException e) {
- Log.e(TAG, "Error on AudioTrack initialization", e);
- notifyAudioTrackInitializationError(e);
-
- // Do not throw exception here but just disabling audioTrack to keep playing
- // video without audio.
- AUDIO_TRACK.setStatus(false);
- }
- if (getState() == TrackRenderer.STATE_STARTED) {
- if (DEBUG) {
- Log.d(TAG, "AudioTrack played");
- }
- AUDIO_TRACK.play();
- }
- }
- }
-
- private void clearDecodeState() {
- mOutputReady = false;
- AUDIO_TRACK.reset();
- }
-
- private void readFormat() throws IOException, ExoPlaybackException {
- int result = mSource.readData(mTrackIndex, mCurrentPositionUs,
- mFormatHolder, mSampleHolder);
- if (result == SampleSource.FORMAT_READ) {
- onInputFormatChanged(mFormatHolder);
- }
- }
-
- private void onInputFormatChanged(MediaFormatHolder formatHolder)
- throws ExoPlaybackException {
- mFormat = formatHolder.format;
- if (DEBUG) {
- Log.d(TAG, "AudioTrack was configured to FORMAT: " + mFormat.toString());
- }
- clearDecodeState();
- AUDIO_TRACK.reconfigure(mFormat.getFrameworkMediaFormatV16());
- }
-
- private boolean feedInputBuffer() throws IOException, ExoPlaybackException {
- if (mInputStreamEnded) {
- return false;
- }
-
- mSampleHolder.data.clear();
- mSampleHolder.size = 0;
- int result = mSource.readData(mTrackIndex, mPresentationTimeUs, mFormatHolder,
- mSampleHolder);
- switch (result) {
- case SampleSource.NOTHING_READ: {
- return false;
- }
- case SampleSource.FORMAT_READ: {
- Log.i(TAG, "Format was read again");
- onInputFormatChanged(mFormatHolder);
- return true;
- }
- case SampleSource.END_OF_STREAM: {
- Log.i(TAG, "End of stream from SampleSource");
- mInputStreamEnded = true;
- return false;
- }
- default: {
- mSampleHolder.data.flip();
- decodeDone(mSampleHolder.data, mSampleHolder.timeUs);
- return true;
- }
- }
- }
-
- private boolean processOutput() throws ExoPlaybackException {
- if (mOutputStreamEnded) {
- return false;
- }
- if (!mOutputReady) {
- if (mInputStreamEnded) {
- mOutputStreamEnded = true;
- mEndOfStreamMs = SystemClock.elapsedRealtime();
- return false;
- }
- return true;
- }
-
- ensureAudioTrackInitialized();
- int handleBufferResult;
- try {
- // To reduce discontinuity, interpolate presentation time.
- mInterpolatedTimeUs = mPresentationTimeUs
- + mPresentationCount * AC3_SAMPLE_DURATION_US;
- handleBufferResult = AUDIO_TRACK.handleBuffer(mOutputBuffer,
- 0, mOutputBuffer.limit(), mInterpolatedTimeUs);
- } catch (AudioTrack.WriteException e) {
- notifyAudioTrackWriteError(e);
- throw new ExoPlaybackException(e);
- }
-
- if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
- Log.i(TAG, "Play discontinuity happened");
- mCurrentPositionUs = Long.MIN_VALUE;
- }
- if ((handleBufferResult & AudioTrack.RESULT_BUFFER_CONSUMED) != 0) {
- mCodecCounters.renderedOutputBufferCount++;
- mOutputReady = false;
- return true;
- }
- return false;
- }
-
- @Override
- protected long getDurationUs() {
- return mSource.getFormat(mTrackIndex).durationUs;
- }
-
- @Override
- protected long getBufferedPositionUs() {
- long pos = mSource.getBufferedPositionUs();
- return pos == UNKNOWN_TIME_US || pos == END_OF_TRACK_US
- ? pos : Math.max(pos, getPositionUs());
- }
-
- @Override
- public long getPositionUs() {
- if (!AUDIO_TRACK.isInitialized()) {
- return mAudioClock.getPositionUs();
- } else if (!AUDIO_TRACK.isEnabled()) {
- if (mInterpolatedTimeUs > 0) {
- return mInterpolatedTimeUs - ESTIMATED_TRACK_RENDERING_DELAY_US;
- }
- return mPresentationTimeUs;
- }
- long audioTrackCurrentPositionUs = AUDIO_TRACK.getCurrentPositionUs(isEnded());
- if (audioTrackCurrentPositionUs == AudioTrack.CURRENT_POSITION_NOT_SET) {
- mPreviousPositionUs = 0L;
- if (DEBUG) {
- long oldPositionUs = Math.max(mCurrentPositionUs, 0);
- long currentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
- Log.d(TAG, "Audio position is not set, diff in us: "
- + String.valueOf(currentPositionUs - oldPositionUs));
- }
- mCurrentPositionUs = Math.max(mPresentationTimeUs, mCurrentPositionUs);
- } else {
- if (mPreviousPositionUs
- > audioTrackCurrentPositionUs + BACKWARD_AUDIO_TRACK_MOVE_THRESHOLD_US) {
- Log.e(TAG, "audio_position BACK JUMP: "
- + (mPreviousPositionUs - audioTrackCurrentPositionUs));
- mCurrentPositionUs = audioTrackCurrentPositionUs;
- } else {
- mCurrentPositionUs = Math.max(mCurrentPositionUs, audioTrackCurrentPositionUs);
- }
- mPreviousPositionUs = audioTrackCurrentPositionUs;
- }
- long upperBound = mPresentationTimeUs + CURRENT_POSITION_FROM_PTS_LIMIT_US;
- if (mCurrentPositionUs > upperBound) {
- mCurrentPositionUs = upperBound;
- }
- return mCurrentPositionUs;
- }
-
- private void decodeDone(ByteBuffer outputBuffer, long presentationTimeUs) {
- if (outputBuffer == null || mOutputBuffer == null) {
- return;
- }
- if (presentationTimeUs < 0) {
- Log.e(TAG, "decodeDone - invalid presentationTimeUs");
- return;
- }
-
- if (TunerDebug.ENABLED) {
- TunerDebug.setAudioPtsUs(presentationTimeUs);
- }
-
- mOutputBuffer.clear();
- Assertions.checkState(mOutputBuffer.remaining() >= outputBuffer.limit());
-
- mOutputBuffer.put(outputBuffer);
- mMonitor.addPts(presentationTimeUs, mOutputBuffer.position(),
- mOutputBuffer.get(AC3_HEADER_BITRATE_OFFSET));
- if (presentationTimeUs == mPresentationTimeUs) {
- mPresentationCount++;
- } else {
- mPresentationCount = 0;
- mPresentationTimeUs = presentationTimeUs;
- }
- mOutputBuffer.flip();
- mOutputReady = true;
- }
-
- private void notifyAudioTrackInitializationError(final AudioTrack.InitializationException e) {
- if (mEventHandler == null || mEventListener == null) {
- return;
- }
- mEventHandler.post(new Runnable() {
- @Override
- public void run() {
- mEventListener.onAudioTrackInitializationError(e);
- }
- });
- }
-
- private void notifyAudioTrackWriteError(final AudioTrack.WriteException e) {
- if (mEventHandler == null || mEventListener == null) {
- return;
- }
- mEventHandler.post(new Runnable() {
- @Override
- public void run() {
- mEventListener.onAudioTrackWriteError(e);
- }
- });
- }
-
- @Override
- public void handleMessage(int messageType, Object message) throws ExoPlaybackException {
- switch (messageType) {
- case MSG_SET_VOLUME:
- AUDIO_TRACK.setVolume((Float) message);
- break;
- case MSG_SET_AUDIO_TRACK:
- boolean enabled = (Integer) message == 1;
- if (enabled == AUDIO_TRACK.isEnabled()) {
- return;
- }
- if (!enabled) {
- // mAudioClock can be different from getPositionUs. In order to sync them,
- // we set mAudioClock.
- mAudioClock.setPositionUs(getPositionUs());
- }
- AUDIO_TRACK.setStatus(enabled);
- if (enabled) {
- // When AUDIO_TRACK is enabled, we need to clear AUDIO_TRACK and seek to
- // the current position. If not, AUDIO_TRACK has the obsolete data.
- seekTo(mAudioClock.getPositionUs());
- }
- break;
- case MSG_SET_PLAYBACK_SPEED:
- mAudioClock.setPlaybackSpeed((Float) message);
- break;
- default:
- super.handleMessage(messageType, message);
- }
- }
-}