blob: f7a08a1d6bd55260f5bcd7d238a73426009c5c82 [file] [log] [blame]
/*
* Copyright 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Original code copied from NDK Native-media sample code */
//#define LOG_NDEBUG 0
#define TAG "CodecUtilsJNI"
#include <log/log.h>
#include <stdint.h>
#include <sys/types.h>
#include <jni.h>
#include <ScopedLocalRef.h>
#include <JNIHelp.h>
typedef ssize_t offs_t;
struct NativeImage {
struct crop {
int left;
int top;
int right;
int bottom;
} crop;
struct plane {
const uint8_t *buffer;
size_t size;
ssize_t colInc;
ssize_t rowInc;
offs_t cropOffs;
size_t cropWidth;
size_t cropHeight;
} plane[3];
int width;
int height;
int format;
long timestamp;
size_t numPlanes;
};
struct ChecksumAlg {
virtual void init() = 0;
virtual void update(uint8_t c) = 0;
virtual uint32_t checksum() = 0;
virtual size_t length() = 0;
protected:
virtual ~ChecksumAlg() {}
};
struct Adler32 : ChecksumAlg {
Adler32() {
init();
}
void init() {
a = 1;
len = b = 0;
}
void update(uint8_t c) {
a += c;
b += a;
++len;
}
uint32_t checksum() {
return (a % 65521) + ((b % 65521) << 16);
}
size_t length() {
return len;
}
private:
uint32_t a, b;
size_t len;
};
static struct ImageFieldsAndMethods {
// android.graphics.ImageFormat
int YUV_420_888;
// android.media.Image
jmethodID methodWidth;
jmethodID methodHeight;
jmethodID methodFormat;
jmethodID methodTimestamp;
jmethodID methodPlanes;
jmethodID methodCrop;
// android.media.Image.Plane
jmethodID methodBuffer;
jmethodID methodPixelStride;
jmethodID methodRowStride;
// android.graphics.Rect
jfieldID fieldLeft;
jfieldID fieldTop;
jfieldID fieldRight;
jfieldID fieldBottom;
} gFields;
static bool gFieldsInitialized = false;
void initializeGlobalFields(JNIEnv *env) {
if (gFieldsInitialized) {
return;
}
{ // ImageFormat
jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
const jfieldID fieldYUV420888 = env->GetStaticFieldID(imageFormatClazz, "YUV_420_888", "I");
gFields.YUV_420_888 = env->GetStaticIntField(imageFormatClazz, fieldYUV420888);
env->DeleteLocalRef(imageFormatClazz);
imageFormatClazz = NULL;
}
{ // Image
jclass imageClazz = env->FindClass("android/media/cts/CodecImage");
gFields.methodWidth = env->GetMethodID(imageClazz, "getWidth", "()I");
gFields.methodHeight = env->GetMethodID(imageClazz, "getHeight", "()I");
gFields.methodFormat = env->GetMethodID(imageClazz, "getFormat", "()I");
gFields.methodTimestamp = env->GetMethodID(imageClazz, "getTimestamp", "()J");
gFields.methodPlanes = env->GetMethodID(
imageClazz, "getPlanes", "()[Landroid/media/cts/CodecImage$Plane;");
gFields.methodCrop = env->GetMethodID(
imageClazz, "getCropRect", "()Landroid/graphics/Rect;");
env->DeleteLocalRef(imageClazz);
imageClazz = NULL;
}
{ // Image.Plane
jclass planeClazz = env->FindClass("android/media/cts/CodecImage$Plane");
gFields.methodBuffer = env->GetMethodID(planeClazz, "getBuffer", "()Ljava/nio/ByteBuffer;");
gFields.methodPixelStride = env->GetMethodID(planeClazz, "getPixelStride", "()I");
gFields.methodRowStride = env->GetMethodID(planeClazz, "getRowStride", "()I");
env->DeleteLocalRef(planeClazz);
planeClazz = NULL;
}
{ // Rect
jclass rectClazz = env->FindClass("android/graphics/Rect");
gFields.fieldLeft = env->GetFieldID(rectClazz, "left", "I");
gFields.fieldTop = env->GetFieldID(rectClazz, "top", "I");
gFields.fieldRight = env->GetFieldID(rectClazz, "right", "I");
gFields.fieldBottom = env->GetFieldID(rectClazz, "bottom", "I");
env->DeleteLocalRef(rectClazz);
rectClazz = NULL;
}
gFieldsInitialized = true;
}
NativeImage *getNativeImage(JNIEnv *env, jobject image) {
if (image == NULL) {
jniThrowNullPointerException(env, "image is null");
return NULL;
}
initializeGlobalFields(env);
NativeImage *img = new NativeImage;
img->format = env->CallIntMethod(image, gFields.methodFormat);
img->width = env->CallIntMethod(image, gFields.methodWidth);
img->height = env->CallIntMethod(image, gFields.methodHeight);
img->timestamp = env->CallLongMethod(image, gFields.methodTimestamp);
jobject cropRect = env->CallObjectMethod(image, gFields.methodCrop);
img->crop.left = env->GetIntField(cropRect, gFields.fieldLeft);
img->crop.top = env->GetIntField(cropRect, gFields.fieldTop);
img->crop.right = env->GetIntField(cropRect, gFields.fieldRight);
img->crop.bottom = env->GetIntField(cropRect, gFields.fieldBottom);
if (img->crop.right == 0 && img->crop.bottom == 0) {
img->crop.right = img->width;
img->crop.bottom = img->height;
}
env->DeleteLocalRef(cropRect);
cropRect = NULL;
if (img->format != gFields.YUV_420_888) {
jniThrowException(
env, "java/lang/UnsupportedOperationException",
"only support YUV_420_888 images");
delete img;
img = NULL;
return NULL;
}
img->numPlanes = 3;
ScopedLocalRef<jobjectArray> planesArray(
env, (jobjectArray)env->CallObjectMethod(image, gFields.methodPlanes));
int xDecim = 0;
int yDecim = 0;
for (size_t ix = 0; ix < img->numPlanes; ++ix) {
ScopedLocalRef<jobject> plane(
env, env->GetObjectArrayElement(planesArray.get(), (jsize)ix));
img->plane[ix].colInc = env->CallIntMethod(plane.get(), gFields.methodPixelStride);
img->plane[ix].rowInc = env->CallIntMethod(plane.get(), gFields.methodRowStride);
ScopedLocalRef<jobject> buffer(
env, env->CallObjectMethod(plane.get(), gFields.methodBuffer));
img->plane[ix].buffer = (const uint8_t *)env->GetDirectBufferAddress(buffer.get());
img->plane[ix].size = env->GetDirectBufferCapacity(buffer.get());
img->plane[ix].cropOffs =
(img->crop.left >> xDecim) * img->plane[ix].colInc
+ (img->crop.top >> yDecim) * img->plane[ix].rowInc;
img->plane[ix].cropHeight =
((img->crop.bottom + (1 << yDecim) - 1) >> yDecim) - (img->crop.top >> yDecim);
img->plane[ix].cropWidth =
((img->crop.right + (1 << xDecim) - 1) >> xDecim) - (img->crop.left >> xDecim);
// sanity check on increments
ssize_t widthOffs =
(((img->width + (1 << xDecim) - 1) >> xDecim) - 1) * img->plane[ix].colInc;
ssize_t heightOffs =
(((img->height + (1 << yDecim) - 1) >> yDecim) - 1) * img->plane[ix].rowInc;
if (widthOffs < 0 || heightOffs < 0
|| widthOffs + heightOffs >= (ssize_t)img->plane[ix].size) {
jniThrowException(
env, "java/lang/IndexOutOfBoundsException", "plane exceeds bytearray");
delete img;
img = NULL;
return NULL;
}
xDecim = yDecim = 1;
}
return img;
}
extern "C" jint Java_android_media_cts_CodecUtils_getImageChecksum(JNIEnv *env,
jclass /*clazz*/, jobject image)
{
NativeImage *img = getNativeImage(env, image);
if (img == NULL) {
return 0;
}
Adler32 adler;
for (size_t ix = 0; ix < img->numPlanes; ++ix) {
const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
const uint8_t *col = row;
ssize_t colInc = img->plane[ix].colInc;
for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
adler.update(*col);
col += colInc;
}
row += img->plane[ix].rowInc;
}
}
ALOGV("adler %zu/%u", adler.length(), adler.checksum());
return adler.checksum();
}
/* tiled copy that loops around source image boundary */
extern "C" void Java_android_media_cts_CodecUtils_copyFlexYUVImage(JNIEnv *env,
jclass /*clazz*/, jobject target, jobject source)
{
NativeImage *tgt = getNativeImage(env, target);
NativeImage *src = getNativeImage(env, source);
if (tgt != NULL && src != NULL) {
ALOGV("copyFlexYUVImage %dx%d (%d,%d..%d,%d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd <= "
"%dx%d (%d, %d..%d, %d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd",
tgt->width, tgt->height,
tgt->crop.left, tgt->crop.top, tgt->crop.right, tgt->crop.bottom,
tgt->plane[0].cropWidth, tgt->plane[0].cropHeight,
tgt->plane[0].rowInc, tgt->plane[0].colInc,
tgt->plane[1].rowInc, tgt->plane[1].colInc,
tgt->plane[2].rowInc, tgt->plane[2].colInc,
src->width, src->height,
src->crop.left, src->crop.top, src->crop.right, src->crop.bottom,
src->plane[0].cropWidth, src->plane[0].cropHeight,
src->plane[0].rowInc, src->plane[0].colInc,
src->plane[1].rowInc, src->plane[1].colInc,
src->plane[2].rowInc, src->plane[2].colInc);
for (size_t ix = 0; ix < tgt->numPlanes; ++ix) {
uint8_t *row = const_cast<uint8_t *>(tgt->plane[ix].buffer) + tgt->plane[ix].cropOffs;
for (size_t y = 0; y < tgt->plane[ix].cropHeight; ++y) {
uint8_t *col = row;
ssize_t colInc = tgt->plane[ix].colInc;
const uint8_t *srcRow = (src->plane[ix].buffer + src->plane[ix].cropOffs
+ src->plane[ix].rowInc * (y % src->plane[ix].cropHeight));
for (size_t x = 0; x < tgt->plane[ix].cropWidth; ++x) {
*col = srcRow[src->plane[ix].colInc * (x % src->plane[ix].cropWidth)];
col += colInc;
}
row += tgt->plane[ix].rowInc;
}
}
}
}