Commit | Line | Data |
---|---|---|
5763fb39 T |
1 | /* |
2 | * Copyright Samsung Electronics Co.,LTD. | |
3 | * Copyright (C) 2015 The Android Open Source Project | |
4 | * | |
5 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | * you may not use this file except in compliance with the License. | |
7 | * You may obtain a copy of the License at | |
8 | * | |
9 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software | |
12 | * distributed under the License is distributed on an "AS IS" BASIS, | |
13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | * See the License for the specific language governing permissions and | |
15 | * limitations under the License. | |
16 | */ | |
17 | ||
18 | #include <sys/mman.h> | |
19 | #include <sys/types.h> | |
20 | ||
21 | #include <linux/videodev2.h> | |
22 | #include <linux/ion.h> | |
23 | ||
24 | #include <ion/ion.h> | |
25 | #include <system/graphics.h> | |
26 | ||
27 | #include <ExynosJpegEncoderForCamera.h> | |
28 | ||
29 | #include "hwjpeg-internal.h" | |
30 | #include "AppMarkerWriter.h" | |
31 | #include "hwjpeg-libcsc.h" | |
32 | #include "IFDWriter.h" | |
33 | ||
34 | // Data length written by H/W without the scan data. | |
35 | #define NECESSARY_JPEG_LENGTH (0x24B + 2 * JPEG_MARKER_SIZE) | |
36 | ||
37 | static size_t GetImageLength(unsigned int width, unsigned int height, int v4l2Format) | |
38 | { | |
39 | size_t size = width * height; | |
40 | ||
41 | switch(v4l2Format) { | |
42 | case V4L2_PIX_FMT_YUYV: | |
43 | case V4L2_PIX_FMT_YVYU: | |
44 | case V4L2_PIX_FMT_UYVY: | |
45 | case V4L2_PIX_FMT_VYUY: | |
46 | case V4L2_PIX_FMT_NV16: | |
47 | case V4L2_PIX_FMT_NV61: | |
48 | case V4L2_PIX_FMT_YUV422P: | |
49 | return size * 2; | |
50 | case V4L2_PIX_FMT_NV12: | |
51 | case V4L2_PIX_FMT_NV21: | |
52 | case V4L2_PIX_FMT_YUV420: | |
53 | return size + (size / 4) * 2; | |
54 | } | |
55 | ||
56 | return 0; | |
57 | } | |
58 | ||
59 | ExynosJpegEncoderForCamera::ExynosJpegEncoderForCamera(bool bBTBComp) | |
60 | : m_phwjpeg4thumb(NULL), m_fdIONClient(-1), m_fdIONThumbImgBuffer(-1), m_pIONThumbImgBuffer(NULL), | |
61 | m_szIONThumbImgBuffer(0), m_pIONThumbJpegBuffer(NULL), m_szIONThumbJpegBuffer(0), | |
62 | m_nThumbWidth(0), m_nThumbHeight(0), m_nThumbQuality(0), | |
63 | m_iHWScalerID(CSC_HW_SC1), m_pStreamBase(NULL), m_fThumbBufferType(0) | |
64 | { | |
65 | m_pAppWriter = new CAppMarkerWriter(); | |
66 | if (!m_pAppWriter) { | |
67 | ALOGE("Failed to allocated an instance of CAppMarkerWriter"); | |
68 | return; | |
69 | } | |
70 | ||
71 | m_phwjpeg4thumb = new CHWJpegV4L2Compressor(); | |
72 | if (!m_phwjpeg4thumb) { | |
73 | ALOGE("Failed to create thumbnail compressor!"); | |
74 | return; | |
75 | } | |
76 | ||
77 | if (!m_phwjpeg4thumb->SetChromaSampFactor(2, 2)) { | |
78 | ALOGE("Failed to configure chroma subsampling factor to YUV420 for thumbnail compression"); | |
79 | } | |
80 | ||
81 | m_pLibCSC = new CLibCSC; | |
82 | if (m_pLibCSC) { | |
83 | if (!m_pLibCSC->init(m_iHWScalerID - CSC_HW_SC0)) { | |
84 | ALOGE("Failed to create LibCSC instance of HW%d", m_iHWScalerID); | |
85 | delete m_pLibCSC; | |
86 | m_pLibCSC = NULL; | |
87 | } | |
88 | } else { | |
89 | ALOGE("Failed to create CLibCSC"); | |
90 | } | |
91 | ||
92 | m_fdIONClient = ion_open(); | |
93 | if (m_fdIONClient < 0) { | |
94 | ALOGERR("Failed to create ION client for thumbnail conversion"); | |
95 | } | |
96 | ||
97 | if (!bBTBComp) | |
98 | SetState(STATE_NO_BTBCOMP); | |
99 | ||
100 | // STATE_THUMBSIZE_CHANGED is to know if thumbnail image size need to be | |
101 | // configured to HWJPEG. If HWJPEG does not support for back-to-back | |
102 | // compression, it should not be configured. | |
103 | if (IsBTBCompressionSupported()) | |
104 | SetState(STATE_THUMBSIZE_CHANGED); | |
105 | ||
106 | // Do not cache clean even the cacheable buffers because | |
107 | // it is guaranteed that the source buffer is not written by CPU | |
108 | GetCompressor().SetAuxFlags(EXYNOS_HWJPEG_AUXOPT_SRC_NOCACHECLEAN); | |
109 | ||
110 | ALOGD("ExynosJpegEncoderForCamera Created: %p, ION %d", this, m_fdIONClient); | |
111 | } | |
112 | ||
113 | ExynosJpegEncoderForCamera::~ExynosJpegEncoderForCamera() | |
114 | { | |
115 | delete m_pAppWriter; | |
116 | delete m_pLibCSC; | |
117 | delete m_phwjpeg4thumb; | |
118 | ||
119 | if (m_pIONThumbImgBuffer != NULL) | |
120 | munmap(m_pIONThumbImgBuffer, m_szIONThumbImgBuffer); | |
121 | ||
122 | if (m_fdIONThumbImgBuffer >= 0) | |
123 | close(m_fdIONThumbImgBuffer); | |
124 | ||
125 | if (m_pIONThumbJpegBuffer) | |
126 | munmap(m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer); | |
127 | ||
128 | if (m_fdIONClient >= 0) | |
129 | ion_close(m_fdIONClient); | |
130 | ||
131 | ALOGD("ExynosJpegEncoderForCamera Destroyed: %p, ION %d, ThumIMG %d ThumbJPG %p", | |
132 | this, m_fdIONClient, m_fdIONThumbImgBuffer, m_pIONThumbJpegBuffer); | |
133 | } | |
134 | ||
135 | int ExynosJpegEncoderForCamera::setThumbnailSize(int w, int h) | |
136 | { | |
137 | if ((m_nThumbWidth == w) && (m_nThumbHeight == h)) | |
138 | return 0; | |
139 | ||
140 | // w == 0 and h == 0 resets thumbnail configuration | |
141 | if (((w | h) != 0) && ((w < 16) || (h < 16))) { | |
142 | ALOGE("Too small thumbnail image size %dx%d", w, h); | |
143 | return -1; | |
144 | } | |
145 | ||
146 | m_nThumbWidth = w; | |
147 | m_nThumbHeight = h; | |
148 | ||
149 | if (IsBTBCompressionSupported()) | |
150 | SetState(STATE_THUMBSIZE_CHANGED); | |
151 | ||
152 | return 0; | |
153 | } | |
154 | ||
155 | int ExynosJpegEncoderForCamera::setThumbnailQuality(int quality) | |
156 | { | |
157 | if (m_nThumbQuality == quality) | |
158 | return 0; | |
159 | ||
160 | if ((quality > 100) || (quality < 1)) { | |
161 | ALOGE("Invalid quality factor %d for thumbnail image", quality); | |
162 | return -1; | |
163 | } | |
164 | ||
165 | m_nThumbQuality = quality; | |
166 | ||
167 | return GetCompressor().SetQuality(0, m_nThumbQuality) ? 0 : -1; | |
168 | } | |
169 | ||
170 | bool ExynosJpegEncoderForCamera::EnsureFormatIsApplied() { | |
171 | if (TestStateEither(STATE_PIXFMT_CHANGED | STATE_SIZE_CHANGED | STATE_THUMBSIZE_CHANGED)) { | |
172 | int thumb_width = m_nThumbWidth; | |
173 | int thumb_height = m_nThumbHeight; | |
174 | int width = 0; | |
175 | int height = 0; | |
176 | ||
177 | if (IsThumbGenerationNeeded() || !IsBTBCompressionSupported()) { | |
178 | thumb_width = 0; | |
179 | thumb_height = 0; | |
180 | } | |
181 | ||
182 | getSize(&width, &height); | |
183 | if (!GetCompressor().SetImageFormat( | |
184 | getColorFormat(), width, height, thumb_width, thumb_height)) | |
185 | return false; | |
186 | ||
187 | ClearState(STATE_PIXFMT_CHANGED | STATE_SIZE_CHANGED | STATE_THUMBSIZE_CHANGED); | |
188 | } | |
189 | ||
190 | return true; | |
191 | } | |
192 | ||
193 | size_t ExynosJpegEncoderForCamera::RemoveTrailingDummies(char *base, size_t len) | |
194 | { | |
195 | ALOG_ASSERT(len > 4); | |
196 | ALOG_ASSERT((base[0] == 0xFF) && (base[1] == 0xD8)); // SOI marker | |
197 | ||
198 | size_t riter = len - 2; | |
199 | ||
200 | while (riter > 0) { | |
201 | if ((base[riter] == 0xFF) && (base[riter + 1] == 0xD9)) { // EOI marker | |
202 | ALOGI_IF(riter < (len - 2), "Found %zu dummies after EOI", len - riter - 2); | |
203 | return riter + 2; | |
204 | } | |
205 | riter--; | |
206 | } | |
207 | ||
208 | ALOGE("EOI is not found!"); | |
209 | ALOG_ASSERT(true); | |
210 | ||
211 | return 0; | |
212 | } | |
213 | ||
214 | void *ExynosJpegEncoderForCamera::tCompressThumbnail(void *p) | |
215 | { | |
216 | ExynosJpegEncoderForCamera *encoder = reinterpret_cast<ExynosJpegEncoderForCamera *>(p); | |
217 | ||
218 | size_t thumblen = encoder->CompressThumbnail(); | |
219 | return reinterpret_cast<void *>(thumblen); | |
220 | } | |
221 | ||
222 | bool ExynosJpegEncoderForCamera::ProcessExif(char *base, size_t limit, | |
223 | exif_attribute_t *exifInfo, | |
224 | debug_attribute_t *debuginfo) | |
225 | { | |
226 | // PREREQUISITES: The main and the thumbnail image size should be configured before. | |
227 | ||
228 | // Sanity chck | |
229 | uint32_t width = 0; | |
230 | uint32_t height = 0; | |
231 | ||
232 | getSize(reinterpret_cast<int *>(&width), reinterpret_cast<int *>(&height)); | |
233 | ||
234 | if (exifInfo) { | |
235 | if ((exifInfo->width != width) || (exifInfo->height != height)) { | |
236 | ALOGE("Inconsistant image dimension: Exif %dx%d, Thumb %dx%d", | |
237 | exifInfo->width, exifInfo->height, width, height); | |
238 | return false; | |
239 | } | |
240 | ||
5763fb39 T |
241 | if (exifInfo->enableThumb) { |
242 | if ((exifInfo->widthThumb != static_cast<uint32_t>(m_nThumbWidth)) || | |
243 | (exifInfo->heightThumb != static_cast<uint32_t>(m_nThumbHeight))) { | |
244 | ALOGE("Inconsistant thumbnail information: Exif %dx%d, Thumb %dx%d", | |
245 | exifInfo->widthThumb, exifInfo->heightThumb, m_nThumbWidth, m_nThumbHeight); | |
246 | return false; | |
247 | } | |
248 | } | |
249 | } | |
250 | ||
251 | // Giving appwriter the address beyond SOS marker | |
252 | // because it is handled by this class | |
253 | size_t align = 16; | |
254 | if (!!(GetDeviceCapabilities() & V4L2_CAP_EXYNOS_JPEG_NO_STREAMBASE_ALIGN)) | |
255 | align = 1; | |
256 | ||
257 | m_pAppWriter->PrepareAppWriter(base + JPEG_MARKER_SIZE, exifInfo, debuginfo); | |
258 | ||
259 | if (limit <= (m_pAppWriter->CalculateAPPSize(0) + NECESSARY_JPEG_LENGTH)) { | |
260 | ALOGE("Too small JPEG stream buffer size, %zu bytes", limit); | |
261 | return false; | |
262 | } | |
263 | ||
264 | bool reserve_thumbspace = true; | |
265 | ||
266 | // If the length of the given stream buffer is too small, and thumbnail | |
267 | // compression is also required, the compressed stream data of the main | |
268 | // image is appeneded after the end of the fields if IFD1. The place is | |
269 | // actually reserved for the embedded thumbnail but the main JPEG stream | |
270 | // is written in this case because it is unknown how the compressed data | |
271 | // of the thumbnail image will be. | |
272 | // After the main and the thumbnail image compressions are completed, | |
273 | // the compressed data of the main image is shifted by the length of the | |
274 | // compressed data of the thumbnail image. Then the compressed data of | |
275 | // the thumbnail image is copied to the place for it. | |
276 | if (!exifInfo || !exifInfo->enableThumb || (limit < (JPEG_MAX_SEGMENT_SIZE * 10))) | |
277 | reserve_thumbspace = false; | |
278 | ||
279 | m_pAppWriter->Write(reserve_thumbspace, JPEG_MARKER_SIZE, align, | |
280 | TestState(STATE_HWFC_ENABLED)); | |
281 | ||
282 | ALOGD("Image compression starts from offset %zu (APPx size %zu, HWFC? %d, NBTB? %d)", | |
283 | PTR_DIFF(base, m_pAppWriter->GetMainStreamBase()), m_pAppWriter->CalculateAPPSize(), | |
284 | TestState(STATE_HWFC_ENABLED),TestState(STATE_NO_BTBCOMP)); | |
285 | ||
286 | return true; | |
287 | } | |
288 | ||
289 | bool ExynosJpegEncoderForCamera::PrepareCompression(bool thumbnail) | |
290 | { | |
291 | if (!thumbnail) | |
292 | return true; | |
293 | ||
294 | if (IsThumbGenerationNeeded()) { | |
295 | if (pthread_create(&m_threadWorker, NULL, | |
296 | tCompressThumbnail, reinterpret_cast<void *>(this)) != 0) { | |
297 | ALOGERR("Failed to create thumbnail generation thread"); | |
298 | return false; | |
299 | } | |
300 | } else { | |
301 | // allocate temporary thumbnail stream buffer | |
302 | // to prevent overflow of the compressed stream | |
303 | if (!AllocThumbJpegBuffer()) { | |
304 | return false; | |
305 | } | |
306 | } | |
307 | ||
308 | if (!TestState(STATE_NO_BTBCOMP) && IsBTBCompressionSupported()) { | |
309 | if (!GetCompressor().SetJpegBuffer2(m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer)) { | |
310 | ALOGE("Failed to configure thumbnail buffer @ %p(size %zu)", | |
311 | m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer); | |
312 | return false; | |
313 | } | |
314 | } | |
315 | ||
316 | return true; | |
317 | } | |
318 | ||
319 | int ExynosJpegEncoderForCamera::encode(int *size, exif_attribute_t *exifInfo, | |
320 | char** pcJpegBuffer, debug_attribute_t *debugInfo) | |
321 | { | |
322 | if (!(*pcJpegBuffer)) { | |
323 | ALOGE("Target stream buffer is not specified"); | |
324 | return -1; | |
325 | } | |
326 | ||
327 | if (*size <= 0) { | |
328 | ALOGE("Too small stram buffer length %d bytes", *size); | |
329 | return -1; | |
330 | } | |
331 | ||
332 | m_pStreamBase = *pcJpegBuffer; | |
333 | m_nStreamSize = *size; // contains max buffer length until the compression finishes | |
334 | ||
335 | char *jpeg_base = m_pStreamBase; | |
336 | ||
337 | ALOGI_IF(!exifInfo, "Exif is not specified. Skipping writing APP1 marker"); | |
338 | ALOGI_IF(!debugInfo, | |
339 | "Debugging information is not specified. Skipping writing APP4 marker"); | |
340 | ALOGD("Given stream buffer size: %d bytes", *size); | |
341 | ||
342 | CStopWatch stopwatch(true); | |
343 | ||
344 | if (!ProcessExif(jpeg_base, m_nStreamSize, exifInfo, debugInfo)) | |
345 | return -1; | |
346 | ||
347 | int buffsize = static_cast<int>(m_nStreamSize - PTR_DIFF(m_pStreamBase, m_pAppWriter->GetMainStreamBase())); | |
348 | if (setOutBuf(m_pAppWriter->GetMainStreamBase(),buffsize) < 0) { | |
349 | ALOGE("Failed to configure stream buffer : addr %p, streamSize %d", | |
350 | m_pAppWriter->GetMainStreamBase(), buffsize); | |
351 | return -1; | |
352 | } | |
353 | ||
5763fb39 T |
354 | bool block_mode = !TestState(STATE_HWFC_ENABLED); |
355 | bool thumbenc = m_pAppWriter->GetThumbStreamBase() != NULL; | |
356 | size_t thumblen = 0; | |
357 | ||
358 | // THUMB REQ? | THUMB IMG GIVEN? | B2B COMP? | HWFC(NONBLOCKING)? | |
359 | // CASE1: O | X | - | X | |
360 | // CASE2: O | X | - | O | |
361 | // CASE3: O | O | X | X | |
362 | // CASE4: O | O | O | X | |
363 | // CASE5: O | O | O | O | |
364 | // CASE6: X | - | - | - | |
365 | // CASE7: O | O | X | O | |
366 | // | |
367 | // CASE1 = thumbenc && IsThumbGenerationNeeded() && block_mode | |
368 | // CASE2 = thumbenc && IsThumbGenerationNeeded() && !block_mode | |
369 | // CASE3 = thumbenc && !IsThumbGenerationNeeded() && !IsBTBCompressionSupported() && !block_mode | |
370 | // CASE4 = thumbenc && !IsThumbGenerationNeeded() && !STATE_NO_BTBCOMP && IsBTBCompressionSupported() && !block_mode | |
371 | // CASE5 = thumbenc && !IsThumbGenerationNeeded() && !STATE_NO_BTBCOMP && IsBTBCompressionSupported() && block_mode | |
372 | // CASE6 = !thumbenc | |
373 | // CASE7 = thumbenc && !IsThumbGenerationNeeded() && STATE_NO_BTBCOMP && block_mode | |
374 | ||
375 | if (!thumbenc) { | |
376 | // Confirm that no thumbnail information is transferred to HWJPEG | |
377 | setThumbnailSize(0, 0); | |
378 | } else if (!IsThumbGenerationNeeded() && IsBTBCompressionSupported() && | |
379 | (m_fThumbBufferType != checkInBufType())) { | |
380 | ALOGE("Buffer types of thumbnail(%d) and main(%d) images should be the same", | |
381 | m_fThumbBufferType, checkInBufType()); | |
382 | return -1; | |
383 | } else if (!IsThumbGenerationNeeded() && (m_fThumbBufferType == 0)) { | |
384 | // Thumbnail buffer configuration failed but the client forces to compress with thumbnail | |
385 | ThumbGenerationNeeded(); | |
386 | SetState(STATE_THUMBSIZE_CHANGED); | |
387 | } | |
388 | ||
389 | if (!EnsureFormatIsApplied()) { | |
390 | ALOGE("Failed to confirm format"); | |
391 | return -1; | |
392 | } | |
393 | ||
394 | if (!PrepareCompression(thumbenc)) { | |
395 | ALOGE("Failed to prepare compression"); | |
396 | return -1; | |
397 | } | |
398 | ||
399 | ssize_t mainlen = GetCompressor().Compress(&thumblen, block_mode); | |
400 | if (mainlen < 0) { | |
401 | ALOGE("Error occured while JPEG compression: %zd", mainlen); | |
402 | return -1; | |
403 | } | |
404 | ||
405 | if (mainlen == 0) { /* non-blocking compression */ | |
406 | ALOGD("Waiting for MCSC run"); | |
407 | return 0; | |
408 | } | |
409 | ||
410 | *size = static_cast<int>(FinishCompression(mainlen, thumblen)); | |
411 | if (*size < 0) | |
412 | return -1; | |
413 | ||
6f7be8c2 | 414 | ALOGD("....compression delay(usec.): HW %u, Total %lu)", |
5763fb39 T |
415 | GetHWDelay(), stopwatch.GetElapsed()); |
416 | ||
417 | return 0; | |
418 | } | |
419 | ||
420 | ssize_t ExynosJpegEncoderForCamera::FinishCompression(size_t mainlen, size_t thumblen) | |
421 | { | |
422 | bool btb = false; | |
423 | size_t max_streamsize = m_nStreamSize; | |
424 | char *mainbase = m_pAppWriter->GetMainStreamBase(); | |
425 | char *thumbbase = m_pAppWriter->GetThumbStreamBase(); | |
426 | ||
427 | m_nStreamSize = 0; | |
428 | ||
429 | mainlen = RemoveTrailingDummies(mainbase, mainlen); | |
430 | ||
431 | // Clearing SOI of the main image written by H/W | |
432 | m_pAppWriter->GetMainStreamBase()[0] = 0; | |
433 | m_pAppWriter->GetMainStreamBase()[1] = 0; | |
434 | ||
435 | if (thumbbase) { | |
436 | if (IsThumbGenerationNeeded()) { | |
437 | void *len; | |
438 | int ret = pthread_join(m_threadWorker, &len); | |
439 | if (ret != 0) { | |
440 | ALOGERR("Failed to wait thumbnail thread(%d)", ret); | |
441 | return -1; | |
442 | } | |
443 | ||
444 | if (len == NULL) | |
445 | ALOGE("Error occurred during thumbnail creation: no thumbnail is embedded"); | |
446 | ||
447 | thumblen = reinterpret_cast<size_t>(len); | |
448 | } else if (TestState(STATE_NO_BTBCOMP) || !IsBTBCompressionSupported()) { | |
449 | thumblen = CompressThumbnailOnly(m_pAppWriter->GetMaxThumbnailSize(), m_nThumbQuality, getColorFormat(), checkInBufType()); | |
450 | } else { | |
451 | btb = true; | |
452 | } | |
453 | ||
454 | size_t max_thumb = min(m_pAppWriter->GetMaxThumbnailSize(), max_streamsize - m_pAppWriter->CalculateAPPSize(0) - mainlen); | |
455 | ||
456 | if (thumblen > max_thumb) { | |
457 | ALOGI("Too large thumbnail (%dx%d) stream size %zu (max: %zu, quality factor %d)", | |
458 | m_nThumbWidth, m_nThumbHeight, thumblen, max_thumb, m_nThumbQuality); | |
459 | ALOGI("Retrying thumbnail compression with quality factor 50"); | |
460 | thumblen = CompressThumbnailOnly(max_thumb, 50, getColorFormat(), checkInBufType()); | |
461 | if (thumblen == 0) | |
462 | return -1; | |
463 | } | |
464 | ||
465 | if (!m_pAppWriter->IsThumbSpaceReserved()) { | |
466 | if (PTR_TO_ULONG(m_pStreamBase + max_streamsize) < | |
467 | PTR_TO_ULONG(mainbase + mainlen + thumblen - JPEG_MARKER_SIZE)) { | |
468 | ALOGE("Too small JPEG buffer length %zu (APP %zu, Main %zu, Thumb %zu)", | |
469 | max_streamsize, m_pAppWriter->CalculateAPPSize(thumblen), mainlen, thumblen); | |
470 | return -1; | |
471 | } | |
472 | ||
473 | // the SOI of the stream of the main image is stored after the APP4 or APP11 segment if they exist. | |
474 | memmove(m_pAppWriter->GetApp1End() + thumblen, m_pAppWriter->GetApp1End(), | |
475 | mainlen + PTR_DIFF(m_pAppWriter->GetApp1End(), m_pAppWriter->GetMainStreamBase())); | |
476 | m_pAppWriter->UpdateApp1Size(thumblen); | |
477 | ||
478 | // m_nAppLength has the value of appwriter.GetExactAPPSize() | |
479 | // Therefore m_nStreamSize should be initialized with thumbnail stream length; | |
480 | } | |
481 | ||
482 | if (thumblen > 0) { | |
483 | memcpy(m_pAppWriter->GetThumbStreamBase(), m_pIONThumbJpegBuffer, thumblen); | |
484 | m_pAppWriter->Finalize(thumblen); | |
485 | } | |
486 | ||
487 | if (m_pAppWriter->IsThumbSpaceReserved()) { | |
488 | // clear the possible stale data in the dummy area after the thumbnail stream | |
489 | memset(m_pAppWriter->GetThumbStreamBase() + thumblen, 0, | |
490 | m_pAppWriter->GetMaxThumbnailSize() - thumblen); | |
491 | } | |
492 | } else { | |
493 | thumblen = 0; | |
494 | } | |
495 | ||
496 | m_nStreamSize += m_pAppWriter->CalculateAPPSize(thumblen) + mainlen; | |
497 | ||
498 | /* | |
499 | * m_nAppLength: The size of APP1 segment and APP4 segment including markers | |
500 | * getJpegSize(): size of the compressed stream of the main image | |
501 | * Note that 2 byte(size of SOI marker) is included in APP1 segment size. | |
502 | * Thus the size of SOI marker in front of the stream is not added. | |
503 | */ | |
504 | ALOGD("Completed image compression (%zd(thumb %zu) bytes, HWFC? %d, BTB? %d)", | |
505 | mainlen, thumblen, TestState(STATE_HWFC_ENABLED), btb); | |
506 | ||
507 | m_pStreamBase[0] = 0xFF; | |
508 | m_pStreamBase[1] = 0xD8; | |
509 | ||
510 | return m_nStreamSize; | |
511 | } | |
512 | ||
513 | /* The logic in WaitForHWFC() is the same with encode() */ | |
514 | ssize_t ExynosJpegEncoderForCamera::WaitForCompression() | |
515 | { | |
516 | if (!TestState(STATE_HWFC_ENABLED)) | |
517 | return m_nStreamSize; | |
518 | ||
519 | size_t thumblen = 0; | |
520 | ssize_t streamlen = GetCompressor().WaitForCompression(&thumblen); | |
521 | if (streamlen < 0) | |
522 | return streamlen; | |
523 | ||
524 | return FinishCompression(streamlen, thumblen); | |
525 | } | |
526 | ||
527 | bool ExynosJpegEncoderForCamera::GenerateThumbnailImage() | |
528 | { | |
529 | if (!m_pLibCSC) { | |
530 | ALOGE("Unable to create thumbnail because of no LibCSC"); | |
531 | return false; | |
532 | } | |
533 | ||
534 | int main_width, main_height; | |
535 | if (getSize(&main_width, &main_height) < 0) { | |
536 | ALOGE("Failed to get main image size"); | |
537 | return false; | |
538 | } | |
539 | ||
540 | int v4l2Format = getColorFormat(); | |
541 | ||
542 | if (!AllocThumbBuffer(v4l2Format)) | |
543 | return false; | |
544 | ||
545 | ALOGD("Generating thumbnail image: %dx%d -> %dx%d", | |
546 | main_width, main_height, m_nThumbWidth, m_nThumbHeight); | |
547 | ||
548 | if (!m_pLibCSC->set_src_format(main_width, main_height, V4L2FMT2HALFMT(v4l2Format))) { | |
549 | ALOGE("Failed to configure the main image format to LibCSC"); | |
550 | return false; | |
551 | } | |
552 | ||
553 | // The source format for thumbnail compression is always NV12 | |
554 | if (!m_pLibCSC->set_dst_format(m_nThumbWidth, m_nThumbHeight, V4L2FMT2HALFMT(v4l2Format))) { | |
555 | ALOGE("Failed to configure the target image format to LibCSC"); | |
556 | return false; | |
557 | } | |
558 | ||
559 | int len_srcbufs[3] = {0, 0, 0}; | |
560 | void *srcbufs[3] = {NULL, NULL, NULL}; | |
561 | int memtype; | |
562 | ||
563 | if (checkInBufType() == JPEG_BUF_TYPE_USER_PTR) { | |
564 | char *bufs[3]; | |
565 | if (getInBuf(bufs, len_srcbufs, 3) < 0) { | |
566 | ALOGE("Failed to retrieve the main image buffers"); | |
567 | return false; | |
568 | } | |
569 | memtype = V4L2_MEMORY_USERPTR; | |
570 | srcbufs[0] = reinterpret_cast<void *>(bufs[0]); | |
571 | srcbufs[1] = reinterpret_cast<void *>(bufs[1]); | |
572 | srcbufs[2] = reinterpret_cast<void *>(bufs[2]); | |
573 | } else { // mainbuftype == JPEG_BUF_TYPE_DMA_BUF | |
574 | int bufs[3]; | |
575 | if (getInBuf(bufs, len_srcbufs, 3) < 0) { | |
576 | ALOGE("Failed to retrieve the main image buffers"); | |
577 | return false; | |
578 | } | |
579 | memtype = V4L2_MEMORY_DMABUF; | |
580 | srcbufs[0] = reinterpret_cast<void *>(bufs[0]); | |
581 | srcbufs[1] = reinterpret_cast<void *>(bufs[1]); | |
582 | srcbufs[2] = reinterpret_cast<void *>(bufs[2]); | |
583 | } | |
584 | ||
585 | if (!m_pLibCSC->set_src_buffer(srcbufs, memtype)) { | |
586 | ALOGE("Failed to configure the main image buffers to LibCSC"); | |
587 | return false; | |
588 | } | |
589 | ||
590 | void *dstbuf[3] = {NULL, NULL, NULL}; | |
591 | dstbuf[0] = reinterpret_cast<void *>(m_fdIONThumbImgBuffer); | |
592 | if (!m_pLibCSC->set_dst_buffer(dstbuf, V4L2_MEMORY_DMABUF)) { | |
593 | ALOGE("Failed to configure the thumbnail source buffer to LibCSC"); | |
594 | return false; | |
595 | } | |
596 | ||
597 | if (!m_pLibCSC->convert()) { | |
598 | ALOGE("Failed to convert the main image to thumbnail with LibCSC"); | |
599 | return false; | |
600 | } | |
601 | ||
602 | return true; | |
603 | } | |
604 | ||
605 | size_t ExynosJpegEncoderForCamera::CompressThumbnail() | |
606 | { | |
5763fb39 T |
607 | unsigned int v4l2Format = getColorFormat(); |
608 | int buftype = checkInBufType(); | |
609 | ||
610 | if (IsThumbGenerationNeeded()) { | |
611 | if (!GenerateThumbnailImage()) | |
612 | return 0; | |
613 | ||
614 | // libcsc output configured by this class is always NV21. | |
615 | v4l2Format = getColorFormat(); | |
616 | buftype = JPEG_BUF_TYPE_DMA_BUF; | |
617 | // reduced setInBuf2() | |
618 | m_fdThumbnailImageBuffer[0] = m_fdIONThumbImgBuffer; | |
619 | m_szThumbnailImageLen[0] = m_szIONThumbImgBuffer; | |
620 | } | |
621 | ||
622 | return CompressThumbnailOnly(m_pAppWriter->GetMaxThumbnailSize(), m_nThumbQuality, v4l2Format, buftype); | |
623 | } | |
624 | ||
625 | bool ExynosJpegEncoderForCamera::AllocThumbBuffer(int v4l2Format) | |
626 | { | |
627 | if (m_fdIONClient < 0) { | |
628 | ALOGE("ION client is not created"); | |
629 | return false; | |
630 | } | |
631 | ||
632 | size_t thumbbufsize = GetImageLength(m_nThumbWidth, m_nThumbHeight, v4l2Format); | |
633 | if (thumbbufsize == 0) { | |
634 | ALOGE("Unsupported V4L2 format %#X for thumbnail", v4l2Format); | |
635 | return false; | |
636 | } | |
637 | ||
638 | if (m_fdIONThumbImgBuffer >= 0) { | |
639 | if (m_szIONThumbImgBuffer >= thumbbufsize) | |
640 | return true; | |
641 | ||
642 | if (m_pIONThumbImgBuffer != NULL) | |
643 | munmap(m_pIONThumbImgBuffer, m_szIONThumbImgBuffer); | |
644 | ||
645 | close(m_fdIONThumbImgBuffer); | |
646 | ||
647 | m_fdIONThumbImgBuffer = -1; | |
648 | m_pIONThumbImgBuffer = NULL; | |
649 | m_szIONThumbImgBuffer = 0; | |
650 | } | |
651 | ||
652 | if (ion_alloc_fd(m_fdIONClient, thumbbufsize, 0, ION_HEAP_SYSTEM_MASK, 0, &m_fdIONThumbImgBuffer) < 0) { | |
653 | ALOGERR("Failed to allocate %zu bytes for NV12 %ux%u", thumbbufsize, m_nThumbHeight, m_nThumbWidth); | |
654 | m_fdIONThumbImgBuffer = -1; | |
655 | return false; | |
656 | } | |
657 | ||
658 | m_szIONThumbImgBuffer = thumbbufsize; | |
659 | ||
660 | return AllocThumbJpegBuffer(); | |
661 | } | |
662 | ||
663 | bool ExynosJpegEncoderForCamera::AllocThumbJpegBuffer() | |
664 | { | |
665 | if (m_fdIONClient < 0) { | |
666 | ALOGE("ION client is not created"); | |
667 | return false; | |
668 | } | |
669 | ||
670 | size_t thumbbufsize = m_nThumbHeight * m_nThumbWidth * 3; | |
671 | ||
672 | if (m_pIONThumbJpegBuffer) { | |
673 | if (m_szIONThumbJpegBuffer >= thumbbufsize) | |
674 | return true; | |
675 | ||
676 | munmap(m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer); | |
677 | ||
678 | m_szIONThumbJpegBuffer = 0; | |
679 | m_pIONThumbJpegBuffer = NULL; | |
680 | } | |
681 | ||
682 | int fd = -1; | |
683 | if (ion_alloc_fd(m_fdIONClient, thumbbufsize, 0, ION_HEAP_SYSTEM_MASK, | |
684 | ION_FLAG_CACHED | ION_FLAG_CACHED_NEEDS_SYNC, &fd) < 0) { | |
685 | ALOGERR("Failed to allocate %zu bytes for thumbnail stream buffer of %ux%u", | |
686 | thumbbufsize, m_nThumbHeight, m_nThumbWidth); | |
687 | return false; | |
688 | } | |
689 | ||
690 | m_pIONThumbJpegBuffer = reinterpret_cast<char *>( | |
691 | mmap(NULL, thumbbufsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); | |
692 | close(fd); | |
693 | if (m_pIONThumbJpegBuffer == MAP_FAILED) { | |
694 | ALOGERR("Failed to map thumbnail stream buffer (%zu bytes)", thumbbufsize); | |
695 | ||
696 | m_pIONThumbJpegBuffer = NULL; | |
697 | } else { | |
698 | m_szIONThumbJpegBuffer = thumbbufsize; | |
699 | } | |
700 | ||
701 | return m_pIONThumbJpegBuffer != NULL; | |
702 | } | |
703 | ||
704 | size_t ExynosJpegEncoderForCamera::CompressThumbnailOnly(size_t limit, int quality, | |
705 | unsigned int v4l2Format, int src_buftype) | |
706 | { | |
707 | if (!m_phwjpeg4thumb->SetImageFormat(v4l2Format, m_nThumbWidth, m_nThumbHeight)) { | |
708 | ALOGE("Failed to configure thumbnail source image format to %#010x, %ux%u", | |
709 | v4l2Format, m_nThumbWidth, m_nThumbHeight); | |
710 | return 0; | |
711 | } | |
712 | ||
713 | unsigned int num_buffers = 1; | |
714 | switch (v4l2Format) { | |
715 | case V4L2_PIX_FMT_YUV420M: | |
716 | case V4L2_PIX_FMT_YVU420M: | |
717 | num_buffers++; | |
cfc9f537 | 718 | [[fallthrough]]; |
5763fb39 T |
719 | case V4L2_PIX_FMT_NV12M: |
720 | case V4L2_PIX_FMT_NV21M: | |
721 | num_buffers++; | |
722 | } | |
723 | ||
724 | if (src_buftype == JPEG_BUF_TYPE_USER_PTR) { | |
725 | if (!m_phwjpeg4thumb->SetImageBuffer(m_pThumbnailImageBuffer, | |
726 | m_szThumbnailImageLen, num_buffers)) { | |
727 | ALOGE("Failed to configure thumbnail buffers(userptr) for thumbnail"); | |
728 | return 0; | |
729 | } | |
730 | } else { // JPEG_BUF_TYPE_DMA_BUF | |
731 | if (!m_phwjpeg4thumb->SetImageBuffer(m_fdThumbnailImageBuffer, | |
732 | m_szThumbnailImageLen, num_buffers)) { | |
733 | ALOGE("Failed to configure thumbnail buffers(dmabuf) for thumbnail"); | |
734 | return 0; | |
735 | } | |
736 | } | |
737 | ||
738 | if (!m_phwjpeg4thumb->SetJpegBuffer(m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer)) { | |
739 | ALOGE("Failed to configure thumbnail stream buffer (addr %p, size %zu)", | |
740 | m_pIONThumbJpegBuffer, m_szIONThumbJpegBuffer); | |
741 | return 0; | |
742 | } | |
743 | ||
744 | // Since the compressed stream of the thumbnail image is to be embedded in | |
745 | // APP1 segment, at the end of Exif metadata, the length of the stream should | |
746 | // not exceed the maximum length of a segment, 64KB minus the length of Exif | |
747 | // metadata. If the stream length is too large, repeat the compression until | |
748 | // the length become proper to embed. | |
749 | while (quality >= 20) { | |
750 | if (!m_phwjpeg4thumb->SetQuality(quality)) { | |
751 | ALOGE("Failed to configure thumbnail quality factor %u", quality); | |
752 | return 0; | |
753 | } | |
754 | ||
755 | ssize_t thumbsize = m_phwjpeg4thumb->Compress(); | |
756 | if (thumbsize < 0) { | |
757 | ALOGE("Failed to compress thumbnail"); | |
758 | return 0; | |
759 | } | |
760 | ||
761 | thumbsize = RemoveTrailingDummies(m_pIONThumbJpegBuffer, thumbsize); | |
762 | if (static_cast<size_t>(thumbsize) > limit) { | |
763 | quality = min(50, quality - 10); | |
764 | ALOGI_IF(quality >= 20, | |
765 | "Too large thumbnail stream size %zu. Retrying with quality factor %d...", | |
766 | thumbsize, quality); | |
767 | } else { | |
768 | return thumbsize; | |
769 | } | |
770 | } | |
771 | ||
772 | ALOG_ASSERT(false, "It should never reach here"); | |
773 | ALOGE("Thumbnail compression finally failed"); | |
774 | ||
775 | return 0; | |
776 | } | |
777 | ||
778 | int ExynosJpegEncoderForCamera::setInBuf2(int *piBuf, int *iSize) | |
779 | { | |
780 | NoThumbGenerationNeeded(); | |
781 | ||
782 | if (!EnsureFormatIsApplied()) | |
783 | return -1; | |
784 | ||
785 | CHWJpegCompressor &hwjpeg = GetCompressor(); | |
786 | unsigned int num_buffers = 3; | |
787 | if (!hwjpeg.GetImageBufferSizes(m_szThumbnailImageLen, &num_buffers)) { | |
788 | ALOGE("Failed to get image buffer sizes"); | |
789 | return -1; | |
790 | } | |
791 | ||
792 | for (unsigned int i = 0; i < num_buffers; i++) { | |
793 | m_szThumbnailImageLen[i] = iSize[i]; | |
794 | m_fdThumbnailImageBuffer[i] = piBuf[i]; | |
795 | } | |
796 | ||
797 | if (IsBTBCompressionSupported() && | |
798 | !hwjpeg.SetImageBuffer2(m_fdThumbnailImageBuffer, m_szThumbnailImageLen, num_buffers)) { | |
799 | ALOGE("Failed to configure thumbnail buffers"); | |
800 | return -1; | |
801 | } | |
802 | ||
803 | m_fThumbBufferType = JPEG_BUF_TYPE_DMA_BUF; | |
804 | ||
805 | return 0; | |
806 | } | |
807 | ||
808 | int ExynosJpegEncoderForCamera::setInBuf2(char **pcBuf, int *iSize) | |
809 | { | |
810 | NoThumbGenerationNeeded(); | |
811 | ||
812 | if (!EnsureFormatIsApplied()) | |
813 | return -1; | |
814 | ||
815 | CHWJpegCompressor &hwjpeg = GetCompressor(); | |
816 | unsigned int num_buffers = 3; | |
817 | if (!hwjpeg.GetImageBufferSizes(m_szThumbnailImageLen, &num_buffers)) { | |
818 | ALOGE("Failed to get image buffer sizes"); | |
819 | return -1; | |
820 | } | |
821 | ||
822 | for (unsigned int i = 0; i < num_buffers; i++) { | |
823 | m_szThumbnailImageLen[i] = iSize[i]; | |
824 | m_pThumbnailImageBuffer[i] = pcBuf[i]; | |
825 | } | |
826 | ||
827 | if (IsBTBCompressionSupported() && | |
828 | !hwjpeg.SetImageBuffer2(m_pThumbnailImageBuffer, m_szThumbnailImageLen, num_buffers)) { | |
829 | ALOGE("Failed to configure thumbnail buffers"); | |
830 | return -1; | |
831 | } | |
832 | ||
833 | m_fThumbBufferType = JPEG_BUF_TYPE_USER_PTR; | |
834 | ||
835 | return 0; | |
836 | } | |
837 | ||
838 | size_t ExynosJpegEncoderForCamera::GetThumbnailImage(char *buffer, size_t buflen) | |
839 | { | |
840 | if (m_fdIONThumbImgBuffer < 0) { | |
841 | ALOGE("No internal thumbnail buffer is allocated"); | |
842 | return 0; | |
843 | } | |
844 | ||
845 | size_t thumbbufsize = GetImageLength(m_nThumbWidth, m_nThumbHeight, getColorFormat()); | |
846 | if (buflen < thumbbufsize) { | |
847 | ALOGE("Too small buffer %zu (thumbnail image size %zu)", buflen, thumbbufsize); | |
848 | return 0; | |
849 | } | |
850 | ||
851 | ALOG_ASSERT(m_szIONThumbImgBuffer >= thumbbufsize, | |
852 | "m_szIONThumbImgBuffer(%zu) is smaller than the thumbnail (%zu)", | |
853 | m_szIONThumbImgBuffer, thumbbufsize); | |
854 | if (m_pIONThumbImgBuffer == NULL) { | |
855 | m_pIONThumbImgBuffer = reinterpret_cast<char *>(mmap( | |
856 | NULL, m_szIONThumbImgBuffer, PROT_READ, MAP_SHARED, m_fdIONThumbImgBuffer, 0)); | |
857 | if (m_pIONThumbImgBuffer == MAP_FAILED) { | |
858 | m_pIONThumbImgBuffer = NULL; | |
859 | ALOGERR("Failed to map thumbnail image buffer (%zu bytes)", m_szIONThumbImgBuffer); | |
860 | return 0; | |
861 | } | |
862 | } | |
863 | ||
864 | memcpy(buffer, m_pIONThumbImgBuffer, thumbbufsize); | |
865 | ||
866 | ALOGD("Copied thumbnail image to %p (%zu bytes)", buffer, thumbbufsize); | |
867 | ||
868 | return m_szIONThumbImgBuffer; | |
869 | } | |
870 | ||
871 | int ExynosJpegEncoderForCamera::destroy() | |
872 | { | |
873 | GetCompressor().Release(); | |
874 | return 0; | |
875 | } |