/* * Copyright (C) 2012 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 androidx.media.filterfw.decoder; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaCodec.BufferInfo; import android.media.MediaExtractor; import android.media.MediaFormat; import android.util.Log; import java.nio.ByteBuffer; @TargetApi(16) abstract class TrackDecoder { interface Listener { void onDecodedOutputAvailable(TrackDecoder decoder); void onEndOfStream(TrackDecoder decoder); } private static final String LOG_TAG = "TrackDecoder"; private static final long TIMEOUT_US = 50; // Timeout for en-queueing and de-queueing buffers. private static final int NO_INPUT_BUFFER = -1; private final int mTrackIndex; private final MediaFormat mMediaFormat; private final Listener mListener; private MediaCodec mMediaCodec; private MediaFormat mOutputFormat; private ByteBuffer[] mCodecInputBuffers; private ByteBuffer[] mCodecOutputBuffers; private boolean mShouldEnqueueEndOfStream; /** * @return a configured {@link MediaCodec}. */ protected abstract MediaCodec initMediaCodec(MediaFormat format); /** * Called when decoded output is available. The implementer is responsible for releasing the * assigned buffer. * * @return {@code true} if any further decoding should be attempted at the moment. */ protected abstract boolean onDataAvailable( MediaCodec codec, ByteBuffer[] buffers, int bufferIndex, BufferInfo info); protected TrackDecoder(int trackIndex, MediaFormat mediaFormat, Listener listener) { mTrackIndex = trackIndex; if (mediaFormat == null) { throw new NullPointerException("mediaFormat cannot be null"); } mMediaFormat = mediaFormat; if (listener == null) { throw new NullPointerException("listener cannot be null"); } mListener = listener; } public void init() { mMediaCodec = initMediaCodec(mMediaFormat); mMediaCodec.start(); mCodecInputBuffers = mMediaCodec.getInputBuffers(); mCodecOutputBuffers = mMediaCodec.getOutputBuffers(); } public void signalEndOfInput() { mShouldEnqueueEndOfStream = true; tryEnqueueEndOfStream(); } public void release() { if (mMediaCodec != null) { mMediaCodec.stop(); mMediaCodec.release(); } } protected MediaCodec getMediaCodec() { return mMediaCodec; } protected void notifyListener() { mListener.onDecodedOutputAvailable(this); } public boolean feedInput(MediaExtractor mediaExtractor) { long presentationTimeUs = 0; int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_US); if (inputBufferIndex != NO_INPUT_BUFFER) { ByteBuffer destinationBuffer = mCodecInputBuffers[inputBufferIndex]; int sampleSize = mediaExtractor.readSampleData(destinationBuffer, 0); // We don't expect to get a sample without any data, so this should never happen. if (sampleSize < 0) { Log.w(LOG_TAG, "Media extractor had sample but no data."); // Signal the end of the track immediately anyway, using the buffer. mMediaCodec.queueInputBuffer( inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); return false; } presentationTimeUs = mediaExtractor.getSampleTime(); mMediaCodec.queueInputBuffer( inputBufferIndex, 0, sampleSize, presentationTimeUs, 0); return mediaExtractor.advance() && mediaExtractor.getSampleTrackIndex() == mTrackIndex; } return false; } private void tryEnqueueEndOfStream() { int inputBufferIndex = mMediaCodec.dequeueInputBuffer(TIMEOUT_US); // We will always eventually have an input buffer, because we keep trying until the last // decoded frame is output. // The EoS does not need to be signaled if the application stops decoding. if (inputBufferIndex != NO_INPUT_BUFFER) { mMediaCodec.queueInputBuffer( inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM); mShouldEnqueueEndOfStream = false; } } public boolean drainOutputBuffer() { BufferInfo outputInfo = new BufferInfo(); int outputBufferIndex = mMediaCodec.dequeueOutputBuffer(outputInfo, TIMEOUT_US); if ((outputInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { mListener.onEndOfStream(this); return false; } if (mShouldEnqueueEndOfStream) { tryEnqueueEndOfStream(); } if (outputBufferIndex >= 0) { return onDataAvailable( mMediaCodec, mCodecOutputBuffers, outputBufferIndex, outputInfo); } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { mCodecOutputBuffers = mMediaCodec.getOutputBuffers(); return true; } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { mOutputFormat = mMediaCodec.getOutputFormat(); Log.d(LOG_TAG, "Output format has changed to " + mOutputFormat); return true; } return false; } }