summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt42
-rw-r--r--media/java/android/media/MediaCodec.java1017
-rw-r--r--media/jni/Android.bp2
-rw-r--r--media/jni/android_media_MediaCodec.cpp1321
-rw-r--r--media/jni/android_media_MediaCodec.h37
5 files changed, 2184 insertions, 235 deletions
diff --git a/api/current.txt b/api/current.txt
index afbb427ed288..2027734124fb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -24737,7 +24737,9 @@ package android.media {
method @Deprecated @NonNull public java.nio.ByteBuffer[] getOutputBuffers();
method @NonNull public android.media.MediaFormat getOutputFormat();
method @NonNull public android.media.MediaFormat getOutputFormat(int);
+ method @NonNull public android.media.MediaCodec.OutputFrame getOutputFrame(int);
method @Nullable public android.media.Image getOutputImage(int);
+ method @NonNull public android.media.MediaCodec.QueueRequest getQueueRequest(int);
method public void queueInputBuffer(int, int, int, long, int) throws android.media.MediaCodec.CryptoException;
method public void queueSecureInputBuffer(int, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int) throws android.media.MediaCodec.CryptoException;
method public void release();
@@ -24761,6 +24763,7 @@ package android.media {
field public static final int BUFFER_FLAG_PARTIAL_FRAME = 8; // 0x8
field @Deprecated public static final int BUFFER_FLAG_SYNC_FRAME = 1; // 0x1
field public static final int CONFIGURE_FLAG_ENCODE = 1; // 0x1
+ field public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2; // 0x2
field public static final int CRYPTO_MODE_AES_CBC = 2; // 0x2
field public static final int CRYPTO_MODE_AES_CTR = 1; // 0x1
field public static final int CRYPTO_MODE_UNENCRYPTED = 0; // 0x0
@@ -24837,6 +24840,24 @@ package android.media {
method public void set(int, int);
}
+ public static final class MediaCodec.GraphicBlock {
+ method protected void finalize();
+ method public static boolean isCodecCopyFreeCompatible(@NonNull String[]);
+ method public boolean isMappable();
+ method @NonNull public android.media.Image map();
+ method @NonNull public static android.media.MediaCodec.GraphicBlock obtain(int, int, int, long, @NonNull String[]);
+ method public void recycle();
+ }
+
+ public static final class MediaCodec.LinearBlock {
+ method protected void finalize();
+ method public static boolean isCodecCopyFreeCompatible(@NonNull String[]);
+ method public boolean isMappable();
+ method @NonNull public java.nio.ByteBuffer map();
+ method @Nullable public static android.media.MediaCodec.LinearBlock obtain(int, @NonNull String[]);
+ method public void recycle();
+ }
+
public static final class MediaCodec.MetricsConstants {
field public static final String CODEC = "android.media.mediacodec.codec";
field public static final String ENCODER = "android.media.mediacodec.encoder";
@@ -24854,6 +24875,27 @@ package android.media {
method public void onFrameRendered(@NonNull android.media.MediaCodec, long, long);
}
+ public static final class MediaCodec.OutputFrame {
+ method public void getChangedKeys(@NonNull java.util.Set<java.lang.String>);
+ method public int getFlags();
+ method @NonNull public android.media.MediaFormat getFormat();
+ method @Nullable public android.media.MediaCodec.GraphicBlock getGraphicBlock();
+ method @Nullable public android.media.MediaCodec.LinearBlock getLinearBlock();
+ method public long getPresentationTimeUs();
+ }
+
+ public final class MediaCodec.QueueRequest {
+ method public void queue();
+ method @NonNull public android.media.MediaCodec.QueueRequest setByteBufferParameter(@NonNull String, @NonNull java.nio.ByteBuffer);
+ method @NonNull public android.media.MediaCodec.QueueRequest setEncryptedLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, @NonNull android.media.MediaCodec.CryptoInfo, long, int);
+ method @NonNull public android.media.MediaCodec.QueueRequest setFloatParameter(@NonNull String, float);
+ method @NonNull public android.media.MediaCodec.QueueRequest setGraphicBlock(@NonNull android.media.MediaCodec.GraphicBlock, long, int);
+ method @NonNull public android.media.MediaCodec.QueueRequest setIntegerParameter(@NonNull String, int);
+ method @NonNull public android.media.MediaCodec.QueueRequest setLinearBlock(@NonNull android.media.MediaCodec.LinearBlock, int, int, long, int);
+ method @NonNull public android.media.MediaCodec.QueueRequest setLongParameter(@NonNull String, long);
+ method @NonNull public android.media.MediaCodec.QueueRequest setStringParameter(@NonNull String, @NonNull String);
+ }
+
public final class MediaCodecInfo {
method @NonNull public String getCanonicalName();
method public android.media.MediaCodecInfo.CodecCapabilities getCapabilitiesForType(String);
diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java
index f780d40d349b..abc7e0b7be0e 100644
--- a/media/java/android/media/MediaCodec.java
+++ b/media/java/android/media/MediaCodec.java
@@ -23,6 +23,7 @@ import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.hardware.HardwareBuffer;
import android.media.MediaCodecInfo.CodecCapabilities;
import android.os.Build;
import android.os.Bundle;
@@ -39,9 +40,13 @@ import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ReadOnlyBufferException;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -1736,7 +1741,25 @@ final public class MediaCodec {
{
int index = msg.arg2;
synchronized(mBufferLock) {
- validateInputByteBuffer(mCachedInputBuffers, index);
+ switch (mBufferMode) {
+ case BUFFER_MODE_LEGACY:
+ validateInputByteBuffer(mCachedInputBuffers, index);
+ break;
+ case BUFFER_MODE_BLOCK:
+ while (mQueueRequests.size() <= index) {
+ mQueueRequests.add(null);
+ }
+ QueueRequest request = mQueueRequests.get(index);
+ if (request == null) {
+ request = new QueueRequest(mCodec, index);
+ mQueueRequests.set(index, request);
+ }
+ request.setAccessible(true);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Unrecognized buffer mode: " + mBufferMode);
+ }
}
mCallback.onInputBufferAvailable(mCodec, index);
break;
@@ -1747,7 +1770,26 @@ final public class MediaCodec {
int index = msg.arg2;
BufferInfo info = (MediaCodec.BufferInfo) msg.obj;
synchronized(mBufferLock) {
- validateOutputByteBuffer(mCachedOutputBuffers, index, info);
+ switch (mBufferMode) {
+ case BUFFER_MODE_LEGACY:
+ validateOutputByteBuffer(mCachedOutputBuffers, index, info);
+ break;
+ case BUFFER_MODE_BLOCK:
+ while (mOutputFrames.size() <= index) {
+ mOutputFrames.add(null);
+ }
+ OutputFrame frame = mOutputFrames.get(index);
+ if (frame == null) {
+ frame = new OutputFrame(index);
+ mOutputFrames.set(index, frame);
+ }
+ frame.setBufferInfo(info);
+ frame.setAccessible(true);
+ break;
+ default:
+ throw new IllegalStateException(
+ "Unrecognized buffer mode: " + mBufferMode);
+ }
}
mCallback.onOutputBufferAvailable(
mCodec, index, info);
@@ -1913,8 +1955,33 @@ final public class MediaCodec {
*/
public static final int CONFIGURE_FLAG_ENCODE = 1;
+ /**
+ * If this codec is to be used with {@link LinearBlock} and/or {@link
+ * GraphicBlock}, pass this flag.
+ * <p>
+ * When this flag is set, the following APIs throw IllegalStateException.
+ * <ul>
+ * <li>{@link #getInputBuffer}
+ * <li>{@link #getInputImage}
+ * <li>{@link #getInputBuffers}
+ * <li>{@link #getOutputBuffer}
+ * <li>{@link #getOutputImage}
+ * <li>{@link #getOutputBuffers}
+ * <li>{@link #queueInputBuffer}
+ * <li>{@link #queueSecureInputBuffer}
+ * <li>{@link #dequeueInputBuffer}
+ * <li>{@link #dequeueOutputBuffer}
+ * </ul>
+ */
+ public static final int CONFIGURE_FLAG_USE_BLOCK_MODEL = 2;
+
/** @hide */
- @IntDef(flag = true, value = { CONFIGURE_FLAG_ENCODE })
+ @IntDef(
+ flag = true,
+ value = {
+ CONFIGURE_FLAG_ENCODE,
+ CONFIGURE_FLAG_USE_BLOCK_MODEL,
+ })
@Retention(RetentionPolicy.SOURCE)
public @interface ConfigureFlag {}
@@ -1984,6 +2051,11 @@ final public class MediaCodec {
descrambler != null ? descrambler.getBinder() : null, flags);
}
+ private static final int BUFFER_MODE_INVALID = -1;
+ private static final int BUFFER_MODE_LEGACY = 0;
+ private static final int BUFFER_MODE_BLOCK = 1;
+ private int mBufferMode = BUFFER_MODE_INVALID;
+
private void configure(
@Nullable MediaFormat format, @Nullable Surface surface,
@Nullable MediaCrypto crypto, @Nullable IHwBinder descramblerBinder,
@@ -2022,6 +2094,13 @@ final public class MediaCodec {
mHasSurface = surface != null;
mCrypto = crypto;
+ synchronized (mBufferLock) {
+ if ((flags & CONFIGURE_FLAG_USE_BLOCK_MODEL) != 0) {
+ mBufferMode = BUFFER_MODE_BLOCK;
+ } else {
+ mBufferMode = BUFFER_MODE_LEGACY;
+ }
+ }
native_configure(keys, values, surface, crypto, descramblerBinder, flags);
}
@@ -2446,6 +2525,9 @@ final public class MediaCodec {
int offset, int size, long presentationTimeUs, int flags)
throws CryptoException {
synchronized(mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
invalidateByteBuffer(mCachedInputBuffers, index);
mDequeuedInputBuffers.remove(index);
}
@@ -2695,6 +2777,9 @@ final public class MediaCodec {
long presentationTimeUs,
int flags) throws CryptoException {
synchronized(mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
invalidateByteBuffer(mCachedInputBuffers, index);
mDequeuedInputBuffers.remove(index);
}
@@ -2726,6 +2811,11 @@ final public class MediaCodec {
* @throws MediaCodec.CodecException upon codec error.
*/
public final int dequeueInputBuffer(long timeoutUs) {
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
+ }
int res = native_dequeueInputBuffer(timeoutUs);
if (res >= 0) {
synchronized(mBufferLock) {
@@ -2738,6 +2828,654 @@ final public class MediaCodec {
private native final int native_dequeueInputBuffer(long timeoutUs);
/**
+ * Section of memory that represents a linear block. Applications may
+ * acquire a block via {@link LinearBlock#obtain} and queue all or part
+ * of the block as an input buffer to a codec, or get a block allocated by
+ * codec as an output buffer from {@link OutputFrame}.
+ *
+ * {@see QueueRequest#setLinearBlock}
+ * {@see QueueRequest#setEncryptedLinearBlock}
+ * {@see OutputFrame#getLinearBlock}
+ */
+ public static final class LinearBlock {
+ // No public constructors.
+ private LinearBlock() {}
+
+ /**
+ * Returns true if the buffer is mappable.
+ * @throws IllegalStateException if invalid
+ */
+ public boolean isMappable() {
+ synchronized (mLock) {
+ if (!mValid) {
+ throw new IllegalStateException();
+ }
+ return mMappable;
+ }
+ }
+
+ /**
+ * Map the memory and return the mapped region.
+ * <p>
+ * The returned memory region becomes inaccessible after
+ * {@link #recycle}, or the buffer is queued to the codecs and not
+ * returned to the client yet.
+ *
+ * @return mapped memory region as {@link ByteBuffer} object
+ * @throws IllegalStateException if not mappable or invalid
+ */
+ public @NonNull ByteBuffer map() {
+ synchronized (mLock) {
+ if (!mValid) {
+ throw new IllegalStateException();
+ }
+ if (!mMappable) {
+ throw new IllegalStateException();
+ }
+ if (mMapped == null) {
+ mMapped = native_map();
+ }
+ return mMapped;
+ }
+ }
+
+ private native ByteBuffer native_map();
+
+ /**
+ * Mark this block as ready to be recycled by the framework once it is
+ * no longer in use. All operations to this object after
+ * this call will cause exceptions, as well as attempt to access the
+ * previously mapped memory region. Caller should clear all references
+ * to this object after this call.
+ * <p>
+ * To avoid excessive memory consumption, it is recommended that callers
+ * recycle buffers as soon as they no longer need the buffers
+ *
+ * @throws IllegalStateException if invalid
+ */
+ public void recycle() {
+ synchronized (mLock) {
+ if (!mValid) {
+ throw new IllegalStateException();
+ }
+ if (mMapped != null) {
+ mMapped.setAccessible(false);
+ mMapped = null;
+ }
+ native_recycle();
+ mValid = false;
+ mNativeContext = 0;
+ }
+ sPool.offer(this);
+ }
+
+ private native void native_recycle();
+
+ private native void native_obtain(int capacity, String[] codecNames);
+
+ @Override
+ protected void finalize() {
+ native_recycle();
+ }
+
+ /**
+ * Returns true if it is possible to allocate a linear block that can be
+ * passed to all listed codecs as input buffers without copying the
+ * content.
+ * <p>
+ * Note that even if this function returns true, {@link #obtain} may
+ * still throw due to invalid arguments or allocation failure.
+ *
+ * @param codecNames list of codecs that the client wants to use a
+ * linear block without copying. Null entries are
+ * ignored.
+ */
+ public static boolean isCodecCopyFreeCompatible(@NonNull String[] codecNames) {
+ return native_checkCompatible(codecNames);
+ }
+
+ private static native boolean native_checkCompatible(@NonNull String[] codecNames);
+
+ /**
+ * Obtain a linear block object no smaller than {@code capacity}.
+ * If {@link #isCodecCopyFreeCompatible} with the same
+ * {@code codecNames} returned true, the returned
+ * {@link LinearBlock} object can be queued to the listed codecs without
+ * copying. The returned {@link LinearBlock} object is always
+ * read/write mappable.
+ *
+ * @param capacity requested capacity of the linear block in bytes
+ * @param codecNames list of codecs that the client wants to use this
+ * linear block without copying. Null entries are
+ * ignored.
+ * @return a linear block object.
+ * @throws IllegalArgumentException if the capacity is invalid or
+ * codecNames contains invalid name
+ * @throws IOException if an error occurred while allocating a buffer
+ */
+ public static @Nullable LinearBlock obtain(
+ int capacity, @NonNull String[] codecNames) {
+ LinearBlock buffer = sPool.poll();
+ if (buffer == null) {
+ buffer = new LinearBlock();
+ }
+ synchronized (buffer.mLock) {
+ buffer.native_obtain(capacity, codecNames);
+ }
+ return buffer;
+ }
+
+ // Called from native
+ private void setInternalStateLocked(long context, boolean isMappable) {
+ mNativeContext = context;
+ mMappable = isMappable;
+ mValid = (context != 0);
+ }
+
+ private static final BlockingQueue<LinearBlock> sPool =
+ new LinkedBlockingQueue<>();
+
+ private final Object mLock = new Object();
+ private boolean mValid = false;
+ private boolean mMappable = false;
+ private ByteBuffer mMapped = null;
+ private long mNativeContext = 0;
+ }
+
+ /**
+ * Section of memory that represents a graphic block. Applications may
+ * acquire a block via {@link GraphicBlock#obtain} and queue
+ * the block as an input buffer to a codec, or get a block allocated by
+ * codec as an output buffer from {@link OutputFrame}.
+ *
+ * {@see QueueRequest#setGraphicBlock}
+ * {@see OutputFrame#getGraphicBlock}
+ */
+ public static final class GraphicBlock {
+ // No public constructors.
+ private GraphicBlock() {}
+
+ /**
+ * Returns true if the buffer is mappable.
+ * @throws IllegalStateException if invalid
+ */
+ public boolean isMappable() {
+ synchronized (mLock) {
+ if (!mValid) {
+ throw new IllegalStateException();
+ }
+ return mMappable;
+ }
+ }
+
+ /**
+ * Map the memory and return the mapped region.
+ * <p>
+ * Calling {@link #recycle} or
+ * {@link QueueRequest#setGraphicBlock} causes the returned
+ * {@link Image} object to be closed, if not already.
+ *
+ * @return mapped memory region as {@link Image} object
+ * @throws IllegalStateException if not mappable or invalid
+ */
+ public @NonNull Image map() {
+ synchronized (mLock) {
+ if (!mValid) {
+ throw new IllegalStateException();
+ }
+ if (!mMappable) {
+ throw new IllegalStateException();
+ }
+ if (mMapped == null) {
+ mMapped = native_map();
+ }
+ return mMapped;
+ }
+ }
+
+ private native Image native_map();
+
+ /**
+ * Mark this block as ready to be recycled by the framework once it is
+ * no longer in use. All operations to this object after
+ * this call will cause exceptions, as well as attempt to access the
+ * previously mapped memory region. Caller should clear all references
+ * to this object after this call.
+ * <p>
+ * To avoid excessive memory consumption, it is recommended that callers
+ * recycle buffers as soon as they no longer need the buffers.
+ *
+ * @throws IllegalStateException if invalid
+ */
+ public void recycle() {
+ synchronized (mLock) {
+ if (!mValid) {
+ throw new IllegalStateException();
+ }
+ if (mMapped != null) {
+ mMapped.close();
+ mMapped = null;
+ }
+ native_recycle();
+ mValid = false;
+ mNativeContext = 0;
+ }
+ sPool.offer(this);
+ }
+
+ private native void native_recycle();
+
+ /**
+ * Returns true if it is possible to allocate a graphic block that
+ * can be passed to all listed codecs as an input buffer without
+ * copying.
+ * <p>
+ * Note that even if this function returns true, {@link #obtain}
+ * may still throw due to invalid arguments or allocation failure.
+ * In addition, choosing a format that is not natively supported by the
+ * codec may cause color conversion.
+ *
+ * @param codecNames list of codecs that the client wants to use a
+ * graphic block without copying. Null entries are
+ * ignored.
+ */
+ public static boolean isCodecCopyFreeCompatible(@NonNull String[] codecNames) {
+ return native_checkCompatible(codecNames);
+ }
+
+ private static native boolean native_checkCompatible(@NonNull String[] codecNames);
+
+ // Called from native
+ private void setInternalStateLocked(long context, boolean isMappable) {
+ mNativeContext = context;
+ mMappable = isMappable;
+ mValid = (context != 0);
+ }
+
+ private static final BlockingQueue<GraphicBlock> sPool =
+ new LinkedBlockingQueue<>();
+
+ /**
+ * Obtain a graphic block object of dimension
+ * {@code width}x{@code height}.
+ * If {@link #isCodecCopyFreeCompatible} with the same
+ * {@code codecNames} returned true, the returned
+ * {@link GraphicBlock} object can be queued to the listed codecs
+ * without copying. The returned {@link GraphicBlock} object is always
+ * read/write mappable.
+ *
+ * @param width requested width of the graphic block
+ * @param height requested height of the graphic block
+ * @param format the format of pixels. One of the {@code COLOR_Format}
+ * values from {@link MediaCodecInfo.CodecCapabilities}.
+ * @param usage the usage of the buffer. @HardwareBuffer.Usage
+ * @param codecNames list of codecs that the client wants to use this
+ * graphic block without copying. Null entries are
+ * ignored.
+ * @return a graphic block object.
+ * @throws IllegalArgumentException if the parameters are invalid or
+ * not supported
+ * @throws IOException if an error occurred while allocating a buffer
+ */
+ public static @NonNull GraphicBlock obtain(
+ int width,
+ int height,
+ int format,
+ @HardwareBuffer.Usage long usage,
+ @NonNull String[] codecNames) {
+ GraphicBlock buffer = sPool.poll();
+ if (buffer == null) {
+ buffer = new GraphicBlock();
+ }
+ if (width < 0 || height < 0) {
+ throw new IllegalArgumentException();
+ }
+ synchronized (buffer.mLock) {
+ buffer.native_obtain(width, height, format, usage, codecNames);
+ }
+ return buffer;
+ }
+
+ private native void native_obtain(
+ int width,
+ int height,
+ int format,
+ @HardwareBuffer.Usage long usage,
+ @NonNull String[] codecNames);
+
+ @Override
+ protected void finalize() {
+ native_recycle();
+ }
+
+ private final Object mLock = new Object();
+ private boolean mValid = false;
+ private boolean mMappable = false;
+ private Image mMapped = null;
+ private long mNativeContext = 0;
+ }
+
+ /**
+ * Builder-like class for queue requests. Use this class to prepare a
+ * queue request and send it.
+ */
+ public final class QueueRequest {
+ // No public constructor
+ private QueueRequest(@NonNull MediaCodec codec, int index) {
+ mCodec = codec;
+ mIndex = index;
+ }
+
+ /**
+ * Set a linear block to this queue request. Exactly one buffer must be
+ * set for a queue request before calling {@link #queue}. It is possible
+ * to use the same {@link LinearBlock} object for multiple queue
+ * requests. The behavior is undefined if the range of the buffer
+ * overlaps for multiple requests, or the application writes into the
+ * region being processed by the codec.
+ *
+ * @param block The linear block object
+ * @param offset The byte offset into the input buffer at which the data starts.
+ * @param size The number of bytes of valid input data.
+ * @param presentationTimeUs The presentation timestamp in microseconds for this
+ * buffer. This is normally the media time at which this
+ * buffer should be presented (rendered). When using an output
+ * surface, this will be propagated as the {@link
+ * SurfaceTexture#getTimestamp timestamp} for the frame (after
+ * conversion to nanoseconds).
+ * @param flags A bitmask of flags
+ * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+ * While not prohibited, most codecs do not use the
+ * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
+ * @return this object
+ * @throws IllegalStateException if a buffer is already set
+ */
+ public @NonNull QueueRequest setLinearBlock(
+ @NonNull LinearBlock block,
+ int offset,
+ int size,
+ long presentationTimeUs,
+ @BufferFlag int flags) {
+ if (!isAccessible()) {
+ throw new IllegalStateException();
+ }
+ if (mLinearBlock != null || mGraphicBlock != null) {
+ throw new IllegalStateException();
+ }
+ mLinearBlock = block;
+ mOffset = offset;
+ mSize = size;
+ mPresentationTimeUs = presentationTimeUs;
+ mFlags = flags;
+ return this;
+ }
+
+ /**
+ * Set an encrypted linear block to this queue request. Exactly one
+ * buffer must be set for a queue request before calling {@link #queue}.
+ *
+ * @param block The linear block object
+ * @param offset The byte offset into the input buffer at which the data starts.
+ * @param presentationTimeUs The presentation timestamp in microseconds for this
+ * buffer. This is normally the media time at which this
+ * buffer should be presented (rendered). When using an output
+ * surface, this will be propagated as the {@link
+ * SurfaceTexture#getTimestamp timestamp} for the frame (after
+ * conversion to nanoseconds).
+ * @param cryptoInfo Metadata describing the structure of the encrypted input sample.
+ * @param flags A bitmask of flags
+ * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+ * While not prohibited, most codecs do not use the
+ * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
+ * @return this object
+ * @throws IllegalStateException if a buffer is already set
+ */
+ public @NonNull QueueRequest setEncryptedLinearBlock(
+ @NonNull LinearBlock block,
+ int offset,
+ @NonNull MediaCodec.CryptoInfo cryptoInfo,
+ long presentationTimeUs,
+ @BufferFlag int flags) {
+ if (!isAccessible()) {
+ throw new IllegalStateException();
+ }
+ if (mLinearBlock != null || mGraphicBlock != null) {
+ throw new IllegalStateException();
+ }
+ mLinearBlock = block;
+ mOffset = offset;
+ mCryptoInfo = cryptoInfo;
+ mPresentationTimeUs = presentationTimeUs;
+ mFlags = flags;
+ return this;
+ }
+
+ /**
+ * Set a graphic block to this queue request. Exactly one buffer must
+ * be set for a queue request before calling {@link #queue}.
+ *
+ * @param block The graphic block object
+ * @param presentationTimeUs The presentation timestamp in microseconds for this
+ * buffer. This is normally the media time at which this
+ * buffer should be presented (rendered). When using an output
+ * surface, this will be propagated as the {@link
+ * SurfaceTexture#getTimestamp timestamp} for the frame (after
+ * conversion to nanoseconds).
+ * @param flags A bitmask of flags
+ * {@link #BUFFER_FLAG_CODEC_CONFIG} and {@link #BUFFER_FLAG_END_OF_STREAM}.
+ * While not prohibited, most codecs do not use the
+ * {@link #BUFFER_FLAG_KEY_FRAME} flag for input buffers.
+ * @return this object
+ * @throws IllegalStateException if a buffer is already set
+ */
+ public @NonNull QueueRequest setGraphicBlock(
+ @NonNull GraphicBlock block,
+ long presentationTimeUs,
+ @BufferFlag int flags) {
+ if (!isAccessible()) {
+ throw new IllegalStateException();
+ }
+ if (mLinearBlock != null || mGraphicBlock != null) {
+ throw new IllegalStateException();
+ }
+ mGraphicBlock = block;
+ mPresentationTimeUs = presentationTimeUs;
+ mFlags = flags;
+ return this;
+ }
+
+ /**
+ * Add a integer parameter. See {@link MediaFormat} for the list of
+ * supported tunings. If there was {@link MediaCodec#setParameters}
+ * call with the same key which is not processed by the codec yet, the
+ * value set from this method will override the unprocessed value.
+ */
+ public @NonNull QueueRequest setIntegerParameter(
+ @NonNull String key, int value) {
+ if (!isAccessible()) {
+ throw new IllegalStateException();
+ }
+ mTuningKeys.add(key);
+ mTuningValues.add(Integer.valueOf(value));
+ return this;
+ }
+
+ /**
+ * Add a long parameter. See {@link MediaFormat} for the list of
+ * supported tunings. If there was {@link MediaCodec#setParameters}
+ * call with the same key which is not processed by the codec yet, the
+ * value set from this method will override the unprocessed value.
+ */
+ public @NonNull QueueRequest setLongParameter(
+ @NonNull String key, long value) {
+ if (!isAccessible()) {
+ throw new IllegalStateException();
+ }
+ mTuningKeys.add(key);
+ mTuningValues.add(Long.valueOf(value));
+ return this;
+ }
+
+ /**
+ * Add a float parameter. See {@link MediaFormat} for the list of
+ * supported tunings. If there was {@link MediaCodec#setParameters}
+ * call with the same key which is not processed by the codec yet, the
+ * value set from this method will override the unprocessed value.
+ */
+ public @NonNull QueueRequest setFloatParameter(
+ @NonNull String key, float value) {
+ if (!isAccessible()) {
+ throw new IllegalStateException();
+ }
+ mTuningKeys.add(key);
+ mTuningValues.add(Float.valueOf(value));
+ return this;
+ }
+
+ /**
+ * Add a {@link ByteBuffer} parameter. See {@link MediaFormat} for the list of
+ * supported tunings. If there was {@link MediaCodec#setParameters}
+ * call with the same key which is not processed by the codec yet, the
+ * value set from this method will override the unprocessed value.
+ */
+ public @NonNull QueueRequest setByteBufferParameter(
+ @NonNull String key, @NonNull ByteBuffer value) {
+ if (!isAccessible()) {
+ throw new IllegalStateException();
+ }
+ mTuningKeys.add(key);
+ mTuningValues.add(value);
+ return this;
+ }
+
+ /**
+ * Add a string parameter. See {@link MediaFormat} for the list of
+ * supported tunings. If there was {@link MediaCodec#setParameters}
+ * call with the same key which is not processed by the codec yet, the
+ * value set from this method will override the unprocessed value.
+ */
+ public @NonNull QueueRequest setStringParameter(
+ @NonNull String key, @NonNull String value) {
+ if (!isAccessible()) {
+ throw new IllegalStateException();
+ }
+ mTuningKeys.add(key);
+ mTuningValues.add(value);
+ return this;
+ }
+
+ /**
+ * Finish building a queue request and queue the buffers with tunings.
+ */
+ public void queue() {
+ if (!isAccessible()) {
+ throw new IllegalStateException();
+ }
+ if (mLinearBlock == null && mGraphicBlock == null) {
+ throw new IllegalStateException();
+ }
+ setAccessible(false);
+ if (mLinearBlock != null) {
+ mCodec.native_queueLinearBlock(
+ mIndex, mLinearBlock, mOffset, mSize, mCryptoInfo,
+ mPresentationTimeUs, mFlags,
+ mTuningKeys, mTuningValues);
+ } else if (mGraphicBlock != null) {
+ mCodec.native_queueGraphicBlock(
+ mIndex, mGraphicBlock, mPresentationTimeUs, mFlags,
+ mTuningKeys, mTuningValues);
+ }
+ clear();
+ }
+
+ @NonNull QueueRequest clear() {
+ mLinearBlock = null;
+ mOffset = 0;
+ mSize = 0;
+ mCryptoInfo = null;
+ mGraphicBlock = null;
+ mPresentationTimeUs = 0;
+ mFlags = 0;
+ mTuningKeys.clear();
+ mTuningValues.clear();
+ return this;
+ }
+
+ boolean isAccessible() {
+ return mAccessible;
+ }
+
+ @NonNull QueueRequest setAccessible(boolean accessible) {
+ mAccessible = accessible;
+ return this;
+ }
+
+ private final MediaCodec mCodec;
+ private final int mIndex;
+ private LinearBlock mLinearBlock = null;
+ private int mOffset = 0;
+ private int mSize = 0;
+ private MediaCodec.CryptoInfo mCryptoInfo = null;
+ private GraphicBlock mGraphicBlock = null;
+ private long mPresentationTimeUs = 0;
+ private @BufferFlag int mFlags = 0;
+ private final ArrayList<String> mTuningKeys = new ArrayList<>();
+ private final ArrayList<Object> mTuningValues = new ArrayList<>();
+
+ private boolean mAccessible = false;
+ }
+
+ private native void native_queueLinearBlock(
+ int index,
+ @NonNull LinearBlock block,
+ int offset,
+ int size,
+ @Nullable CryptoInfo cryptoInfo,
+ long presentationTimeUs,
+ int flags,
+ @NonNull ArrayList<String> keys,
+ @NonNull ArrayList<Object> values);
+
+ private native void native_queueGraphicBlock(
+ int index,
+ @NonNull GraphicBlock block,
+ long presentationTimeUs,
+ int flags,
+ @NonNull ArrayList<String> keys,
+ @NonNull ArrayList<Object> values);
+
+ private final ArrayList<QueueRequest> mQueueRequests = new ArrayList<>();
+
+ /**
+ * Return a clear {@link QueueRequest} object for an input slot index.
+ *
+ * @param index input slot index from
+ * {@link Callback#onInputBufferAvailable}
+ * @return queue request object
+ * @throws IllegalStateException if not using block model
+ * @throws IllegalArgumentException if the input slot is not available or
+ * the index is out of range
+ */
+ public @NonNull QueueRequest getQueueRequest(int index) {
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_BLOCK) {
+ throw new IllegalStateException();
+ }
+ if (index < 0 || index >= mQueueRequests.size()) {
+ throw new IllegalArgumentException();
+ }
+ QueueRequest request = mQueueRequests.get(index);
+ if (request == null) {
+ throw new IllegalArgumentException();
+ }
+ if (!request.isAccessible()) {
+ throw new IllegalArgumentException();
+ }
+ return request.clear();
+ }
+ }
+
+ /**
* If a non-negative timeout had been specified in the call
* to {@link #dequeueOutputBuffer}, indicates that the call timed out.
*/
@@ -2789,8 +3527,13 @@ final public class MediaCodec {
@OutputBufferInfo
public final int dequeueOutputBuffer(
@NonNull BufferInfo info, long timeoutUs) {
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
+ }
int res = native_dequeueOutputBuffer(info, timeoutUs);
- synchronized(mBufferLock) {
+ synchronized (mBufferLock) {
if (res == INFO_OUTPUT_BUFFERS_CHANGED) {
cacheBuffers(false /* input */);
} else if (res >= 0) {
@@ -2826,15 +3569,7 @@ final public class MediaCodec {
* @throws MediaCodec.CodecException upon codec error.
*/
public final void releaseOutputBuffer(int index, boolean render) {
- BufferInfo info = null;
- synchronized(mBufferLock) {
- invalidateByteBuffer(mCachedOutputBuffers, index);
- mDequeuedOutputBuffers.remove(index);
- if (mHasSurface) {
- info = mDequeuedOutputInfos.remove(index);
- }
- }
- releaseOutputBuffer(index, render, false /* updatePTS */, 0 /* dummy */);
+ releaseOutputBufferInternal(index, render, false /* updatePTS */, 0 /* dummy */);
}
/**
@@ -2887,16 +3622,33 @@ final public class MediaCodec {
* @throws MediaCodec.CodecException upon codec error.
*/
public final void releaseOutputBuffer(int index, long renderTimestampNs) {
+ releaseOutputBufferInternal(
+ index, true /* render */, true /* updatePTS */, renderTimestampNs);
+ }
+
+ private void releaseOutputBufferInternal(
+ int index, boolean render, boolean updatePts, long renderTimestampNs) {
BufferInfo info = null;
synchronized(mBufferLock) {
- invalidateByteBuffer(mCachedOutputBuffers, index);
- mDequeuedOutputBuffers.remove(index);
- if (mHasSurface) {
- info = mDequeuedOutputInfos.remove(index);
+ switch (mBufferMode) {
+ case BUFFER_MODE_LEGACY:
+ invalidateByteBuffer(mCachedOutputBuffers, index);
+ mDequeuedOutputBuffers.remove(index);
+ if (mHasSurface) {
+ info = mDequeuedOutputInfos.remove(index);
+ }
+ break;
+ case BUFFER_MODE_BLOCK:
+ OutputFrame frame = mOutputFrames.get(index);
+ frame.setAccessible(false);
+ frame.clear();
+ break;
+ default:
+ throw new IllegalStateException();
}
}
releaseOutputBuffer(
- index, true /* render */, true /* updatePTS */, renderTimestampNs);
+ index, render, updatePts, renderTimestampNs);
}
@UnsupportedAppUsage
@@ -3116,6 +3868,8 @@ final public class MediaCodec {
mCachedOutputBuffers = null;
mDequeuedInputBuffers.clear();
mDequeuedOutputBuffers.clear();
+ mQueueRequests.clear();
+ mOutputFrames.clear();
}
}
@@ -3154,11 +3908,16 @@ final public class MediaCodec {
*/
@NonNull
public ByteBuffer[] getInputBuffers() {
- if (mCachedInputBuffers == null) {
- throw new IllegalStateException();
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
+ if (mCachedInputBuffers == null) {
+ throw new IllegalStateException();
+ }
+ // FIXME: check codec status
+ return mCachedInputBuffers;
}
- // FIXME: check codec status
- return mCachedInputBuffers;
}
/**
@@ -3185,11 +3944,16 @@ final public class MediaCodec {
*/
@NonNull
public ByteBuffer[] getOutputBuffers() {
- if (mCachedOutputBuffers == null) {
- throw new IllegalStateException();
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
+ if (mCachedOutputBuffers == null) {
+ throw new IllegalStateException();
+ }
+ // FIXME: check codec status
+ return mCachedOutputBuffers;
}
- // FIXME: check codec status
- return mCachedOutputBuffers;
}
/**
@@ -3212,8 +3976,13 @@ final public class MediaCodec {
*/
@Nullable
public ByteBuffer getInputBuffer(int index) {
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
+ }
ByteBuffer newBuffer = getBuffer(true /* input */, index);
- synchronized(mBufferLock) {
+ synchronized (mBufferLock) {
invalidateByteBuffer(mCachedInputBuffers, index);
mDequeuedInputBuffers.put(index, newBuffer);
}
@@ -3241,8 +4010,13 @@ final public class MediaCodec {
*/
@Nullable
public Image getInputImage(int index) {
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
+ }
Image newImage = getImage(true /* input */, index);
- synchronized(mBufferLock) {
+ synchronized (mBufferLock) {
invalidateByteBuffer(mCachedInputBuffers, index);
mDequeuedInputBuffers.put(index, newImage);
}
@@ -3270,8 +4044,13 @@ final public class MediaCodec {
*/
@Nullable
public ByteBuffer getOutputBuffer(int index) {
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
+ }
ByteBuffer newBuffer = getBuffer(false /* input */, index);
- synchronized(mBufferLock) {
+ synchronized (mBufferLock) {
invalidateByteBuffer(mCachedOutputBuffers, index);
mDequeuedOutputBuffers.put(index, newBuffer);
}
@@ -3298,8 +4077,13 @@ final public class MediaCodec {
*/
@Nullable
public Image getOutputImage(int index) {
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_LEGACY) {
+ throw new IllegalStateException();
+ }
+ }
Image newImage = getImage(false /* input */, index);
- synchronized(mBufferLock) {
+ synchronized (mBufferLock) {
invalidateByteBuffer(mCachedOutputBuffers, index);
mDequeuedOutputBuffers.put(index, newImage);
}
@@ -3307,6 +4091,149 @@ final public class MediaCodec {
}
/**
+ * A single output frame and its associated metadata.
+ */
+ public static final class OutputFrame {
+ // No public constructor
+ OutputFrame(int index) {
+ mIndex = index;
+ }
+
+ /**
+ * Returns the output linear block, or null if this frame is empty.
+ *
+ * @throws IllegalStateException if this output frame is not linear.
+ */
+ public @Nullable LinearBlock getLinearBlock() {
+ if (mGraphicBlock != null) {
+ throw new IllegalStateException();
+ }
+ return mLinearBlock;
+ }
+
+ /**
+ * Returns the output graphic block, or null if this frame is empty.
+ *
+ * @throws IllegalStateException if this output frame is not graphic.
+ */
+ public @Nullable GraphicBlock getGraphicBlock() {
+ if (mLinearBlock != null) {
+ throw new IllegalStateException();
+ }
+ return mGraphicBlock;
+ }
+
+ /**
+ * Returns the presentation timestamp in microseconds.
+ */
+ public long getPresentationTimeUs() {
+ return mPresentationTimeUs;
+ }
+
+ /**
+ * Returns the buffer flags.
+ */
+ public @BufferFlag int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Returns a read-only {@link MediaFormat} for this frame. The returned
+ * object is valid only while the client is holding the output frame.
+ */
+ public @NonNull MediaFormat getFormat() {
+ return mFormat;
+ }
+
+ /**
+ * Populate {@code keys} with the name of entries that has changed from
+ * the previous frame. The entries may have been removed/changed/added.
+ * Client can find out what the change is by querying {@link MediaFormat}
+ * object returned from {@link #getFormat}.
+ */
+ public void getChangedKeys(@NonNull Set<String> keys) {
+ keys.clear();
+ keys.addAll(mChangedKeys);
+ }
+
+ void clear() {
+ mLinearBlock = null;
+ mGraphicBlock = null;
+ mFormat = null;
+ mChangedKeys.clear();
+ mLoaded = false;
+ }
+
+ boolean isAccessible() {
+ return mAccessible;
+ }
+
+ void setAccessible(boolean accessible) {
+ mAccessible = accessible;
+ }
+
+ void setBufferInfo(MediaCodec.BufferInfo info) {
+ mPresentationTimeUs = info.presentationTimeUs;
+ mFlags = info.flags;
+ }
+
+ boolean isLoaded() {
+ return mLoaded;
+ }
+
+ void setLoaded(boolean loaded) {
+ mLoaded = loaded;
+ }
+
+ private final int mIndex;
+ private LinearBlock mLinearBlock = null;
+ private GraphicBlock mGraphicBlock = null;
+ private long mPresentationTimeUs = 0;
+ private @BufferFlag int mFlags = 0;
+ private MediaFormat mFormat = null;
+ private final ArrayList<String> mChangedKeys = new ArrayList<>();
+ private boolean mAccessible = false;
+ private boolean mLoaded = false;
+ }
+
+ private final ArrayList<OutputFrame> mOutputFrames = new ArrayList<>();
+
+ /**
+ * Returns an {@link OutputFrame} object.
+ *
+ * @param index output buffer index from
+ * {@link Callback#onOutputBufferAvailable}
+ * @return {@link OutputFrame} object describing the output buffer
+ * @throws IllegalStateException if not using block model
+ * @throws IllegalArgumentException if the output buffer is not available or
+ * the index is out of range
+ */
+ public @NonNull OutputFrame getOutputFrame(int index) {
+ synchronized (mBufferLock) {
+ if (mBufferMode != BUFFER_MODE_BLOCK) {
+ throw new IllegalStateException();
+ }
+ if (index < 0 || index >= mOutputFrames.size()) {
+ throw new IllegalArgumentException();
+ }
+ OutputFrame frame = mOutputFrames.get(index);
+ if (frame == null) {
+ throw new IllegalArgumentException();
+ }
+ if (!frame.isAccessible()) {
+ throw new IllegalArgumentException();
+ }
+ if (!frame.isLoaded()) {
+ native_getOutputFrame(frame, index);
+ frame.setLoaded(true);
+ }
+ return frame;
+ }
+ }
+
+ private native void native_getOutputFrame(OutputFrame frame, int index);
+
+ /**
* The content is scaled to the surface dimensions
*/
public static final int VIDEO_SCALING_MODE_SCALE_TO_FIT = 1;
@@ -3889,7 +4816,9 @@ final public class MediaCodec {
@Override
public void close() {
if (mIsImageValid) {
- java.nio.NioUtils.freeDirectBuffer(mBuffer);
+ if (mBuffer != null) {
+ java.nio.NioUtils.freeDirectBuffer(mBuffer);
+ }
mIsImageValid = false;
}
}
@@ -3908,7 +4837,6 @@ final public class MediaCodec {
super.setCropRect(cropRect);
}
-
public MediaImage(
@NonNull ByteBuffer buffer, @NonNull ByteBuffer info, boolean readOnly,
long timestamp, int xOffset, int yOffset, @Nullable Rect cropRect) {
@@ -3963,7 +4891,6 @@ final public class MediaCodec {
throw new UnsupportedOperationException("unexpected strides: "
+ colInc + " pixel, " + rowInc + " row on plane " + ix);
}
-
buffer.clear();
buffer.position(mBuffer.position() + planeOffset
+ (xOffset / horiz) * colInc + (yOffset / vert) * rowInc);
@@ -3983,6 +4910,30 @@ final public class MediaCodec {
super.setCropRect(cropRect);
}
+ public MediaImage(
+ @NonNull Image.Plane[] planes, int width, int height, int format, boolean readOnly,
+ long timestamp, int xOffset, int yOffset, @Nullable Rect cropRect) {
+ mWidth = width;
+ mHeight = height;
+ mFormat = format;
+ mTimestamp = timestamp;
+ mIsImageValid = true;
+ mIsReadOnly = readOnly;
+ mBuffer = null;
+ mInfo = null;
+ mPlanes = planes;
+
+ // save offsets and info
+ mXOffset = xOffset;
+ mYOffset = yOffset;
+
+ if (cropRect == null) {
+ cropRect = new Rect(0, 0, mWidth, mHeight);
+ }
+ cropRect.offset(-xOffset, -yOffset);
+ super.setCropRect(cropRect);
+ }
+
private class MediaPlane extends Plane {
public MediaPlane(@NonNull ByteBuffer buffer, int rowInc, int colInc) {
mData = buffer;
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 536a061190d7..2305b44c87ad 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -1,6 +1,8 @@
cc_library_shared {
name: "libmedia_jni",
+ defaults: ["libcodec2-internal-defaults"],
+
srcs: [
"android_media_ImageWriter.cpp",
"android_media_ImageReader.cpp",
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 20980a90e9fe..ab6966d5d1c3 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -18,6 +18,8 @@
#define LOG_TAG "MediaCodec-JNI"
#include <utils/Log.h>
+#include <type_traits>
+
#include "android_media_MediaCodec.h"
#include "android_media_MediaCrypto.h"
@@ -31,13 +33,20 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
+#include <C2Buffer.h>
+
#include <android/hardware/cas/native/1.0/IDescrambler.h>
+#include <binder/MemoryHeapBase.h>
+
#include <cutils/compiler.h>
#include <gui/Surface.h>
+#include <hidlmemory/FrameworkUtils.h>
+
#include <media/MediaCodecBuffer.h>
+#include <media/hardware/VideoAPI.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -47,7 +56,6 @@
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/PersistentSurface.h>
#include <mediadrm/ICrypto.h>
-#include <nativehelper/ScopedLocalRef.h>
#include <system/window.h>
@@ -110,6 +118,39 @@ static struct {
jfieldID levelField;
} gCodecInfo;
+static struct {
+ jclass clazz;
+ jobject nativeByteOrder;
+ jmethodID orderId;
+ jmethodID asReadOnlyBufferId;
+ jmethodID positionId;
+ jmethodID limitId;
+} gByteBufferInfo;
+
+static struct {
+ jmethodID sizeId;
+ jmethodID getId;
+ jmethodID addId;
+} gArrayListInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID ctorId;
+ jmethodID setInternalStateId;
+ jfieldID contextId;
+ jfieldID validId;
+ jfieldID lockId;
+} gLinearBlockInfo;
+
+static struct {
+ jclass clazz;
+ jmethodID ctorId;
+ jmethodID setInternalStateId;
+ jfieldID contextId;
+ jfieldID validId;
+ jfieldID lockId;
+} gGraphicBlockInfo;
+
struct fields_t {
jmethodID postEventFromNativeID;
jmethodID lockAndGetContextID;
@@ -123,11 +164,65 @@ struct fields_t {
jfieldID cryptoInfoPatternID;
jfieldID patternEncryptBlocksID;
jfieldID patternSkipBlocksID;
+ jfieldID queueRequestIndexID;
+ jfieldID outputFrameLinearBlockID;
+ jfieldID outputFrameGraphicBlockID;
+ jfieldID outputFrameChangedKeysID;
+ jfieldID outputFrameFormatID;
};
static fields_t gFields;
static const void *sRefBaseOwner;
+struct JMediaCodecLinearBlock {
+ std::shared_ptr<C2Buffer> mBuffer;
+ std::shared_ptr<C2ReadView> mReadonlyMapping;
+
+ std::shared_ptr<C2LinearBlock> mBlock;
+ std::shared_ptr<C2WriteView> mReadWriteMapping;
+
+ sp<IMemoryHeap> mHeap;
+ sp<hardware::HidlMemory> mMemory;
+
+ sp<MediaCodecBuffer> mLegacyBuffer;
+
+ std::once_flag mCopyWarningFlag;
+
+ std::shared_ptr<C2Buffer> toC2Buffer(size_t offset, size_t size) {
+ if (mBuffer) {
+ if (mBuffer->data().type() != C2BufferData::LINEAR) {
+ return nullptr;
+ }
+ C2ConstLinearBlock block = mBuffer->data().linearBlocks().front();
+ if (offset == 0 && size == block.capacity()) {
+ return mBuffer;
+ }
+ return C2Buffer::CreateLinearBuffer(block.subBlock(offset, size));
+ }
+ if (mBlock) {
+ return C2Buffer::CreateLinearBuffer(mBlock->share(offset, size, C2Fence{}));
+ }
+ return nullptr;
+ }
+
+ sp<hardware::HidlMemory> toHidlMemory() {
+ if (mMemory) {
+ return mMemory;
+ }
+ return nullptr;
+ }
+};
+
+struct JMediaCodecGraphicBlock {
+ std::shared_ptr<C2Buffer> mBuffer;
+ std::shared_ptr<const C2GraphicView> mReadonlyMapping;
+
+ std::shared_ptr<C2GraphicBlock> mBlock;
+ std::shared_ptr<C2GraphicView> mReadWriteMapping;
+
+ sp<MediaCodecBuffer> mLegacyBuffer;
+};
+
////////////////////////////////////////////////////////////////////////////////
JMediaCodec::JMediaCodec(
@@ -141,8 +236,6 @@ JMediaCodec::JMediaCodec(
mClass = (jclass)env->NewGlobalRef(clazz);
mObject = env->NewWeakGlobalRef(thiz);
- cacheJavaObjects(env);
-
mLooper = new ALooper;
mLooper->setName("MediaCodec_looper");
@@ -163,45 +256,6 @@ JMediaCodec::JMediaCodec(
CHECK((mCodec != NULL) != (mInitStatus != OK));
}
-void JMediaCodec::cacheJavaObjects(JNIEnv *env) {
- jclass clazz = (jclass)env->FindClass("java/nio/ByteBuffer");
- mByteBufferClass = (jclass)env->NewGlobalRef(clazz);
- CHECK(mByteBufferClass != 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);
- mNativeByteOrderObj = env->NewGlobalRef(nativeByteOrderObj);
- CHECK(mNativeByteOrderObj != NULL);
- env->DeleteLocalRef(nativeByteOrderObj);
- nativeByteOrderObj = NULL;
-
- mByteBufferOrderMethodID = env->GetMethodID(
- mByteBufferClass,
- "order",
- "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");
- CHECK(mByteBufferOrderMethodID != NULL);
-
- mByteBufferAsReadOnlyBufferMethodID = env->GetMethodID(
- mByteBufferClass, "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
- CHECK(mByteBufferAsReadOnlyBufferMethodID != NULL);
-
- mByteBufferPositionMethodID = env->GetMethodID(
- mByteBufferClass, "position", "(I)Ljava/nio/Buffer;");
- CHECK(mByteBufferPositionMethodID != NULL);
-
- mByteBufferLimitMethodID = env->GetMethodID(
- mByteBufferClass, "limit", "(I)Ljava/nio/Buffer;");
- CHECK(mByteBufferLimitMethodID != NULL);
-}
-
status_t JMediaCodec::initCheck() const {
return mInitStatus;
}
@@ -247,19 +301,6 @@ JMediaCodec::~JMediaCodec() {
mObject = NULL;
env->DeleteGlobalRef(mClass);
mClass = NULL;
- deleteJavaObjects(env);
-}
-
-void JMediaCodec::deleteJavaObjects(JNIEnv *env) {
- env->DeleteGlobalRef(mByteBufferClass);
- mByteBufferClass = NULL;
- env->DeleteGlobalRef(mNativeByteOrderObj);
- mNativeByteOrderObj = NULL;
-
- mByteBufferOrderMethodID = NULL;
- mByteBufferAsReadOnlyBufferMethodID = NULL;
- mByteBufferPositionMethodID = NULL;
- mByteBufferLimitMethodID = NULL;
}
status_t JMediaCodec::enableOnFrameRenderedListener(jboolean enable) {
@@ -300,6 +341,12 @@ status_t JMediaCodec::configure(
mSurfaceTextureClient.clear();
}
+ constexpr int32_t CONFIGURE_FLAG_ENCODE = 1;
+ AString mime;
+ CHECK(format->findString("mime", &mime));
+ mGraphicOutput = (mime.startsWithIgnoreCase("video/") || mime.startsWithIgnoreCase("image/"))
+ && !(flags & CONFIGURE_FLAG_ENCODE);
+
return mCodec->configure(
format, mSurfaceTextureClient, crypto, descrambler, flags);
}
@@ -370,6 +417,32 @@ status_t JMediaCodec::queueSecureInputBuffer(
presentationTimeUs, flags, errorDetailMsg);
}
+status_t JMediaCodec::queueBuffer(
+ size_t index, const std::shared_ptr<C2Buffer> &buffer, int64_t timeUs,
+ uint32_t flags, const sp<AMessage> &tunings, AString *errorDetailMsg) {
+ return mCodec->queueBuffer(
+ index, buffer, timeUs, flags, tunings, errorDetailMsg);
+}
+
+status_t JMediaCodec::queueEncryptedLinearBlock(
+ size_t index,
+ const sp<hardware::HidlMemory> &buffer,
+ size_t offset,
+ const CryptoPlugin::SubSample *subSamples,
+ size_t numSubSamples,
+ const uint8_t key[16],
+ const uint8_t iv[16],
+ CryptoPlugin::Mode mode,
+ const CryptoPlugin::Pattern &pattern,
+ int64_t presentationTimeUs,
+ uint32_t flags,
+ const sp<AMessage> &tunings,
+ AString *errorDetailMsg) {
+ return mCodec->queueEncryptedBuffer(
+ index, buffer, offset, subSamples, numSubSamples, key, iv, mode, pattern,
+ presentationTimeUs, flags, tunings, errorDetailMsg);
+}
+
status_t JMediaCodec::dequeueInputBuffer(size_t *index, int64_t timeoutUs) {
return mCodec->dequeueInputBuffer(index, timeoutUs);
}
@@ -444,7 +517,7 @@ status_t JMediaCodec::getBuffers(
}
*bufArray = (jobjectArray)env->NewObjectArray(
- buffers.size(), mByteBufferClass, NULL);
+ buffers.size(), gByteBufferInfo.clazz, NULL);
if (*bufArray == NULL) {
return NO_MEMORY;
}
@@ -470,6 +543,39 @@ status_t JMediaCodec::getBuffers(
return OK;
}
+template <typename T>
+static jobject CreateByteBuffer(
+ JNIEnv *env, T *base, size_t capacity, size_t offset, size_t size,
+ bool readOnly, bool clearBuffer) {
+ jobject byteBuffer =
+ env->NewDirectByteBuffer(
+ const_cast<typename std::remove_const<T>::type *>(base),
+ capacity);
+ if (readOnly && byteBuffer != NULL) {
+ jobject readOnlyBuffer = env->CallObjectMethod(
+ byteBuffer, gByteBufferInfo.asReadOnlyBufferId);
+ env->DeleteLocalRef(byteBuffer);
+ byteBuffer = readOnlyBuffer;
+ }
+ if (byteBuffer == NULL) {
+ return nullptr;
+ }
+ jobject me = env->CallObjectMethod(
+ byteBuffer, gByteBufferInfo.orderId, gByteBufferInfo.nativeByteOrder);
+ env->DeleteLocalRef(me);
+ me = env->CallObjectMethod(
+ byteBuffer, gByteBufferInfo.limitId,
+ clearBuffer ? capacity : offset + size);
+ env->DeleteLocalRef(me);
+ me = env->CallObjectMethod(
+ byteBuffer, gByteBufferInfo.positionId,
+ clearBuffer ? 0 : offset);
+ env->DeleteLocalRef(me);
+ me = NULL;
+ return byteBuffer;
+}
+
+
// static
template <typename T>
status_t JMediaCodec::createByteBufferFromABuffer(
@@ -488,29 +594,9 @@ status_t JMediaCodec::createByteBufferFromABuffer(
return OK;
}
- jobject byteBuffer =
- env->NewDirectByteBuffer(buffer->base(), buffer->capacity());
- if (readOnly && byteBuffer != NULL) {
- jobject readOnlyBuffer = env->CallObjectMethod(
- byteBuffer, mByteBufferAsReadOnlyBufferMethodID);
- env->DeleteLocalRef(byteBuffer);
- byteBuffer = readOnlyBuffer;
- }
- if (byteBuffer == NULL) {
- return NO_MEMORY;
- }
- jobject me = env->CallObjectMethod(
- byteBuffer, mByteBufferOrderMethodID, mNativeByteOrderObj);
- env->DeleteLocalRef(me);
- me = env->CallObjectMethod(
- byteBuffer, mByteBufferLimitMethodID,
- clearBuffer ? buffer->capacity() : (buffer->offset() + buffer->size()));
- env->DeleteLocalRef(me);
- me = env->CallObjectMethod(
- byteBuffer, mByteBufferPositionMethodID,
- clearBuffer ? 0 : buffer->offset());
- env->DeleteLocalRef(me);
- me = NULL;
+ jobject byteBuffer = CreateByteBuffer(
+ env, buffer->base(), buffer->capacity(), buffer->offset(), buffer->size(),
+ readOnly, clearBuffer);
*buf = byteBuffer;
return OK;
@@ -628,6 +714,92 @@ status_t JMediaCodec::getImage(
return OK;
}
+status_t JMediaCodec::getOutputFrame(
+ JNIEnv *env, jobject frame, size_t index) const {
+ sp<MediaCodecBuffer> buffer;
+
+ status_t err = mCodec->getOutputBuffer(index, &buffer);
+ if (err != OK) {
+ return err;
+ }
+
+ if (buffer->size() > 0) {
+ // asC2Buffer clears internal reference, so set the reference again.
+ std::shared_ptr<C2Buffer> c2Buffer = buffer->asC2Buffer();
+ buffer->copy(c2Buffer);
+ if (c2Buffer) {
+ switch (c2Buffer->data().type()) {
+ case C2BufferData::LINEAR: {
+ std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
+ context->mBuffer = c2Buffer;
+ ScopedLocalRef<jobject> linearBlock{env, env->NewObject(
+ gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)};
+ env->SetLongField(
+ linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release());
+ env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
+ break;
+ }
+ case C2BufferData::GRAPHIC: {
+ std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock};
+ context->mBuffer = c2Buffer;
+ ScopedLocalRef<jobject> graphicBlock{env, env->NewObject(
+ gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)};
+ env->SetLongField(
+ graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release());
+ env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get());
+ break;
+ }
+ case C2BufferData::LINEAR_CHUNKS: [[fallthrough]];
+ case C2BufferData::GRAPHIC_CHUNKS: [[fallthrough]];
+ case C2BufferData::INVALID: [[fallthrough]];
+ default:
+ return INVALID_OPERATION;
+ }
+ } else {
+ if (!mGraphicOutput) {
+ std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
+ context->mLegacyBuffer = buffer;
+ ScopedLocalRef<jobject> linearBlock{env, env->NewObject(
+ gLinearBlockInfo.clazz, gLinearBlockInfo.ctorId)};
+ env->SetLongField(
+ linearBlock.get(), gLinearBlockInfo.contextId, (jlong)context.release());
+ env->SetObjectField(frame, gFields.outputFrameLinearBlockID, linearBlock.get());
+ } else {
+ std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock};
+ context->mLegacyBuffer = buffer;
+ ScopedLocalRef<jobject> graphicBlock{env, env->NewObject(
+ gGraphicBlockInfo.clazz, gGraphicBlockInfo.ctorId)};
+ env->SetLongField(
+ graphicBlock.get(), gGraphicBlockInfo.contextId, (jlong)context.release());
+ env->SetObjectField(frame, gFields.outputFrameGraphicBlockID, graphicBlock.get());
+ }
+ }
+ }
+
+ jobject format;
+ err = getOutputFormat(env, index, &format);
+ if (err != OK) {
+ return err;
+ }
+ env->SetObjectField(frame, gFields.outputFrameFormatID, format);
+ env->DeleteLocalRef(format);
+ format = nullptr;
+
+ sp<RefBase> obj;
+ if (buffer->meta()->findObject("changedKeys", &obj) && obj) {
+ sp<MediaCodec::WrapperObject<std::set<std::string>>> changedKeys{
+ (decltype(changedKeys.get()))obj.get()};
+ ScopedLocalRef<jobject> changedKeysObj{env, env->GetObjectField(
+ frame, gFields.outputFrameChangedKeysID)};
+ for (const std::string &key : changedKeys->value) {
+ ScopedLocalRef<jstring> keyStr{env, env->NewStringUTF(key.c_str())};
+ (void)env->CallBooleanMethod(changedKeysObj.get(), gArrayListInfo.addId, keyStr.get());
+ }
+ }
+ return OK;
+}
+
+
status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const {
AString name;
@@ -1428,6 +1600,139 @@ static void android_media_MediaCodec_queueInputBuffer(
env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
}
+struct NativeCryptoInfo {
+ NativeCryptoInfo(JNIEnv *env, jobject cryptoInfoObj)
+ : mEnv{env},
+ mIvObj{env, (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoIVID)},
+ mKeyObj{env, (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoKeyID)} {
+ mNumSubSamples = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoNumSubSamplesID);
+
+ ScopedLocalRef<jintArray> numBytesOfClearDataObj{env, (jintArray)env->GetObjectField(
+ cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID)};
+
+ ScopedLocalRef<jintArray> numBytesOfEncryptedDataObj{env, (jintArray)env->GetObjectField(
+ cryptoInfoObj, gFields.cryptoInfoNumBytesOfEncryptedDataID)};
+
+ jint jmode = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoModeID);
+ if (jmode == gCryptoModes.Unencrypted) {
+ mMode = CryptoPlugin::kMode_Unencrypted;
+ } else if (jmode == gCryptoModes.AesCtr) {
+ mMode = CryptoPlugin::kMode_AES_CTR;
+ } else if (jmode == gCryptoModes.AesCbc) {
+ mMode = CryptoPlugin::kMode_AES_CBC;
+ } else {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
+
+ ScopedLocalRef<jobject> patternObj{
+ env, env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID)};
+
+ CryptoPlugin::Pattern pattern;
+ if (patternObj.get() == nullptr) {
+ pattern.mEncryptBlocks = 0;
+ pattern.mSkipBlocks = 0;
+ } else {
+ pattern.mEncryptBlocks = env->GetIntField(
+ patternObj.get(), gFields.patternEncryptBlocksID);
+ pattern.mSkipBlocks = env->GetIntField(
+ patternObj.get(), gFields.patternSkipBlocksID);
+ }
+
+ mErr = OK;
+ if (mNumSubSamples <= 0) {
+ mErr = -EINVAL;
+ } else if (numBytesOfClearDataObj == nullptr
+ && numBytesOfEncryptedDataObj == nullptr) {
+ mErr = -EINVAL;
+ } else if (numBytesOfEncryptedDataObj != nullptr
+ && env->GetArrayLength(numBytesOfEncryptedDataObj.get()) < mNumSubSamples) {
+ mErr = -ERANGE;
+ } else if (numBytesOfClearDataObj != nullptr
+ && env->GetArrayLength(numBytesOfClearDataObj.get()) < mNumSubSamples) {
+ mErr = -ERANGE;
+ // subSamples array may silently overflow if number of samples are too large. Use
+ // INT32_MAX as maximum allocation size may be less than SIZE_MAX on some platforms
+ } else if (CC_UNLIKELY(mNumSubSamples >= (signed)(INT32_MAX / sizeof(*mSubSamples))) ) {
+ mErr = -EINVAL;
+ } else {
+ jint *numBytesOfClearData =
+ (numBytesOfClearDataObj == nullptr)
+ ? nullptr
+ : env->GetIntArrayElements(numBytesOfClearDataObj.get(), nullptr);
+
+ jint *numBytesOfEncryptedData =
+ (numBytesOfEncryptedDataObj == nullptr)
+ ? nullptr
+ : env->GetIntArrayElements(numBytesOfEncryptedDataObj.get(), nullptr);
+
+ mSubSamples = new CryptoPlugin::SubSample[mNumSubSamples];
+
+ for (jint i = 0; i < mNumSubSamples; ++i) {
+ mSubSamples[i].mNumBytesOfClearData =
+ (numBytesOfClearData == nullptr) ? 0 : numBytesOfClearData[i];
+
+ mSubSamples[i].mNumBytesOfEncryptedData =
+ (numBytesOfEncryptedData == nullptr) ? 0 : numBytesOfEncryptedData[i];
+ }
+
+ if (numBytesOfEncryptedData != nullptr) {
+ env->ReleaseIntArrayElements(
+ numBytesOfEncryptedDataObj.get(), numBytesOfEncryptedData, 0);
+ numBytesOfEncryptedData = nullptr;
+ }
+
+ if (numBytesOfClearData != nullptr) {
+ env->ReleaseIntArrayElements(
+ numBytesOfClearDataObj.get(), numBytesOfClearData, 0);
+ numBytesOfClearData = nullptr;
+ }
+ }
+
+ if (mErr == OK && mKeyObj.get() != nullptr) {
+ if (env->GetArrayLength(mKeyObj.get()) != 16) {
+ mErr = -EINVAL;
+ } else {
+ mKey = env->GetByteArrayElements(mKeyObj.get(), nullptr);
+ }
+ }
+
+ if (mErr == OK && mIvObj.get() != nullptr) {
+ if (env->GetArrayLength(mIvObj.get()) != 16) {
+ mErr = -EINVAL;
+ } else {
+ mIv = env->GetByteArrayElements(mIvObj.get(), nullptr);
+ }
+ }
+ }
+
+ ~NativeCryptoInfo() {
+ if (mIv != nullptr) {
+ mEnv->ReleaseByteArrayElements(mIvObj.get(), mIv, 0);
+ }
+
+ if (mKey != nullptr) {
+ mEnv->ReleaseByteArrayElements(mKeyObj.get(), mKey, 0);
+ }
+
+ if (mSubSamples != nullptr) {
+ delete[] mSubSamples;
+ }
+ }
+
+ JNIEnv *mEnv{nullptr};
+ ScopedLocalRef<jbyteArray> mIvObj;
+ ScopedLocalRef<jbyteArray> mKeyObj;
+ status_t mErr{OK};
+
+ CryptoPlugin::SubSample *mSubSamples{nullptr};
+ int32_t mNumSubSamples{0};
+ jbyte *mIv{nullptr};
+ jbyte *mKey{nullptr};
+ enum CryptoPlugin::Mode mMode;
+ CryptoPlugin::Pattern mPattern;
+};
+
static void android_media_MediaCodec_queueSecureInputBuffer(
JNIEnv *env,
jobject thiz,
@@ -1445,152 +1750,274 @@ static void android_media_MediaCodec_queueSecureInputBuffer(
return;
}
- jint numSubSamples =
- env->GetIntField(cryptoInfoObj, gFields.cryptoInfoNumSubSamplesID);
-
- jintArray numBytesOfClearDataObj =
- (jintArray)env->GetObjectField(
- cryptoInfoObj, gFields.cryptoInfoNumBytesOfClearDataID);
+ NativeCryptoInfo cryptoInfo{env, cryptoInfoObj};
+ AString errorDetailMsg;
- jintArray numBytesOfEncryptedDataObj =
- (jintArray)env->GetObjectField(
- cryptoInfoObj, gFields.cryptoInfoNumBytesOfEncryptedDataID);
+ status_t err = cryptoInfo.mErr;
+ if (err == OK) {
+ err = codec->queueSecureInputBuffer(
+ index, offset,
+ cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples,
+ (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv,
+ cryptoInfo.mMode,
+ cryptoInfo.mPattern,
+ timestampUs,
+ flags,
+ &errorDetailMsg);
+ }
- jbyteArray keyObj =
- (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoKeyID);
+ throwExceptionAsNecessary(
+ env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
+}
- jbyteArray ivObj =
- (jbyteArray)env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoIVID);
+static status_t ConvertKeyValueListsToAMessage(
+ JNIEnv *env, jobject keys, jobject values, sp<AMessage> *msg) {
+ static struct Fields {
+ explicit Fields(JNIEnv *env) {
+ ScopedLocalRef<jclass> clazz{env, env->FindClass("java/lang/String")};
+ CHECK(clazz.get() != NULL);
+ mStringClass = (jclass)env->NewGlobalRef(clazz.get());
- jint jmode = env->GetIntField(cryptoInfoObj, gFields.cryptoInfoModeID);
- enum CryptoPlugin::Mode mode;
- if (jmode == gCryptoModes.Unencrypted) {
- mode = CryptoPlugin::kMode_Unencrypted;
- } else if (jmode == gCryptoModes.AesCtr) {
- mode = CryptoPlugin::kMode_AES_CTR;
- } else if (jmode == gCryptoModes.AesCbc) {
- mode = CryptoPlugin::kMode_AES_CBC;
- } else {
- throwExceptionAsNecessary(env, INVALID_OPERATION);
- return;
- }
+ clazz.reset(env->FindClass("java/lang/Integer"));
+ CHECK(clazz.get() != NULL);
+ mIntegerClass = (jclass)env->NewGlobalRef(clazz.get());
- jobject patternObj = env->GetObjectField(cryptoInfoObj, gFields.cryptoInfoPatternID);
+ mIntegerValueId = env->GetMethodID(clazz.get(), "intValue", "()I");
+ CHECK(mIntegerValueId != NULL);
- CryptoPlugin::Pattern pattern;
- if (patternObj == NULL) {
- pattern.mEncryptBlocks = 0;
- pattern.mSkipBlocks = 0;
- } else {
- pattern.mEncryptBlocks = env->GetIntField(patternObj, gFields.patternEncryptBlocksID);
- pattern.mSkipBlocks = env->GetIntField(patternObj, gFields.patternSkipBlocksID);
- }
-
- status_t err = OK;
-
- CryptoPlugin::SubSample *subSamples = NULL;
- jbyte *key = NULL;
- jbyte *iv = NULL;
-
- if (numSubSamples <= 0) {
- err = -EINVAL;
- } else if (numBytesOfClearDataObj == NULL
- && numBytesOfEncryptedDataObj == NULL) {
- err = -EINVAL;
- } else if (numBytesOfEncryptedDataObj != NULL
- && env->GetArrayLength(numBytesOfEncryptedDataObj) < numSubSamples) {
- err = -ERANGE;
- } else if (numBytesOfClearDataObj != NULL
- && env->GetArrayLength(numBytesOfClearDataObj) < numSubSamples) {
- err = -ERANGE;
- // subSamples array may silently overflow if number of samples are too large. Use
- // INT32_MAX as maximum allocation size may be less than SIZE_MAX on some platforms
- } else if ( CC_UNLIKELY(numSubSamples >= (signed)(INT32_MAX / sizeof(*subSamples))) ) {
- err = -EINVAL;
- } else {
- jboolean isCopy;
+ clazz.reset(env->FindClass("java/lang/Long"));
+ CHECK(clazz.get() != NULL);
+ mLongClass = (jclass)env->NewGlobalRef(clazz.get());
- jint *numBytesOfClearData =
- (numBytesOfClearDataObj == NULL)
- ? NULL
- : env->GetIntArrayElements(numBytesOfClearDataObj, &isCopy);
+ mLongValueId = env->GetMethodID(clazz.get(), "longValue", "()J");
+ CHECK(mLongValueId != NULL);
- jint *numBytesOfEncryptedData =
- (numBytesOfEncryptedDataObj == NULL)
- ? NULL
- : env->GetIntArrayElements(numBytesOfEncryptedDataObj, &isCopy);
+ clazz.reset(env->FindClass("java/lang/Float"));
+ CHECK(clazz.get() != NULL);
+ mFloatClass = (jclass)env->NewGlobalRef(clazz.get());
- subSamples = new CryptoPlugin::SubSample[numSubSamples];
+ mFloatValueId = env->GetMethodID(clazz.get(), "floatValue", "()F");
+ CHECK(mFloatValueId != NULL);
- for (jint i = 0; i < numSubSamples; ++i) {
- subSamples[i].mNumBytesOfClearData =
- (numBytesOfClearData == NULL) ? 0 : numBytesOfClearData[i];
+ clazz.reset(env->FindClass("java/util/ArrayList"));
+ CHECK(clazz.get() != NULL);
- subSamples[i].mNumBytesOfEncryptedData =
- (numBytesOfEncryptedData == NULL)
- ? 0 : numBytesOfEncryptedData[i];
+ mByteBufferArrayId = env->GetMethodID(gByteBufferInfo.clazz, "array", "()[B");
+ CHECK(mByteBufferArrayId != NULL);
}
- if (numBytesOfEncryptedData != NULL) {
- env->ReleaseIntArrayElements(
- numBytesOfEncryptedDataObj, numBytesOfEncryptedData, 0);
- numBytesOfEncryptedData = NULL;
+ jclass mStringClass;
+ jclass mIntegerClass;
+ jmethodID mIntegerValueId;
+ jclass mLongClass;
+ jmethodID mLongValueId;
+ jclass mFloatClass;
+ jmethodID mFloatValueId;
+ jmethodID mByteBufferArrayId;
+ } sFields{env};
+
+ jint size = env->CallIntMethod(keys, gArrayListInfo.sizeId);
+ if (size != env->CallIntMethod(values, gArrayListInfo.sizeId)) {
+ return BAD_VALUE;
+ }
+
+ sp<AMessage> result{new AMessage};
+ for (jint i = 0; i < size; ++i) {
+ ScopedLocalRef<jstring> jkey{
+ env, (jstring)env->CallObjectMethod(keys, gArrayListInfo.getId, i)};
+ const char *tmp = env->GetStringUTFChars(jkey.get(), nullptr);
+ AString key;
+ if (tmp) {
+ key.setTo(tmp);
+ }
+ env->ReleaseStringUTFChars(jkey.get(), tmp);
+ if (key.empty()) {
+ return NO_MEMORY;
}
- if (numBytesOfClearData != NULL) {
- env->ReleaseIntArrayElements(
- numBytesOfClearDataObj, numBytesOfClearData, 0);
- numBytesOfClearData = NULL;
+ ScopedLocalRef<jobject> jvalue{
+ env, env->CallObjectMethod(values, gArrayListInfo.getId, i)};
+
+ if (env->IsInstanceOf(jvalue.get(), sFields.mStringClass)) {
+ const char *tmp = env->GetStringUTFChars((jstring)jvalue.get(), nullptr);
+ AString value;
+ if (tmp) {
+ value.setTo(tmp);
+ }
+ env->ReleaseStringUTFChars((jstring)jvalue.get(), tmp);
+ if (value.empty()) {
+ return NO_MEMORY;
+ }
+ result->setString(key.c_str(), value);
+ } else if (env->IsInstanceOf(jvalue.get(), sFields.mIntegerClass)) {
+ jint value = env->CallIntMethod(jvalue.get(), sFields.mIntegerValueId);
+ result->setInt32(key.c_str(), value);
+ } else if (env->IsInstanceOf(jvalue.get(), sFields.mLongClass)) {
+ jlong value = env->CallLongMethod(jvalue.get(), sFields.mLongValueId);
+ result->setInt64(key.c_str(), value);
+ } else if (env->IsInstanceOf(jvalue.get(), sFields.mFloatClass)) {
+ jfloat value = env->CallFloatMethod(jvalue.get(), sFields.mFloatValueId);
+ result->setFloat(key.c_str(), value);
+ } else if (env->IsInstanceOf(jvalue.get(), gByteBufferInfo.clazz)) {
+ jint position = env->CallIntMethod(jvalue.get(), gByteBufferInfo.positionId);
+ jint limit = env->CallIntMethod(jvalue.get(), gByteBufferInfo.limitId);
+ sp<ABuffer> buffer{new ABuffer(limit - position)};
+ void *data = env->GetDirectBufferAddress(jvalue.get());
+ if (data != nullptr) {
+ memcpy(buffer->data(),
+ static_cast<const uint8_t *>(data) + position,
+ buffer->size());
+ } else {
+ ScopedLocalRef<jbyteArray> byteArray{env, (jbyteArray)env->CallObjectMethod(
+ jvalue.get(), sFields.mByteBufferArrayId)};
+ env->GetByteArrayRegion(byteArray.get(), position, buffer->size(),
+ reinterpret_cast<jbyte *>(buffer->data()));
+ }
+ result->setBuffer(key.c_str(), buffer);
}
}
- if (err == OK && keyObj != NULL) {
- if (env->GetArrayLength(keyObj) != 16) {
- err = -EINVAL;
- } else {
- jboolean isCopy;
- key = env->GetByteArrayElements(keyObj, &isCopy);
- }
+ *msg = result;
+ return OK;
+}
+
+static void android_media_MediaCodec_native_queueLinearBlock(
+ JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
+ jint offset, jint size, jobject cryptoInfoObj,
+ jlong presentationTimeUs, jint flags, jobject keys, jobject values) {
+ ALOGV("android_media_MediaCodec_native_queueLinearBlock");
+
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == nullptr) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
}
- if (err == OK && ivObj != NULL) {
- if (env->GetArrayLength(ivObj) != 16) {
- err = -EINVAL;
- } else {
- jboolean isCopy;
- iv = env->GetByteArrayElements(ivObj, &isCopy);
+ sp<AMessage> tunings;
+ status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings);
+ if (err != OK) {
+ throwExceptionAsNecessary(env, err);
+ return;
+ }
+
+ std::shared_ptr<C2Buffer> buffer;
+ sp<hardware::HidlMemory> memory;
+ ScopedLocalRef<jobject> lock{env, env->GetObjectField(bufferObj, gLinearBlockInfo.lockId)};
+ if (env->MonitorEnter(lock.get()) == JNI_OK) {
+ if (env->GetBooleanField(bufferObj, gLinearBlockInfo.validId)) {
+ JMediaCodecLinearBlock *context =
+ (JMediaCodecLinearBlock *)env->GetLongField(bufferObj, gLinearBlockInfo.contextId);
+ if (cryptoInfoObj != nullptr) {
+ memory = context->toHidlMemory();
+ } else {
+ buffer = context->toC2Buffer(offset, size);
+ }
}
+ env->MonitorExit(lock.get());
+ } else {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
}
AString errorDetailMsg;
+ if (cryptoInfoObj != nullptr) {
+ if (!memory) {
+ throwExceptionAsNecessary(env, BAD_VALUE);
+ return;
+ }
- if (err == OK) {
- err = codec->queueSecureInputBuffer(
- index, offset,
- subSamples, numSubSamples,
- (const uint8_t *)key, (const uint8_t *)iv,
- mode,
- pattern,
- timestampUs,
+ NativeCryptoInfo cryptoInfo{env, cryptoInfoObj};
+ size_t cryptoSize = 0;
+ for (int i = 0; i < cryptoInfo.mNumSubSamples; ++i) {
+ cryptoSize += cryptoInfo.mSubSamples[i].mNumBytesOfClearData;
+ cryptoSize += cryptoInfo.mSubSamples[i].mNumBytesOfEncryptedData;
+ }
+ err = codec->queueEncryptedLinearBlock(
+ index,
+ memory,
+ offset,
+ cryptoInfo.mSubSamples, cryptoInfo.mNumSubSamples,
+ (const uint8_t *)cryptoInfo.mKey, (const uint8_t *)cryptoInfo.mIv,
+ cryptoInfo.mMode,
+ cryptoInfo.mPattern,
+ presentationTimeUs,
flags,
+ tunings,
&errorDetailMsg);
+ } else {
+ if (!buffer) {
+ throwExceptionAsNecessary(env, BAD_VALUE);
+ return;
+ }
+ err = codec->queueBuffer(
+ index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
}
+ throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str());
+}
+
+static void android_media_MediaCodec_native_queueGraphicBlock(
+ JNIEnv *env, jobject thiz, jint index, jobject bufferObj,
+ jlong presentationTimeUs, jint flags, jobject keys, jobject values) {
+ ALOGV("android_media_MediaCodec_native_queueGraphicBlock");
+
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
- if (iv != NULL) {
- env->ReleaseByteArrayElements(ivObj, iv, 0);
- iv = NULL;
+ if (codec == NULL) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
}
- if (key != NULL) {
- env->ReleaseByteArrayElements(keyObj, key, 0);
- key = NULL;
+ sp<AMessage> tunings;
+ status_t err = ConvertKeyValueListsToAMessage(env, keys, values, &tunings);
+ if (err != OK) {
+ throwExceptionAsNecessary(env, err);
+ return;
}
- delete[] subSamples;
- subSamples = NULL;
+ std::shared_ptr<C2Buffer> buffer;
+ std::shared_ptr<C2GraphicBlock> block;
+ ScopedLocalRef<jobject> lock{env, env->GetObjectField(bufferObj, gGraphicBlockInfo.lockId)};
+ if (env->MonitorEnter(lock.get()) == JNI_OK) {
+ if (env->GetBooleanField(bufferObj, gGraphicBlockInfo.validId)) {
+ JMediaCodecGraphicBlock *context = (JMediaCodecGraphicBlock *)env->GetLongField(
+ bufferObj, gGraphicBlockInfo.contextId);
+ buffer = context->mBuffer;
+ block = context->mBlock;
+ }
+ env->MonitorExit(lock.get());
+ } else {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
- throwExceptionAsNecessary(
- env, err, ACTION_CODE_FATAL, errorDetailMsg.empty() ? NULL : errorDetailMsg.c_str());
+ if (!block && !buffer) {
+ throwExceptionAsNecessary(env, BAD_VALUE);
+ return;
+ }
+ if (!buffer) {
+ buffer = C2Buffer::CreateGraphicBuffer(block->share(block->crop(), C2Fence{}));
+ }
+ AString errorDetailMsg;
+ err = codec->queueBuffer(index, buffer, presentationTimeUs, flags, tunings, &errorDetailMsg);
+ throwExceptionAsNecessary(env, err, ACTION_CODE_FATAL, errorDetailMsg.c_str());
+}
+
+static void android_media_MediaCodec_native_getOutputFrame(
+ JNIEnv *env, jobject thiz, jobject frame, jint index) {
+ ALOGV("android_media_MediaCodec_native_getOutputFrame");
+
+ sp<JMediaCodec> codec = getMediaCodec(env, thiz);
+
+ if (codec == NULL) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return;
+ }
+
+ status_t err = codec->getOutputFrame(env, frame, index);
+ if (err != OK) {
+ throwExceptionAsNecessary(env, err);
+ }
}
static jint android_media_MediaCodec_dequeueInputBuffer(
@@ -1991,6 +2418,31 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) {
gFields.patternSkipBlocksID = env->GetFieldID(clazz.get(), "mSkipBlocks", "I");
CHECK(gFields.patternSkipBlocksID != NULL);
+ clazz.reset(env->FindClass("android/media/MediaCodec$QueueRequest"));
+ CHECK(clazz.get() != NULL);
+
+ gFields.queueRequestIndexID = env->GetFieldID(clazz.get(), "mIndex", "I");
+ CHECK(gFields.queueRequestIndexID != NULL);
+
+ clazz.reset(env->FindClass("android/media/MediaCodec$OutputFrame"));
+ CHECK(clazz.get() != NULL);
+
+ gFields.outputFrameLinearBlockID =
+ env->GetFieldID(clazz.get(), "mLinearBlock", "Landroid/media/MediaCodec$LinearBlock;");
+ CHECK(gFields.outputFrameLinearBlockID != NULL);
+
+ gFields.outputFrameGraphicBlockID =
+ env->GetFieldID(clazz.get(), "mGraphicBlock", "Landroid/media/MediaCodec$GraphicBlock;");
+ CHECK(gFields.outputFrameGraphicBlockID != NULL);
+
+ gFields.outputFrameChangedKeysID =
+ env->GetFieldID(clazz.get(), "mChangedKeys", "Ljava/util/ArrayList;");
+ CHECK(gFields.outputFrameChangedKeysID != NULL);
+
+ gFields.outputFrameFormatID =
+ env->GetFieldID(clazz.get(), "mFormat", "Landroid/media/MediaFormat;");
+ CHECK(gFields.outputFrameFormatID != NULL);
+
clazz.reset(env->FindClass("android/media/MediaCodec$CryptoException"));
CHECK(clazz.get() != NULL);
@@ -2105,6 +2557,96 @@ static void android_media_MediaCodec_native_init(JNIEnv *env) {
field = env->GetFieldID(clazz.get(), "level", "I");
CHECK(field != NULL);
gCodecInfo.levelField = field;
+
+ clazz.reset(env->FindClass("java/nio/ByteBuffer"));
+ CHECK(clazz.get() != NULL);
+ gByteBufferInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+ 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);
+
+ ScopedLocalRef<jobject> nativeByteOrderObj{
+ env, env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID)};
+ gByteBufferInfo.nativeByteOrder = env->NewGlobalRef(nativeByteOrderObj.get());
+ CHECK(gByteBufferInfo.nativeByteOrder != NULL);
+ nativeByteOrderObj.reset();
+
+ gByteBufferInfo.orderId = env->GetMethodID(
+ clazz.get(),
+ "order",
+ "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");
+ CHECK(gByteBufferInfo.orderId != NULL);
+
+ gByteBufferInfo.asReadOnlyBufferId = env->GetMethodID(
+ clazz.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");
+ CHECK(gByteBufferInfo.asReadOnlyBufferId != NULL);
+
+ gByteBufferInfo.positionId = env->GetMethodID(
+ clazz.get(), "position", "(I)Ljava/nio/Buffer;");
+ CHECK(gByteBufferInfo.positionId != NULL);
+
+ gByteBufferInfo.limitId = env->GetMethodID(
+ clazz.get(), "limit", "(I)Ljava/nio/Buffer;");
+ CHECK(gByteBufferInfo.limitId != NULL);
+
+ clazz.reset(env->FindClass("java/util/ArrayList"));
+ CHECK(clazz.get() != NULL);
+
+ gArrayListInfo.sizeId = env->GetMethodID(clazz.get(), "size", "()I");
+ CHECK(gArrayListInfo.sizeId != NULL);
+
+ gArrayListInfo.getId = env->GetMethodID(clazz.get(), "get", "(I)Ljava/lang/Object;");
+ CHECK(gArrayListInfo.getId != NULL);
+
+ gArrayListInfo.addId = env->GetMethodID(clazz.get(), "add", "(Ljava/lang/Object;)Z");
+ CHECK(gArrayListInfo.addId != NULL);
+
+ clazz.reset(env->FindClass("android/media/MediaCodec$LinearBlock"));
+ CHECK(clazz.get() != NULL);
+
+ gLinearBlockInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+ gLinearBlockInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+ CHECK(gLinearBlockInfo.ctorId != NULL);
+
+ gLinearBlockInfo.setInternalStateId = env->GetMethodID(
+ clazz.get(), "setInternalStateLocked", "(JZ)V");
+ CHECK(gLinearBlockInfo.setInternalStateId != NULL);
+
+ gLinearBlockInfo.contextId = env->GetFieldID(clazz.get(), "mNativeContext", "J");
+ CHECK(gLinearBlockInfo.contextId != NULL);
+
+ gLinearBlockInfo.validId = env->GetFieldID(clazz.get(), "mValid", "Z");
+ CHECK(gLinearBlockInfo.validId != NULL);
+
+ gLinearBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;");
+ CHECK(gLinearBlockInfo.lockId != NULL);
+
+ clazz.reset(env->FindClass("android/media/MediaCodec$GraphicBlock"));
+ CHECK(clazz.get() != NULL);
+
+ gGraphicBlockInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+ gGraphicBlockInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+ CHECK(gGraphicBlockInfo.ctorId != NULL);
+
+ gGraphicBlockInfo.setInternalStateId = env->GetMethodID(
+ clazz.get(), "setInternalStateLocked", "(JZ)V");
+ CHECK(gGraphicBlockInfo.setInternalStateId != NULL);
+
+ gGraphicBlockInfo.contextId = env->GetFieldID(clazz.get(), "mNativeContext", "J");
+ CHECK(gGraphicBlockInfo.contextId != NULL);
+
+ gGraphicBlockInfo.validId = env->GetFieldID(clazz.get(), "mValid", "Z");
+ CHECK(gGraphicBlockInfo.validId != NULL);
+
+ gGraphicBlockInfo.lockId = env->GetFieldID(clazz.get(), "mLock", "Ljava/lang/Object;");
+ CHECK(gGraphicBlockInfo.lockId != NULL);
}
static void android_media_MediaCodec_native_setup(
@@ -2155,6 +2697,345 @@ static void android_media_MediaCodec_native_finalize(
android_media_MediaCodec_release(env, thiz);
}
+// MediaCodec.LinearBlock
+
+static jobject android_media_MediaCodec_LinearBlock_native_map(
+ JNIEnv *env, jobject thiz) {
+ JMediaCodecLinearBlock *context =
+ (JMediaCodecLinearBlock *)env->GetLongField(thiz, gLinearBlockInfo.contextId);
+ if (context->mBuffer) {
+ std::shared_ptr<C2Buffer> buffer = context->mBuffer;
+ if (!context->mReadonlyMapping) {
+ const C2BufferData data = buffer->data();
+ if (data.type() != C2BufferData::LINEAR) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ if (data.linearBlocks().size() != 1u) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ C2ConstLinearBlock block = data.linearBlocks().front();
+ context->mReadonlyMapping =
+ std::make_shared<C2ReadView>(block.map().get());
+ }
+ return CreateByteBuffer(
+ env,
+ context->mReadonlyMapping->data(), // base
+ context->mReadonlyMapping->capacity(), // capacity
+ 0u, // offset
+ context->mReadonlyMapping->capacity(), // size
+ true, // readOnly
+ true /* clearBuffer */);
+ } else if (context->mBlock) {
+ std::shared_ptr<C2LinearBlock> block = context->mBlock;
+ if (!context->mReadWriteMapping) {
+ context->mReadWriteMapping =
+ std::make_shared<C2WriteView>(block->map().get());
+ }
+ return CreateByteBuffer(
+ env,
+ context->mReadWriteMapping->base(),
+ context->mReadWriteMapping->capacity(),
+ context->mReadWriteMapping->offset(),
+ context->mReadWriteMapping->size(),
+ false, // readOnly
+ true /* clearBuffer */);
+ } else if (context->mLegacyBuffer) {
+ return CreateByteBuffer(
+ env,
+ context->mLegacyBuffer->base(),
+ context->mLegacyBuffer->capacity(),
+ context->mLegacyBuffer->offset(),
+ context->mLegacyBuffer->size(),
+ true, // readOnly
+ true /* clearBuffer */);
+ } else if (context->mHeap) {
+ return CreateByteBuffer(
+ env,
+ static_cast<uint8_t *>(context->mHeap->getBase()) + context->mHeap->getOffset(),
+ context->mHeap->getSize(),
+ 0,
+ context->mHeap->getSize(),
+ false, // readOnly
+ true /* clearBuffer */);
+ }
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+}
+
+static void android_media_MediaCodec_LinearBlock_native_recycle(
+ JNIEnv *env, jobject thiz) {
+ JMediaCodecLinearBlock *context =
+ (JMediaCodecLinearBlock *)env->GetLongField(thiz, gLinearBlockInfo.contextId);
+ env->CallVoidMethod(thiz, gLinearBlockInfo.setInternalStateId, 0, false);
+ delete context;
+}
+
+static void PopulateNamesVector(
+ JNIEnv *env, jobjectArray codecNames, std::vector<std::string> *names) {
+ jsize length = env->GetArrayLength(codecNames);
+ for (jsize i = 0; i < length; ++i) {
+ jstring jstr = static_cast<jstring>(env->GetObjectArrayElement(codecNames, i));
+ if (jstr == nullptr) {
+ // null entries are ignored
+ continue;
+ }
+ const char *cstr = env->GetStringUTFChars(jstr, nullptr);
+ if (cstr == nullptr) {
+ throwExceptionAsNecessary(env, BAD_VALUE);
+ return;
+ }
+ names->emplace_back(cstr);
+ env->ReleaseStringUTFChars(jstr, cstr);
+ }
+}
+
+static void android_media_MediaCodec_LinearBlock_native_obtain(
+ JNIEnv *env, jobject thiz, jint capacity, jobjectArray codecNames) {
+ std::unique_ptr<JMediaCodecLinearBlock> context{new JMediaCodecLinearBlock};
+ std::vector<std::string> names;
+ PopulateNamesVector(env, codecNames, &names);
+ bool hasSecure = false;
+ bool hasNonSecure = false;
+ for (const std::string &name : names) {
+ if (name.length() >= 7 && name.substr(name.length() - 7) == ".secure") {
+ hasSecure = true;
+ } else {
+ hasNonSecure = true;
+ }
+ }
+ if (hasSecure && !hasNonSecure) {
+ context->mHeap = new MemoryHeapBase(capacity);
+ context->mMemory = hardware::fromHeap(context->mHeap);
+ } else {
+ context->mBlock = MediaCodec::FetchLinearBlock(capacity, names);
+ if (!context->mBlock) {
+ jniThrowException(env, "java/io/IOException", nullptr);
+ return;
+ }
+ }
+ env->CallVoidMethod(
+ thiz,
+ gLinearBlockInfo.setInternalStateId,
+ (jlong)context.release(),
+ true /* isMappable */);
+}
+
+static jboolean android_media_MediaCodec_LinearBlock_checkCompatible(
+ JNIEnv *env, jobjectArray codecNames) {
+ std::vector<std::string> names;
+ PopulateNamesVector(env, codecNames, &names);
+ bool isCompatible = false;
+ bool hasSecure = false;
+ bool hasNonSecure = false;
+ for (const std::string &name : names) {
+ if (name.length() >= 7 && name.substr(name.length() - 7) == ".secure") {
+ hasSecure = true;
+ } else {
+ hasNonSecure = true;
+ }
+ }
+ if (hasSecure && hasNonSecure) {
+ return false;
+ }
+ status_t err = MediaCodec::CanFetchLinearBlock(names, &isCompatible);
+ if (err != OK) {
+ throwExceptionAsNecessary(env, err);
+ }
+ return isCompatible;
+}
+
+// MediaCodec.GraphicBlock
+
+template <class T>
+static jobject CreateImage(JNIEnv *env, const std::shared_ptr<T> &view) {
+ bool readOnly = std::is_const<T>::value;
+ const C2PlanarLayout layout = view->layout();
+ jint format = HAL_PIXEL_FORMAT_YCBCR_420_888;
+ switch (layout.type) {
+ case C2PlanarLayout::TYPE_YUV: {
+ if (layout.numPlanes != 3) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ const C2PlaneInfo & yPlane = layout.planes[C2PlanarLayout::PLANE_Y];
+ const C2PlaneInfo & uPlane = layout.planes[C2PlanarLayout::PLANE_U];
+ const C2PlaneInfo & vPlane = layout.planes[C2PlanarLayout::PLANE_V];
+ if (yPlane.rowSampling != 1 || yPlane.colSampling != 1) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ if (uPlane.rowSampling != vPlane.rowSampling
+ || uPlane.colSampling != vPlane.colSampling) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ if (uPlane.rowSampling == 2 && uPlane.colSampling == 2) {
+ format = HAL_PIXEL_FORMAT_YCBCR_420_888;
+ break;
+ } else if (uPlane.rowSampling == 1 && uPlane.colSampling == 2) {
+ format = HAL_PIXEL_FORMAT_YCBCR_422_888;
+ break;
+ } else if (uPlane.rowSampling == 1 && uPlane.colSampling == 1) {
+ format = HAL_PIXEL_FORMAT_YCBCR_444_888;
+ break;
+ }
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ case C2PlanarLayout::TYPE_RGB: {
+ if (layout.numPlanes != 3) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ format = HAL_PIXEL_FORMAT_FLEX_RGB_888;
+ break;
+ }
+ case C2PlanarLayout::TYPE_RGBA: {
+ if (layout.numPlanes != 4) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ format = HAL_PIXEL_FORMAT_FLEX_RGBA_8888;
+ break;
+ }
+ case C2PlanarLayout::TYPE_YUVA:
+ [[fallthrough]];
+ default:
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+
+ ScopedLocalRef<jclass> planeClazz(
+ env, env->FindClass("android/media/MediaCodec$MediaImage$MediaPlane"));
+ ScopedLocalRef<jobjectArray> planeArray{
+ env, env->NewObjectArray(layout.numPlanes, planeClazz.get(), NULL)};
+ CHECK(planeClazz.get() != NULL);
+ jmethodID planeConstructID = env->GetMethodID(planeClazz.get(), "<init>",
+ "([Ljava/nio/ByteBuffer;IIIII)V");
+
+ // plane indices are happened to be Y-U-V and R-G-B(-A) order.
+ for (uint32_t i = 0; i < layout.numPlanes; ++i) {
+ const C2PlaneInfo &plane = layout.planes[i];
+ if (plane.rowInc < 0 || plane.colInc < 0) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ ssize_t minOffset = plane.minOffset(view->width(), view->height());
+ ssize_t maxOffset = plane.maxOffset(view->width(), view->height());
+ ScopedLocalRef<jobject> byteBuffer{env, CreateByteBuffer(
+ env,
+ view->data()[plane.rootIx] + plane.offset + minOffset,
+ maxOffset - minOffset + 1,
+ 0,
+ maxOffset - minOffset + 1,
+ readOnly,
+ true)};
+
+ ScopedLocalRef<jobject> jPlane{env, env->NewObject(
+ planeClazz.get(), planeConstructID,
+ byteBuffer.get(), plane.rowInc, plane.colInc)};
+ }
+
+ ScopedLocalRef<jclass> imageClazz(
+ env, env->FindClass("android/media/MediaCodec$MediaImage"));
+ CHECK(imageClazz.get() != NULL);
+
+ jmethodID imageConstructID = env->GetMethodID(imageClazz.get(), "<init>",
+ "([Landroid/media/Image$Plane;IIIZJIILandroid/graphics/Rect;)V");
+
+ jobject img = env->NewObject(imageClazz.get(), imageConstructID,
+ planeArray.get(),
+ view->width(),
+ view->height(),
+ format,
+ (jboolean)readOnly /* readOnly */,
+ (jlong)0 /* timestamp */,
+ (jint)0 /* xOffset */, (jint)0 /* yOffset */, nullptr /* cropRect */);
+
+ // if MediaImage creation fails, return null
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return nullptr;
+ }
+
+ return img;
+}
+
+static jobject android_media_MediaCodec_GraphicBlock_native_map(
+ JNIEnv *env, jobject thiz) {
+ JMediaCodecGraphicBlock *context =
+ (JMediaCodecGraphicBlock *)env->GetLongField(thiz, gGraphicBlockInfo.contextId);
+ if (context->mBuffer) {
+ std::shared_ptr<C2Buffer> buffer = context->mBuffer;
+ if (!context->mReadonlyMapping) {
+ const C2BufferData data = buffer->data();
+ if (data.type() != C2BufferData::GRAPHIC) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ if (data.graphicBlocks().size() != 1u) {
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+ }
+ C2ConstGraphicBlock block = data.graphicBlocks().front();
+ context->mReadonlyMapping =
+ std::make_shared<const C2GraphicView>(block.map().get());
+ }
+ return CreateImage(env, context->mReadonlyMapping);
+ } else if (context->mBlock) {
+ std::shared_ptr<C2GraphicBlock> block = context->mBlock;
+ if (!context->mReadWriteMapping) {
+ context->mReadWriteMapping =
+ std::make_shared<C2GraphicView>(block->map().get());
+ }
+ return CreateImage(env, context->mReadWriteMapping);
+ } else if (context->mLegacyBuffer) {
+ }
+ throwExceptionAsNecessary(env, INVALID_OPERATION);
+ return nullptr;
+}
+
+static void android_media_MediaCodec_GraphicBlock_native_recycle(
+ JNIEnv *env, jobject thiz) {
+ JMediaCodecGraphicBlock *context =
+ (JMediaCodecGraphicBlock *)env->GetLongField(thiz, gGraphicBlockInfo.contextId);
+ env->CallVoidMethod(thiz, gGraphicBlockInfo.setInternalStateId, 0, false /* isMappable */);
+ delete context;
+}
+
+static void android_media_MediaCodec_GraphicBlock_native_obtain(
+ JNIEnv *env, jobject thiz,
+ jint width, jint height, jint format, jlong usage, jobjectArray codecNames) {
+ std::unique_ptr<JMediaCodecGraphicBlock> context{new JMediaCodecGraphicBlock};
+ std::vector<std::string> names;
+ PopulateNamesVector(env, codecNames, &names);
+ context->mBlock = MediaCodec::FetchGraphicBlock(width, height, format, usage, names);
+ if (!context->mBlock) {
+ jniThrowException(env, "java/io/IOException", nullptr);
+ return;
+ }
+ env->CallVoidMethod(
+ thiz,
+ gGraphicBlockInfo.setInternalStateId,
+ (jlong)context.release(),
+ true /*isMappable */);
+}
+
+static jboolean android_media_MediaCodec_GraphicBlock_checkCompatible(
+ JNIEnv *env, jobjectArray codecNames) {
+ std::vector<std::string> names;
+ PopulateNamesVector(env, codecNames, &names);
+ bool isCompatible = false;
+ status_t err = MediaCodec::CanFetchGraphicBlock(names, &isCompatible);
+ if (err != OK) {
+ throwExceptionAsNecessary(env, err);
+ }
+ return isCompatible;
+}
+
static const JNINativeMethod gMethods[] = {
{ "native_release", "()V", (void *)android_media_MediaCodec_release },
@@ -2200,6 +3081,19 @@ static const JNINativeMethod gMethods[] = {
{ "native_queueSecureInputBuffer", "(IILandroid/media/MediaCodec$CryptoInfo;JI)V",
(void *)android_media_MediaCodec_queueSecureInputBuffer },
+ { "native_queueLinearBlock",
+ "(ILandroid/media/MediaCodec$LinearBlock;IILandroid/media/MediaCodec$CryptoInfo;JI"
+ "Ljava/util/ArrayList;Ljava/util/ArrayList;)V",
+ (void *)android_media_MediaCodec_native_queueLinearBlock },
+
+ { "native_queueGraphicBlock",
+ "(ILandroid/media/MediaCodec$GraphicBlock;JILjava/util/ArrayList;Ljava/util/ArrayList;)V",
+ (void *)android_media_MediaCodec_native_queueGraphicBlock },
+
+ { "native_getOutputFrame",
+ "(Landroid/media/MediaCodec$OutputFrame;I)V",
+ (void *)android_media_MediaCodec_native_getOutputFrame },
+
{ "native_dequeueInputBuffer", "(J)I",
(void *)android_media_MediaCodec_dequeueInputBuffer },
@@ -2254,7 +3148,50 @@ static const JNINativeMethod gMethods[] = {
(void *)android_media_MediaCodec_native_finalize },
};
+static const JNINativeMethod gLinearBlockMethods[] = {
+ { "native_map", "()Ljava/nio/ByteBuffer;",
+ (void *)android_media_MediaCodec_LinearBlock_native_map },
+
+ { "native_recycle", "()V",
+ (void *)android_media_MediaCodec_LinearBlock_native_recycle },
+
+ { "native_obtain", "(I[Ljava/lang/String;)V",
+ (void *)android_media_MediaCodec_LinearBlock_native_obtain },
+
+ { "native_checkCompatible", "([Ljava/lang/String;)Z",
+ (void *)android_media_MediaCodec_LinearBlock_checkCompatible },
+};
+
+static const JNINativeMethod gGraphicBlockMethods[] = {
+ { "native_map", "()Landroid/media/Image;",
+ (void *)android_media_MediaCodec_GraphicBlock_native_map },
+
+ { "native_recycle", "()V",
+ (void *)android_media_MediaCodec_GraphicBlock_native_recycle },
+
+ { "native_obtain", "(IIIJ[Ljava/lang/String;)V",
+ (void *)android_media_MediaCodec_GraphicBlock_native_obtain },
+
+ { "native_checkCompatible", "([Ljava/lang/String;)Z",
+ (void *)android_media_MediaCodec_GraphicBlock_checkCompatible },
+};
+
int register_android_media_MediaCodec(JNIEnv *env) {
- return AndroidRuntime::registerNativeMethods(env,
+ int result = AndroidRuntime::registerNativeMethods(env,
"android/media/MediaCodec", gMethods, NELEM(gMethods));
+ if (result != JNI_OK) {
+ return result;
+ }
+ result = AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaCodec$LinearBlock",
+ gLinearBlockMethods,
+ NELEM(gLinearBlockMethods));
+ if (result != JNI_OK) {
+ return result;
+ }
+ result = AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaCodec$GraphicBlock",
+ gGraphicBlockMethods,
+ NELEM(gGraphicBlockMethods));
+ return result;
}
diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h
index ce1c805b6366..1d12e776d43e 100644
--- a/media/jni/android_media_MediaCodec.h
+++ b/media/jni/android_media_MediaCodec.h
@@ -27,6 +27,8 @@
#include <media/stagefright/foundation/AHandler.h>
#include <utils/Errors.h>
+class C2Buffer;
+
namespace android {
struct ABuffer;
@@ -39,6 +41,7 @@ struct MediaCodec;
struct PersistentSurface;
class Surface;
namespace hardware {
+class HidlMemory;
namespace cas {
namespace native {
namespace V1_0 {
@@ -97,6 +100,26 @@ struct JMediaCodec : public AHandler {
uint32_t flags,
AString *errorDetailMsg);
+ status_t queueBuffer(
+ size_t index, const std::shared_ptr<C2Buffer> &buffer,
+ int64_t timeUs, uint32_t flags, const sp<AMessage> &tunings,
+ AString *errorDetailMsg);
+
+ status_t queueEncryptedLinearBlock(
+ size_t index,
+ const sp<hardware::HidlMemory> &buffer,
+ size_t offset,
+ const CryptoPlugin::SubSample *subSamples,
+ size_t numSubSamples,
+ const uint8_t key[16],
+ const uint8_t iv[16],
+ CryptoPlugin::Mode mode,
+ const CryptoPlugin::Pattern &pattern,
+ int64_t presentationTimeUs,
+ uint32_t flags,
+ const sp<AMessage> &tunings,
+ AString *errorDetailMsg);
+
status_t dequeueInputBuffer(size_t *index, int64_t timeoutUs);
status_t dequeueOutputBuffer(
@@ -120,6 +143,9 @@ struct JMediaCodec : public AHandler {
status_t getImage(
JNIEnv *env, bool input, size_t index, jobject *image) const;
+ status_t getOutputFrame(
+ JNIEnv *env, jobject frame, size_t index) const;
+
status_t getName(JNIEnv *env, jstring *name) const;
status_t getCodecInfo(JNIEnv *env, jobject *codecInfo) const;
@@ -147,17 +173,10 @@ private:
jweak mObject;
sp<Surface> mSurfaceTextureClient;
- // java objects cached
- jclass mByteBufferClass;
- jobject mNativeByteOrderObj;
- jmethodID mByteBufferOrderMethodID;
- jmethodID mByteBufferPositionMethodID;
- jmethodID mByteBufferLimitMethodID;
- jmethodID mByteBufferAsReadOnlyBufferMethodID;
-
sp<ALooper> mLooper;
sp<MediaCodec> mCodec;
AString mNameAtCreation;
+ bool mGraphicOutput{false};
std::once_flag mReleaseFlag;
sp<AMessage> mCallbackNotification;
@@ -170,8 +189,6 @@ private:
JNIEnv *env, bool readOnly, bool clearBuffer, const sp<T> &buffer,
jobject *buf) const;
- void cacheJavaObjects(JNIEnv *env);
- void deleteJavaObjects(JNIEnv *env);
void handleCallback(const sp<AMessage> &msg);
void handleFrameRenderedNotification(const sp<AMessage> &msg);