268 lines
8.7 KiB
C++
268 lines
8.7 KiB
C++
/*
|
|
* Copyright (C) 2023 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <algorithm>
|
|
#include <charconv>
|
|
#include <numeric>
|
|
#include <string_view>
|
|
#include <math.h>
|
|
|
|
#include <log/log.h>
|
|
|
|
#include "debug.h"
|
|
#include "list_qemu_cameras.h"
|
|
#include "QemuCamera.h"
|
|
#include "qemu_channel.h"
|
|
|
|
namespace android {
|
|
namespace hardware {
|
|
namespace camera {
|
|
namespace provider {
|
|
namespace implementation {
|
|
namespace hw {
|
|
namespace {
|
|
bool findToken(const std::string_view str, const std::string_view key, std::string_view* value) {
|
|
size_t pos = 0;
|
|
while (true) {
|
|
pos = str.find(key, pos);
|
|
if (pos == std::string_view::npos) {
|
|
return false;
|
|
} else if ((!pos || str[pos - 1] == ' ') &&
|
|
(str.size() >= (pos + key.size() + 1)) &&
|
|
(str[pos + key.size()] == '=')) {
|
|
const size_t vbegin = pos + key.size() + 1;
|
|
const size_t vend = str.find(' ', vbegin);
|
|
if (vend == std::string_view::npos) {
|
|
*value = str.substr(vbegin, str.size() - vbegin);
|
|
} else {
|
|
*value = str.substr(vbegin, vend - vbegin);
|
|
}
|
|
return true;
|
|
} else {
|
|
++pos;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool parseResolutions(const std::string_view str, std::vector<Rect<uint16_t>>* supportedResolutions) {
|
|
const char* i = &*str.begin();
|
|
const char* const end = &*str.end();
|
|
if (i == end) {
|
|
return FAILURE(false);
|
|
}
|
|
|
|
while (true) {
|
|
Rect<uint16_t> resolution;
|
|
std::from_chars_result r = std::from_chars(i, end, resolution.width, 10);
|
|
if (r.ec != std::errc()) {
|
|
return FAILURE(false);
|
|
}
|
|
i = r.ptr;
|
|
if (i == end) {
|
|
return FAILURE(false);
|
|
}
|
|
if (*i != 'x') {
|
|
return FAILURE(false);
|
|
}
|
|
r = std::from_chars(i + 1, end, resolution.height, 10);
|
|
if (r.ec != std::errc()) {
|
|
return FAILURE(false);
|
|
}
|
|
i = r.ptr;
|
|
|
|
if ((resolution.width > 0) && (resolution.height > 0)) {
|
|
supportedResolutions->push_back(resolution);
|
|
}
|
|
|
|
if (i == end) {
|
|
break;
|
|
} else {
|
|
if (*i == ',') {
|
|
++i;
|
|
} else {
|
|
return FAILURE(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Rect<uint16_t> calcThumbnailResolution(const double aspectRatio,
|
|
const size_t targetArea) {
|
|
// round to a multiple of 16, a tad down
|
|
const uint16_t height =
|
|
((uint16_t(sqrt(targetArea / aspectRatio)) + 7) >> 4) << 4;
|
|
|
|
// round width to be even
|
|
const uint16_t width = (uint16_t(height * aspectRatio + 1) >> 1) << 1;
|
|
|
|
return {width, height};
|
|
}
|
|
|
|
struct RectAreaComparator {
|
|
bool operator()(Rect<uint16_t> lhs, Rect<uint16_t> rhs) const {
|
|
const size_t lArea = lhs.area();
|
|
const size_t rArea = rhs.area();
|
|
|
|
if (lArea < rArea) {
|
|
return true;
|
|
} else if (lArea > rArea) {
|
|
return false;
|
|
} else {
|
|
return lhs.width < rhs.width;
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
bool listQemuCameras(const std::function<void(HwCameraFactory)>& cameraSink) {
|
|
using namespace std::literals;
|
|
static const char kListQuery[] = "list";
|
|
|
|
const auto fd = qemuOpenChannel();
|
|
if (!fd.ok()) { return false; }
|
|
|
|
std::vector<uint8_t> data;
|
|
if (qemuRunQuery(fd.get(), kListQuery, sizeof(kListQuery), &data) < 0) {
|
|
return FAILURE(false);
|
|
}
|
|
|
|
const char* i = reinterpret_cast<const char*>(&*data.begin());
|
|
const char* const end = reinterpret_cast<const char*>(&*data.end());
|
|
|
|
while (i < end) {
|
|
const char* const lf = std::find(i, end, '\n');
|
|
if (lf == end) {
|
|
if (*i) {
|
|
return FAILURE(false);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// line='name=virtualscene channel=0 pix=876758866 dir=back framedims=640x480,352x288,320x240,176x144,1280x720,1280x960'
|
|
const std::string_view line(i, lf - i);
|
|
|
|
std::string_view name;
|
|
if (!findToken(line, "name"sv, &name)) { return false; }
|
|
|
|
std::string_view dir;
|
|
if (!findToken(line, "dir"sv, &dir)) { return FAILURE(false); }
|
|
|
|
std::string_view framedims;
|
|
if (!findToken(line, "framedims"sv, &framedims)) { return FAILURE(false); }
|
|
|
|
QemuCamera::Parameters params;
|
|
if (!parseResolutions(framedims, ¶ms.supportedResolutions)) {
|
|
return FAILURE(false);
|
|
}
|
|
|
|
if (params.supportedResolutions.empty()) {
|
|
return FAILURE(false);
|
|
} else {
|
|
std::sort(params.supportedResolutions.begin(),
|
|
params.supportedResolutions.end(),
|
|
RectAreaComparator());
|
|
|
|
std::vector<Rect<uint16_t>> thumbnailResolutions;
|
|
thumbnailResolutions.push_back({0, 0});
|
|
|
|
for (const Rect<uint16_t> res : params.supportedResolutions) {
|
|
const double aspectRatio = double(res.width) / res.height;
|
|
const size_t resArea4 = res.area() / 4;
|
|
Rect<uint16_t> thumbnailRes;
|
|
size_t thumbnailResArea;
|
|
|
|
do {
|
|
thumbnailRes = calcThumbnailResolution(aspectRatio, 4900);
|
|
thumbnailResArea = thumbnailRes.area();
|
|
if ((thumbnailResArea > 0) && (thumbnailResArea < resArea4)) {
|
|
thumbnailResolutions.push_back(thumbnailRes);
|
|
} else {
|
|
thumbnailRes = calcThumbnailResolution(aspectRatio, 1800);
|
|
thumbnailResArea = thumbnailRes.area();
|
|
if ((thumbnailResArea > 0) && (thumbnailRes.area() < resArea4)) {
|
|
thumbnailResolutions.push_back(thumbnailRes);
|
|
} else {
|
|
// `res` is too small for a thumbnail
|
|
}
|
|
break;
|
|
}
|
|
|
|
thumbnailRes = calcThumbnailResolution(aspectRatio, 19500);
|
|
thumbnailResArea = thumbnailRes.area();
|
|
if ((thumbnailResArea > 0) && (thumbnailRes.area() < resArea4)) {
|
|
thumbnailResolutions.push_back(thumbnailRes);
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
thumbnailRes = calcThumbnailResolution(aspectRatio, 77000);
|
|
thumbnailResArea = thumbnailRes.area();
|
|
if ((thumbnailResArea > 0) && (thumbnailRes.area() < resArea4)) {
|
|
thumbnailResolutions.push_back(thumbnailRes);
|
|
}
|
|
} while (false);
|
|
}
|
|
|
|
std::sort(thumbnailResolutions.begin(), thumbnailResolutions.end(),
|
|
RectAreaComparator());
|
|
|
|
thumbnailResolutions.erase(std::unique(thumbnailResolutions.begin(),
|
|
thumbnailResolutions.end()),
|
|
thumbnailResolutions.end());
|
|
|
|
params.availableThumbnailResolutions = std::move(thumbnailResolutions);
|
|
}
|
|
|
|
ALOGD("%s:%d found a '%.*s' QEMU camera, dir=%.*s framedims=%.*s",
|
|
__func__, __LINE__,
|
|
int(name.size()), name.data(),
|
|
int(dir.size()), dir.data(),
|
|
int(framedims.size()), framedims.data());
|
|
|
|
params.name = std::string(name.begin(), name.end());
|
|
|
|
params.sensorSize = std::accumulate(
|
|
std::next(params.supportedResolutions.begin()),
|
|
params.supportedResolutions.end(),
|
|
params.supportedResolutions.front(),
|
|
[](Rect<uint16_t> z, Rect<uint16_t> x) -> Rect<uint16_t> {
|
|
return {std::max(z.width, x.width),
|
|
std::max(z.height, x.height)};
|
|
});
|
|
|
|
params.isBackFacing = (dir == "back"sv);
|
|
|
|
cameraSink([params = std::move(params)]() {
|
|
return std::make_unique<QemuCamera>(params);
|
|
});
|
|
|
|
i = lf + 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace hw
|
|
} // namespace implementation
|
|
} // namespace provider
|
|
} // namespace camera
|
|
} // namespace hardware
|
|
} // namespace android
|