• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 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 
17 #include <sys/types.h>
18 
19 #include "system/graphics.h"
20 #define LOG_TAG "JpegUtilTest"
21 
22 #include <array>
23 #include <cstdint>
24 #include <cstring>
25 
26 #include "android/hardware_buffer.h"
27 #include "gmock/gmock.h"
28 #include "gtest/gtest.h"
29 #include "jpeglib.h"
30 #include "util/JpegUtil.h"
31 #include "util/Util.h"
32 #include "utils/Errors.h"
33 
34 namespace android {
35 namespace companion {
36 namespace virtualcamera {
37 namespace {
38 
39 using testing::Eq;
40 using testing::Gt;
41 using testing::Optional;
42 using testing::VariantWith;
43 
44 constexpr int kOutputBufferSize = 1024 * 1024;  // 1 MiB.
45 constexpr int kJpegQuality = 80;
46 
47 // Create black YUV420 buffer for testing purposes.
createHardwareBufferForTest(const int width,const int height)48 std::shared_ptr<AHardwareBuffer> createHardwareBufferForTest(const int width,
49                                                              const int height) {
50   const AHardwareBuffer_Desc desc{.width = static_cast<uint32_t>(width),
51                                   .height = static_cast<uint32_t>(height),
52                                   .layers = 1,
53                                   .format = AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
54                                   .usage = AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
55                                   .stride = 0,
56                                   .rfu0 = 0,
57                                   .rfu1 = 0};
58 
59   AHardwareBuffer* hwBufferPtr;
60   int status = AHardwareBuffer_allocate(&desc, &hwBufferPtr);
61   if (status != NO_ERROR) {
62     ALOGE(
63         "%s: Failed to allocate hardware buffer for temporary framebuffer: %d",
64         __func__, status);
65     return nullptr;
66   }
67 
68   std::shared_ptr<AHardwareBuffer> hwBuffer(hwBufferPtr,
69                                             AHardwareBuffer_release);
70 
71   YCbCrLockGuard yCbCrLock(hwBuffer, AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN);
72   const android_ycbcr& ycbr = (*yCbCrLock);
73 
74   uint8_t* y = reinterpret_cast<uint8_t*>(ycbr.y);
75   for (int r = 0; r < height; r++) {
76     memset(y + r * ycbr.ystride, 0x00, width);
77   }
78 
79   uint8_t* cb = reinterpret_cast<uint8_t*>(ycbr.cb);
80   uint8_t* cr = reinterpret_cast<uint8_t*>(ycbr.cr);
81   for (int r = 0; r < height / 2; r++) {
82     for (int c = 0; c < width / 2; c++) {
83       cb[r * ycbr.cstride + c * ycbr.chroma_step] = 0xff / 2;
84       cr[r * ycbr.cstride + c * ycbr.chroma_step] = 0xff / 2;
85     }
86   }
87 
88   return hwBuffer;
89 }
90 
91 // Decode JPEG header, return image resolution on success or error message on error.
verifyHeaderAndGetResolution(const uint8_t * data,int size)92 std::variant<std::string, Resolution> verifyHeaderAndGetResolution(
93     const uint8_t* data, int size) {
94   struct jpeg_decompress_struct ctx;
95   struct jpeg_error_mgr jerr;
96 
97   struct DecompressionError {
98     bool success = true;
99     std::string error;
100   } result;
101 
102   ctx.client_data = &result;
103 
104   ctx.err = jpeg_std_error(&jerr);
105   ctx.err->error_exit = [](j_common_ptr cinfo) {
106     reinterpret_cast<DecompressionError*>(cinfo->client_data)->success = false;
107   };
108   ctx.err->output_message = [](j_common_ptr cinfo) {
109     char buffer[JMSG_LENGTH_MAX];
110     (*cinfo->err->format_message)(cinfo, buffer);
111     reinterpret_cast<DecompressionError*>(cinfo->client_data)->error = buffer;
112     ALOGE("libjpeg error: %s", buffer);
113   };
114 
115   jpeg_create_decompress(&ctx);
116   jpeg_mem_src(&ctx, data, size);
117   jpeg_read_header(&ctx, /*require_image=*/true);
118 
119   if (!result.success) {
120     jpeg_destroy_decompress(&ctx);
121     return result.error;
122   }
123 
124   Resolution resolution(ctx.image_width, ctx.image_height);
125   jpeg_destroy_decompress(&ctx);
126   return resolution;
127 }
128 
TEST(JpegUtil,roundToDctSize)129 TEST(JpegUtil, roundToDctSize) {
130   EXPECT_THAT(roundTo2DctSize(Resolution(640, 480)), Eq(Resolution(640, 480)));
131   EXPECT_THAT(roundTo2DctSize(Resolution(5, 5)), Eq(Resolution(16, 16)));
132   EXPECT_THAT(roundTo2DctSize(Resolution(32, 32)), Eq(Resolution(32, 32)));
133   EXPECT_THAT(roundTo2DctSize(Resolution(33, 32)), Eq(Resolution(48, 32)));
134   EXPECT_THAT(roundTo2DctSize(Resolution(32, 33)), Eq(Resolution(32, 48)));
135 }
136 
137 class JpegUtilTest : public ::testing::Test {
138  public:
SetUp()139   void SetUp() override {
140     std::fill(mOutputBuffer.begin(), mOutputBuffer.end(), 0);
141   }
142 
143  protected:
compress(int imageWidth,int imageHeight,std::shared_ptr<AHardwareBuffer> inBuffer)144   std::optional<size_t> compress(int imageWidth, int imageHeight,
145                                  std::shared_ptr<AHardwareBuffer> inBuffer) {
146     return compressJpeg(imageWidth, imageHeight, kJpegQuality, inBuffer,
147                         /*app1ExifData=*/{}, mOutputBuffer.size(),
148                         mOutputBuffer.data());
149   }
150 
151   std::array<uint8_t, kOutputBufferSize> mOutputBuffer;
152 };
153 
TEST_F(JpegUtilTest,compressImageSizeAlignedWithDctSucceeds)154 TEST_F(JpegUtilTest, compressImageSizeAlignedWithDctSucceeds) {
155   std::shared_ptr<AHardwareBuffer> inBuffer =
156       createHardwareBufferForTest(640, 480);
157 
158   std::optional<size_t> compressedSize = compress(640, 480, inBuffer);
159 
160   EXPECT_THAT(compressedSize, Optional(Gt(0)));
161   EXPECT_THAT(verifyHeaderAndGetResolution(mOutputBuffer.data(),
162                                            compressedSize.value()),
163               VariantWith<Resolution>(Resolution(640, 480)));
164 }
165 
TEST_F(JpegUtilTest,compressImageSizeNotAlignedWidthDctSucceeds)166 TEST_F(JpegUtilTest, compressImageSizeNotAlignedWidthDctSucceeds) {
167   std::shared_ptr<AHardwareBuffer> inBuffer =
168       createHardwareBufferForTest(640, 480);
169 
170   std::optional<size_t> compressedSize = compress(630, 470, inBuffer);
171 
172   EXPECT_THAT(compressedSize, Optional(Gt(0)));
173   EXPECT_THAT(verifyHeaderAndGetResolution(mOutputBuffer.data(),
174                                            compressedSize.value()),
175               VariantWith<Resolution>(Resolution(630, 470)));
176 }
177 
TEST_F(JpegUtilTest,compressImageWithBufferNotAlignedWithDctFails)178 TEST_F(JpegUtilTest, compressImageWithBufferNotAlignedWithDctFails) {
179   std::shared_ptr<AHardwareBuffer> inBuffer =
180       createHardwareBufferForTest(641, 480);
181 
182   std::optional<size_t> compressedSize = compress(640, 480, inBuffer);
183 
184   EXPECT_THAT(compressedSize, Eq(std::nullopt));
185 }
186 
TEST_F(JpegUtilTest,compressImageWithBufferTooSmallFails)187 TEST_F(JpegUtilTest, compressImageWithBufferTooSmallFails) {
188   std::shared_ptr<AHardwareBuffer> inBuffer =
189       createHardwareBufferForTest(634, 464);
190 
191   std::optional<size_t> compressedSize = compress(640, 480, inBuffer);
192 
193   EXPECT_THAT(compressedSize, Eq(std::nullopt));
194 }
195 
196 }  // namespace
197 }  // namespace virtualcamera
198 }  // namespace companion
199 }  // namespace android
200