• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:
~ChecksumAlgChecksumAlg72     virtual ~ChecksumAlg() {}
73 };
74 
75 struct Adler32 : ChecksumAlg {
Adler32Adler3276     Adler32() {
77         init();
78     }
initAdler3279     void init() {
80         a = 1;
81         len = b = 0;
82     }
updateAdler3283     void update(uint8_t c) {
84         a += c;
85         b += a;
86         ++len;
87     }
checksumAdler3288     uint32_t checksum() {
89         return (a % 65521) + ((b % 65521) << 16);
90     }
lengthAdler3291     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 
initializeGlobalFields(JNIEnv * env)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 
getNativeImage(JNIEnv * env,jobject image,jobject area=NULL)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 
Java_android_media_cts_CodecUtils_getImageChecksumAlder32(JNIEnv * env,jclass,jobject image)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 
Java_android_media_cts_CodecUtils_getImageChecksumMD5(JNIEnv * env,jclass,jobject image)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 */
Java_android_media_cts_CodecUtils_copyFlexYUVImage(JNIEnv * env,jclass,jobject target,jobject source)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 
Java_android_media_cts_CodecUtils_fillImageRectWithYUV(JNIEnv * env,jclass,jobject image,jobject area,jint y,jint u,jint v)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 
getRawStats(NativeImage * img,jlong rawStats[10])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 
Raw2YUVStats(jlong rawStats[10],jfloat stats[9])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 
Java_android_media_cts_CodecUtils_getRawStats(JNIEnv * env,jclass,jobject image,jobject area)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 
Java_android_media_cts_CodecUtils_getYUVStats(JNIEnv * env,jclass,jobject image,jobject area)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 
Java_android_media_cts_CodecUtils_Raw2YUVStats(JNIEnv * env,jclass,jobject jrawStats)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