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
17 #include <algorithm>
18 #include <charconv>
19 #include <numeric>
20 #include <string_view>
21 #include <math.h>
22
23 #include <log/log.h>
24
25 #include "debug.h"
26 #include "list_qemu_cameras.h"
27 #include "QemuCamera.h"
28 #include "qemu_channel.h"
29
30 namespace android {
31 namespace hardware {
32 namespace camera {
33 namespace provider {
34 namespace implementation {
35 namespace hw {
36 namespace {
findToken(const std::string_view str,const std::string_view key,std::string_view * value)37 bool findToken(const std::string_view str, const std::string_view key, std::string_view* value) {
38 size_t pos = 0;
39 while (true) {
40 pos = str.find(key, pos);
41 if (pos == std::string_view::npos) {
42 return false;
43 } else if ((!pos || str[pos - 1] == ' ') &&
44 (str.size() >= (pos + key.size() + 1)) &&
45 (str[pos + key.size()] == '=')) {
46 const size_t vbegin = pos + key.size() + 1;
47 const size_t vend = str.find(' ', vbegin);
48 if (vend == std::string_view::npos) {
49 *value = str.substr(vbegin, str.size() - vbegin);
50 } else {
51 *value = str.substr(vbegin, vend - vbegin);
52 }
53 return true;
54 } else {
55 ++pos;
56 }
57 }
58 }
59
parseResolutions(const std::string_view str,std::vector<Rect<uint16_t>> * supportedResolutions)60 bool parseResolutions(const std::string_view str, std::vector<Rect<uint16_t>>* supportedResolutions) {
61 const char* i = &*str.begin();
62 const char* const end = &*str.end();
63 if (i == end) {
64 return FAILURE(false);
65 }
66
67 while (true) {
68 Rect<uint16_t> resolution;
69 std::from_chars_result r = std::from_chars(i, end, resolution.width, 10);
70 if (r.ec != std::errc()) {
71 return FAILURE(false);
72 }
73 i = r.ptr;
74 if (i == end) {
75 return FAILURE(false);
76 }
77 if (*i != 'x') {
78 return FAILURE(false);
79 }
80 r = std::from_chars(i + 1, end, resolution.height, 10);
81 if (r.ec != std::errc()) {
82 return FAILURE(false);
83 }
84 i = r.ptr;
85
86 if ((resolution.width > 0) && (resolution.height > 0)) {
87 supportedResolutions->push_back(resolution);
88 }
89
90 if (i == end) {
91 break;
92 } else {
93 if (*i == ',') {
94 ++i;
95 } else {
96 return FAILURE(false);
97 }
98 }
99 }
100
101 return true;
102 }
103
calcThumbnailResolution(const double aspectRatio,const size_t targetArea)104 Rect<uint16_t> calcThumbnailResolution(const double aspectRatio,
105 const size_t targetArea) {
106 // round to a multiple of 16, a tad down
107 const uint16_t height =
108 ((uint16_t(sqrt(targetArea / aspectRatio)) + 7) >> 4) << 4;
109
110 // round width to be even
111 const uint16_t width = (uint16_t(height * aspectRatio + 1) >> 1) << 1;
112
113 return {width, height};
114 }
115
116 struct RectAreaComparator {
operator ()android::hardware::camera::provider::implementation::hw::__anon9a409dc00111::RectAreaComparator117 bool operator()(Rect<uint16_t> lhs, Rect<uint16_t> rhs) const {
118 const size_t lArea = lhs.area();
119 const size_t rArea = rhs.area();
120
121 if (lArea < rArea) {
122 return true;
123 } else if (lArea > rArea) {
124 return false;
125 } else {
126 return lhs.width < rhs.width;
127 }
128 }
129 };
130
131 } // namespace
132
listQemuCameras(const std::function<void (HwCameraFactory)> & cameraSink)133 bool listQemuCameras(const std::function<void(HwCameraFactory)>& cameraSink) {
134 using namespace std::literals;
135 static const char kListQuery[] = "list";
136
137 const auto fd = qemuOpenChannel();
138 if (!fd.ok()) { return false; }
139
140 std::vector<uint8_t> data;
141 if (qemuRunQuery(fd.get(), kListQuery, sizeof(kListQuery), &data) < 0) {
142 return FAILURE(false);
143 }
144
145 const char* i = reinterpret_cast<const char*>(&*data.begin());
146 const char* const end = reinterpret_cast<const char*>(&*data.end());
147
148 while (i < end) {
149 const char* const lf = std::find(i, end, '\n');
150 if (lf == end) {
151 if (*i) {
152 return FAILURE(false);
153 } else {
154 break;
155 }
156 }
157
158 // line='name=virtualscene channel=0 pix=876758866 dir=back framedims=640x480,352x288,320x240,176x144,1280x720,1280x960'
159 const std::string_view line(i, lf - i);
160
161 std::string_view name;
162 if (!findToken(line, "name"sv, &name)) { return false; }
163
164 std::string_view dir;
165 if (!findToken(line, "dir"sv, &dir)) { return FAILURE(false); }
166
167 std::string_view framedims;
168 if (!findToken(line, "framedims"sv, &framedims)) { return FAILURE(false); }
169
170 QemuCamera::Parameters params;
171 if (!parseResolutions(framedims, ¶ms.supportedResolutions)) {
172 return FAILURE(false);
173 }
174
175 if (params.supportedResolutions.empty()) {
176 return FAILURE(false);
177 } else {
178 std::sort(params.supportedResolutions.begin(),
179 params.supportedResolutions.end(),
180 RectAreaComparator());
181
182 std::vector<Rect<uint16_t>> thumbnailResolutions;
183 thumbnailResolutions.push_back({0, 0});
184
185 for (const Rect<uint16_t> res : params.supportedResolutions) {
186 const double aspectRatio = double(res.width) / res.height;
187 const size_t resArea4 = res.area() / 4;
188 Rect<uint16_t> thumbnailRes;
189 size_t thumbnailResArea;
190
191 do {
192 thumbnailRes = calcThumbnailResolution(aspectRatio, 4900);
193 thumbnailResArea = thumbnailRes.area();
194 if ((thumbnailResArea > 0) && (thumbnailResArea < resArea4)) {
195 thumbnailResolutions.push_back(thumbnailRes);
196 } else {
197 thumbnailRes = calcThumbnailResolution(aspectRatio, 1800);
198 thumbnailResArea = thumbnailRes.area();
199 if ((thumbnailResArea > 0) && (thumbnailRes.area() < resArea4)) {
200 thumbnailResolutions.push_back(thumbnailRes);
201 } else {
202 // `res` is too small for a thumbnail
203 }
204 break;
205 }
206
207 thumbnailRes = calcThumbnailResolution(aspectRatio, 19500);
208 thumbnailResArea = thumbnailRes.area();
209 if ((thumbnailResArea > 0) && (thumbnailRes.area() < resArea4)) {
210 thumbnailResolutions.push_back(thumbnailRes);
211 } else {
212 break;
213 }
214
215 thumbnailRes = calcThumbnailResolution(aspectRatio, 77000);
216 thumbnailResArea = thumbnailRes.area();
217 if ((thumbnailResArea > 0) && (thumbnailRes.area() < resArea4)) {
218 thumbnailResolutions.push_back(thumbnailRes);
219 }
220 } while (false);
221 }
222
223 std::sort(thumbnailResolutions.begin(), thumbnailResolutions.end(),
224 RectAreaComparator());
225
226 thumbnailResolutions.erase(std::unique(thumbnailResolutions.begin(),
227 thumbnailResolutions.end()),
228 thumbnailResolutions.end());
229
230 params.availableThumbnailResolutions = std::move(thumbnailResolutions);
231 }
232
233 ALOGD("%s:%d found a '%.*s' QEMU camera, dir=%.*s framedims=%.*s",
234 __func__, __LINE__,
235 int(name.size()), name.data(),
236 int(dir.size()), dir.data(),
237 int(framedims.size()), framedims.data());
238
239 params.name = std::string(name.begin(), name.end());
240
241 params.sensorSize = std::accumulate(
242 std::next(params.supportedResolutions.begin()),
243 params.supportedResolutions.end(),
244 params.supportedResolutions.front(),
245 [](Rect<uint16_t> z, Rect<uint16_t> x) -> Rect<uint16_t> {
246 return {std::max(z.width, x.width),
247 std::max(z.height, x.height)};
248 });
249
250 params.isBackFacing = (dir == "back"sv);
251
252 cameraSink([params = std::move(params)]() {
253 return std::make_unique<QemuCamera>(params);
254 });
255
256 i = lf + 1;
257 }
258
259 return true;
260 }
261
262 } // namespace hw
263 } // namespace implementation
264 } // namespace provider
265 } // namespace camera
266 } // namespace hardware
267 } // namespace android
268