diff options
-rw-r--r-- | api/current.txt | 12 | ||||
-rw-r--r-- | media/java/android/media/MediaCodec.java | 505 | ||||
-rw-r--r-- | media/jni/android_media_MediaCodec.cpp | 260 | ||||
-rw-r--r-- | media/jni/android_media_MediaCodec.h | 10 |
4 files changed, 736 insertions, 51 deletions
diff --git a/api/current.txt b/api/current.txt index f588524..b6e4993 100644 --- a/api/current.txt +++ b/api/current.txt @@ -14287,10 +14287,16 @@ package android.media { method public final int dequeueOutputBuffer(android.media.MediaCodec.BufferInfo, long); method public final void flush(); method public android.media.MediaCodecInfo getCodecInfo(); - method public java.nio.ByteBuffer[] getInputBuffers(); + method public java.nio.ByteBuffer getInputBuffer(int); + method public deprecated java.nio.ByteBuffer[] getInputBuffers(); + method public final android.media.MediaFormat getInputFormat(); + method public android.media.Image getInputImage(int); method public final java.lang.String getName(); - method public java.nio.ByteBuffer[] getOutputBuffers(); + method public java.nio.ByteBuffer getOutputBuffer(int); + method public deprecated java.nio.ByteBuffer[] getOutputBuffers(); method public final android.media.MediaFormat getOutputFormat(); + method public final android.media.MediaFormat getOutputFormat(int); + method public android.media.Image getOutputImage(int); method public final void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException; method public final void queueSecureInputBuffer(int, int, android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException; method public final void release(); @@ -14309,7 +14315,7 @@ package android.media { field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1 field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1 field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0 - field public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd + field public static final deprecated int INFO_OUTPUT_BUFFERS_CHANGED = -3; // 0xfffffffd field public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; // 0xfffffffe field public static final int INFO_TRY_AGAIN_LATER = -1; // 0xffffffff field public static final java.lang.String PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync"; diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index b362adb..4a94cde 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -16,6 +16,7 @@ package android.media; +import android.media.Image; import android.media.MediaCodecInfo; import android.media.MediaCodecList; import android.media.MediaCrypto; @@ -40,11 +41,15 @@ import java.util.Map; * MediaCodec codec = MediaCodec.createDecoderByType(type); * codec.configure(format, ...); * codec.start(); + * + * // if API level <= 20, get input and output buffer arrays here * ByteBuffer[] inputBuffers = codec.getInputBuffers(); * ByteBuffer[] outputBuffers = codec.getOutputBuffers(); * for (;;) { * int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs); * if (inputBufferIndex >= 0) { + * // if API level >= 21, get input buffer here + * ByteBuffer inputBuffer = codec.getInputBuffer(inputBufferIndex); * // fill inputBuffers[inputBufferIndex] with valid data * ... * codec.queueInputBuffer(inputBufferIndex, ...); @@ -52,13 +57,17 @@ import java.util.Map; * * int outputBufferIndex = codec.dequeueOutputBuffer(timeoutUs); * if (outputBufferIndex >= 0) { + * // if API level >= 21, get output buffer here + * ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex); * // outputBuffer is ready to be processed or rendered. * ... * codec.releaseOutputBuffer(outputBufferIndex, ...); * } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) { + * // no needed to handle if API level >= 21 and using getOutputBuffer(int) * outputBuffers = codec.getOutputBuffers(); * } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { * // Subsequent data will conform to new format. + * // can ignore if API level >= 21 and using getOutputFormat(outputBufferIndex) * MediaFormat format = codec.getOutputFormat(); * ... * } @@ -70,9 +79,11 @@ import java.util.Map; * * Each codec maintains a number of input and output buffers that are * referred to by index in API calls. + * <p> + * For API levels 20 and below: * The contents of these buffers are represented by the ByteBuffer[] arrays * accessible through {@link #getInputBuffers} and {@link #getOutputBuffers}. - * + * <p> * After a successful call to {@link #start} the client "owns" neither * input nor output buffers, subsequent calls to {@link #dequeueInputBuffer} * and {@link #dequeueOutputBuffer} then transfer ownership from the codec @@ -101,19 +112,19 @@ import java.util.Map; * first few buffers submitted to the codec object after starting it must * be codec specific data marked as such using the flag {@link #BUFFER_FLAG_CODEC_CONFIG} * in a call to {@link #queueInputBuffer}. - * + * <p> * Codec specific data included in the format passed to {@link #configure} * (in ByteBuffer entries with keys "csd-0", "csd-1", ...) is automatically * submitted to the codec, this data MUST NOT be submitted explicitly by the * client. - * + * <p> * Once the client reaches the end of the input data it signals the end of * the input stream by specifying a flag of {@link #BUFFER_FLAG_END_OF_STREAM} in the call to * {@link #queueInputBuffer}. The codec will continue to return output buffers * until it eventually signals the end of the output stream by specifying * the same flag ({@link #BUFFER_FLAG_END_OF_STREAM}) on the BufferInfo returned in * {@link #dequeueOutputBuffer}. - * + * <p> * In order to start decoding data that's not adjacent to previously submitted * data (i.e. after a seek) it is necessary to {@link #flush} the decoder. * Any input or output buffers the client may own at the point of the flush are @@ -299,16 +310,23 @@ final public class MediaCodec { switch (msg.arg1) { case CB_INPUT_AVAILABLE: { - mCallback.onInputBufferAvailable(mCodec, msg.arg2 /* index */); + int index = msg.arg2; + synchronized(mBufferLock) { + validateInputByteBuffer(mCachedInputBuffers, index); + } + mCallback.onInputBufferAvailable(mCodec, index); break; } case CB_OUTPUT_AVAILABLE: { + int index = msg.arg2; + BufferInfo info = (MediaCodec.BufferInfo) msg.obj; + synchronized(mBufferLock) { + validateOutputByteBuffer(mCachedOutputBuffers, index, info); + } mCallback.onOutputBufferAvailable( - mCodec, - msg.arg2 /* index */, - (MediaCodec.BufferInfo) msg.obj); + mCodec, index, info); break; } @@ -401,6 +419,7 @@ final public class MediaCodec { } else { mEventHandler = null; } + mBufferLock = new Object(); native_setup(name, nameIsType, encoder); } @@ -415,7 +434,12 @@ final public class MediaCodec { * component instance instead of relying on the garbage collector * to do this for you at some point in the future. */ - public native final void release(); + public final void release() { + freeAllTrackedBuffers(); // free buffers first + native_release(); + } + + private native final void native_release(); /** * If this codec is to be used as an encoder, pass this flag. @@ -489,7 +513,14 @@ final public class MediaCodec { * @throws MediaCodec.CodecException upon codec error. Note that some codec errors * for start may be attributed to future method calls. */ - public native final void start(); + public final void start() { + native_start(); + synchronized(mBufferLock) { + cacheBuffers(true /* input */); + cacheBuffers(false /* input */); + } + } + private native final void native_start(); /** * Finish the decode/encode session, note that the codec instance @@ -500,6 +531,7 @@ final public class MediaCodec { */ public final void stop() { native_stop(); + freeAllTrackedBuffers(); if (mEventHandler != null) { mEventHandler.removeMessages(EVENT_CALLBACK); @@ -516,7 +548,19 @@ final public class MediaCodec { * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ - public native final void flush(); + public final void flush() { + synchronized(mBufferLock) { + invalidateByteBuffers(mCachedInputBuffers); + invalidateByteBuffers(mCachedOutputBuffers); + invalidateByteBuffers(mDequeuedInputBuffers); + invalidateByteBuffers(mDequeuedOutputBuffers); + freeImages(mDequeuedInputImages); + freeImages(mDequeuedOutputImages); + } + native_flush(); + } + + private native final void native_flush(); /** * Thrown when an internal codec error occurs. @@ -602,8 +646,11 @@ final public class MediaCodec { /** * After filling a range of the input buffer at the specified index - * submit it to the component. - * + * submit it to the component. Once an input buffer is queued to + * the codec, it MUST not be used until it is later retrieved by + * {#getInputBuffer} in response to a {#dequeueInputBuffer} + * response. + * <p> * Many decoders require the actual compressed data stream to be * preceded by "codec specific data", i.e. setup data used to initialize * the codec such as PPS/SPS in the case of AVC video or code tables @@ -611,9 +658,16 @@ final public class MediaCodec { * The class {@link android.media.MediaExtractor} provides codec * specific data as part of * the returned track format in entries named "csd-0", "csd-1" ... - * - * These buffers should be submitted using the flag {@link #BUFFER_FLAG_CODEC_CONFIG}. - * + * <p> + * These buffers can be submitted directly after {@link #start} or + * {@link #flush} by specifying the flag {@link + * #BUFFER_FLAG_CODEC_CONFIG}. However, if you configure the + * codec with a {@link MediaFormat} containing these keys, they + * will be automatically submitted by MediaCodec directly after + * start. Therefore, the use of {@link + * #BUFFER_FLAG_CODEC_CONFIG} flag is discouraged and is + * recommended only for advanced users. + * <p> * To indicate that this is the final piece of input data (or rather that * no more input data follows unless the decoder is subsequently flushed) * specify the flag {@link #BUFFER_FLAG_END_OF_STREAM}. @@ -634,7 +688,19 @@ final public class MediaCodec { * @throws CryptoException if a crypto object has been specified in * {@link #configure} */ - public native final void queueInputBuffer( + public final void queueInputBuffer( + int index, + int offset, int size, long presentationTimeUs, int flags) + throws CryptoException { + synchronized(mBufferLock) { + invalidateByteBuffer(mCachedInputBuffers, index); + updateDequeuedByteBuffer(mDequeuedInputBuffers, index, null); + } + native_queueInputBuffer( + index, offset, size, presentationTimeUs, flags); + } + + private native final void native_queueInputBuffer( int index, int offset, int size, long presentationTimeUs, int flags) throws CryptoException; @@ -740,7 +806,21 @@ final public class MediaCodec { * An error code associated with the exception helps identify the * reason for the failure. */ - public native final void queueSecureInputBuffer( + public final void queueSecureInputBuffer( + int index, + int offset, + CryptoInfo info, + long presentationTimeUs, + int flags) throws CryptoException { + synchronized(mBufferLock) { + invalidateByteBuffer(mCachedInputBuffers, index); + updateDequeuedByteBuffer(mDequeuedInputBuffers, index, null); + } + native_queueSecureInputBuffer( + index, offset, info, presentationTimeUs, flags); + } + + private native final void native_queueSecureInputBuffer( int index, int offset, CryptoInfo info, @@ -757,7 +837,17 @@ final public class MediaCodec { * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ - public native final int dequeueInputBuffer(long timeoutUs); + public final int dequeueInputBuffer(long timeoutUs) { + int res = native_dequeueInputBuffer(timeoutUs); + if (res >= 0) { + synchronized(mBufferLock) { + validateInputByteBuffer(mCachedInputBuffers, res); + } + } + return res; + } + + private native final int native_dequeueInputBuffer(long timeoutUs); /** * If a non-negative timeout had been specified in the call @@ -767,7 +857,10 @@ final public class MediaCodec { /** * The output format has changed, subsequent data will follow the new - * format. {@link #getOutputFormat} returns the new format. + * format. {@link #getOutputFormat()} returns the new format. Note, that + * you can also use the new {@link #getOutputFormat(int)} method to + * get the format for a specific output buffer. This frees you from + * having to track output format changes. */ public static final int INFO_OUTPUT_FORMAT_CHANGED = -2; @@ -775,6 +868,11 @@ final public class MediaCodec { * The output buffers have changed, the client must refer to the new * set of output buffers returned by {@link #getOutputBuffers} from * this point on. + * + * @deprecated This return value can be ignored as {@link + * #getOutputBuffers} has been deprecated. Client should + * request a current buffer using on of the get-buffer or + * get-image methods each time one has been dequeued. */ public static final int INFO_OUTPUT_BUFFERS_CHANGED = -3; @@ -787,13 +885,31 @@ final public class MediaCodec { * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ - public native final int dequeueOutputBuffer( + public final int dequeueOutputBuffer( + BufferInfo info, long timeoutUs) { + int res = native_dequeueOutputBuffer(info, timeoutUs); + synchronized(mBufferLock) { + if (res == INFO_OUTPUT_BUFFERS_CHANGED) { + cacheBuffers(false /* input */); + } else if (res >= 0) { + validateOutputByteBuffer(mCachedOutputBuffers, res, info); + } + } + return res; + } + + private native final int native_dequeueOutputBuffer( BufferInfo info, long timeoutUs); /** * If you are done with a buffer, use this call to return the buffer to * the codec. If you previously specified a surface when configuring this * video decoder you can optionally render the buffer. + * + * Once an output buffer is released to the codec, it MUST not + * be used until it is later retrieved by {#getOutputBuffer} in + * response to a {#dequeueOutputBuffer} response + * * @param index The index of a client-owned output buffer previously returned * from a call to {@link #dequeueOutputBuffer}. * @param render If a valid surface was specified when configuring the codec, @@ -802,6 +918,10 @@ final public class MediaCodec { * @throws MediaCodec.CodecException upon codec error. */ public final void releaseOutputBuffer(int index, boolean render) { + synchronized(mBufferLock) { + invalidateByteBuffer(mCachedOutputBuffers, index); + updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, null); + } releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */); } @@ -842,12 +962,22 @@ final public class MediaCodec { * </td></tr> * </table> * + * Once an output buffer is released to the codec, it MUST not + * be used until it is later retrieved by {#getOutputBuffer} in + * response to a {#dequeueOutputBuffer} response + * * @param index The index of a client-owned output buffer previously returned * from a call to {@link #dequeueOutputBuffer}. * @param renderTimestampNs The timestamp to associate with this buffer when * it is sent to the Surface. + * @throws IllegalStateException if not in the Executing state. + * @throws MediaCodec.CodecException upon codec error. */ public final void releaseOutputBuffer(int index, long renderTimestampNs) { + synchronized(mBufferLock) { + invalidateByteBuffer(mCachedOutputBuffers, index); + updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, null); + } releaseOutputBuffer( index, true /* render */, true /* updatePTS */, renderTimestampNs); } @@ -866,34 +996,341 @@ final public class MediaCodec { /** * Call this after dequeueOutputBuffer signals a format change by returning - * {@link #INFO_OUTPUT_FORMAT_CHANGED} - * @throws IllegalStateException if not in the Executing state. + * {@link #INFO_OUTPUT_FORMAT_CHANGED}. + * You can also call this after {@link #configure} returns + * successfully to get the output format initially configured + * for the codec. Do this to determine what optional + * configuration parameters were supported by the codec. + * + * @throws IllegalStateException if not in the Executing or + * Configured state. * @throws MediaCodec.CodecException upon codec error. */ public final MediaFormat getOutputFormat() { - return new MediaFormat(getOutputFormatNative()); + return new MediaFormat(getFormatNative(false /* input */)); } - private native final Map<String, Object> getOutputFormatNative(); + /** + * Call this after {@link #configure} returns successfully to + * get the input format accepted by the codec. Do this to + * determine what optional configuration parameters were + * supported by the codec. + * + * @throws IllegalStateException if not in the Executing or + * Configured state. + * @throws MediaCodec.CodecException upon codec error. + */ + public final MediaFormat getInputFormat() { + return new MediaFormat(getFormatNative(true /* input */)); + } /** - * Call this after start() returns. + * Returns the output format for a specific output buffer. + * + * @param index The index of a client-owned input buffer previously + * returned from a call to {@link #dequeueInputBuffer}. + * + * @return the format for the output buffer, or null if the index + * is not a dequeued output buffer. + */ + public final MediaFormat getOutputFormat(int index) { + return new MediaFormat(getOutputFormatNative(index)); + } + + private native final Map<String, Object> getFormatNative(boolean input); + + private native final Map<String, Object> getOutputFormatNative(int index); + + private ByteBuffer[] mCachedInputBuffers; + private ByteBuffer[] mCachedOutputBuffers; + private ByteBuffer[] mDequeuedInputBuffers; + private ByteBuffer[] mDequeuedOutputBuffers; + private Image[] mDequeuedInputImages; + private Image[] mDequeuedOutputImages; + final private Object mBufferLock; + + private final void invalidateByteBuffer( + ByteBuffer[] buffers, int index) { + if (index >= 0 && index < buffers.length) { + ByteBuffer buffer = buffers[index]; + if (buffer != null) { + buffer.setAccessible(false); + } + } + } + + private final void validateInputByteBuffer( + ByteBuffer[] buffers, int index) { + if (index >= 0 && index < buffers.length) { + ByteBuffer buffer = buffers[index]; + if (buffer != null) { + buffer.setAccessible(true); + buffer.clear(); + } + } + } + + private final void validateOutputByteBuffer( + ByteBuffer[] buffers, int index, BufferInfo info) { + if (index >= 0 && index < buffers.length) { + ByteBuffer buffer = buffers[index]; + if (buffer != null) { + buffer.setAccessible(true); + buffer.limit(info.offset + info.size).position(info.offset); + } + } + } + + private final void invalidateByteBuffers(ByteBuffer[] buffers) { + if (buffers != null) { + for (ByteBuffer buffer: buffers) { + if (buffer != null) { + buffer.setAccessible(false); + } + } + } + } + + private final void freeByteBuffer(ByteBuffer buffer) { + if (buffer != null /* && buffer.isDirect() */) { + // all of our ByteBuffers are direct + java.nio.NioUtils.freeDirectBuffer(buffer); + } + } + + private final void freeByteBuffers(ByteBuffer[] buffers) { + if (buffers != null) { + for (ByteBuffer buffer: buffers) { + freeByteBuffer(buffer); + } + } + } + + private final void freeImage(Image image) { + if (image != null) { + image.close(); + } + } + + private final void freeImages(Image[] images) { + if (images != null) { + for (Image image: images) { + freeImage(image); + } + } + } + + private final void freeAllTrackedBuffers() { + freeByteBuffers(mCachedInputBuffers); + freeByteBuffers(mCachedOutputBuffers); + freeImages(mDequeuedInputImages); + freeImages(mDequeuedOutputImages); + freeByteBuffers(mDequeuedInputBuffers); + freeByteBuffers(mDequeuedOutputBuffers); + mCachedInputBuffers = null; + mCachedOutputBuffers = null; + mDequeuedInputImages = null; + mDequeuedOutputImages = null; + mDequeuedInputBuffers = null; + mDequeuedOutputBuffers = null; + } + + private final void cacheBuffers(boolean input) { + ByteBuffer[] buffers = getBuffers(input); + invalidateByteBuffers(buffers); + if (input) { + mCachedInputBuffers = buffers; + mDequeuedInputImages = new Image[buffers.length]; + mDequeuedInputBuffers = new ByteBuffer[buffers.length]; + } else { + mCachedOutputBuffers = buffers; + mDequeuedOutputImages = new Image[buffers.length]; + mDequeuedOutputBuffers = new ByteBuffer[buffers.length]; + } + } + + /** + * Retrieve the set of input buffers. Call this after start() + * returns. After calling this method, any ByteBuffers + * previously returned by an earlier call to this method MUST no + * longer be used. + * + * @deprecated Use the new {@link #getInputBuffer} method instead + * each time an input buffer is dequeued. + * * @throws IllegalStateException if not in the Executing state. * @throws MediaCodec.CodecException upon codec error. */ public ByteBuffer[] getInputBuffers() { - return getBuffers(true /* input */); + if (mCachedInputBuffers == null) { + throw new IllegalStateException(); + } + // FIXME: check codec status + return mCachedInputBuffers; } /** - * Call this after start() returns and whenever dequeueOutputBuffer - * signals an output buffer change by returning - * {@link #INFO_OUTPUT_BUFFERS_CHANGED} - * @throws IllegalStateException if not in the Executing state. + * Retrieve the set of output buffers. Call this after start() + * returns and whenever dequeueOutputBuffer signals an output + * buffer change by returning {@link + * #INFO_OUTPUT_BUFFERS_CHANGED}. After calling this method, any + * ByteBuffers previously returned by an earlier call to this + * method MUST no longer be used. + * + * @deprecated Use the new {@link #getOutputBuffer} method instead + * each time an output buffer is dequeued. This method is not + * supported if codec is configured in asynchronous mode. + * + * @throws IllegalStateException if not in the Executing state, + * or codec is configured in asynchronous mode. * @throws MediaCodec.CodecException upon codec error. */ public ByteBuffer[] getOutputBuffers() { - return getBuffers(false /* input */); + if (mCachedOutputBuffers == null) { + throw new IllegalStateException(); + } + // FIXME: check codec status + return mCachedOutputBuffers; + } + + private boolean updateDequeuedByteBuffer( + ByteBuffer[] buffers, int index, ByteBuffer newBuffer) { + if (index < 0 || index >= buffers.length) { + return false; + } + freeByteBuffer(buffers[index]); + buffers[index] = newBuffer; + return newBuffer != null; + } + + private boolean updateDequeuedImage( + Image[] images, int index, Image newImage) { + if (index < 0 || index >= images.length) { + return false; + } + freeImage(images[index]); + images[index] = newImage; + return newImage != null; + } + + /** + * Returns a cleared, writable ByteBuffer object for a dequeued + * input buffer index to contain the input data. + * + * After calling this method any ByteBuffer or Image object + * previously returned for the same input index MUST no longer + * be used. + * + * @param index The index of a client-owned input buffer previously + * returned from a call to {@link #dequeueInputBuffer}, + * or received via an onInputBufferAvailable callback. + * + * @return the input buffer, or null if the index is not a dequeued + * input buffer, or if the codec is configured for surface input. + * + * @throws IllegalStateException if not in the Executing state. + * @throws MediaCodec.CodecException upon codec error. + */ + public ByteBuffer getInputBuffer(int index) { + ByteBuffer newBuffer = getBuffer(true /* input */, index); + synchronized(mBufferLock) { + if (updateDequeuedByteBuffer(mDequeuedInputBuffers, index, newBuffer)) { + updateDequeuedImage(mDequeuedInputImages, index, null); + invalidateByteBuffer(mCachedInputBuffers, index); + return newBuffer; + } + } + return null; + } + + /** + * Returns a writable Image object for a dequeued input buffer + * index to contain the raw input video frame. + * + * After calling this method any ByteBuffer or Image object + * previously returned for the same input index MUST no longer + * be used. + * + * @param index The index of a client-owned input buffer previously + * returned from a call to {@link #dequeueInputBuffer}, + * or received via an onInputBufferAvailable callback. + * + * @return the input image, or null if the index is not a + * dequeued input buffer, or not a ByteBuffer that contains a + * raw image. + * + * @throws IllegalStateException if not in the Executing state. + * @throws MediaCodec.CodecException upon codec error. + */ + public Image getInputImage(int index) { + Image newImage = getImage(true /* input */, index); + if (updateDequeuedImage(mDequeuedInputImages, index, newImage)) { + updateDequeuedByteBuffer(mDequeuedInputBuffers, index, null); + invalidateByteBuffer(mCachedInputBuffers, index); + return newImage; + } + return null; + } + + /** + * Returns a read-only ByteBuffer for a dequeued output buffer + * index. The position and limit of the returned buffer are set + * to the valid output data. + * + * After calling this method, any ByteBuffer or Image object + * previously returned for the same output index MUST no longer + * be used. + * + * @param index The index of a client-owned output buffer previously + * returned from a call to {@link #dequeueOutputBuffer}, + * or received via an onOutputBufferAvailable callback. + * + * @return the output buffer, or null if the index is not a dequeued + * output buffer, or the codec is configured with an output surface. + * + * @throws IllegalStateException if not in the Executing state. + * @throws MediaCodec.CodecException upon codec error. + */ + public ByteBuffer getOutputBuffer(int index) { + ByteBuffer newBuffer = getBuffer(false /* input */, index); + synchronized(mBufferLock) { + if (updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, newBuffer)) { + updateDequeuedImage(mDequeuedOutputImages, index, null); + invalidateByteBuffer(mCachedOutputBuffers, index); + return newBuffer; + } + } + return null; + } + + /** + * Returns a read-only Image object for a dequeued output buffer + * index that contains the raw video frame. + * + * After calling this method, any ByteBuffer or Image object previously + * returned for the same output index MUST no longer be used. + * + * @param index The index of a client-owned output buffer previously + * returned from a call to {@link #dequeueOutputBuffer}, + * or received via an onOutputBufferAvailable callback. + * + * @return the output image, or null if the index is not a + * dequeued output buffer, not a raw video frame, or if the codec + * was configured with an output surface. + * + * @throws IllegalStateException if not in the Executing state. + * @throws MediaCodec.CodecException upon codec error. + */ + public Image getOutputImage(int index) { + Image newImage = getImage(false /* input */, index); + synchronized(mBufferLock) { + if (updateDequeuedImage(mDequeuedOutputImages, index, newImage)) { + updateDequeuedByteBuffer(mDequeuedOutputBuffers, index, null); + invalidateByteBuffer(mCachedOutputBuffers, index); + return newImage; + } + } + return null; } /** @@ -1054,6 +1491,10 @@ final public class MediaCodec { private native final ByteBuffer[] getBuffers(boolean input); + private native final ByteBuffer getBuffer(boolean input, int index); + + private native final Image getImage(boolean input, int index); + private static native final void native_init(); private native final void native_setup( diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index f9e4566..efdacfb 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -260,10 +260,21 @@ status_t JMediaCodec::signalEndOfInputStream() { return mCodec->signalEndOfInputStream(); } -status_t JMediaCodec::getOutputFormat(JNIEnv *env, jobject *format) const { +status_t JMediaCodec::getFormat(JNIEnv *env, bool input, jobject *format) const { sp<AMessage> msg; status_t err; - if ((err = mCodec->getOutputFormat(&msg)) != OK) { + err = input ? mCodec->getInputFormat(&msg) : mCodec->getOutputFormat(&msg); + if (err != OK) { + return err; + } + + return ConvertMessageToMap(env, msg, format); +} + +status_t JMediaCodec::getOutputFormat(JNIEnv *env, size_t index, jobject *format) const { + sp<AMessage> msg; + status_t err; + if ((err = mCodec->getOutputFormat(index, &msg)) != OK) { return err; } @@ -295,6 +306,11 @@ status_t JMediaCodec::getBuffers( CHECK(orderID != NULL); + jmethodID asReadOnlyBufferID = env->GetMethodID( + byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;"); + + CHECK(asReadOnlyBufferID != NULL); + ScopedLocalRef<jclass> byteOrderClass( env, env->FindClass("java/nio/ByteOrder")); @@ -327,6 +343,12 @@ status_t JMediaCodec::getBuffers( env->NewDirectByteBuffer( buffer->base(), buffer->capacity()); + if (!input && byteBuffer != NULL) { + jobject readOnlyBuffer = env->CallObjectMethod( + byteBuffer, asReadOnlyBufferID); + env->DeleteLocalRef(byteBuffer); + byteBuffer = readOnlyBuffer; + } if (byteBuffer == NULL) { env->DeleteLocalRef(nativeByteOrderObj); return NO_MEMORY; @@ -349,6 +371,130 @@ status_t JMediaCodec::getBuffers( return OK; } +status_t JMediaCodec::getBuffer( + JNIEnv *env, bool input, size_t index, jobject *buf) const { + sp<ABuffer> buffer; + + status_t err = + input + ? mCodec->getInputBuffer(index, &buffer) + : mCodec->getOutputBuffer(index, &buffer); + + if (err != OK) { + return err; + } + + ScopedLocalRef<jclass> byteBufferClass( + env, env->FindClass("java/nio/ByteBuffer")); + + CHECK(byteBufferClass.get() != NULL); + + jmethodID orderID = env->GetMethodID( + byteBufferClass.get(), + "order", + "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;"); + + CHECK(orderID != NULL); + + jmethodID asReadOnlyBufferID = env->GetMethodID( + byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;"); + + CHECK(asReadOnlyBufferID != NULL); + + jmethodID positionID = env->GetMethodID( + byteBufferClass.get(), "position", "(I)Ljava/nio/Buffer;"); + + CHECK(positionID != NULL); + + jmethodID limitID = env->GetMethodID( + byteBufferClass.get(), "limit", "(I)Ljava/nio/Buffer;"); + + CHECK(limitID != NULL); + + ScopedLocalRef<jclass> byteOrderClass( + env, env->FindClass("java/nio/ByteOrder")); + + CHECK(byteOrderClass.get() != NULL); + + jmethodID nativeOrderID = env->GetStaticMethodID( + byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;"); + CHECK(nativeOrderID != NULL); + + jobject nativeByteOrderObj = + env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID); + CHECK(nativeByteOrderObj != NULL); + + // if this is an ABuffer that doesn't actually hold any accessible memory, + // use a null ByteBuffer + if (buffer->base() == NULL) { + *buf = NULL; + return OK; + } + + jobject byteBuffer = + env->NewDirectByteBuffer( + buffer->base(), + buffer->capacity()); + if (!input && byteBuffer != NULL) { + jobject readOnlyBuffer = env->CallObjectMethod( + byteBuffer, asReadOnlyBufferID); + env->DeleteLocalRef(byteBuffer); + byteBuffer = readOnlyBuffer; + } + if (byteBuffer == NULL) { + env->DeleteLocalRef(nativeByteOrderObj); + return NO_MEMORY; + } + jobject me = env->CallObjectMethod( + byteBuffer, orderID, nativeByteOrderObj); + env->DeleteLocalRef(me); + me = env->CallObjectMethod( + byteBuffer, positionID, + input ? 0 : buffer->offset()); + env->DeleteLocalRef(me); + me = env->CallObjectMethod( + byteBuffer, limitID, + input ? buffer->capacity() : (buffer->offset() + buffer->size())); + env->DeleteLocalRef(me); + me = NULL; + + env->DeleteLocalRef(nativeByteOrderObj); + nativeByteOrderObj = NULL; + + *buf = byteBuffer; + return OK; +} + +status_t JMediaCodec::getImage( + JNIEnv *env, bool input, size_t index, jobject *buf) const { + sp<ABuffer> buffer; + + status_t err = + input + ? mCodec->getInputBuffer(index, &buffer) + : mCodec->getOutputBuffer(index, &buffer); + + if (err != OK) { + return err; + } + + // if this is an ABuffer that doesn't actually hold any accessible memory, + // use a null ByteBuffer + *buf = NULL; + if (buffer->base() == NULL) { + return OK; + } + + // check if buffer is an image + AString imageData; + if (!buffer->meta()->findString("image-data", &imageData)) { + return OK; + } + + return OK; +} + + status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const { AString name; @@ -959,9 +1105,32 @@ static void android_media_MediaCodec_signalEndOfInputStream(JNIEnv* env, throwExceptionAsNecessary(env, err); } -static jobject android_media_MediaCodec_getOutputFormatNative( - JNIEnv *env, jobject thiz) { - ALOGV("android_media_MediaCodec_getOutputFormatNative"); +static jobject android_media_MediaCodec_getFormatNative( + JNIEnv *env, jobject thiz, jboolean input) { + ALOGV("android_media_MediaCodec_getFormatNative"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + jobject format; + status_t err = codec->getFormat(env, input, &format); + + if (err == OK) { + return format; + } + + throwExceptionAsNecessary(env, err); + + return NULL; +} + +static jobject android_media_MediaCodec_getOutputFormatForIndexNative( + JNIEnv *env, jobject thiz, jint index) { + ALOGV("android_media_MediaCodec_getOutputFormatForIndexNative"); sp<JMediaCodec> codec = getMediaCodec(env, thiz); @@ -971,7 +1140,7 @@ static jobject android_media_MediaCodec_getOutputFormatNative( } jobject format; - status_t err = codec->getOutputFormat(env, &format); + status_t err = codec->getOutputFormat(env, index, &format); if (err == OK) { return format; @@ -1008,6 +1177,58 @@ static jobjectArray android_media_MediaCodec_getBuffers( return NULL; } +static jobject android_media_MediaCodec_getBuffer( + JNIEnv *env, jobject thiz, jboolean input, jint index) { + ALOGV("android_media_MediaCodec_getBuffer"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + jobject buffer; + status_t err = codec->getBuffer(env, input, index, &buffer); + + if (err == OK) { + return buffer; + } + + // if we're out of memory, an exception was already thrown + if (err != NO_MEMORY) { + throwExceptionAsNecessary(env, err); + } + + return NULL; +} + +static jobject android_media_MediaCodec_getImage( + JNIEnv *env, jobject thiz, jboolean input, jint index) { + ALOGV("android_media_MediaCodec_getImage"); + + sp<JMediaCodec> codec = getMediaCodec(env, thiz); + + if (codec == NULL) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return NULL; + } + + jobject image; + status_t err = codec->getImage(env, input, index, &image); + + if (err == OK) { + return image; + } + + // if we're out of memory, an exception was already thrown + if (err != NO_MEMORY) { + throwExceptionAsNecessary(env, err); + } + + return NULL; +} + static jobject android_media_MediaCodec_getName( JNIEnv *env, jobject thiz) { ALOGV("android_media_MediaCodec_getName"); @@ -1168,7 +1389,7 @@ static void android_media_MediaCodec_native_finalize( } static JNINativeMethod gMethods[] = { - { "release", "()V", (void *)android_media_MediaCodec_release }, + { "native_release", "()V", (void *)android_media_MediaCodec_release }, { "native_setCallback", "(Landroid/media/MediaCodec$Callback;)V", @@ -1182,20 +1403,20 @@ static JNINativeMethod gMethods[] = { { "createInputSurface", "()Landroid/view/Surface;", (void *)android_media_MediaCodec_createInputSurface }, - { "start", "()V", (void *)android_media_MediaCodec_start }, + { "native_start", "()V", (void *)android_media_MediaCodec_start }, { "native_stop", "()V", (void *)android_media_MediaCodec_stop }, - { "flush", "()V", (void *)android_media_MediaCodec_flush }, + { "native_flush", "()V", (void *)android_media_MediaCodec_flush }, - { "queueInputBuffer", "(IIIJI)V", + { "native_queueInputBuffer", "(IIIJI)V", (void *)android_media_MediaCodec_queueInputBuffer }, - { "queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V", + { "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V", (void *)android_media_MediaCodec_queueSecureInputBuffer }, - { "dequeueInputBuffer", "(J)I", + { "native_dequeueInputBuffer", "(J)I", (void *)android_media_MediaCodec_dequeueInputBuffer }, - { "dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I", + { "native_dequeueOutputBuffer", "(Landroid/media/MediaCodec$BufferInfo;J)I", (void *)android_media_MediaCodec_dequeueOutputBuffer }, { "releaseOutputBuffer", "(IZZJ)V", @@ -1204,12 +1425,21 @@ static JNINativeMethod gMethods[] = { { "signalEndOfInputStream", "()V", (void *)android_media_MediaCodec_signalEndOfInputStream }, - { "getOutputFormatNative", "()Ljava/util/Map;", - (void *)android_media_MediaCodec_getOutputFormatNative }, + { "getFormatNative", "(Z)Ljava/util/Map;", + (void *)android_media_MediaCodec_getFormatNative }, + + { "getOutputFormatNative", "(I)Ljava/util/Map;", + (void *)android_media_MediaCodec_getOutputFormatForIndexNative }, { "getBuffers", "(Z)[Ljava/nio/ByteBuffer;", (void *)android_media_MediaCodec_getBuffers }, + { "getBuffer", "(ZI)Ljava/nio/ByteBuffer;", + (void *)android_media_MediaCodec_getBuffer }, + + { "getImage", "(ZI)Landroid/media/Image;", + (void *)android_media_MediaCodec_getImage }, + { "getName", "()Ljava/lang/String;", (void *)android_media_MediaCodec_getName }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index a70fa48..2e650e3 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -86,11 +86,19 @@ struct JMediaCodec : public AHandler { status_t signalEndOfInputStream(); - status_t getOutputFormat(JNIEnv *env, jobject *format) const; + status_t getFormat(JNIEnv *env, bool input, jobject *format) const; + + status_t getOutputFormat(JNIEnv *env, size_t index, jobject *format) const; status_t getBuffers( JNIEnv *env, bool input, jobjectArray *bufArray) const; + status_t getBuffer( + JNIEnv *env, bool input, size_t index, jobject *buf) const; + + status_t getImage( + JNIEnv *env, bool input, size_t index, jobject *image) const; + status_t getName(JNIEnv *env, jstring *name) const; status_t setParameters(const sp<AMessage> ¶ms); |