1 /*
2 * Copyright (C) 2022 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 #include <array>
18 #include <cstring>
19 #include <cstdio>
20 #include <inttypes.h>
21 #include <memory.h>
22 #include <vector>
23 #include <iostream>
24 #include <utils/Log.h>
25 #include <setjmp.h>
26
27 #include <android/hardware/camera/device/3.2/types.h>
28
29 #include "jni.h"
30 #include <nativehelper/JNIHelp.h>
31
32 extern "C" {
33 #include "jpeglib.h"
34 }
35
36 using namespace std;
37 using namespace android;
38
39 using android::hardware::camera::device::V3_2::CameraBlob;
40 using android::hardware::camera::device::V3_2::CameraBlobId;
41
42 class Transform;
43 struct Plane;
44
sgn(int val)45 inline int sgn(int val) { return (0 < val) - (val < 0); }
46
min(int a,int b)47 inline int min(int a, int b) { return a < b ? a : b; }
48
max(int a,int b)49 inline int max(int a, int b) { return a > b ? a : b; }
50
51 /**
52 * Represents a combined cropping and rotation transformation.
53 *
54 * The transformation maps the coordinates (mOrigX, mOrigY) and (mOneX, mOneY)
55 * in the input image to the origin and (mOutputWidth, mOutputHeight)
56 * respectively.
57 */
58 class Transform {
59 public:
60 Transform(int origX, int origY, int oneX, int oneY);
61
62 static Transform forCropFollowedByRotation(int cropLeft, int cropTop,
63 int cropRight, int cropBottom, int rot90);
64
getOutputWidth() const65 inline int getOutputWidth() const { return mOutputWidth; }
66
getOutputHeight() const67 inline int getOutputHeight() const { return mOutputHeight; }
68
69 bool operator==(const Transform& other) const;
70
71 /**
72 * Transforms the input coordinates. Coordinates outside the cropped region
73 * are clamped to valid values.
74 */
75 void map(int x, int y, int* outX, int* outY) const;
76
77 private:
78 int mOutputWidth;
79 int mOutputHeight;
80
81 // The coordinates of the point to map the origin to.
82 const int mOrigX, mOrigY;
83 // The coordinates of the point to map the point (getOutputWidth(),
84 // getOutputHeight()) to.
85 const int mOneX, mOneY;
86
87 // A matrix for the rotational component.
88 int mMat00, mMat01;
89 int mMat10, mMat11;
90 };
91
92 /**
93 * Represents a model for accessing pixel data for a single plane of an image.
94 * Note that the actual data is not owned by this class, and the underlying
95 * data does not need to be stored in separate planes.
96 */
97 struct Plane {
98 // The dimensions of this plane of the image
99 int width;
100 int height;
101
102 // A pointer to raw pixel data
103 const unsigned char* data;
104 // The difference in address between consecutive pixels in the same row
105 int pixelStride;
106 // The difference in address between the start of consecutive rows
107 int rowStride;
108 };
109
110 /**
111 * Provides an interface for simultaneously reading a certain number of rows of
112 * an image plane as contiguous arrays, suitable for use with libjpeg.
113 */
114 template <unsigned int ROWS>
115 class RowIterator {
116 public:
117 /**
118 * Creates a new RowIterator which will crop and rotate with the given
119 * transform.
120 *
121 * @param plane the plane to iterate over
122 * @param transform the transformation to map output values into the
123 * coordinate space of the plane
124 * @param rowLength the length of the rows returned via LoadAt(). If this is
125 * longer than the width of the output (after applying the transform), then
126 * the right-most value is repeated.
127 */
128 inline RowIterator(Plane plane, Transform transform, int rowLength);
129
130 /**
131 * Returns an array of pointers into consecutive rows of contiguous image
132 * data starting at y. That is, samples within each row are contiguous.
133 * However, the individual arrays pointed-to may be separate.
134 * When the end of the image is reached, the last row of the image is
135 * repeated.
136 * The returned pointers are valid until the next call to loadAt().
137 */
138 inline const std::array<unsigned char*, ROWS> loadAt(int baseY);
139
140 private:
141 Plane mPlane;
142 Transform mTransform;
143 // The length of a row, with padding to the next multiple of 64.
144 int mPaddedRowLength;
145 std::vector<unsigned char> mBuffer;
146 };
147
148 template <unsigned int ROWS>
RowIterator(Plane plane,Transform transform,int rowLength)149 RowIterator<ROWS>::RowIterator(Plane plane, Transform transform,
150 int rowLength)
151 : mPlane(plane), mTransform(transform) {
152 mPaddedRowLength = rowLength;
153 mBuffer = std::vector<unsigned char>(rowLength * ROWS);
154 }
155
156 template <unsigned int ROWS>
loadAt(int baseY)157 const std::array<unsigned char*, ROWS> RowIterator<ROWS>::loadAt(int baseY) {
158 std::array<unsigned char*, ROWS> bufPtrs;
159 for (unsigned int i = 0; i < ROWS; i++) {
160 bufPtrs[i] = &mBuffer[mPaddedRowLength * i];
161 }
162
163 if (mPlane.width == 0 || mPlane.height == 0) {
164 return bufPtrs;
165 }
166
167 for (unsigned int i = 0; i < ROWS; i++) {
168 int y = i + baseY;
169 y = min(y, mTransform.getOutputHeight() - 1);
170
171 int output_width = mPaddedRowLength;
172 output_width = min(output_width, mTransform.getOutputWidth());
173 output_width = min(output_width, mPlane.width);
174
175 // Each row in the output image will be copied into buf_ by gathering pixels
176 // along an axis-aligned line in the plane.
177 // The line is defined by (startX, startY) -> (endX, endY), computed via the
178 // current Transform.
179 int startX;
180 int startY;
181 mTransform.map(0, y, &startX, &startY);
182
183 int endX;
184 int endY;
185 mTransform.map(output_width - 1, y, &endX, &endY);
186
187 // Clamp (startX, startY) and (endX, endY) to the valid bounds of the plane.
188 startX = min(startX, mPlane.width - 1);
189 startY = min(startY, mPlane.height - 1);
190 endX = min(endX, mPlane.width - 1);
191 endY = min(endY, mPlane.height - 1);
192 startX = max(startX, 0);
193 startY = max(startY, 0);
194 endX = max(endX, 0);
195 endY = max(endY, 0);
196
197 // To reduce work inside the copy-loop, precompute the start, end, and
198 // stride relating the values to be gathered from mPlane into buf
199 // for this particular scan-line.
200 int dx = sgn(endX - startX);
201 int dy = sgn(endY - startY);
202 if (!(dx == 0 || dy == 0)) {
203 ALOGE("%s: Unexpected bounds: %dx%d %dx%d!", __FUNCTION__, startX, endX, startY, endY);
204 return bufPtrs;
205 }
206
207 // The index into mPlane.data of (startX, startY)
208 int plane_start = startX * mPlane.pixelStride + startY * mPlane.rowStride;
209 // The index into mPlane.data of (endX, endY)
210 int plane_end = endX * mPlane.pixelStride + endY * mPlane.rowStride;
211 // The stride, in terms of indices in plane_data, required to enumerate the
212 // samples between the start and end points.
213 int stride = dx * mPlane.pixelStride + dy * mPlane.rowStride;
214 // In the degenerate-case of a 1x1 plane, startX and endX are equal, so
215 // stride would be 0, resulting in an infinite-loop. To avoid this case,
216 // use a stride of at-least 1.
217 if (stride == 0) {
218 stride = 1;
219 }
220
221 int outX = 0;
222 for (int idx = plane_start; idx >= min(plane_start, plane_end) &&
223 idx <= max(plane_start, plane_end); idx += stride) {
224 bufPtrs[i][outX] = mPlane.data[idx];
225 outX++;
226 }
227
228 // Fill the remaining right-edge of the buffer by extending the last
229 // value.
230 unsigned char right_padding_value = bufPtrs[i][outX - 1];
231 for (; outX < mPaddedRowLength; outX++) {
232 bufPtrs[i][outX] = right_padding_value;
233 }
234 }
235
236 return bufPtrs;
237 }
238
239 template <typename T>
safeDelete(T & t)240 void safeDelete(T& t) {
241 delete t;
242 t = nullptr;
243 }
244
245 template <typename T>
safeDeleteArray(T & t)246 void safeDeleteArray(T& t) {
247 delete[] t;
248 t = nullptr;
249 }
250
Transform(int origX,int origY,int oneX,int oneY)251 Transform::Transform(int origX, int origY, int oneX, int oneY)
252 : mOrigX(origX), mOrigY(origY), mOneX(oneX), mOneY(oneY) {
253 if (origX == oneX || origY == oneY) {
254 // Handle the degenerate case of cropping to a 0x0 rectangle.
255 mMat00 = 0;
256 mMat01 = 0;
257 mMat10 = 0;
258 mMat11 = 0;
259 return;
260 }
261
262 if (oneX > origX && oneY > origY) {
263 // 0-degree rotation
264 mMat00 = 1;
265 mMat01 = 0;
266 mMat10 = 0;
267 mMat11 = 1;
268 mOutputWidth = abs(oneX - origX);
269 mOutputHeight = abs(oneY - origY);
270 } else if (oneX < origX && oneY > origY) {
271 // 90-degree CCW rotation
272 mMat00 = 0;
273 mMat01 = -1;
274 mMat10 = 1;
275 mMat11 = 0;
276 mOutputWidth = abs(oneY - origY);
277 mOutputHeight = abs(oneX - origX);
278 } else if (oneX > origX && oneY < origY) {
279 // 270-degree CCW rotation
280 mMat00 = 0;
281 mMat01 = 1;
282 mMat10 = -1;
283 mMat11 = 0;
284 mOutputWidth = abs(oneY - origY);
285 mOutputHeight = abs(oneX - origX);;
286 } else if (oneX < origX && oneY < origY) {
287 // 180-degree CCW rotation
288 mMat00 = -1;
289 mMat01 = 0;
290 mMat10 = 0;
291 mMat11 = -1;
292 mOutputWidth = abs(oneX - origX);
293 mOutputHeight = abs(oneY - origY);
294 }
295 }
296
forCropFollowedByRotation(int cropLeft,int cropTop,int cropRight,int cropBottom,int rot90)297 Transform Transform::forCropFollowedByRotation(int cropLeft, int cropTop, int cropRight,
298 int cropBottom, int rot90) {
299 // The input crop-region excludes cropRight and cropBottom, so transform the
300 // crop rect such that it defines the entire valid region of pixels
301 // inclusively.
302 cropRight -= 1;
303 cropBottom -= 1;
304
305 int cropXLow = min(cropLeft, cropRight);
306 int cropYLow = min(cropTop, cropBottom);
307 int cropXHigh = max(cropLeft, cropRight);
308 int cropYHigh = max(cropTop, cropBottom);
309 rot90 %= 4;
310 if (rot90 == 0) {
311 return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1);
312 } else if (rot90 == 1) {
313 return Transform(cropXHigh, cropYLow, cropXLow - 1, cropYHigh + 1);
314 } else if (rot90 == 2) {
315 return Transform(cropXHigh, cropYHigh, cropXLow - 1, cropYLow - 1);
316 } else if (rot90 == 3) {
317 return Transform(cropXLow, cropYHigh, cropXHigh + 1, cropYLow - 1);
318 }
319 // Impossible case.
320 return Transform(cropXLow, cropYLow, cropXHigh + 1, cropYHigh + 1);
321 }
322
operator ==(const Transform & other) const323 bool Transform::operator==(const Transform& other) const {
324 return other.mOrigX == mOrigX && //
325 other.mOrigY == mOrigY && //
326 other.mOneX == mOneX && //
327 other.mOneY == mOneY;
328 }
329
330 /**
331 * Transforms the input coordinates. Coordinates outside the cropped region
332 * are clamped to valid values.
333 */
map(int x,int y,int * outX,int * outY) const334 void Transform::map(int x, int y, int* outX, int* outY) const {
335 x = max(x, 0);
336 y = max(y, 0);
337 x = min(x, getOutputWidth() - 1);
338 y = min(y, getOutputHeight() - 1);
339 *outX = x * mMat00 + y * mMat01 + mOrigX;
340 *outY = x * mMat10 + y * mMat11 + mOrigY;
341 }
342
compress(int img_width,int img_height,RowIterator<16> & y_row_generator,RowIterator<8> & cb_row_generator,RowIterator<8> & cr_row_generator,unsigned char * out_buf,size_t out_buf_capacity,std::function<void (size_t)> flush,int quality)343 int compress(int img_width, int img_height, RowIterator<16>& y_row_generator,
344 RowIterator<8>& cb_row_generator, RowIterator<8>& cr_row_generator,
345 unsigned char* out_buf, size_t out_buf_capacity, std::function<void(size_t)> flush,
346 int quality) {
347 // libjpeg requires the use of setjmp/longjmp to recover from errors. Since
348 // this doesn't play well with RAII, we must use pointers and manually call
349 // delete. See POSIX documentation for longjmp() for details on why the
350 // volatile keyword is necessary.
351 volatile jpeg_compress_struct cinfov;
352
353 jpeg_compress_struct& cinfo =
354 *const_cast<struct jpeg_compress_struct*>(&cinfov);
355
356 JSAMPROW* volatile yArr = nullptr;
357 JSAMPROW* volatile cbArr = nullptr;
358 JSAMPROW* volatile crArr = nullptr;
359
360 JSAMPARRAY imgArr[3];
361
362 // Error handling
363
364 struct my_error_mgr {
365 struct jpeg_error_mgr pub;
366 jmp_buf setjmp_buffer;
367 } err;
368
369 cinfo.err = jpeg_std_error(&err.pub);
370
371 // Default error_exit will call exit(), so override
372 // to return control via setjmp/longjmp.
373 err.pub.error_exit = [](j_common_ptr cinfo) {
374 my_error_mgr* myerr = reinterpret_cast<my_error_mgr*>(cinfo->err);
375
376 (*cinfo->err->output_message)(cinfo);
377
378 // Return control to the setjmp point (see call to setjmp()).
379 longjmp(myerr->setjmp_buffer, 1);
380 };
381
382 cinfo.err = (struct jpeg_error_mgr*)&err;
383
384 // Set the setjmp point to return to in case of error.
385 if (setjmp(err.setjmp_buffer)) {
386 // If libjpeg hits an error, control will jump to this point (see call to
387 // longjmp()).
388 jpeg_destroy_compress(&cinfo);
389
390 safeDeleteArray(yArr);
391 safeDeleteArray(cbArr);
392 safeDeleteArray(crArr);
393
394 return -1;
395 }
396
397 // Create jpeg compression context
398 jpeg_create_compress(&cinfo);
399
400 // Stores data needed by our c-style callbacks into libjpeg
401 struct ClientData {
402 unsigned char* out_buf;
403 size_t out_buf_capacity;
404 std::function<void(size_t)> flush;
405 int totalOutputBytes;
406 } clientData{out_buf, out_buf_capacity, flush, 0};
407
408 cinfo.client_data = &clientData;
409
410 // Initialize destination manager
411 jpeg_destination_mgr dest;
412
413 dest.init_destination = [](j_compress_ptr cinfo) {
414 ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
415
416 cinfo->dest->next_output_byte = cdata.out_buf;
417 cinfo->dest->free_in_buffer = cdata.out_buf_capacity;
418 };
419
420 dest.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
421 ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);
422
423 size_t numBytesInBuffer = cdata.out_buf_capacity;
424 cdata.flush(numBytesInBuffer);
425 cdata.totalOutputBytes += numBytesInBuffer;
426
427 // Reset the buffer
428 cinfo->dest->next_output_byte = cdata.out_buf;
429 cinfo->dest->free_in_buffer = cdata.out_buf_capacity;
430
431 return true;
432 };
433
434 dest.term_destination = [](j_compress_ptr cinfo __unused) {
435 // do nothing to terminate the output buffer
436 };
437
438 cinfo.dest = &dest;
439
440 // Set jpeg parameters
441 cinfo.image_width = img_width;
442 cinfo.image_height = img_height;
443 cinfo.input_components = 3;
444
445 // Set defaults based on the above values
446 jpeg_set_defaults(&cinfo);
447
448 jpeg_set_quality(&cinfo, quality, true);
449
450 cinfo.dct_method = JDCT_IFAST;
451
452 cinfo.raw_data_in = true;
453
454 jpeg_set_colorspace(&cinfo, JCS_YCbCr);
455
456 cinfo.comp_info[0].h_samp_factor = 2;
457 cinfo.comp_info[0].v_samp_factor = 2;
458 cinfo.comp_info[1].h_samp_factor = 1;
459 cinfo.comp_info[1].v_samp_factor = 1;
460 cinfo.comp_info[2].h_samp_factor = 1;
461 cinfo.comp_info[2].v_samp_factor = 1;
462
463 jpeg_start_compress(&cinfo, true);
464
465 yArr = new JSAMPROW[cinfo.comp_info[0].v_samp_factor * DCTSIZE];
466 cbArr = new JSAMPROW[cinfo.comp_info[1].v_samp_factor * DCTSIZE];
467 crArr = new JSAMPROW[cinfo.comp_info[2].v_samp_factor * DCTSIZE];
468
469 imgArr[0] = const_cast<JSAMPARRAY>(yArr);
470 imgArr[1] = const_cast<JSAMPARRAY>(cbArr);
471 imgArr[2] = const_cast<JSAMPARRAY>(crArr);
472
473 for (int y = 0; y < img_height; y += DCTSIZE * 2) {
474 std::array<unsigned char*, 16> yData = y_row_generator.loadAt(y);
475 std::array<unsigned char*, 8> cbData = cb_row_generator.loadAt(y / 2);
476 std::array<unsigned char*, 8> crData = cr_row_generator.loadAt(y / 2);
477
478 for (int row = 0; row < DCTSIZE * 2; row++) {
479 yArr[row] = yData[row];
480 }
481 for (int row = 0; row < DCTSIZE; row++) {
482 cbArr[row] = cbData[row];
483 crArr[row] = crData[row];
484 }
485
486 jpeg_write_raw_data(&cinfo, imgArr, DCTSIZE * 2);
487 }
488
489 jpeg_finish_compress(&cinfo);
490
491 int numBytesInBuffer = cinfo.dest->next_output_byte - out_buf;
492
493 flush(numBytesInBuffer);
494
495 clientData.totalOutputBytes += numBytesInBuffer;
496
497 safeDeleteArray(yArr);
498 safeDeleteArray(cbArr);
499 safeDeleteArray(crArr);
500
501 jpeg_destroy_compress(&cinfo);
502
503 return clientData.totalOutputBytes;
504 }
505
compress(int width,int height,unsigned char * yBuf,int yPStride,int yRStride,unsigned char * cbBuf,int cbPStride,int cbRStride,unsigned char * crBuf,int crPStride,int crRStride,unsigned char * outBuf,size_t outBufCapacity,int quality,int cropLeft,int cropTop,int cropRight,int cropBottom,int rot90)506 int compress(
507 /** Input image dimensions */
508 int width, int height,
509 /** Y Plane */
510 unsigned char* yBuf, int yPStride, int yRStride,
511 /** Cb Plane */
512 unsigned char* cbBuf, int cbPStride, int cbRStride,
513 /** Cr Plane */
514 unsigned char* crBuf, int crPStride, int crRStride,
515 /** Output */
516 unsigned char* outBuf, size_t outBufCapacity,
517 /** Jpeg compression parameters */
518 int quality,
519 /** Crop */
520 int cropLeft, int cropTop, int cropRight, int cropBottom,
521 /** Rotation (multiple of 90). For example, rot90 = 1 implies a 90 degree
522 * rotation. */
523 int rot90) {
524 int finalWidth;
525 int finalHeight;
526 finalWidth = cropRight - cropLeft;
527 finalHeight = cropBottom - cropTop;
528
529 rot90 %= 4;
530 // for 90 and 270-degree rotations, flip the final width and height
531 if (rot90 == 1) {
532 finalWidth = cropBottom - cropTop;
533 finalHeight = cropRight - cropLeft;
534 } else if (rot90 == 3) {
535 finalWidth = cropBottom - cropTop;
536 finalHeight = cropRight - cropLeft;
537 }
538
539 const Plane yP = {width, height, yBuf, yPStride, yRStride};
540 const Plane cbP = {width / 2, height / 2, cbBuf, cbPStride, cbRStride};
541 const Plane crP = {width / 2, height / 2, crBuf, crPStride, crRStride};
542
543 auto flush = [](size_t numBytes __unused) {
544 // do nothing
545 };
546
547 // Round up to the nearest multiple of 64.
548 int y_row_length = (finalWidth + 16 + 63) & ~63;
549 int cb_row_length = (finalWidth / 2 + 16 + 63) & ~63;
550 int cr_row_length = (finalWidth / 2 + 16 + 63) & ~63;
551
552 Transform yTrans = Transform::forCropFollowedByRotation(
553 cropLeft, cropTop, cropRight, cropBottom, rot90);
554
555 Transform chromaTrans = Transform::forCropFollowedByRotation(
556 cropLeft / 2, cropTop / 2, cropRight / 2, cropBottom / 2, rot90);
557
558 RowIterator<16> yIter(yP, yTrans, y_row_length);
559 RowIterator<8> cbIter(cbP, chromaTrans, cb_row_length);
560 RowIterator<8> crIter(crP, chromaTrans, cr_row_length);
561
562 return compress(finalWidth, finalHeight, yIter, cbIter, crIter, outBuf, outBufCapacity, flush,
563 quality);
564 }
565
566 extern "C" {
567
JpegEncoder_compressJpegFromYUV420p(JNIEnv * env,jclass clazz __unused,jint width,jint height,jobject yBuf,jint yPStride,jint yRStride,jobject cbBuf,jint cbPStride,jint cbRStride,jobject crBuf,jint crPStride,jint crRStride,jobject outBuf,jint outBufCapacity,jint quality,jint cropLeft,jint cropTop,jint cropRight,jint cropBottom,jint rot90)568 static jint JpegEncoder_compressJpegFromYUV420p(
569 JNIEnv* env, jclass clazz __unused,
570 /** Input image dimensions */
571 jint width, jint height,
572 /** Y Plane */
573 jobject yBuf, jint yPStride, jint yRStride,
574 /** Cb Plane */
575 jobject cbBuf, jint cbPStride, jint cbRStride,
576 /** Cr Plane */
577 jobject crBuf, jint crPStride, jint crRStride,
578 /** Output */
579 jobject outBuf, jint outBufCapacity,
580 /** Jpeg compression parameters */
581 jint quality,
582 /** Crop */
583 jint cropLeft, jint cropTop, jint cropRight, jint cropBottom,
584 /** Rotation (multiple of 90). For example, rot90 = 1 implies a 90 degree
585 * rotation. */
586 jint rot90) {
587 // ALOGE("Insert log inside JpegEncoder_compressJpegFromYUV420p");
588 jbyte* y = (jbyte*)env->GetDirectBufferAddress(yBuf);
589 jbyte* cb = (jbyte*)env->GetDirectBufferAddress(cbBuf);
590 jbyte* cr = (jbyte*)env->GetDirectBufferAddress(crBuf);
591 jbyte* out = (jbyte*)env->GetDirectBufferAddress(outBuf);
592
593 size_t actualJpegSize = compress(width, height,
594 (unsigned char*)y, yPStride, yRStride,
595 (unsigned char*)cb, cbPStride, cbRStride,
596 (unsigned char*)cr, crPStride, crRStride,
597 (unsigned char*)out, (size_t)outBufCapacity,
598 quality, cropLeft, cropTop, cropRight, cropBottom, rot90);
599
600 size_t finalJpegSize = actualJpegSize + sizeof(CameraBlob);
601 if (finalJpegSize > outBufCapacity) {
602 // ALOGE("%s: Final jpeg buffer %zu not large enough for the jpeg blob header with "\
603 // "capacity %d", __FUNCTION__, finalJpegSize, outBufCapacity);
604 return actualJpegSize;
605 }
606
607 int8_t* header = static_cast<int8_t *> (out) +
608 (outBufCapacity - sizeof(CameraBlob));
609 CameraBlob *blob = reinterpret_cast<CameraBlob *> (header);
610 blob->blobId = CameraBlobId::JPEG;
611 blob->blobSize = actualJpegSize;
612
613 return actualJpegSize;
614 }
615
616 } // extern "C"
617
RegisterMethodsOrDie(JNIEnv * env,const char * className,const JNINativeMethod * gMethods,int numMethods)618 static inline int RegisterMethodsOrDie(JNIEnv* env, const char* className,
619 const JNINativeMethod* gMethods, int numMethods) {
620 int res = jniRegisterNativeMethods(env, className, gMethods, numMethods);
621 return res;
622 }
623
624 static const JNINativeMethod gJpegEncoderMethods[] = {
625 {"compressJpegFromYUV420pNative",
626 "(IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IILjava/nio/ByteBuffer;IIIIIII)I",
627 (void*)JpegEncoder_compressJpegFromYUV420p}};
628
register_android_hardware_camera2_impl_JpegEncoder(JNIEnv * env)629 int register_android_hardware_camera2_impl_JpegEncoder(JNIEnv* env) {
630 return RegisterMethodsOrDie(env, "androidx/camera/extensions/impl/advanced/JpegEncoder",
631 gJpegEncoderMethods, NELEM(gJpegEncoderMethods));
632 }
633
JNI_OnLoad(JavaVM * vm,void * reserved)634 jint JNI_OnLoad(JavaVM* vm, void* reserved) {
635 JNIEnv* env;
636
637 if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
638 return -1;
639 }
640
641 if (register_android_hardware_camera2_impl_JpegEncoder(env) < 0) {
642 ALOGE("ERROR: JpegEncoder native registration failed");
643 return -1;
644 }
645
646 return JNI_VERSION_1_6;
647 }