• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 #include "system/graphics.h"
18 #define LOG_TAG "JpegUtil"
19 #include <cstddef>
20 #include <cstdint>
21 #include <optional>
22 #include <vector>
23 
24 #include "JpegUtil.h"
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 "util/Util.h"
31 #include "utils/Errors.h"
32 
33 namespace android {
34 namespace companion {
35 namespace virtualcamera {
36 namespace {
37 
38 constexpr int k2DCTSIZE = 2 * DCTSIZE;
39 
40 class LibJpegContext {
41  public:
LibJpegContext(int width,int height,int quality,const size_t outBufferSize,void * outBuffer)42   LibJpegContext(int width, int height, int quality, const size_t outBufferSize,
43                  void* outBuffer)
44       : mWidth(width),
45         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.
80     jpeg_set_quality(&mCompressStruct, quality, 1);
81     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 
setApp1Data(const uint8_t * app1Data,const size_t size)98   LibJpegContext& setApp1Data(const uint8_t* app1Data, const size_t size) {
99     mApp1Data = app1Data;
100     mApp1DataSize = size;
101     return *this;
102   }
103 
compress(std::shared_ptr<AHardwareBuffer> inBuffer)104   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)) {
132       ALOGE(
133           "%s: Compressing YUV420 buffer with size %dx%d not aligned with 2 * "
134           "DCTSIZE (%d) is not currently supported.",
135           __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);
144       return std::nullopt;
145     }
146 
147     // Chroma planes have 1/2 resolution of the original image.
148     const int cHeight = inBufferHeight / 2;
149     const int cWidth = inBufferWidth / 2;
150 
151     // Prepare arrays of pointers to scanlines of each plane.
152     std::vector<JSAMPROW> yLines(inBufferHeight);
153     std::vector<JSAMPROW> cbLines(cHeight);
154     std::vector<JSAMPROW> crLines(cHeight);
155 
156     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);
159 
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.
163     const int cSamples = cWidth * cHeight;
164     std::vector<uint8_t> cb_plane(cSamples);
165     std::vector<uint8_t> cr_plane(cSamples);
166 
167     // TODO(b/301023410) - Use libyuv or ARM SIMD for "unzipping" the data.
168     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;
177     }
178 
179     // Collect pointers to individual scanline of each plane.
180     for (int i = 0; i < inBufferHeight; ++i) {
181       yLines[i] = y + i * ycbr.ystride;
182     }
183     for (int i = 0; i < cHeight; ++i) {
184       cbLines[i] = cb_plane.data() + i * cWidth;
185       crLines[i] = cr_plane.data() + i * cWidth;
186     }
187 
188     return compress(yLines, cbLines, crLines);
189   }
190 
191  private:
setSuccess(const boolean success)192   void setSuccess(const boolean success) {
193     mSuccess = success;
194   }
195 
initDestination()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 
termDestination()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   //
213   // Returns size of compressed image in bytes on success, empty optional otherwise.
compress(std::vector<JSAMPROW> & yLines,std::vector<JSAMPROW> & cbLines,std::vector<JSAMPROW> & crLines)214   std::optional<size_t> compress(std::vector<JSAMPROW>& yLines,
215                                  std::vector<JSAMPROW>& cbLines,
216                                  std::vector<JSAMPROW>& crLines) {
217     jpeg_start_compress(&mCompressStruct, TRUE);
218 
219     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 
225     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);
236         return std::nullopt;
237       }
238     }
239     jpeg_finish_compress(&mCompressStruct);
240     return mEncodedSize;
241   }
242 
243   // === libjpeg callbacks below ===
244 
onOutputError(j_common_ptr cinfo)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 
onErrorExit(j_common_ptr cinfo)251   static void onErrorExit(j_common_ptr cinfo) {
252     static_cast<LibJpegContext*>(cinfo->client_data)->setSuccess(false);
253   };
254 
onInitDestination(j_compress_ptr cinfo)255   static void onInitDestination(j_compress_ptr cinfo) {
256     static_cast<LibJpegContext*>(cinfo->client_data)->initDestination();
257   }
258 
onEmptyOutputBuffer(j_compress_ptr cinfo __unused)259   static int onEmptyOutputBuffer(j_compress_ptr cinfo __unused) {
260     ALOGV("%s:%d Out of buffer", __FUNCTION__, __LINE__);
261     return 0;
262   }
263 
onTermDestination(j_compress_ptr cinfo)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 
272   // APP1 data.
273   const uint8_t* mApp1Data = nullptr;
274   size_t mApp1DataSize = 0;
275 
276   // 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 
roundTo2DCTMultiple(const int n)292 int roundTo2DCTMultiple(const int n) {
293   const int mod = n % k2DCTSIZE;
294   return mod == 0 ? n : n + (k2DCTSIZE - mod);
295 }
296 
297 }  // namespace
298 
compressJpeg(const int width,const int height,const int quality,std::shared_ptr<AHardwareBuffer> inBuffer,const std::vector<uint8_t> & app1ExifData,size_t outBufferSize,void * outBuffer)299 std::optional<size_t> compressJpeg(const int width, const int height,
300                                    const int quality,
301                                    std::shared_ptr<AHardwareBuffer> inBuffer,
302                                    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   }
308   return context.compress(inBuffer);
309 }
310 
roundTo2DctSize(const Resolution resolution)311 Resolution roundTo2DctSize(const Resolution resolution) {
312   return Resolution(roundTo2DCTMultiple(resolution.width),
313                     roundTo2DCTMultiple(resolution.height));
314 }
315 
316 }  // namespace virtualcamera
317 }  // namespace companion
318 }  // namespace android
319