blob: d4d3972108354787b4a4b0462423a36aa07e20dd [file] [log] [blame]
/*
* Copyright 2019 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "hal_adapter/camera_metadata_inspector.h"
#include <algorithm>
#include <utility>
#include <base/bind.h>
#include <base/command_line.h>
#include <base/memory/ptr_util.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <system/camera_metadata.h>
#include "cros-camera/common.h"
namespace {
const char kSeparator[] = " | ";
const int kSeparatorWidth = 3;
const char kArrow[] = " => ";
const int kArrowWidth = 4;
const char kEllipsis[] = "...";
const int kEllipsisWidth = 3;
// The ASCII escape color code for request metadata. It's green.
const int kRequestColor = 32;
// The ASCII escape color code for result metadata. It's yellow.
const int kResultColor = 33;
// The max column width for an output line.
const int kMaxLineWidth = 100;
// The format is HH:MM:ss.SSS, such as 23:59:59.999.
const int kTimestampWidth = 12;
// The format is (Req|Res)(frame % 1000), such as Req087.
const int kIdentifierWidth = 6;
// The length of the longest request/result key name (lensShadingCorrectionMap).
const int kMaxKeyWidth = 24;
// The remaining line width is the space for value.
const int kMaxValueWidth =
kMaxLineWidth - (kTimestampWidth + kSeparatorWidth + kIdentifierWidth +
kSeparatorWidth + kMaxKeyWidth + kSeparatorWidth);
// Formats the key of |entry| as {tag_section}.{tag_name}. Returns empty string
// on error.
std::string FormatEntryKey(const camera_metadata_ro_entry_t& entry) {
const char* tag_section = get_camera_metadata_section_name(entry.tag);
if (tag_section == nullptr) {
LOGF(ERROR) << "Failed to get section name of " << entry.tag;
return "";
}
const char* tag_name = get_camera_metadata_tag_name(entry.tag);
if (tag_name == nullptr) {
LOGF(ERROR) << "Failed to get tag name of " << entry.tag;
return "";
}
return base::StringPrintf("%s.%s", tag_section, tag_name);
}
// Formats the value at index |idx| of |entry| according to its type. Uses the
// name of enum if possible, and falls back to numeric value if not found.
// Returns empty string on error.
std::string FormatEntryValueAt(const camera_metadata_ro_entry_t& entry,
size_t idx) {
switch (entry.type) {
case TYPE_BYTE: {
uint8_t val = entry.data.u8[idx];
char buf[64];
if (camera_metadata_enum_snprint(entry.tag, val, buf, sizeof(buf)) == 0) {
return buf;
}
return std::to_string(val);
}
case TYPE_INT32: {
int32_t val = entry.data.i32[idx];
char buf[64];
if (camera_metadata_enum_snprint(entry.tag, val, buf, sizeof(buf)) == 0) {
return buf;
}
return std::to_string(val);
}
case TYPE_INT64:
return std::to_string(entry.data.i64[idx]);
case TYPE_FLOAT:
return base::StringPrintf("%.2g", entry.data.f[idx]);
case TYPE_DOUBLE:
return base::StringPrintf("%.2g", entry.data.d[idx]);
case TYPE_RATIONAL: {
camera_metadata_rational_t val = entry.data.r[idx];
return base::StringPrintf("%d/%d", val.numerator, val.denominator);
}
default:
LOGF(ERROR) << "Unknown entry type " << entry.type;
return "";
}
}
} // namespace
namespace cros {
std::string DiffData::FormatKey(int width) {
DCHECK_GE(width, kEllipsisWidth);
if (width >= key.size()) {
return key;
}
auto dot_pos = key.find('.', key.size() - width - 1);
if (dot_pos != std::string::npos) {
return key.substr(dot_pos + 1);
} else {
return kEllipsis + key.substr(key.size() - (width - kEllipsisWidth));
}
}
std::string DiffData::FormatValue(int width) {
DCHECK_GE(width, kEllipsisWidth + kArrowWidth + kEllipsisWidth);
int old_width = old_val.size();
int new_width = new_val.size();
int limit = width - kArrowWidth;
if (old_width + new_width > limit) {
if (std::min(old_width, new_width) * 2 <= limit) {
// It can fit into |limit| by only trimming the longer one.
if (old_width > new_width) {
old_width = limit - new_width;
} else {
new_width = limit - old_width;
}
} else {
// Distribute the width evenly if we need to trim both.
old_width = limit / 2;
new_width = limit - old_width;
}
}
auto Format = [&](const std::string& s, int w) {
if (s.size() <= w) {
return s;
}
return s.substr(0, w - kEllipsisWidth) + kEllipsis;
};
return Format(old_val, old_width) + kArrow + Format(new_val, new_width);
}
// static
std::unique_ptr<CameraMetadataInspector> CameraMetadataInspector::Create(
int partial_result_count) {
auto cl = base::CommandLine::ForCurrentProcess();
base::FilePath output_path =
cl->GetSwitchValuePath("metadata_inspector_output");
if (output_path.empty()) {
return nullptr;
}
base::File output_file = {
output_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_APPEND};
if (!output_file.IsValid()) {
LOGF(ERROR) << "Failed to open output file " << output_path.value();
return nullptr;
}
auto GetRE2FromSwitch = [&](base::StringPiece name) -> std::unique_ptr<RE2> {
std::string value = cl->GetSwitchValueASCII(name);
if (value.empty()) {
return nullptr;
}
RE2::Options option;
option.set_case_sensitive(false);
return std::make_unique<RE2>(value, option);
};
std::unique_ptr<RE2> allowlist =
GetRE2FromSwitch("metadata_inspector_allowlist");
if (allowlist && !allowlist->ok()) {
LOGF(ERROR) << "Failed to build regex for allowlist: "
<< allowlist->error();
return nullptr;
}
std::unique_ptr<RE2> denylist =
GetRE2FromSwitch("metadata_inspector_denylist");
if (denylist && !denylist->ok()) {
LOGF(ERROR) << "Failed to build regex for denylist: " << denylist->error();
return nullptr;
}
auto thread = std::make_unique<base::Thread>("CameraMetadataInspectorThread");
if (!thread->Start()) {
LOGF(ERROR) << "Failed to start thread";
return nullptr;
}
return base::WrapUnique(new CameraMetadataInspector(
partial_result_count, std::move(output_file), std::move(allowlist),
std::move(denylist), std::move(thread)));
}
CameraMetadataInspector::CameraMetadataInspector(
int partial_result_count,
base::File output_file,
std::unique_ptr<RE2> allowlist,
std::unique_ptr<RE2> denylist,
std::unique_ptr<base::Thread> thread)
: partial_result_count_(partial_result_count),
output_file_(std::move(output_file)),
allowlist_(std::move(allowlist)),
denylist_(std::move(denylist)),
thread_(std::move(thread)) {
result_sequence_checker_.DetachFromSequence();
}
void CameraMetadataInspector::Write(base::StringPiece msg) {
output_file_.WriteAtCurrentPos(msg.data(), msg.size());
output_file_.Flush();
}
bool CameraMetadataInspector::ShouldIgnoreKey(const std::string& key) {
if (key.empty()) {
return true;
}
if (allowlist_ && !RE2::PartialMatch(key, *allowlist_)) {
return true;
}
if (denylist_ && RE2::PartialMatch(key, *denylist_)) {
return true;
}
return false;
}
CameraMetadataInspector::DataMap CameraMetadataInspector::MapFromMetadata(
const camera_metadata_t* metadata) {
CameraMetadataInspector::DataMap map;
size_t n = get_camera_metadata_entry_count(metadata);
for (size_t i = 0; i < n; i++) {
camera_metadata_ro_entry_t entry;
int ret = get_camera_metadata_ro_entry(metadata, i, &entry);
if (ret != 0) {
LOGF(ERROR) << "Failed to get the metadata entry at " << i;
continue;
}
std::string key = FormatEntryKey(entry);
if (ShouldIgnoreKey(key)) {
continue;
}
std::vector<std::string> values;
values.reserve(entry.count);
for (size_t j = 0; j < entry.count; j++) {
values.push_back(FormatEntryValueAt(entry, j));
}
map[key] = base::JoinString(values, " ");
}
return map;
}
std::vector<DiffData> CameraMetadataInspector::Compare(const DataMap& old_map,
const DataMap& new_map) {
std::vector<std::string> keys;
for (auto& map : {old_map, new_map}) {
for (auto& elem : map) {
keys.push_back(elem.first);
}
}
std::sort(keys.begin(), keys.end());
keys.erase(std::unique(keys.begin(), keys.end()), keys.end());
auto GetValue = [&](const DataMap& map, const std::string& key) {
auto it = map.find(key);
return it != map.end() ? it->second : "nil";
};
std::vector<DiffData> diffs;
for (const auto& key : keys) {
std::string old_val = GetValue(old_map, key);
std::string new_val = GetValue(new_map, key);
if (old_val != new_val) {
DiffData diff = {key, old_val, new_val};
diffs.push_back(diff);
}
}
return diffs;
}
void CameraMetadataInspector::InspectOnThread(Kind kind,
const std::string& kind_name,
int color,
base::Time time,
int frame_number,
camera_metadata_t* metadata) {
DCHECK(thread_->task_runner()->BelongsToCurrentThread());
auto map = MapFromMetadata(metadata);
base::Time::Exploded exploded;
time.LocalExplode(&exploded);
auto diffs = Compare(latest_map[static_cast<size_t>(kind)], map);
std::stringstream ss;
for (auto diff : diffs) {
ss << "\e[" << color << "m";
ss << base::StringPrintf("%02d:%02d:%02d.%03d", exploded.hour,
exploded.minute, exploded.second,
exploded.millisecond);
ss << kSeparator;
ss << base::StringPrintf("%s%03d", kind_name.c_str(), frame_number % 1000);
ss << kSeparator;
ss << base::StringPrintf("%*s", kMaxKeyWidth,
diff.FormatKey(kMaxKeyWidth).c_str());
ss << kSeparator;
ss << diff.FormatValue(kMaxValueWidth);
ss << "\e[0m\n";
}
Write(ss.str());
latest_map[static_cast<size_t>(kind)] = map;
free_camera_metadata(metadata);
}
void CameraMetadataInspector::InspectRequest(
const camera3_capture_request_t* request) {
if (request->settings == nullptr) {
return;
}
thread_->task_runner()->PostTask(
FROM_HERE,
base::Bind(&CameraMetadataInspector::InspectOnThread,
base::Unretained(this), Kind::kRequest, "Req", kRequestColor,
base::Time::Now(), request->frame_number,
clone_camera_metadata(request->settings)));
}
void CameraMetadataInspector::InspectResult(
const camera3_capture_result_t* result) {
DCHECK(result_sequence_checker_.CalledOnValidSequence());
if (result->result == nullptr) {
return;
}
pending_result_.append(result->result);
if (result->partial_result != partial_result_count_) {
return;
}
thread_->task_runner()->PostTask(
FROM_HERE, base::Bind(&CameraMetadataInspector::InspectOnThread,
base::Unretained(this), Kind::kResult, "Res",
kResultColor, base::Time::Now(),
result->frame_number, pending_result_.release()));
}
} // namespace cros