diff options
Diffstat (limited to 'src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java')
-rw-r--r-- | src/com/android/tv/tuner/exoplayer/ac3/Ac3PassthroughTrackRenderer.java | 540 |
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); - } - } -} |