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