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