diff options
author | Anirudh Dewani <anirudhd@google.com> | 2013-06-14 11:07:53 -0400 |
---|---|---|
committer | Ian Ni-Lewis <ilewis@google.com> | 2013-09-12 21:02:33 +0000 |
commit | 1935465586159c96667b2efc74174b260b85d429 (patch) | |
tree | 1bd096fa6b167ef275b1124f9a92ab8a31167237 /common | |
parent | 911211b897afb338a9820d70293f2fb3e39a72d6 (diff) | |
download | android-1935465586159c96667b2efc74174b260b85d429.tar.gz |
Basic Media Decoder sample that builds on a Utility class - common/media/MediaCodecWrapper
Change-Id: I23be233009ccc6ad1e5f1305d53bdf8f436451b6
Diffstat (limited to 'common')
-rw-r--r-- | common/assets/video/vid_bigbuckbunny.mp4 | bin | 0 -> 5510872 bytes | |||
-rw-r--r-- | common/src/com/example/android/common/media/MediaCodecWrapper.java | 386 |
2 files changed, 386 insertions, 0 deletions
diff --git a/common/assets/video/vid_bigbuckbunny.mp4 b/common/assets/video/vid_bigbuckbunny.mp4 Binary files differnew file mode 100644 index 00000000..81d11df5 --- /dev/null +++ b/common/assets/video/vid_bigbuckbunny.mp4 diff --git a/common/src/com/example/android/common/media/MediaCodecWrapper.java b/common/src/com/example/android/common/media/MediaCodecWrapper.java new file mode 100644 index 00000000..a511221f --- /dev/null +++ b/common/src/com/example/android/common/media/MediaCodecWrapper.java @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2013 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.example.android.common.media; + +import android.media.*; +import android.os.Handler; +import android.os.Looper; +import android.view.Surface; + +import java.nio.ByteBuffer; +import java.util.ArrayDeque; +import java.util.Queue; + +/** + * Simplifies the MediaCodec interface by wrapping around the buffer processing operations. + */ +public class MediaCodecWrapper { + + // Handler to use for {@code OutputSampleListener} and {code OutputFormatChangedListener} + // callbacks + private Handler mHandler; + + + // Callback when media output format changes. + public interface OutputFormatChangedListener { + void outputFormatChanged(MediaCodecWrapper sender, MediaFormat newFormat); + } + + private OutputFormatChangedListener mOutputFormatChangedListener = null; + + /** + * Callback for decodes frames. Observers can register a listener for optional stream + * of decoded data + */ + public interface OutputSampleListener { + void outputSample(MediaCodecWrapper sender, MediaCodec.BufferInfo info, ByteBuffer buffer); + } + + /** + * The {@link MediaCodec} that is managed by this class. + */ + private MediaCodec mDecoder; + + // References to the internal buffers managed by the codec. The codec + // refers to these buffers by index, never by reference so it's up to us + // to keep track of which buffer is which. + private ByteBuffer[] mInputBuffers; + private ByteBuffer[] mOutputBuffers; + + // Indices of the input buffers that are currently available for writing. We'll + // consume these in the order they were dequeued from the codec. + private Queue<Integer> mAvailableInputBuffers; + + // Indices of the output buffers that currently hold valid data, in the order + // they were produced by the codec. + private Queue<Integer> mAvailableOutputBuffers; + + // Information about each output buffer, by index. Each entry in this array + // is valid if and only if its index is currently contained in mAvailableOutputBuffers. + private MediaCodec.BufferInfo[] mOutputBufferInfo; + + // An (optional) stream that will receive decoded data. + private OutputSampleListener mOutputSampleListener; + + private MediaCodecWrapper(MediaCodec codec) { + mDecoder = codec; + codec.start(); + mInputBuffers = codec.getInputBuffers(); + mOutputBuffers = codec.getOutputBuffers(); + mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length]; + mAvailableInputBuffers = new ArrayDeque<Integer>(mOutputBuffers.length); + mAvailableOutputBuffers = new ArrayDeque<Integer>(mInputBuffers.length); + } + + /** + * Releases resources and ends the encoding/decoding session. + */ + public void stopAndRelease() { + mDecoder.stop(); + mDecoder.release(); + mDecoder = null; + mHandler = null; + } + + /** + * Getter for the registered {@link OutputFormatChangedListener} + */ + public OutputFormatChangedListener getOutputFormatChangedListener() { + return mOutputFormatChangedListener; + } + + /** + * + * @param outputFormatChangedListener the listener for callback. + * @param handler message handler for posting the callback. + */ + public void setOutputFormatChangedListener(final OutputFormatChangedListener + outputFormatChangedListener, Handler handler) { + mOutputFormatChangedListener = outputFormatChangedListener; + + // Making sure we don't block ourselves due to a bad implementation of the callback by + // using a handler provided by client. + Looper looper; + mHandler = handler; + if (outputFormatChangedListener != null && mHandler == null) { + if ((looper = Looper.myLooper()) != null) { + mHandler = new Handler(); + } else { + throw new IllegalArgumentException( + "Looper doesn't exist in the calling thread"); + } + } + } + + /** + * Constructs the {@link MediaCodecWrapper} wrapper object around the video codec. + * The codec is created using the encapsulated information in the + * {@link MediaFormat} object. + * + * @param trackFormat The format of the media object to be decoded. + * @param surface Surface to render the decoded frames. + * @return + */ + public static MediaCodecWrapper fromVideoFormat(final MediaFormat trackFormat, + Surface surface) { + MediaCodecWrapper result = null; + MediaCodec videoCodec = null; + + // BEGIN_INCLUDE(create_codec) + final String mimeType = trackFormat.getString(MediaFormat.KEY_MIME); + + // Check to see if this is actually a video mime type. If it is, then create + // a codec that can decode this mime type. + if (mimeType.contains("video/")) { + videoCodec = MediaCodec.createDecoderByType(mimeType); + videoCodec.configure(trackFormat, surface, null, 0); + + } + + // If codec creation was successful, then create a wrapper object around the + // newly created codec. + if (videoCodec != null) { + result = new MediaCodecWrapper(videoCodec); + } + // END_INCLUDE(create_codec) + + return result; + } + + + /** + * Write a media sample to the decoder. + * + * A "sample" here refers to a single atomic access unit in the media stream. The definition + * of "access unit" is dependent on the type of encoding used, but it typically refers to + * a single frame of video or a few seconds of audio. {@link android.media.MediaExtractor} + * extracts data from a stream one sample at a time. + * + * @param input A ByteBuffer containing the input data for one sample. The buffer must be set + * up for reading, with its position set to the beginning of the sample data and its limit + * set to the end of the sample data. + * + * @param presentationTimeUs The time, relative to the beginning of the media stream, + * at which this buffer should be rendered. + * + * @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int, + * int, int, long, int)} + * + * @throws MediaCodec.CryptoException + */ + public boolean writeSample(final ByteBuffer input, + final MediaCodec.CryptoInfo crypto, + final long presentationTimeUs, + final int flags) throws MediaCodec.CryptoException, WriteException { + boolean result = false; + int size = input.remaining(); + + // check if we have dequed input buffers available from the codec + if (size > 0 && !mAvailableInputBuffers.isEmpty()) { + int index = mAvailableInputBuffers.remove(); + ByteBuffer buffer = mInputBuffers[index]; + + // we can't write our sample to a lesser capacity input buffer. + if (size > buffer.capacity()) { + throw new MediaCodecWrapper.WriteException(String.format( + "Insufficient capacity in MediaCodec buffer: " + + "tried to write %d, buffer capacity is %d.", + input.remaining(), + buffer.capacity())); + } + + buffer.clear(); + buffer.put(input); + + // Submit the buffer to the codec for decoding. The presentationTimeUs + // indicates the position (play time) for the current sample. + if (crypto == null) { + mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags); + } else { + mDecoder.queueSecureInputBuffer(index, 0, crypto, presentationTimeUs, flags); + } + result = true; + } + return result; + } + + static MediaCodec.CryptoInfo cryptoInfo= new MediaCodec.CryptoInfo(); + + /** + * Write a media sample to the decoder. + * + * A "sample" here refers to a single atomic access unit in the media stream. The definition + * of "access unit" is dependent on the type of encoding used, but it typically refers to + * a single frame of video or a few seconds of audio. {@link android.media.MediaExtractor} + * extracts data from a stream one sample at a time. + * + * @param extractor Instance of {@link android.media.MediaExtractor} wrapping the media. + * + * @param presentationTimeUs The time, relative to the beginning of the media stream, + * at which this buffer should be rendered. + * + * @param flags Flags to pass to the decoder. See {@link MediaCodec#queueInputBuffer(int, + * int, int, long, int)} + * + * @throws MediaCodec.CryptoException + */ + public boolean writeSample(final MediaExtractor extractor, + final boolean isSecure, + final long presentationTimeUs, + int flags) { + boolean result = false; + boolean isEos = false; + + if (!mAvailableInputBuffers.isEmpty()) { + int index = mAvailableInputBuffers.remove(); + ByteBuffer buffer = mInputBuffers[index]; + + // reads the sample from the file using extractor into the buffer + int size = extractor.readSampleData(buffer, 0); + if (size <= 0) { + flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM; + } + + // Submit the buffer to the codec for decoding. The presentationTimeUs + // indicates the position (play time) for the current sample. + if (!isSecure) { + mDecoder.queueInputBuffer(index, 0, size, presentationTimeUs, flags); + } else { + extractor.getSampleCryptoInfo(cryptoInfo); + mDecoder.queueSecureInputBuffer(index, 0, cryptoInfo, presentationTimeUs, flags); + } + + result = true; + } + return result; + } + + /** + * Performs a peek() operation in the queue to extract media info for the buffer ready to be + * released i.e. the head element of the queue. + * + * @param out_bufferInfo An output var to hold the buffer info. + * + * @return True, if the peek was successful. + */ + public boolean peekSample(MediaCodec.BufferInfo out_bufferInfo) { + // dequeue available buffers and synchronize our data structures with the codec. + update(); + boolean result = false; + if (!mAvailableOutputBuffers.isEmpty()) { + int index = mAvailableOutputBuffers.peek(); + MediaCodec.BufferInfo info = mOutputBufferInfo[index]; + // metadata of the sample + out_bufferInfo.set( + info.offset, + info.size, + info.presentationTimeUs, + info.flags); + result = true; + } + return result; + } + + /** + * Processes, releases and optionally renders the output buffer available at the head of the + * queue. All observers are notified with a callback. See {@link + * OutputSampleListener#outputSample(MediaCodecWrapper, android.media.MediaCodec.BufferInfo, + * java.nio.ByteBuffer)} + * + * @param render True, if the buffer is to be rendered on the {@link Surface} configured + * + */ + public void popSample(boolean render) { + // dequeue available buffers and synchronize our data structures with the codec. + update(); + if (!mAvailableOutputBuffers.isEmpty()) { + int index = mAvailableOutputBuffers.remove(); + + if (render && mOutputSampleListener != null) { + ByteBuffer buffer = mOutputBuffers[index]; + MediaCodec.BufferInfo info = mOutputBufferInfo[index]; + mOutputSampleListener.outputSample(this, info, buffer); + } + + // releases the buffer back to the codec + mDecoder.releaseOutputBuffer(index, render); + } + } + + /** + * Synchronize this object's state with the internal state of the wrapped + * MediaCodec. + */ + private void update() { + // BEGIN_INCLUDE(update_codec_state) + int index; + + // Get valid input buffers from the codec to fill later in the same order they were + // made available by the codec. + while ((index = mDecoder.dequeueInputBuffer(0)) != MediaCodec.INFO_TRY_AGAIN_LATER) { + mAvailableInputBuffers.add(index); + } + + + // Likewise with output buffers. If the output buffers have changed, start using the + // new set of output buffers. If the output format has changed, notify listeners. + MediaCodec.BufferInfo info = new MediaCodec.BufferInfo(); + while ((index = mDecoder.dequeueOutputBuffer(info, 0)) != MediaCodec.INFO_TRY_AGAIN_LATER) { + switch (index) { + case MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED: + mOutputBuffers = mDecoder.getOutputBuffers(); + mOutputBufferInfo = new MediaCodec.BufferInfo[mOutputBuffers.length]; + mAvailableOutputBuffers.clear(); + break; + case MediaCodec.INFO_OUTPUT_FORMAT_CHANGED: + if (mOutputFormatChangedListener != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + mOutputFormatChangedListener + .outputFormatChanged(MediaCodecWrapper.this, + mDecoder.getOutputFormat()); + + } + }); + } + break; + default: + // Making sure the index is valid before adding to output buffers. We've already + // handled INFO_TRY_AGAIN_LATER, INFO_OUTPUT_FORMAT_CHANGED & + // INFO_OUTPUT_BUFFERS_CHANGED i.e all the other possible return codes but + // asserting index value anyways for future-proofing the code. + if(index >= 0) { + mOutputBufferInfo[index] = info; + mAvailableOutputBuffers.add(index); + } else { + throw new IllegalStateException("Unknown status from dequeueOutputBuffer"); + } + break; + } + + } + // END_INCLUDE(update_codec_state) + + } + + private class WriteException extends Throwable { + private WriteException(final String detailMessage) { + super(detailMessage); + } + } +} |