blob: b0345845a3a908db52e5774e4652f7da41991c2a [file] [log] [blame]
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +01001/*
2 * Copyright (C) 2023 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// #define LOG_NDEBUG 0
Jan Sebechlebskyb3771312024-03-15 10:38:02 +010017#include "system/graphics.h"
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010018#define LOG_TAG "JpegUtil"
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010019#include <cstddef>
20#include <cstdint>
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +010021#include <optional>
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +010022#include <vector>
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010023
Jan Sebechlebskyb3771312024-03-15 10:38:02 +010024#include "JpegUtil.h"
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010025#include "android/hardware_buffer.h"
26#include "jpeglib.h"
27#include "log/log.h"
28#include "ui/GraphicBuffer.h"
29#include "ui/GraphicBufferMapper.h"
Jan Sebechlebskyb3771312024-03-15 10:38:02 +010030#include "util/Util.h"
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010031#include "utils/Errors.h"
32
33namespace android {
34namespace companion {
35namespace virtualcamera {
36namespace {
37
Jan Sebechlebskyb3771312024-03-15 10:38:02 +010038constexpr int k2DCTSIZE = 2 * DCTSIZE;
39
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010040class LibJpegContext {
41 public:
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +010042 LibJpegContext(int width, int height, int quality, const size_t outBufferSize,
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +010043 void* outBuffer)
44 : mWidth(width),
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010045 mHeight(height),
46 mDstBufferSize(outBufferSize),
47 mDstBuffer(outBuffer) {
48 // Initialize error handling for libjpeg.
49 // We call jpeg_std_error to initialize standard error
50 // handling and then override:
51 // * output_message not to print to stderr, but use ALOG instead.
52 // * error_exit not to terminate the process, but failure flag instead.
53 mCompressStruct.err = jpeg_std_error(&mErrorMgr);
54 mCompressStruct.err->output_message = onOutputError;
55 mCompressStruct.err->error_exit = onErrorExit;
56 jpeg_create_compress(&mCompressStruct);
57
58 // Configure input image parameters.
59 mCompressStruct.image_width = width;
60 mCompressStruct.image_height = height;
61 mCompressStruct.input_components = 3;
62 mCompressStruct.in_color_space = JCS_YCbCr;
63 // We pass pointer to this instance as a client data so we can
64 // access this object from the static callbacks invoked by
65 // libjpeg.
66 mCompressStruct.client_data = this;
67
68 // Configure destination manager for libjpeg.
69 mCompressStruct.dest = &mDestinationMgr;
70 mDestinationMgr.init_destination = onInitDestination;
71 mDestinationMgr.empty_output_buffer = onEmptyOutputBuffer;
72 mDestinationMgr.term_destination = onTermDestination;
73 mDestinationMgr.next_output_byte = reinterpret_cast<JOCTET*>(mDstBuffer);
74 mDestinationMgr.free_in_buffer = mDstBufferSize;
75
76 // Configure everything else based on input configuration above.
77 jpeg_set_defaults(&mCompressStruct);
78
79 // Set quality and colorspace.
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +010080 jpeg_set_quality(&mCompressStruct, quality, 1);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010081 jpeg_set_colorspace(&mCompressStruct, JCS_YCbCr);
82
83 // Configure RAW input mode - this let's libjpeg know we're providing raw,
84 // subsampled YCbCr data.
85 mCompressStruct.raw_data_in = 1;
86 mCompressStruct.dct_method = JDCT_IFAST;
87
88 // Configure sampling factors - this states that every 2 Y
89 // samples share 1 Cb & 1 Cr component vertically & horizontally (YUV420).
90 mCompressStruct.comp_info[0].h_samp_factor = 2;
91 mCompressStruct.comp_info[0].v_samp_factor = 2;
92 mCompressStruct.comp_info[1].h_samp_factor = 1;
93 mCompressStruct.comp_info[1].v_samp_factor = 1;
94 mCompressStruct.comp_info[2].h_samp_factor = 1;
95 mCompressStruct.comp_info[2].v_samp_factor = 1;
96 }
97
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +010098 LibJpegContext& setApp1Data(const uint8_t* app1Data, const size_t size) {
99 mApp1Data = app1Data;
100 mApp1DataSize = size;
101 return *this;
102 }
103
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100104 std::optional<size_t> compress(std::shared_ptr<AHardwareBuffer> inBuffer) {
105 GraphicBuffer* gBuffer = GraphicBuffer::fromAHardwareBuffer(inBuffer.get());
106
107 if (gBuffer == nullptr) {
108 ALOGE("%s: Input graphic buffer is nullptr", __func__);
109 return std::nullopt;
110 }
111
112 if (gBuffer->getPixelFormat() != HAL_PIXEL_FORMAT_YCbCr_420_888) {
113 // This should never happen since we're allocating the temporary buffer
114 // with YUV420 layout above.
115 ALOGE("%s: Cannot compress non-YUV buffer (pixelFormat %d)", __func__,
116 gBuffer->getPixelFormat());
117 return std::nullopt;
118 }
119
120 YCbCrLockGuard yCbCrLock(inBuffer, AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
121 if (yCbCrLock.getStatus() != OK) {
122 ALOGE("%s: Failed to lock the input buffer: %s", __func__,
123 statusToString(yCbCrLock.getStatus()).c_str());
124 return std::nullopt;
125 }
126 const android_ycbcr& ycbr = *yCbCrLock;
127
128 const int inBufferWidth = gBuffer->getWidth();
129 const int inBufferHeight = gBuffer->getHeight();
130
131 if (inBufferWidth % k2DCTSIZE || (inBufferHeight % k2DCTSIZE)) {
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100132 ALOGE(
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100133 "%s: Compressing YUV420 buffer with size %dx%d not aligned with 2 * "
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100134 "DCTSIZE (%d) is not currently supported.",
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100135 __func__, inBufferWidth, inBufferHeight, DCTSIZE);
136 return std::nullopt;
137 }
138
139 if (inBufferWidth < mWidth || inBufferHeight < mHeight) {
140 ALOGE(
141 "%s: Input buffer has smaller size (%dx%d) than image to be "
142 "compressed (%dx%d)",
143 __func__, inBufferWidth, inBufferHeight, mWidth, mHeight);
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100144 return std::nullopt;
145 }
146
147 // Chroma planes have 1/2 resolution of the original image.
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100148 const int cHeight = inBufferHeight / 2;
149 const int cWidth = inBufferWidth / 2;
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100150
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100151 // Prepare arrays of pointers to scanlines of each plane.
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100152 std::vector<JSAMPROW> yLines(inBufferHeight);
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100153 std::vector<JSAMPROW> cbLines(cHeight);
154 std::vector<JSAMPROW> crLines(cHeight);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100155
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100156 uint8_t* y = static_cast<uint8_t*>(ycbr.y);
157 uint8_t* cb = static_cast<uint8_t*>(ycbr.cb);
158 uint8_t* cr = static_cast<uint8_t*>(ycbr.cr);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100159
160 // Since UV samples might be interleaved (semiplanar) we need to copy
161 // them to separate planes, since libjpeg doesn't directly
162 // support processing semiplanar YUV.
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100163 const int cSamples = cWidth * cHeight;
164 std::vector<uint8_t> cb_plane(cSamples);
165 std::vector<uint8_t> cr_plane(cSamples);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100166
167 // TODO(b/301023410) - Use libyuv or ARM SIMD for "unzipping" the data.
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100168 int out_idx = 0;
169 for (int i = 0; i < cHeight; ++i) {
170 for (int j = 0; j < cWidth; ++j) {
171 cb_plane[out_idx] = cb[j * ycbr.chroma_step];
172 cr_plane[out_idx] = cr[j * ycbr.chroma_step];
173 out_idx++;
174 }
175 cb += ycbr.cstride;
176 cr += ycbr.cstride;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100177 }
178
179 // Collect pointers to individual scanline of each plane.
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100180 for (int i = 0; i < inBufferHeight; ++i) {
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100181 yLines[i] = y + i * ycbr.ystride;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100182 }
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100183 for (int i = 0; i < cHeight; ++i) {
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100184 cbLines[i] = cb_plane.data() + i * cWidth;
185 crLines[i] = cr_plane.data() + i * cWidth;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100186 }
187
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100188 return compress(yLines, cbLines, crLines);
189 }
190
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100191 private:
192 void setSuccess(const boolean success) {
193 mSuccess = success;
194 }
195
196 void initDestination() {
197 mDestinationMgr.next_output_byte = reinterpret_cast<JOCTET*>(mDstBuffer);
198 mDestinationMgr.free_in_buffer = mDstBufferSize;
199 ALOGV("%s:%d jpeg start: %p [%zu]", __FUNCTION__, __LINE__, mDstBuffer,
200 mDstBufferSize);
201 }
202
203 void termDestination() {
204 mEncodedSize = mDstBufferSize - mDestinationMgr.free_in_buffer;
205 ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__, mEncodedSize);
206 }
207
208 // Perform actual compression.
209 //
210 // Takes vector of pointers to Y / Cb / Cr scanlines as an input. Length of
211 // each vector needs to correspond to height of corresponding plane.
212 //
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100213 // Returns size of compressed image in bytes on success, empty optional otherwise.
214 std::optional<size_t> compress(std::vector<JSAMPROW>& yLines,
215 std::vector<JSAMPROW>& cbLines,
216 std::vector<JSAMPROW>& crLines) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100217 jpeg_start_compress(&mCompressStruct, TRUE);
218
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100219 if (mApp1Data != nullptr && mApp1DataSize > 0) {
220 ALOGV("%s: Writing exif, size %zu B", __func__, mApp1DataSize);
221 jpeg_write_marker(&mCompressStruct, JPEG_APP0 + 1,
222 static_cast<const JOCTET*>(mApp1Data), mApp1DataSize);
223 }
224
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100225 while (mCompressStruct.next_scanline < mCompressStruct.image_height) {
226 const uint32_t batchSize = DCTSIZE * 2;
227 const uint32_t nl = mCompressStruct.next_scanline;
228 JSAMPARRAY planes[3]{&yLines[nl], &cbLines[nl / 2], &crLines[nl / 2]};
229
230 uint32_t done = jpeg_write_raw_data(&mCompressStruct, planes, batchSize);
231
232 if (done != batchSize) {
233 ALOGE("%s: compressed %u lines, expected %u (total %u/%u)",
234 __FUNCTION__, done, batchSize, mCompressStruct.next_scanline,
235 mCompressStruct.image_height);
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100236 return std::nullopt;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100237 }
238 }
239 jpeg_finish_compress(&mCompressStruct);
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100240 return mEncodedSize;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100241 }
242
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100243 // === libjpeg callbacks below ===
244
245 static void onOutputError(j_common_ptr cinfo) {
246 char buffer[JMSG_LENGTH_MAX];
247 (*cinfo->err->format_message)(cinfo, buffer);
248 ALOGE("libjpeg error: %s", buffer);
249 };
250
251 static void onErrorExit(j_common_ptr cinfo) {
252 static_cast<LibJpegContext*>(cinfo->client_data)->setSuccess(false);
253 };
254
255 static void onInitDestination(j_compress_ptr cinfo) {
256 static_cast<LibJpegContext*>(cinfo->client_data)->initDestination();
257 }
258
259 static int onEmptyOutputBuffer(j_compress_ptr cinfo __unused) {
260 ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__);
261 return 0;
262 }
263
264 static void onTermDestination(j_compress_ptr cinfo) {
265 static_cast<LibJpegContext*>(cinfo->client_data)->termDestination();
266 }
267
268 jpeg_compress_struct mCompressStruct;
269 jpeg_error_mgr mErrorMgr;
270 jpeg_destination_mgr mDestinationMgr;
271
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100272 // APP1 data.
273 const uint8_t* mApp1Data = nullptr;
274 size_t mApp1DataSize = 0;
275
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100276 // Dimensions of the input image.
277 int mWidth;
278 int mHeight;
279
280 // Destination buffer and it's capacity.
281 size_t mDstBufferSize;
282 void* mDstBuffer;
283
284 // This will be set to size of encoded data
285 // written to the outputBuffer when encoding finishes.
286 size_t mEncodedSize;
287 // Set to true/false based on whether the encoding
288 // was successful.
289 boolean mSuccess = true;
290};
291
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100292int roundTo2DCTMultiple(const int n) {
293 const int mod = n % k2DCTSIZE;
294 return mod == 0 ? n : n + (k2DCTSIZE - mod);
295}
296
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100297} // namespace
298
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100299std::optional<size_t> compressJpeg(const int width, const int height,
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100300 const int quality,
301 std::shared_ptr<AHardwareBuffer> inBuffer,
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100302 const std::vector<uint8_t>& app1ExifData,
303 size_t outBufferSize, void* outBuffer) {
304 LibJpegContext context(width, height, quality, outBufferSize, outBuffer);
305 if (!app1ExifData.empty()) {
306 context.setApp1Data(app1ExifData.data(), app1ExifData.size());
307 }
Jan Sebechlebskyb3771312024-03-15 10:38:02 +0100308 return context.compress(inBuffer);
309}
310
311Resolution roundTo2DctSize(const Resolution resolution) {
312 return Resolution(roundTo2DCTMultiple(resolution.width),
313 roundTo2DCTMultiple(resolution.height));
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100314}
315
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100316} // namespace virtualcamera
317} // namespace companion
318} // namespace android