blob: 98f2448eb665ef29ee2e47fa491afc540e149605 [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
17#define LOG_TAG "JpegUtil"
18#include "JpegUtil.h"
19
20#include <cstddef>
21#include <cstdint>
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +010022#include <optional>
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +010023#include <vector>
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010024
25#include "android/hardware_buffer.h"
26#include "jpeglib.h"
27#include "log/log.h"
28#include "ui/GraphicBuffer.h"
29#include "ui/GraphicBufferMapper.h"
30#include "utils/Errors.h"
31
32namespace android {
33namespace companion {
34namespace virtualcamera {
35namespace {
36
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010037class LibJpegContext {
38 public:
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +010039 LibJpegContext(int width, int height, int quality, const size_t outBufferSize,
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +010040 void* outBuffer)
41 : mWidth(width),
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010042 mHeight(height),
43 mDstBufferSize(outBufferSize),
44 mDstBuffer(outBuffer) {
45 // Initialize error handling for libjpeg.
46 // We call jpeg_std_error to initialize standard error
47 // handling and then override:
48 // * output_message not to print to stderr, but use ALOG instead.
49 // * error_exit not to terminate the process, but failure flag instead.
50 mCompressStruct.err = jpeg_std_error(&mErrorMgr);
51 mCompressStruct.err->output_message = onOutputError;
52 mCompressStruct.err->error_exit = onErrorExit;
53 jpeg_create_compress(&mCompressStruct);
54
55 // Configure input image parameters.
56 mCompressStruct.image_width = width;
57 mCompressStruct.image_height = height;
58 mCompressStruct.input_components = 3;
59 mCompressStruct.in_color_space = JCS_YCbCr;
60 // We pass pointer to this instance as a client data so we can
61 // access this object from the static callbacks invoked by
62 // libjpeg.
63 mCompressStruct.client_data = this;
64
65 // Configure destination manager for libjpeg.
66 mCompressStruct.dest = &mDestinationMgr;
67 mDestinationMgr.init_destination = onInitDestination;
68 mDestinationMgr.empty_output_buffer = onEmptyOutputBuffer;
69 mDestinationMgr.term_destination = onTermDestination;
70 mDestinationMgr.next_output_byte = reinterpret_cast<JOCTET*>(mDstBuffer);
71 mDestinationMgr.free_in_buffer = mDstBufferSize;
72
73 // Configure everything else based on input configuration above.
74 jpeg_set_defaults(&mCompressStruct);
75
76 // Set quality and colorspace.
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +010077 jpeg_set_quality(&mCompressStruct, quality, 1);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +010078 jpeg_set_colorspace(&mCompressStruct, JCS_YCbCr);
79
80 // Configure RAW input mode - this let's libjpeg know we're providing raw,
81 // subsampled YCbCr data.
82 mCompressStruct.raw_data_in = 1;
83 mCompressStruct.dct_method = JDCT_IFAST;
84
85 // Configure sampling factors - this states that every 2 Y
86 // samples share 1 Cb & 1 Cr component vertically & horizontally (YUV420).
87 mCompressStruct.comp_info[0].h_samp_factor = 2;
88 mCompressStruct.comp_info[0].v_samp_factor = 2;
89 mCompressStruct.comp_info[1].h_samp_factor = 1;
90 mCompressStruct.comp_info[1].v_samp_factor = 1;
91 mCompressStruct.comp_info[2].h_samp_factor = 1;
92 mCompressStruct.comp_info[2].v_samp_factor = 1;
93 }
94
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +010095 LibJpegContext& setApp1Data(const uint8_t* app1Data, const size_t size) {
96 mApp1Data = app1Data;
97 mApp1DataSize = size;
98 return *this;
99 }
100
101 std::optional<size_t> compress(const android_ycbcr& ycbr) {
102 // TODO(b/301023410) - Add support for compressing image sizes not aligned
103 // with DCT size.
104 if (mWidth % (2 * DCTSIZE) || (mHeight % (2 * DCTSIZE))) {
105 ALOGE(
106 "%s: Compressing YUV420 image with size %dx%d not aligned with 2 * "
107 "DCTSIZE (%d) is not currently supported.",
108 __func__, mWidth, mHeight, 2 * DCTSIZE);
109 return std::nullopt;
110 }
111
112 // Chroma planes have 1/2 resolution of the original image.
113 const int cHeight = mHeight / 2;
114 const int cWidth = mWidth / 2;
115
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100116 // Prepare arrays of pointers to scanlines of each plane.
117 std::vector<JSAMPROW> yLines(mHeight);
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100118 std::vector<JSAMPROW> cbLines(cHeight);
119 std::vector<JSAMPROW> crLines(cHeight);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100120
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100121 uint8_t* y = static_cast<uint8_t*>(ycbr.y);
122 uint8_t* cb = static_cast<uint8_t*>(ycbr.cb);
123 uint8_t* cr = static_cast<uint8_t*>(ycbr.cr);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100124
125 // Since UV samples might be interleaved (semiplanar) we need to copy
126 // them to separate planes, since libjpeg doesn't directly
127 // support processing semiplanar YUV.
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100128 const int cSamples = cWidth * cHeight;
129 std::vector<uint8_t> cb_plane(cSamples);
130 std::vector<uint8_t> cr_plane(cSamples);
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100131
132 // TODO(b/301023410) - Use libyuv or ARM SIMD for "unzipping" the data.
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100133 int out_idx = 0;
134 for (int i = 0; i < cHeight; ++i) {
135 for (int j = 0; j < cWidth; ++j) {
136 cb_plane[out_idx] = cb[j * ycbr.chroma_step];
137 cr_plane[out_idx] = cr[j * ycbr.chroma_step];
138 out_idx++;
139 }
140 cb += ycbr.cstride;
141 cr += ycbr.cstride;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100142 }
143
144 // Collect pointers to individual scanline of each plane.
145 for (int i = 0; i < mHeight; ++i) {
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100146 yLines[i] = y + i * ycbr.ystride;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100147 }
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100148 for (int i = 0; i < cHeight; ++i) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100149 cbLines[i] = cb_plane.data() + i * (mWidth / 2);
150 crLines[i] = cr_plane.data() + i * (mWidth / 2);
151 }
152
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100153 return compress(yLines, cbLines, crLines);
154 }
155
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100156 std::optional<size_t> compressBlackImage() {
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100157 // We only really need to prepare one scanline for Y and one shared scanline
158 // for Cb & Cr.
159 std::vector<uint8_t> yLine(mWidth, 0);
160 std::vector<uint8_t> chromaLine(mWidth / 2, 0xff / 2);
161
162 std::vector<JSAMPROW> yLines(mHeight, yLine.data());
163 std::vector<JSAMPROW> cLines(mHeight / 2, chromaLine.data());
164
165 return compress(yLines, cLines, cLines);
166 }
167
168 private:
169 void setSuccess(const boolean success) {
170 mSuccess = success;
171 }
172
173 void initDestination() {
174 mDestinationMgr.next_output_byte = reinterpret_cast<JOCTET*>(mDstBuffer);
175 mDestinationMgr.free_in_buffer = mDstBufferSize;
176 ALOGV("%s:%d jpeg start: %p [%zu]", __FUNCTION__, __LINE__, mDstBuffer,
177 mDstBufferSize);
178 }
179
180 void termDestination() {
181 mEncodedSize = mDstBufferSize - mDestinationMgr.free_in_buffer;
182 ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__, mEncodedSize);
183 }
184
185 // Perform actual compression.
186 //
187 // Takes vector of pointers to Y / Cb / Cr scanlines as an input. Length of
188 // each vector needs to correspond to height of corresponding plane.
189 //
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100190 // Returns size of compressed image in bytes on success, empty optional otherwise.
191 std::optional<size_t> compress(std::vector<JSAMPROW>& yLines,
192 std::vector<JSAMPROW>& cbLines,
193 std::vector<JSAMPROW>& crLines) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100194 jpeg_start_compress(&mCompressStruct, TRUE);
195
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100196 if (mApp1Data != nullptr && mApp1DataSize > 0) {
197 ALOGV("%s: Writing exif, size %zu B", __func__, mApp1DataSize);
198 jpeg_write_marker(&mCompressStruct, JPEG_APP0 + 1,
199 static_cast<const JOCTET*>(mApp1Data), mApp1DataSize);
200 }
201
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100202 while (mCompressStruct.next_scanline < mCompressStruct.image_height) {
203 const uint32_t batchSize = DCTSIZE * 2;
204 const uint32_t nl = mCompressStruct.next_scanline;
205 JSAMPARRAY planes[3]{&yLines[nl], &cbLines[nl / 2], &crLines[nl / 2]};
206
207 uint32_t done = jpeg_write_raw_data(&mCompressStruct, planes, batchSize);
208
209 if (done != batchSize) {
210 ALOGE("%s: compressed %u lines, expected %u (total %u/%u)",
211 __FUNCTION__, done, batchSize, mCompressStruct.next_scanline,
212 mCompressStruct.image_height);
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100213 return std::nullopt;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100214 }
215 }
216 jpeg_finish_compress(&mCompressStruct);
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100217 return mEncodedSize;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100218 }
219
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100220 // === libjpeg callbacks below ===
221
222 static void onOutputError(j_common_ptr cinfo) {
223 char buffer[JMSG_LENGTH_MAX];
224 (*cinfo->err->format_message)(cinfo, buffer);
225 ALOGE("libjpeg error: %s", buffer);
226 };
227
228 static void onErrorExit(j_common_ptr cinfo) {
229 static_cast<LibJpegContext*>(cinfo->client_data)->setSuccess(false);
230 };
231
232 static void onInitDestination(j_compress_ptr cinfo) {
233 static_cast<LibJpegContext*>(cinfo->client_data)->initDestination();
234 }
235
236 static int onEmptyOutputBuffer(j_compress_ptr cinfo __unused) {
237 ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__);
238 return 0;
239 }
240
241 static void onTermDestination(j_compress_ptr cinfo) {
242 static_cast<LibJpegContext*>(cinfo->client_data)->termDestination();
243 }
244
245 jpeg_compress_struct mCompressStruct;
246 jpeg_error_mgr mErrorMgr;
247 jpeg_destination_mgr mDestinationMgr;
248
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100249 // APP1 data.
250 const uint8_t* mApp1Data = nullptr;
251 size_t mApp1DataSize = 0;
252
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100253 // Dimensions of the input image.
254 int mWidth;
255 int mHeight;
256
257 // Destination buffer and it's capacity.
258 size_t mDstBufferSize;
259 void* mDstBuffer;
260
261 // This will be set to size of encoded data
262 // written to the outputBuffer when encoding finishes.
263 size_t mEncodedSize;
264 // Set to true/false based on whether the encoding
265 // was successful.
266 boolean mSuccess = true;
267};
268
269} // namespace
270
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100271std::optional<size_t> compressJpeg(const int width, const int height,
272 const int quality, const android_ycbcr& ycbcr,
273 const std::vector<uint8_t>& app1ExifData,
274 size_t outBufferSize, void* outBuffer) {
275 LibJpegContext context(width, height, quality, outBufferSize, outBuffer);
276 if (!app1ExifData.empty()) {
277 context.setApp1Data(app1ExifData.data(), app1ExifData.size());
278 }
279 return context.compress(ycbcr);
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100280}
281
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100282std::optional<size_t> compressBlackJpeg(const int width, const int height,
283 const int quality,
284 const std::vector<uint8_t>& app1ExifData,
285 size_t outBufferSize, void* outBuffer) {
286 LibJpegContext context(width, height, quality, outBufferSize, outBuffer);
287 if (!app1ExifData.empty()) {
288 context.setApp1Data(app1ExifData.data(), app1ExifData.size());
289 }
290 return context.compressBlackImage();
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100291}
292
293} // namespace virtualcamera
294} // namespace companion
295} // namespace android