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