| /* |
| * 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; |
| } |
| |
| } |