Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9bd8b1fc authored by Lajos Molnar's avatar Lajos Molnar Committed by Android (Google) Code Review
Browse files

Merge "Implement MediaCodec.getImage methods" into lmp-dev

parents 7d93e279 7de28d34
Loading
Loading
Loading
Loading
+175 −0
Original line number Diff line number Diff line
@@ -16,7 +16,10 @@

package android.media;

import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.media.Image;
import android.media.Image.Plane;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaCrypto;
@@ -29,6 +32,7 @@ import android.view.Surface;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.util.Arrays;
import java.util.Map;

@@ -1537,4 +1541,175 @@ final public class MediaCodec {
    }

    private long mNativeContext;

    /** @hide */
    public static class MediaImage extends Image {
        private final boolean mIsReadOnly;
        private boolean mIsValid;
        private final int mWidth;
        private final int mHeight;
        private final int mFormat;
        private long mTimestamp;
        private final Plane[] mPlanes;
        private final ByteBuffer mBuffer;
        private final ByteBuffer mInfo;
        private final int mXOffset;
        private final int mYOffset;

        private final static int TYPE_YUV = 1;

        public int getFormat() {
            checkValid();
            return mFormat;
        }

        public int getHeight() {
            checkValid();
            return mHeight;
        }

        public int getWidth() {
            checkValid();
            return mWidth;
        }

        public long getTimestamp() {
            checkValid();
            return mTimestamp;
        }

        public Plane[] getPlanes() {
            checkValid();
            return Arrays.copyOf(mPlanes, mPlanes.length);
        }

        public void close() {
            if (mIsValid) {
                java.nio.NioUtils.freeDirectBuffer(mBuffer);
                mIsValid = false;
            }
        }

        /**
         * Set the crop rectangle associated with this frame.
         * <p>
         * The crop rectangle specifies the region of valid pixels in the image,
         * using coordinates in the largest-resolution plane.
         */
        public void setCropRect(Rect cropRect) {
            if (mIsReadOnly) {
                throw new ReadOnlyBufferException();
            }
            super.setCropRect(cropRect);
        }

        private void checkValid() {
            if (!mIsValid) {
                throw new IllegalStateException("Image is already released");
            }
        }

        private int readInt(ByteBuffer buffer, boolean asLong) {
            if (asLong) {
                return (int)buffer.getLong();
            } else {
                return buffer.getInt();
            }
        }

        public MediaImage(
                ByteBuffer buffer, ByteBuffer info, boolean readOnly,
                long timestamp, int xOffset, int yOffset, Rect cropRect) {
            mFormat = ImageFormat.YUV_420_888;
            mTimestamp = timestamp;
            mIsValid = true;
            mIsReadOnly = buffer.isReadOnly();
            mBuffer = buffer.duplicate();
            if (cropRect != null) {
                cropRect.offset(-xOffset, -yOffset);
            }
            mCropRect = cropRect;

            // save offsets and info
            mXOffset = xOffset;
            mYOffset = yOffset;
            mInfo = info;

            // read media-info.  the size of media info can be 80 or 156 depending on
            // whether it was created on a 32- or 64-bit process.  See MediaImage
            if (info.remaining() == 80 || info.remaining() == 156) {
                boolean sizeIsLong = info.remaining() == 156;
                int type = info.getInt();
                if (type != TYPE_YUV) {
                    throw new UnsupportedOperationException("unsupported type: " + type);
                }
                int numPlanes = readInt(info, sizeIsLong);
                if (numPlanes != 3) {
                    throw new RuntimeException("unexpected number of planes: " + numPlanes);
                }
                mWidth = readInt(info, sizeIsLong);
                mHeight = readInt(info, sizeIsLong);
                if (mWidth < 1 || mHeight < 1) {
                    throw new UnsupportedOperationException(
                            "unsupported size: " + mWidth + "x" + mHeight);
                }
                int bitDepth = readInt(info, sizeIsLong);
                if (bitDepth != 8) {
                    throw new UnsupportedOperationException("unsupported bit depth: " + bitDepth);
                }
                mPlanes = new MediaPlane[numPlanes];
                for (int ix = 0; ix < numPlanes; ix++) {
                    int planeOffset = readInt(info, sizeIsLong);
                    int colInc = readInt(info, sizeIsLong);
                    int rowInc = readInt(info, sizeIsLong);
                    int horiz = readInt(info, sizeIsLong);
                    int vert = readInt(info, sizeIsLong);
                    if (horiz != vert || horiz != (ix == 0 ? 1 : 2)) {
                        throw new UnsupportedOperationException("unexpected subsampling: "
                                + horiz + "x" + vert + " on plane " + ix);
                    }

                    buffer.clear();
                    buffer.position(mBuffer.position() + planeOffset
                            + (xOffset / horiz) * colInc + (yOffset / vert) * rowInc);
                    buffer.limit(buffer.position() + Utils.divUp(bitDepth, 8)
                            + (mHeight / vert - 1) * rowInc + (mWidth / horiz - 1) * colInc);
                    mPlanes[ix] = new MediaPlane(buffer.slice(), rowInc, colInc);
                }
            } else {
                throw new UnsupportedOperationException(
                        "unsupported info length: " + info.remaining());
            }
        }

        private class MediaPlane extends Plane {
            public MediaPlane(ByteBuffer buffer, int rowInc, int colInc) {
                mData = buffer;
                mRowInc = rowInc;
                mColInc = colInc;
            }

            @Override
            public int getRowStride() {
                checkValid();
                return mRowInc;
            }

            @Override
            public int getPixelStride() {
                checkValid();
                return mColInc;
            }

            @Override
            public ByteBuffer getBuffer() {
                checkValid();
                return mData;
            }

            private final int mRowInc;
            private final int mColInc;
            private final ByteBuffer mData;
        }
    }
}
+163 −132
Original line number Diff line number Diff line
@@ -90,6 +90,8 @@ JMediaCodec::JMediaCodec(
    mClass = (jclass)env->NewGlobalRef(clazz);
    mObject = env->NewWeakGlobalRef(thiz);

    cacheJavaObjects(env);

    mLooper = new ALooper;
    mLooper->setName("MediaCodec_looper");

@@ -105,6 +107,45 @@ JMediaCodec::JMediaCodec(
    }
}

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 mCodec != NULL ? OK : NO_INIT;
}
@@ -148,6 +189,19 @@ 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::setCallback(jobject cb) {
@@ -298,177 +352,89 @@ status_t JMediaCodec::getBuffers(
        return err;
    }

    ScopedLocalRef<jclass> byteBufferClass(
            env, env->FindClass("java/nio/ByteBuffer"));

    CHECK(byteBufferClass.get() != NULL);

    jmethodID orderID = env->GetMethodID(
            byteBufferClass.get(),
            "order",
            "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");

    CHECK(orderID != NULL);

    jmethodID asReadOnlyBufferID = env->GetMethodID(
            byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");

    CHECK(asReadOnlyBufferID != NULL);

    ScopedLocalRef<jclass> byteOrderClass(
            env, env->FindClass("java/nio/ByteOrder"));

    CHECK(byteOrderClass.get() != NULL);

    jmethodID nativeOrderID = env->GetStaticMethodID(
            byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;");
    CHECK(nativeOrderID != NULL);

    jobject nativeByteOrderObj =
        env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID);
    CHECK(nativeByteOrderObj != NULL);

    *bufArray = (jobjectArray)env->NewObjectArray(
            buffers.size(), byteBufferClass.get(), NULL);
            buffers.size(), mByteBufferClass, NULL);
    if (*bufArray == NULL) {
        env->DeleteLocalRef(nativeByteOrderObj);
        return NO_MEMORY;
    }

    for (size_t i = 0; i < buffers.size(); ++i) {
        const sp<ABuffer> &buffer = buffers.itemAt(i);

        // if this is an ABuffer that doesn't actually hold any accessible memory,
        // use a null ByteBuffer
        if (buffer->base() == NULL) {
            continue;
        }
        jobject byteBuffer =
            env->NewDirectByteBuffer(
                buffer->base(),
                buffer->capacity());
        if (!input && byteBuffer != NULL) {
            jobject readOnlyBuffer = env->CallObjectMethod(
                    byteBuffer, asReadOnlyBufferID);
            env->DeleteLocalRef(byteBuffer);
            byteBuffer = readOnlyBuffer;
        }
        if (byteBuffer == NULL) {
            env->DeleteLocalRef(nativeByteOrderObj);
            return NO_MEMORY;
        jobject byteBuffer = NULL;
        err = createByteBufferFromABuffer(
                env, !input /* readOnly */, true /* clearBuffer */, buffer, &byteBuffer);
        if (err != OK) {
            return err;
        }
        jobject me = env->CallObjectMethod(
                byteBuffer, orderID, nativeByteOrderObj);
        env->DeleteLocalRef(me);
        me = NULL;

        if (byteBuffer != NULL) {
            env->SetObjectArrayElement(
                    *bufArray, i, byteBuffer);

            env->DeleteLocalRef(byteBuffer);
            byteBuffer = NULL;
        }

    env->DeleteLocalRef(nativeByteOrderObj);
    nativeByteOrderObj = NULL;

    return OK;
    }

status_t JMediaCodec::getBuffer(
        JNIEnv *env, bool input, size_t index, jobject *buf) const {
    sp<ABuffer> buffer;

    status_t err =
        input
            ? mCodec->getInputBuffer(index, &buffer)
            : mCodec->getOutputBuffer(index, &buffer);

    if (err != OK) {
        return err;
    return OK;
}

    ScopedLocalRef<jclass> byteBufferClass(
            env, env->FindClass("java/nio/ByteBuffer"));

    CHECK(byteBufferClass.get() != NULL);

    jmethodID orderID = env->GetMethodID(
            byteBufferClass.get(),
            "order",
            "(Ljava/nio/ByteOrder;)Ljava/nio/ByteBuffer;");

    CHECK(orderID != NULL);

    jmethodID asReadOnlyBufferID = env->GetMethodID(
            byteBufferClass.get(), "asReadOnlyBuffer", "()Ljava/nio/ByteBuffer;");

    CHECK(asReadOnlyBufferID != NULL);

    jmethodID positionID = env->GetMethodID(
            byteBufferClass.get(), "position", "(I)Ljava/nio/Buffer;");

    CHECK(positionID != NULL);

    jmethodID limitID = env->GetMethodID(
            byteBufferClass.get(), "limit", "(I)Ljava/nio/Buffer;");

    CHECK(limitID != NULL);

    ScopedLocalRef<jclass> byteOrderClass(
            env, env->FindClass("java/nio/ByteOrder"));

    CHECK(byteOrderClass.get() != NULL);

    jmethodID nativeOrderID = env->GetStaticMethodID(
            byteOrderClass.get(), "nativeOrder", "()Ljava/nio/ByteOrder;");
    CHECK(nativeOrderID != NULL);

    jobject nativeByteOrderObj =
        env->CallStaticObjectMethod(byteOrderClass.get(), nativeOrderID);
    CHECK(nativeByteOrderObj != NULL);

// static
status_t JMediaCodec::createByteBufferFromABuffer(
        JNIEnv *env, bool readOnly, bool clearBuffer, const sp<ABuffer> &buffer,
        jobject *buf) const {
    // if this is an ABuffer that doesn't actually hold any accessible memory,
    // use a null ByteBuffer
    if (buffer->base() == NULL) {
    *buf = NULL;
    if (buffer->base() == NULL) {
        return OK;
    }

    jobject byteBuffer =
        env->NewDirectByteBuffer(
            buffer->base(),
            buffer->capacity());
    if (!input && byteBuffer != NULL) {
        env->NewDirectByteBuffer(buffer->base(), buffer->capacity());
    if (readOnly && byteBuffer != NULL) {
        jobject readOnlyBuffer = env->CallObjectMethod(
                byteBuffer, asReadOnlyBufferID);
                byteBuffer, mByteBufferAsReadOnlyBufferMethodID);
        env->DeleteLocalRef(byteBuffer);
        byteBuffer = readOnlyBuffer;
    }
    if (byteBuffer == NULL) {
        env->DeleteLocalRef(nativeByteOrderObj);
        return NO_MEMORY;
    }
    jobject me = env->CallObjectMethod(
            byteBuffer, orderID, nativeByteOrderObj);
            byteBuffer, mByteBufferOrderMethodID, mNativeByteOrderObj);
    env->DeleteLocalRef(me);
    me = env->CallObjectMethod(
            byteBuffer, limitID,
            input ? buffer->capacity() : (buffer->offset() + buffer->size()));
            byteBuffer, mByteBufferLimitMethodID,
            clearBuffer ? buffer->capacity() : (buffer->offset() + buffer->size()));
    env->DeleteLocalRef(me);
    me = env->CallObjectMethod(
            byteBuffer, positionID,
            input ? 0 : buffer->offset());
            byteBuffer, mByteBufferPositionMethodID,
            clearBuffer ? 0 : buffer->offset());
    env->DeleteLocalRef(me);
    me = NULL;

    env->DeleteLocalRef(nativeByteOrderObj);
    nativeByteOrderObj = NULL;

    *buf = byteBuffer;
    return OK;
}

status_t JMediaCodec::getBuffer(
        JNIEnv *env, bool input, size_t index, jobject *buf) const {
    sp<ABuffer> buffer;

    status_t err =
        input
            ? mCodec->getInputBuffer(index, &buffer)
            : mCodec->getOutputBuffer(index, &buffer);

    if (err != OK) {
        return err;
    }

    return createByteBufferFromABuffer(
            env, !input /* readOnly */, input /* clearBuffer */, buffer, buf);
}

status_t JMediaCodec::getImage(
        JNIEnv *env, bool input, size_t index, jobject *buf) const {
    sp<ABuffer> buffer;
@@ -490,14 +456,79 @@ status_t JMediaCodec::getImage(
    }

    // check if buffer is an image
    AString imageData;
    if (!buffer->meta()->findString("image-data", &imageData)) {
    sp<ABuffer> imageData;
    if (!buffer->meta()->findBuffer("image-data", &imageData)) {
        return OK;
    }

    int64_t timestamp = 0;
    if (!input && buffer->meta()->findInt64("timeUs", &timestamp)) {
        timestamp *= 1000; // adjust to ns
    }

    jobject byteBuffer = NULL;
    err = createByteBufferFromABuffer(
            env, !input /* readOnly */, input /* clearBuffer */, buffer, &byteBuffer);
    if (err != OK) {
        return OK;
    }

    jobject infoBuffer = NULL;
    err = createByteBufferFromABuffer(
            env, true /* readOnly */, true /* clearBuffer */, imageData, &infoBuffer);
    if (err != OK) {
        env->DeleteLocalRef(byteBuffer);
        byteBuffer = NULL;
        return OK;
    }

    jobject cropRect = NULL;
    int32_t left, top, right, bottom;
    if (buffer->meta()->findRect("crop-rect", &left, &top, &right, &bottom)) {
        ScopedLocalRef<jclass> rectClazz(
                env, env->FindClass("android/graphics/Rect"));
        CHECK(rectClazz.get() != NULL);

        jmethodID rectConstructID = env->GetMethodID(
                rectClazz.get(), "<init>", "(IIII)V");

        cropRect = env->NewObject(
                rectClazz.get(), rectConstructID, left, top, right + 1, bottom + 1);
    }

    ScopedLocalRef<jclass> imageClazz(
            env, env->FindClass("android/media/MediaCodec$MediaImage"));
    CHECK(imageClazz.get() != NULL);

    jmethodID imageConstructID = env->GetMethodID(imageClazz.get(), "<init>",
            "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;ZJIILandroid/graphics/Rect;)V");

    *buf = env->NewObject(imageClazz.get(), imageConstructID,
            byteBuffer, infoBuffer,
            (jboolean)!input /* readOnly */,
            (jlong)timestamp,
            (jint)0 /* xOffset */, (jint)0 /* yOffset */, cropRect);

    // if MediaImage creation fails, return null
    if (env->ExceptionCheck()) {
        env->ExceptionDescribe();
        env->ExceptionClear();
        *buf = NULL;
    }

    if (cropRect != NULL) {
        env->DeleteLocalRef(cropRect);
        cropRect = NULL;
    }

    env->DeleteLocalRef(byteBuffer);
    byteBuffer = NULL;

    env->DeleteLocalRef(infoBuffer);
    infoBuffer = NULL;

    return OK;
}

status_t JMediaCodec::getName(JNIEnv *env, jstring *nameStr) const {
    AString name;
+16 −0
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@

namespace android {

struct ABuffer;
struct ALooper;
struct AMessage;
struct AString;
@@ -121,11 +122,26 @@ 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;

    sp<AMessage> mCallbackNotification;

    status_t createByteBufferFromABuffer(
            JNIEnv *env, bool readOnly, bool clearBuffer, const sp<ABuffer> &buffer,
            jobject *buf) const;

    void cacheJavaObjects(JNIEnv *env);
    void deleteJavaObjects(JNIEnv *env);

    DISALLOW_EVIL_CONSTRUCTORS(JMediaCodec);
};