blob: 8569effe3d905802f094ff17bcffa4a916b75517 [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 Sebechlebsky9ae496f2023-12-05 15:56:28 +0100156 private:
157 void setSuccess(const boolean success) {
158 mSuccess = success;
159 }
160
161 void initDestination() {
162 mDestinationMgr.next_output_byte = reinterpret_cast<JOCTET*>(mDstBuffer);
163 mDestinationMgr.free_in_buffer = mDstBufferSize;
164 ALOGV("%s:%d jpeg start: %p [%zu]", __FUNCTION__, __LINE__, mDstBuffer,
165 mDstBufferSize);
166 }
167
168 void termDestination() {
169 mEncodedSize = mDstBufferSize - mDestinationMgr.free_in_buffer;
170 ALOGV("%s:%d Done with jpeg: %zu", __FUNCTION__, __LINE__, mEncodedSize);
171 }
172
173 // Perform actual compression.
174 //
175 // Takes vector of pointers to Y / Cb / Cr scanlines as an input. Length of
176 // each vector needs to correspond to height of corresponding plane.
177 //
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100178 // Returns size of compressed image in bytes on success, empty optional otherwise.
179 std::optional<size_t> compress(std::vector<JSAMPROW>& yLines,
180 std::vector<JSAMPROW>& cbLines,
181 std::vector<JSAMPROW>& crLines) {
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100182 jpeg_start_compress(&mCompressStruct, TRUE);
183
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100184 if (mApp1Data != nullptr && mApp1DataSize > 0) {
185 ALOGV("%s: Writing exif, size %zu B", __func__, mApp1DataSize);
186 jpeg_write_marker(&mCompressStruct, JPEG_APP0 + 1,
187 static_cast<const JOCTET*>(mApp1Data), mApp1DataSize);
188 }
189
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100190 while (mCompressStruct.next_scanline < mCompressStruct.image_height) {
191 const uint32_t batchSize = DCTSIZE * 2;
192 const uint32_t nl = mCompressStruct.next_scanline;
193 JSAMPARRAY planes[3]{&yLines[nl], &cbLines[nl / 2], &crLines[nl / 2]};
194
195 uint32_t done = jpeg_write_raw_data(&mCompressStruct, planes, batchSize);
196
197 if (done != batchSize) {
198 ALOGE("%s: compressed %u lines, expected %u (total %u/%u)",
199 __FUNCTION__, done, batchSize, mCompressStruct.next_scanline,
200 mCompressStruct.image_height);
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100201 return std::nullopt;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100202 }
203 }
204 jpeg_finish_compress(&mCompressStruct);
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100205 return mEncodedSize;
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100206 }
207
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100208 // === libjpeg callbacks below ===
209
210 static void onOutputError(j_common_ptr cinfo) {
211 char buffer[JMSG_LENGTH_MAX];
212 (*cinfo->err->format_message)(cinfo, buffer);
213 ALOGE("libjpeg error: %s", buffer);
214 };
215
216 static void onErrorExit(j_common_ptr cinfo) {
217 static_cast<LibJpegContext*>(cinfo->client_data)->setSuccess(false);
218 };
219
220 static void onInitDestination(j_compress_ptr cinfo) {
221 static_cast<LibJpegContext*>(cinfo->client_data)->initDestination();
222 }
223
224 static int onEmptyOutputBuffer(j_compress_ptr cinfo __unused) {
225 ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__);
226 return 0;
227 }
228
229 static void onTermDestination(j_compress_ptr cinfo) {
230 static_cast<LibJpegContext*>(cinfo->client_data)->termDestination();
231 }
232
233 jpeg_compress_struct mCompressStruct;
234 jpeg_error_mgr mErrorMgr;
235 jpeg_destination_mgr mDestinationMgr;
236
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100237 // APP1 data.
238 const uint8_t* mApp1Data = nullptr;
239 size_t mApp1DataSize = 0;
240
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100241 // Dimensions of the input image.
242 int mWidth;
243 int mHeight;
244
245 // Destination buffer and it's capacity.
246 size_t mDstBufferSize;
247 void* mDstBuffer;
248
249 // This will be set to size of encoded data
250 // written to the outputBuffer when encoding finishes.
251 size_t mEncodedSize;
252 // Set to true/false based on whether the encoding
253 // was successful.
254 boolean mSuccess = true;
255};
256
257} // namespace
258
Jan Sebechlebsky4ce32082024-02-14 16:02:11 +0100259std::optional<size_t> compressJpeg(const int width, const int height,
260 const int quality, const android_ycbcr& ycbcr,
261 const std::vector<uint8_t>& app1ExifData,
262 size_t outBufferSize, void* outBuffer) {
263 LibJpegContext context(width, height, quality, outBufferSize, outBuffer);
264 if (!app1ExifData.empty()) {
265 context.setApp1Data(app1ExifData.data(), app1ExifData.size());
266 }
267 return context.compress(ycbcr);
Jan Sebechlebsky9ae496f2023-12-05 15:56:28 +0100268}
269
Jan Sebechlebsky5cb39962023-11-22 17:33:07 +0100270} // namespace virtualcamera
271} // namespace companion
272} // namespace android