Home | History | Annotate | Download | only in libmediandkjni
      1 /*
      2  * Copyright 2014 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 /* Original code copied from NDK Native-media sample code */
     18 
     19 //#define LOG_NDEBUG 0
     20 #define TAG "CodecUtilsJNI"
     21 #include <log/log.h>
     22 
     23 #include <stdint.h>
     24 #include <sys/types.h>
     25 #include <jni.h>
     26 
     27 // workaround for using ScopedLocalRef with system runtime
     28 // TODO: Remove this after b/74632104 is fixed
     29 namespace std
     30 {
     31   typedef decltype(nullptr) nullptr_t;
     32 }
     33 
     34 #include <nativehelper/JNIHelp.h>
     35 #include <nativehelper/ScopedLocalRef.h>
     36 
     37 #include <math.h>
     38 
     39 #include "md5_utils.h"
     40 
     41 typedef ssize_t offs_t;
     42 
     43 struct NativeImage {
     44     struct crop {
     45         int left;
     46         int top;
     47         int right;
     48         int bottom;
     49     } crop;
     50     struct plane {
     51         const uint8_t *buffer;
     52         size_t size;
     53         ssize_t colInc;
     54         ssize_t rowInc;
     55         offs_t cropOffs;
     56         size_t cropWidth;
     57         size_t cropHeight;
     58     } plane[3];
     59     int width;
     60     int height;
     61     int format;
     62     long timestamp;
     63     size_t numPlanes;
     64 };
     65 
     66 struct ChecksumAlg {
     67     virtual void init() = 0;
     68     virtual void update(uint8_t c) = 0;
     69     virtual uint32_t checksum() = 0;
     70     virtual size_t length() = 0;
     71 protected:
     72     virtual ~ChecksumAlg() {}
     73 };
     74 
     75 struct Adler32 : ChecksumAlg {
     76     Adler32() {
     77         init();
     78     }
     79     void init() {
     80         a = 1;
     81         len = b = 0;
     82     }
     83     void update(uint8_t c) {
     84         a += c;
     85         b += a;
     86         ++len;
     87     }
     88     uint32_t checksum() {
     89         return (a % 65521) + ((b % 65521) << 16);
     90     }
     91     size_t length() {
     92         return len;
     93     }
     94 private:
     95     uint32_t a, b;
     96     size_t len;
     97 };
     98 
     99 static struct ImageFieldsAndMethods {
    100     // android.graphics.ImageFormat
    101     int YUV_420_888;
    102     // android.media.Image
    103     jmethodID methodWidth;
    104     jmethodID methodHeight;
    105     jmethodID methodFormat;
    106     jmethodID methodTimestamp;
    107     jmethodID methodPlanes;
    108     jmethodID methodCrop;
    109     // android.media.Image.Plane
    110     jmethodID methodBuffer;
    111     jmethodID methodPixelStride;
    112     jmethodID methodRowStride;
    113     // android.graphics.Rect
    114     jfieldID fieldLeft;
    115     jfieldID fieldTop;
    116     jfieldID fieldRight;
    117     jfieldID fieldBottom;
    118 } gFields;
    119 static bool gFieldsInitialized = false;
    120 
    121 void initializeGlobalFields(JNIEnv *env) {
    122     if (gFieldsInitialized) {
    123         return;
    124     }
    125     {   // ImageFormat
    126         jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
    127         const jfieldID fieldYUV420888 = env->GetStaticFieldID(imageFormatClazz, "YUV_420_888", "I");
    128         gFields.YUV_420_888 = env->GetStaticIntField(imageFormatClazz, fieldYUV420888);
    129         env->DeleteLocalRef(imageFormatClazz);
    130         imageFormatClazz = NULL;
    131     }
    132 
    133     {   // Image
    134         jclass imageClazz = env->FindClass("android/media/cts/CodecImage");
    135         gFields.methodWidth  = env->GetMethodID(imageClazz, "getWidth", "()I");
    136         gFields.methodHeight = env->GetMethodID(imageClazz, "getHeight", "()I");
    137         gFields.methodFormat = env->GetMethodID(imageClazz, "getFormat", "()I");
    138         gFields.methodTimestamp = env->GetMethodID(imageClazz, "getTimestamp", "()J");
    139         gFields.methodPlanes = env->GetMethodID(
    140                 imageClazz, "getPlanes", "()[Landroid/media/cts/CodecImage$Plane;");
    141         gFields.methodCrop   = env->GetMethodID(
    142                 imageClazz, "getCropRect", "()Landroid/graphics/Rect;");
    143         env->DeleteLocalRef(imageClazz);
    144         imageClazz = NULL;
    145     }
    146 
    147     {   // Image.Plane
    148         jclass planeClazz = env->FindClass("android/media/cts/CodecImage$Plane");
    149         gFields.methodBuffer = env->GetMethodID(planeClazz, "getBuffer", "()Ljava/nio/ByteBuffer;");
    150         gFields.methodPixelStride = env->GetMethodID(planeClazz, "getPixelStride", "()I");
    151         gFields.methodRowStride = env->GetMethodID(planeClazz, "getRowStride", "()I");
    152         env->DeleteLocalRef(planeClazz);
    153         planeClazz = NULL;
    154     }
    155 
    156     {   // Rect
    157         jclass rectClazz = env->FindClass("android/graphics/Rect");
    158         gFields.fieldLeft   = env->GetFieldID(rectClazz, "left", "I");
    159         gFields.fieldTop    = env->GetFieldID(rectClazz, "top", "I");
    160         gFields.fieldRight  = env->GetFieldID(rectClazz, "right", "I");
    161         gFields.fieldBottom = env->GetFieldID(rectClazz, "bottom", "I");
    162         env->DeleteLocalRef(rectClazz);
    163         rectClazz = NULL;
    164     }
    165     gFieldsInitialized = true;
    166 }
    167 
    168 NativeImage *getNativeImage(JNIEnv *env, jobject image, jobject area = NULL) {
    169     if (image == NULL) {
    170         jniThrowNullPointerException(env, "image is null");
    171         return NULL;
    172     }
    173 
    174     initializeGlobalFields(env);
    175 
    176     NativeImage *img = new NativeImage;
    177     img->format = env->CallIntMethod(image, gFields.methodFormat);
    178     img->width  = env->CallIntMethod(image, gFields.methodWidth);
    179     img->height = env->CallIntMethod(image, gFields.methodHeight);
    180     img->timestamp = env->CallLongMethod(image, gFields.methodTimestamp);
    181 
    182     jobject cropRect = NULL;
    183     if (area == NULL) {
    184         cropRect = env->CallObjectMethod(image, gFields.methodCrop);
    185         area = cropRect;
    186     }
    187 
    188     img->crop.left   = env->GetIntField(area, gFields.fieldLeft);
    189     img->crop.top    = env->GetIntField(area, gFields.fieldTop);
    190     img->crop.right  = env->GetIntField(area, gFields.fieldRight);
    191     img->crop.bottom = env->GetIntField(area, gFields.fieldBottom);
    192     if (img->crop.right == 0 && img->crop.bottom == 0) {
    193         img->crop.right  = img->width;
    194         img->crop.bottom = img->height;
    195     }
    196 
    197     if (cropRect != NULL) {
    198         env->DeleteLocalRef(cropRect);
    199         cropRect = NULL;
    200     }
    201 
    202     if (img->format != gFields.YUV_420_888) {
    203         jniThrowException(
    204                 env, "java/lang/UnsupportedOperationException",
    205                 "only support YUV_420_888 images");
    206         delete img;
    207         img = NULL;
    208         return NULL;
    209     }
    210     img->numPlanes = 3;
    211 
    212     ScopedLocalRef<jobjectArray> planesArray(
    213             env, (jobjectArray)env->CallObjectMethod(image, gFields.methodPlanes));
    214     int xDecim = 0;
    215     int yDecim = 0;
    216     for (size_t ix = 0; ix < img->numPlanes; ++ix) {
    217         ScopedLocalRef<jobject> plane(
    218                 env, env->GetObjectArrayElement(planesArray.get(), (jsize)ix));
    219         img->plane[ix].colInc = env->CallIntMethod(plane.get(), gFields.methodPixelStride);
    220         img->plane[ix].rowInc = env->CallIntMethod(plane.get(), gFields.methodRowStride);
    221         ScopedLocalRef<jobject> buffer(
    222                 env, env->CallObjectMethod(plane.get(), gFields.methodBuffer));
    223 
    224         img->plane[ix].buffer = (const uint8_t *)env->GetDirectBufferAddress(buffer.get());
    225         img->plane[ix].size = env->GetDirectBufferCapacity(buffer.get());
    226 
    227         img->plane[ix].cropOffs =
    228             (img->crop.left >> xDecim) * img->plane[ix].colInc
    229                     + (img->crop.top >> yDecim) * img->plane[ix].rowInc;
    230         img->plane[ix].cropHeight =
    231             ((img->crop.bottom + (1 << yDecim) - 1) >> yDecim) - (img->crop.top >> yDecim);
    232         img->plane[ix].cropWidth =
    233             ((img->crop.right + (1 << xDecim) - 1) >> xDecim) - (img->crop.left >> xDecim);
    234 
    235         // sanity check on increments
    236         ssize_t widthOffs =
    237             (((img->width + (1 << xDecim) - 1) >> xDecim) - 1) * img->plane[ix].colInc;
    238         ssize_t heightOffs =
    239             (((img->height + (1 << yDecim) - 1) >> yDecim) - 1) * img->plane[ix].rowInc;
    240         if (widthOffs < 0 || heightOffs < 0
    241                 || widthOffs + heightOffs >= (ssize_t)img->plane[ix].size) {
    242             jniThrowException(
    243                     env, "java/lang/IndexOutOfBoundsException", "plane exceeds bytearray");
    244             delete img;
    245             img = NULL;
    246             return NULL;
    247         }
    248         xDecim = yDecim = 1;
    249     }
    250     return img;
    251 }
    252 
    253 extern "C" jint Java_android_media_cts_CodecUtils_getImageChecksumAlder32(JNIEnv *env,
    254         jclass /*clazz*/, jobject image)
    255 {
    256     NativeImage *img = getNativeImage(env, image);
    257     if (img == NULL) {
    258         return 0;
    259     }
    260 
    261     Adler32 adler;
    262     for (size_t ix = 0; ix < img->numPlanes; ++ix) {
    263         const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
    264         for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
    265             const uint8_t *col = row;
    266             ssize_t colInc = img->plane[ix].colInc;
    267             for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
    268                 adler.update(*col);
    269                 col += colInc;
    270             }
    271             row += img->plane[ix].rowInc;
    272         }
    273     }
    274     ALOGV("adler %zu/%u", adler.length(), adler.checksum());
    275     return adler.checksum();
    276 }
    277 
    278 extern "C" jstring Java_android_media_cts_CodecUtils_getImageChecksumMD5(JNIEnv *env,
    279         jclass /*clazz*/, jobject image)
    280 {
    281     NativeImage *img = getNativeImage(env, image);
    282     if (img == NULL) {
    283         return 0;
    284     }
    285 
    286     MD5Context md5;
    287     char res[33];
    288     MD5Init(&md5);
    289 
    290     for (size_t ix = 0; ix < img->numPlanes; ++ix) {
    291         const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
    292         for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
    293             const uint8_t *col = row;
    294             ssize_t colInc = img->plane[ix].colInc;
    295             for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
    296                 MD5Update(&md5, col, 1);
    297                 col += colInc;
    298             }
    299             row += img->plane[ix].rowInc;
    300         }
    301     }
    302 
    303     static const char hex[16] = {
    304         '0', '1', '2', '3', '4', '5', '6', '7',
    305         '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
    306     };
    307     uint8_t tmp[16];
    308 
    309     MD5Final(tmp, &md5);
    310     for (int i = 0; i < 16; i++) {
    311         res[i * 2 + 0] = hex[tmp[i] >> 4];
    312         res[i * 2 + 1] = hex[tmp[i] & 0xf];
    313     }
    314     res[32] = 0;
    315 
    316     return env->NewStringUTF(res);
    317 }
    318 
    319 /* tiled copy that loops around source image boundary */
    320 extern "C" void Java_android_media_cts_CodecUtils_copyFlexYUVImage(JNIEnv *env,
    321         jclass /*clazz*/, jobject target, jobject source)
    322 {
    323     NativeImage *tgt = getNativeImage(env, target);
    324     NativeImage *src = getNativeImage(env, source);
    325     if (tgt != NULL && src != NULL) {
    326         ALOGV("copyFlexYUVImage %dx%d (%d,%d..%d,%d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd <= "
    327                 "%dx%d (%d, %d..%d, %d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd",
    328                 tgt->width, tgt->height,
    329                 tgt->crop.left, tgt->crop.top, tgt->crop.right, tgt->crop.bottom,
    330                 tgt->plane[0].cropWidth, tgt->plane[0].cropHeight,
    331                 tgt->plane[0].rowInc, tgt->plane[0].colInc,
    332                 tgt->plane[1].rowInc, tgt->plane[1].colInc,
    333                 tgt->plane[2].rowInc, tgt->plane[2].colInc,
    334                 src->width, src->height,
    335                 src->crop.left, src->crop.top, src->crop.right, src->crop.bottom,
    336                 src->plane[0].cropWidth, src->plane[0].cropHeight,
    337                 src->plane[0].rowInc, src->plane[0].colInc,
    338                 src->plane[1].rowInc, src->plane[1].colInc,
    339                 src->plane[2].rowInc, src->plane[2].colInc);
    340         for (size_t ix = 0; ix < tgt->numPlanes; ++ix) {
    341             uint8_t *row = const_cast<uint8_t *>(tgt->plane[ix].buffer) + tgt->plane[ix].cropOffs;
    342             for (size_t y = 0; y < tgt->plane[ix].cropHeight; ++y) {
    343                 uint8_t *col = row;
    344                 ssize_t colInc = tgt->plane[ix].colInc;
    345                 const uint8_t *srcRow = (src->plane[ix].buffer + src->plane[ix].cropOffs
    346                         + src->plane[ix].rowInc * (y % src->plane[ix].cropHeight));
    347                 for (size_t x = 0; x < tgt->plane[ix].cropWidth; ++x) {
    348                     *col = srcRow[src->plane[ix].colInc * (x % src->plane[ix].cropWidth)];
    349                     col += colInc;
    350                 }
    351                 row += tgt->plane[ix].rowInc;
    352             }
    353         }
    354     }
    355 }
    356 
    357 extern "C" void Java_android_media_cts_CodecUtils_fillImageRectWithYUV(JNIEnv *env,
    358         jclass /*clazz*/, jobject image, jobject area, jint y, jint u, jint v)
    359 {
    360     NativeImage *img = getNativeImage(env, image, area);
    361     if (img == NULL) {
    362         return;
    363     }
    364 
    365     for (size_t ix = 0; ix < img->numPlanes; ++ix) {
    366         const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
    367         uint8_t val = ix == 0 ? y : ix == 1 ? u : v;
    368         for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
    369             uint8_t *col = (uint8_t *)row;
    370             ssize_t colInc = img->plane[ix].colInc;
    371             for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
    372                 *col = val;
    373                 col += colInc;
    374             }
    375             row += img->plane[ix].rowInc;
    376         }
    377     }
    378 }
    379 
    380 void getRawStats(NativeImage *img, jlong rawStats[10])
    381 {
    382     // this works best if crop area is even
    383 
    384     uint64_t sum_x[3]  = { 0, 0, 0 }; // Y, U, V
    385     uint64_t sum_xx[3] = { 0, 0, 0 }; // YY, UU, VV
    386     uint64_t sum_xy[3] = { 0, 0, 0 }; // YU, YV, UV
    387 
    388     const uint8_t *yrow = img->plane[0].buffer + img->plane[0].cropOffs;
    389     const uint8_t *urow = img->plane[1].buffer + img->plane[1].cropOffs;
    390     const uint8_t *vrow = img->plane[2].buffer + img->plane[2].cropOffs;
    391 
    392     ssize_t ycolInc = img->plane[0].colInc;
    393     ssize_t ucolInc = img->plane[1].colInc;
    394     ssize_t vcolInc = img->plane[2].colInc;
    395 
    396     ssize_t yrowInc = img->plane[0].rowInc;
    397     ssize_t urowInc = img->plane[1].rowInc;
    398     ssize_t vrowInc = img->plane[2].rowInc;
    399 
    400     size_t rightOdd = img->crop.right & 1;
    401     size_t bottomOdd = img->crop.bottom & 1;
    402 
    403     for (size_t y = img->plane[0].cropHeight; y; --y) {
    404         uint8_t *ycol = (uint8_t *)yrow;
    405         uint8_t *ucol = (uint8_t *)urow;
    406         uint8_t *vcol = (uint8_t *)vrow;
    407 
    408         for (size_t x = img->plane[0].cropWidth; x; --x) {
    409             uint64_t Y = *ycol;
    410             uint64_t U = *ucol;
    411             uint64_t V = *vcol;
    412 
    413             sum_x[0] += Y;
    414             sum_x[1] += U;
    415             sum_x[2] += V;
    416             sum_xx[0] += Y * Y;
    417             sum_xx[1] += U * U;
    418             sum_xx[2] += V * V;
    419             sum_xy[0] += Y * U;
    420             sum_xy[1] += Y * V;
    421             sum_xy[2] += U * V;
    422 
    423             ycol += ycolInc;
    424             if (rightOdd ^ (x & 1)) {
    425                 ucol += ucolInc;
    426                 vcol += vcolInc;
    427             }
    428         }
    429 
    430         yrow += yrowInc;
    431         if (bottomOdd ^ (y & 1)) {
    432             urow += urowInc;
    433             vrow += vrowInc;
    434         }
    435     }
    436 
    437     rawStats[0] = img->plane[0].cropWidth * (uint64_t)img->plane[0].cropHeight;
    438     for (size_t i = 0; i < 3; i++) {
    439         rawStats[i + 1] = sum_x[i];
    440         rawStats[i + 4] = sum_xx[i];
    441         rawStats[i + 7] = sum_xy[i];
    442     }
    443 }
    444 
    445 bool Raw2YUVStats(jlong rawStats[10], jfloat stats[9]) {
    446     int64_t sum_x[3], sum_xx[3]; // Y, U, V
    447     int64_t sum_xy[3];           // YU, YV, UV
    448 
    449     int64_t num = rawStats[0];   // #Y,U,V
    450     for (size_t i = 0; i < 3; i++) {
    451         sum_x[i] = rawStats[i + 1];
    452         sum_xx[i] = rawStats[i + 4];
    453         sum_xy[i] = rawStats[i + 7];
    454     }
    455 
    456     if (num > 0) {
    457         stats[0] = sum_x[0] / (float)num;  // y average
    458         stats[1] = sum_x[1] / (float)num;  // u average
    459         stats[2] = sum_x[2] / (float)num;  // v average
    460 
    461         // 60 bits for 4Mpixel image
    462         // adding 1 to avoid degenerate case when deviation is 0
    463         stats[3] = sqrtf((sum_xx[0] + 1) * num - sum_x[0] * sum_x[0]) / num; // y stdev
    464         stats[4] = sqrtf((sum_xx[1] + 1) * num - sum_x[1] * sum_x[1]) / num; // u stdev
    465         stats[5] = sqrtf((sum_xx[2] + 1) * num - sum_x[2] * sum_x[2]) / num; // v stdev
    466 
    467         // yu covar
    468         stats[6] = (float)(sum_xy[0] + 1 - sum_x[0] * sum_x[1] / num) / num / stats[3] / stats[4];
    469         // yv covar
    470         stats[7] = (float)(sum_xy[1] + 1 - sum_x[0] * sum_x[2] / num) / num / stats[3] / stats[5];
    471         // uv covar
    472         stats[8] = (float)(sum_xy[2] + 1 - sum_x[1] * sum_x[2] / num) / num / stats[4] / stats[5];
    473         return true;
    474     } else {
    475         return false;
    476     }
    477 }
    478 
    479 extern "C" jobject Java_android_media_cts_CodecUtils_getRawStats(JNIEnv *env,
    480         jclass /*clazz*/, jobject image, jobject area)
    481 {
    482     NativeImage *img = getNativeImage(env, image, area);
    483     if (img == NULL) {
    484         return NULL;
    485     }
    486 
    487     jlong rawStats[10];
    488     getRawStats(img, rawStats);
    489     jlongArray jstats = env->NewLongArray(10);
    490     if (jstats != NULL) {
    491         env->SetLongArrayRegion(jstats, 0, 10, rawStats);
    492     }
    493     return jstats;
    494 }
    495 
    496 extern "C" jobject Java_android_media_cts_CodecUtils_getYUVStats(JNIEnv *env,
    497         jclass /*clazz*/, jobject image, jobject area)
    498 {
    499     NativeImage *img = getNativeImage(env, image, area);
    500     if (img == NULL) {
    501         return NULL;
    502     }
    503 
    504     jlong rawStats[10];
    505     getRawStats(img, rawStats);
    506     jfloat stats[9];
    507     jfloatArray jstats = NULL;
    508     if (Raw2YUVStats(rawStats, stats)) {
    509         jstats = env->NewFloatArray(9);
    510         if (jstats != NULL) {
    511             env->SetFloatArrayRegion(jstats, 0, 9, stats);
    512         }
    513     } else {
    514         jniThrowRuntimeException(env, "empty area");
    515     }
    516 
    517     return jstats;
    518 }
    519 
    520 extern "C" jobject Java_android_media_cts_CodecUtils_Raw2YUVStats(JNIEnv *env,
    521         jclass /*clazz*/, jobject jrawStats)
    522 {
    523     jfloatArray jstats = NULL;
    524     jlong rawStats[10];
    525     env->GetLongArrayRegion((jlongArray)jrawStats, 0, 10, rawStats);
    526     if (!env->ExceptionCheck()) {
    527         jfloat stats[9];
    528         if (Raw2YUVStats(rawStats, stats)) {
    529             jstats = env->NewFloatArray(9);
    530             if (jstats != NULL) {
    531                 env->SetFloatArrayRegion(jstats, 0, 9, stats);
    532             }
    533         } else {
    534             jniThrowRuntimeException(env, "no raw statistics");
    535         }
    536     }
    537     return jstats;
    538 }
    539