522 lines
21 KiB
C++
522 lines
21 KiB
C++
// encode_bench.cpp - what JPEG XL encoding costs the tower, per input format,
|
|
// and whether one save worker keeps up without piling frames up or overheating.
|
|
// Colour lossy encodes go through the production src/camera/JpegXlEncoder.cpp
|
|
// unchanged; lossless and 16-bit encodes use a bench-only path that sets
|
|
// uses_original_profile correctly (production's distance 0 is not lossless,
|
|
// known-issues #20). Frames come from camera_bench.py `save`.
|
|
//
|
|
// encode_bench matrix <frames> <out>
|
|
// encode time / size for every input x effort x threads, a distance sweep
|
|
// with PSNR, and the de-Bayer/unpack step on its own. One JSON line each.
|
|
// encode_bench pipeline <frames> <out> <kind> <workers> <threads> <secs> <period_ms> [effort] [distance]
|
|
// kind: rgb8 | bilinear | ea | raw8 | raw12
|
|
// period_ms 0: workers process back to back (maximum throughput).
|
|
// period_ms > 0: one frame is queued every period_ms, as a scan keeps one
|
|
// per waypoint; reports latency and backlog. Samples CPU temperature,
|
|
// clock, load and (if readable) package power once a second.
|
|
// encode_bench matrix12 <frames> <out>
|
|
// only the 12-bit parts of matrix.
|
|
// encode_bench idle <frames> <out> <secs>
|
|
// the same samples with nothing running: the baseline.
|
|
#include "fgc/JpegXlEncoder.h"
|
|
|
|
#include <jxl/decode.h>
|
|
#include <jxl/decode_cxx.h>
|
|
#include <jxl/encode.h>
|
|
#include <jxl/encode_cxx.h>
|
|
#include <jxl/thread_parallel_runner.h>
|
|
#include <jxl/thread_parallel_runner_cxx.h>
|
|
|
|
#include <opencv2/core.hpp>
|
|
#include <opencv2/imgproc.hpp>
|
|
|
|
#include <sys/resource.h>
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <condition_variable>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <deque>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
namespace fs = std::filesystem;
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
static int W = 0, H = 0;
|
|
|
|
static int jsonInt(const std::string& text, const std::string& key) {
|
|
auto at = text.find("\"" + key + "\"");
|
|
return at == std::string::npos ? 0 : std::atoi(text.c_str() + text.find(':', at) + 1);
|
|
}
|
|
|
|
static std::vector<uint8_t> load(const std::string& p) {
|
|
std::ifstream f(p, std::ios::binary);
|
|
return {std::istreambuf_iterator<char>(f), {}};
|
|
}
|
|
|
|
static double msSince(Clock::time_point a) {
|
|
return std::chrono::duration<double, std::milli>(Clock::now() - a).count();
|
|
}
|
|
|
|
static long long fsize(const std::string& p) {
|
|
std::error_code ec;
|
|
auto s = fs::file_size(p, ec);
|
|
return ec ? -1 : static_cast<long long>(s);
|
|
}
|
|
|
|
static std::vector<uint16_t> unpack12p(const std::vector<uint8_t>& in) {
|
|
std::vector<uint16_t> out(static_cast<size_t>(W) * H);
|
|
for (size_t i = 0, o = 0; o + 1 < out.size(); i += 3, o += 2) {
|
|
out[o] = static_cast<uint16_t>(in[i] | ((in[i + 1] & 0x0F) << 8));
|
|
out[o + 1] = static_cast<uint16_t>((in[i + 1] >> 4) | (in[i + 2] << 4));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
// Bench-only encoder: any channel count, 8 or 16-bit container, lossless when
|
|
// distance is 0 (with uses_original_profile set, which lossless requires).
|
|
static bool encodeBench(const std::string& path, const void* img, int channels, int bits,
|
|
float distance, int effort, int threads) {
|
|
auto enc = JxlEncoderMake(nullptr);
|
|
auto runner = JxlThreadParallelRunnerMake(nullptr, threads);
|
|
JxlEncoderSetParallelRunner(enc.get(), JxlThreadParallelRunner, runner.get());
|
|
JxlBasicInfo bi;
|
|
JxlEncoderInitBasicInfo(&bi);
|
|
bi.xsize = W; bi.ysize = H; bi.bits_per_sample = bits; bi.num_color_channels = channels;
|
|
bi.uses_original_profile = distance == 0.0f ? JXL_TRUE : JXL_FALSE;
|
|
if (JxlEncoderSetBasicInfo(enc.get(), &bi) != JXL_ENC_SUCCESS) return false;
|
|
JxlColorEncoding ce = {};
|
|
JxlColorEncodingSetToSRGB(&ce, channels < 3 ? JXL_TRUE : JXL_FALSE);
|
|
JxlEncoderSetColorEncoding(enc.get(), &ce);
|
|
auto* fs = JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
|
|
JxlEncoderFrameSettingsSetOption(fs, JXL_ENC_FRAME_SETTING_EFFORT, effort);
|
|
if (distance == 0.0f) {
|
|
JxlEncoderSetFrameDistance(fs, 0);
|
|
JxlEncoderSetFrameLossless(fs, JXL_TRUE);
|
|
} else {
|
|
JxlEncoderSetFrameDistance(fs, distance);
|
|
}
|
|
// 12-bit values in 16-bit words: without this, libjxl reads them as the full
|
|
// 0..65535 range and rescales, so "lossless" keeps only the top bits
|
|
// (measured: every pixel changed, by up to 3839).
|
|
JxlBitDepth depth = {JXL_BIT_DEPTH_FROM_CODESTREAM, 0, 0};
|
|
JxlEncoderSetFrameBitDepth(fs, &depth);
|
|
const bool wide = bits > 8;
|
|
JxlPixelFormat pf = {static_cast<uint32_t>(channels), wide ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8,
|
|
JXL_NATIVE_ENDIAN, 0};
|
|
const size_t bytes = static_cast<size_t>(W) * H * channels * (wide ? 2 : 1);
|
|
if (JxlEncoderAddImageFrame(fs, &pf, img, bytes) != JXL_ENC_SUCCESS) return false;
|
|
JxlEncoderCloseInput(enc.get());
|
|
std::vector<uint8_t> buf(1 << 22);
|
|
uint8_t* next = buf.data();
|
|
size_t avail = buf.size();
|
|
JxlEncoderStatus r;
|
|
while ((r = JxlEncoderProcessOutput(enc.get(), &next, &avail)) == JXL_ENC_NEED_MORE_OUTPUT) {
|
|
size_t off = next - buf.data();
|
|
buf.resize(buf.size() * 2);
|
|
next = buf.data() + off;
|
|
avail = buf.size() - off;
|
|
}
|
|
if (r != JXL_ENC_SUCCESS) return false;
|
|
std::ofstream(path, std::ios::binary)
|
|
.write(reinterpret_cast<char*>(buf.data()), next - buf.data());
|
|
return true;
|
|
}
|
|
|
|
// PSNR (dB) of a decoded 8-bit colour file against the original pixels.
|
|
static double psnr8(const std::string& path, const uint8_t* ref) {
|
|
auto data = load(path);
|
|
auto dec = JxlDecoderMake(nullptr);
|
|
JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_FULL_IMAGE);
|
|
JxlDecoderSetInput(dec.get(), data.data(), data.size());
|
|
JxlDecoderCloseInput(dec.get());
|
|
JxlPixelFormat pf = {3, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
|
|
std::vector<uint8_t> px;
|
|
for (;;) {
|
|
auto s = JxlDecoderProcessInput(dec.get());
|
|
if (s == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
|
|
size_t n;
|
|
JxlDecoderImageOutBufferSize(dec.get(), &pf, &n);
|
|
px.resize(n);
|
|
JxlDecoderSetImageOutBuffer(dec.get(), &pf, px.data(), n);
|
|
} else if (s == JXL_DEC_FULL_IMAGE || s == JXL_DEC_SUCCESS) {
|
|
break;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
double se = 0;
|
|
for (size_t i = 0; i < px.size(); ++i) {
|
|
double d = double(px[i]) - double(ref[i]);
|
|
se += d * d;
|
|
}
|
|
double mse = se / px.size();
|
|
return mse == 0 ? 99.0 : 10 * std::log10(255.0 * 255.0 / mse);
|
|
}
|
|
|
|
struct Frames {
|
|
std::vector<uint8_t> rgb, bayer, b12;
|
|
std::vector<uint16_t> u12;
|
|
};
|
|
|
|
// One full save job of the given kind: the conversion it needs, then the encode
|
|
// and the file write. Returns the conversion time in ms through `prep_ms`.
|
|
static bool runJob(const Frames& f, const std::string& kind, const std::string& path, int effort,
|
|
float distance, int threads, double* prep_ms = nullptr) {
|
|
auto t0 = Clock::now();
|
|
if (kind == "rgb8") {
|
|
if (prep_ms) *prep_ms = 0;
|
|
return fgc::JpegXlEncoder::encodeToFile(path, f.rgb.data(), W, H, 3, distance, effort,
|
|
threads);
|
|
}
|
|
if (kind == "bilinear" || kind == "ea") {
|
|
cv::Mat b(H, W, CV_8UC1, const_cast<uint8_t*>(f.bayer.data())), c;
|
|
// The camera's BayerRG8 is OpenCV's BayerBG (measured 2026-09-30).
|
|
cv::cvtColor(b, c, kind == "ea" ? cv::COLOR_BayerBG2RGB_EA : cv::COLOR_BayerBG2RGB);
|
|
if (prep_ms) *prep_ms = msSince(t0);
|
|
return fgc::JpegXlEncoder::encodeToFile(path, c.data, W, H, 3, distance, effort, threads);
|
|
}
|
|
if (kind == "raw8") {
|
|
if (prep_ms) *prep_ms = 0;
|
|
return encodeBench(path, f.bayer.data(), 1, 8, 0.0f, effort, threads);
|
|
}
|
|
if (kind == "raw12") {
|
|
auto u = unpack12p(f.b12);
|
|
if (prep_ms) *prep_ms = msSince(t0);
|
|
return encodeBench(path, u.data(), 1, 12, 0.0f, effort, threads);
|
|
}
|
|
if (kind == "colour12") { // 12-bit raw -> 16-bit de-Bayer -> lossy colour
|
|
auto u = unpack12p(f.b12);
|
|
cv::Mat b(H, W, CV_16UC1, u.data()), c;
|
|
cv::cvtColor(b, c, cv::COLOR_BayerBG2RGB);
|
|
if (prep_ms) *prep_ms = msSince(t0);
|
|
return encodeBench(path, c.data, 3, 12, distance, effort, threads);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ---- samples: temperature, clock, CPU load, package power ------------------
|
|
|
|
static double readNum(const std::string& p, double fallback = -1) {
|
|
std::ifstream f(p);
|
|
double v;
|
|
return (f >> v) ? v : fallback;
|
|
}
|
|
|
|
static double cpuTempC() {
|
|
double best = -1;
|
|
for (auto& z : fs::directory_iterator("/sys/class/thermal")) {
|
|
if (z.path().filename().string().rfind("thermal_zone", 0) != 0) continue;
|
|
std::ifstream t(z.path() / "type");
|
|
std::string type;
|
|
t >> type;
|
|
double v = readNum((z.path() / "temp").string());
|
|
if (type == "x86_pkg_temp") return v / 1000;
|
|
best = std::max(best, v / 1000);
|
|
}
|
|
return best;
|
|
}
|
|
|
|
static double avgMhz() {
|
|
double sum = 0;
|
|
int n = 0;
|
|
for (int i = 0; i < 64; ++i) {
|
|
double v = readNum("/sys/devices/system/cpu/cpu" + std::to_string(i) +
|
|
"/cpufreq/scaling_cur_freq");
|
|
if (v < 0) break;
|
|
sum += v / 1000;
|
|
++n;
|
|
}
|
|
return n ? sum / n : -1;
|
|
}
|
|
|
|
struct CpuTimes { unsigned long long busy = 0, total = 0; };
|
|
static CpuTimes cpuTimes() {
|
|
std::ifstream f("/proc/stat");
|
|
std::string cpu;
|
|
unsigned long long v[8] = {};
|
|
f >> cpu >> v[0] >> v[1] >> v[2] >> v[3] >> v[4] >> v[5] >> v[6] >> v[7];
|
|
CpuTimes t;
|
|
for (auto x : v) t.total += x;
|
|
t.busy = t.total - v[3] - v[4];
|
|
return t;
|
|
}
|
|
|
|
// The package energy counter is root-only. FGC_RAPL names a copy of it that a
|
|
// root loop refreshes (see README), so the bench itself need not run as root.
|
|
static const std::string kRapl = std::getenv("FGC_RAPL")
|
|
? std::getenv("FGC_RAPL")
|
|
: "/sys/class/powercap/intel-rapl:0/energy_uj";
|
|
|
|
struct Sampler {
|
|
std::atomic<bool> stop{false};
|
|
std::thread th;
|
|
std::function<void(std::string&)> extra; // appends ", \"k\": v" fields
|
|
std::vector<double> temps, mhz, watts, load;
|
|
|
|
void start(double secs_total) {
|
|
th = std::thread([this, secs_total] {
|
|
auto t0 = Clock::now();
|
|
CpuTimes prev = cpuTimes();
|
|
double e0 = readNum(kRapl);
|
|
auto tp = Clock::now();
|
|
while (!stop && msSince(t0) < secs_total * 1000 + 500) {
|
|
std::this_thread::sleep_for(std::chrono::seconds(1));
|
|
CpuTimes now = cpuTimes();
|
|
double pct = now.total > prev.total
|
|
? 100.0 * (now.busy - prev.busy) / (now.total - prev.total)
|
|
: 0;
|
|
prev = now;
|
|
double e1 = readNum(kRapl);
|
|
double w = (e0 >= 0 && e1 >= e0) ? (e1 - e0) / 1e6 / (msSince(tp) / 1000) : -1;
|
|
e0 = e1;
|
|
tp = Clock::now();
|
|
double tc = cpuTempC(), mh = avgMhz();
|
|
temps.push_back(tc); mhz.push_back(mh); load.push_back(pct);
|
|
if (w >= 0) watts.push_back(w);
|
|
std::string x;
|
|
if (extra) extra(x);
|
|
std::printf("{\"t\": %.0f, \"temp_c\": %.1f, \"mhz\": %.0f, \"cpu_pct\": %.1f, "
|
|
"\"watts\": %.2f%s}\n",
|
|
msSince(t0) / 1000, tc, mh, pct, w, x.c_str());
|
|
std::fflush(stdout);
|
|
}
|
|
});
|
|
}
|
|
void finish() { stop = true; if (th.joinable()) th.join(); }
|
|
};
|
|
|
|
static double mean(const std::vector<double>& v) {
|
|
double s = 0;
|
|
for (double x : v) s += x;
|
|
return v.empty() ? -1 : s / v.size();
|
|
}
|
|
static double maxOf(const std::vector<double>& v) {
|
|
return v.empty() ? -1 : *std::max_element(v.begin(), v.end());
|
|
}
|
|
static double minOf(const std::vector<double>& v) {
|
|
return v.empty() ? -1 : *std::min_element(v.begin(), v.end());
|
|
}
|
|
static double pct(std::vector<double> v, double p) {
|
|
if (v.empty()) return -1;
|
|
std::sort(v.begin(), v.end());
|
|
return v[std::min(v.size() - 1, static_cast<size_t>(p * (v.size() - 1) + 0.5))];
|
|
}
|
|
|
|
static double cpuSecondsSelf() {
|
|
rusage r{};
|
|
getrusage(RUSAGE_SELF, &r);
|
|
return r.ru_utime.tv_sec + r.ru_utime.tv_usec / 1e6 + r.ru_stime.tv_sec + r.ru_stime.tv_usec / 1e6;
|
|
}
|
|
|
|
// ---- modes -------------------------------------------------------------------
|
|
|
|
static void matrix(const Frames& f, const std::string& out, bool only12) {
|
|
auto timeJob = [&](const std::string& kind, int e, float d, int t, int reps,
|
|
const std::string& path) {
|
|
std::vector<double> tot, prep;
|
|
for (int i = 0; i < reps; ++i) {
|
|
double p = 0;
|
|
auto t0 = Clock::now();
|
|
runJob(f, kind, path, e, d, t, &p);
|
|
tot.push_back(msSince(t0));
|
|
prep.push_back(p);
|
|
}
|
|
std::sort(tot.begin(), tot.end());
|
|
std::sort(prep.begin(), prep.end());
|
|
return std::make_pair(tot[tot.size() / 2], prep[prep.size() / 2]);
|
|
};
|
|
auto emit = [&](const std::string& kind, int e, float d, int t, double ms, double prep,
|
|
long long bytes, double ps = -1) {
|
|
std::printf("{\"kind\": \"%s\", \"effort\": %d, \"distance\": %.1f, \"threads\": %d, "
|
|
"\"ms\": %.1f, \"prep_ms\": %.1f, \"bytes\": %lld",
|
|
kind.c_str(), e, d, t, ms, prep, bytes);
|
|
if (ps >= 0) std::printf(", \"psnr_db\": %.2f", ps);
|
|
std::printf("}\n");
|
|
std::fflush(stdout);
|
|
};
|
|
|
|
// Effort x threads at today's distance, for every colour input.
|
|
if (!only12)
|
|
for (const std::string kind : {"rgb8", "bilinear", "ea"})
|
|
for (int t : {1, 2, 3, 4})
|
|
for (int e : {1, 2, 3, 4, 5}) {
|
|
const std::string p = out + "/m_" + kind + ".jxl";
|
|
auto r = timeJob(kind, e, 0.8f, t, e == 5 && t < 3 ? 1 : 3, p);
|
|
emit(kind, e, 0.8f, t, r.first, r.second, fsize(p));
|
|
}
|
|
// Distance sweep at effort 4, 4 threads, with the loss it costs.
|
|
if (!only12)
|
|
for (const std::string kind : {"rgb8", "ea"}) {
|
|
cv::Mat ref;
|
|
if (kind == "ea") {
|
|
cv::Mat b(H, W, CV_8UC1, const_cast<uint8_t*>(f.bayer.data()));
|
|
cv::cvtColor(b, ref, cv::COLOR_BayerBG2RGB_EA);
|
|
}
|
|
const uint8_t* refp = kind == "rgb8" ? f.rgb.data() : ref.data;
|
|
for (float d : {0.5f, 0.8f, 1.0f, 1.5f, 2.0f, 3.0f}) {
|
|
const std::string p = out + "/d_" + kind + ".jxl";
|
|
auto r = timeJob(kind, 4, d, 4, 3, p);
|
|
emit(kind, 4, d, 4, r.first, r.second, fsize(p), psnr8(p, refp));
|
|
}
|
|
}
|
|
// Lossless raw mosaic, 8 and 12 bit.
|
|
for (const std::string kind : {"raw8", "raw12"}) {
|
|
if (only12 && kind == "raw8") continue;
|
|
for (int t : {1, 2, 3, 4})
|
|
for (int e : {1, 2, 3}) {
|
|
const std::string p = out + "/m_" + kind + ".jxl";
|
|
auto r = timeJob(kind, e, 0.0f, t, 3, p);
|
|
emit(kind, e, 0.0f, t, r.first, r.second, fsize(p));
|
|
}
|
|
}
|
|
// 12-bit colour, for comparison with the 8-bit colour paths.
|
|
for (int e : {1, 4}) {
|
|
const std::string p = out + "/m_colour12.jxl";
|
|
auto r = timeJob("colour12", e, 0.8f, 4, 3, p);
|
|
emit("colour12", e, 0.8f, 4, r.first, r.second, fsize(p));
|
|
}
|
|
}
|
|
|
|
static int pipeline(const Frames& f, const std::string& out, const std::string& kind, int workers,
|
|
int threads, double secs, int period_ms, int effort, float distance) {
|
|
std::mutex mu;
|
|
std::condition_variable cv;
|
|
std::deque<Clock::time_point> queue; // enqueue times
|
|
std::atomic<long long> done{0};
|
|
std::atomic<bool> producing{true}, quit{false};
|
|
std::vector<double> lat; // enqueue -> written, ms
|
|
size_t max_q = 0;
|
|
const bool paced = period_ms > 0;
|
|
|
|
Sampler s;
|
|
s.extra = [&](std::string& x) {
|
|
size_t q;
|
|
{ std::lock_guard<std::mutex> l(mu); q = queue.size(); }
|
|
x = ", \"done\": " + std::to_string(done.load()) + ", \"queue\": " + std::to_string(q);
|
|
};
|
|
const double cpu0 = cpuSecondsSelf();
|
|
auto t0 = Clock::now();
|
|
s.start(secs);
|
|
|
|
std::vector<std::thread> ws;
|
|
for (int w = 0; w < workers; ++w)
|
|
ws.emplace_back([&, w] {
|
|
const std::string path = out + "/p_" + kind + "_" + std::to_string(w) + ".jxl";
|
|
for (;;) {
|
|
Clock::time_point enq;
|
|
if (paced) {
|
|
std::unique_lock<std::mutex> l(mu);
|
|
cv.wait(l, [&] { return !queue.empty() || quit; });
|
|
if (quit) return;
|
|
enq = queue.front();
|
|
queue.pop_front();
|
|
} else {
|
|
if (msSince(t0) >= secs * 1000) return;
|
|
enq = Clock::now();
|
|
}
|
|
runJob(f, kind, path, effort, distance, threads);
|
|
++done;
|
|
std::lock_guard<std::mutex> l(mu);
|
|
lat.push_back(msSince(enq));
|
|
}
|
|
});
|
|
|
|
if (paced) {
|
|
auto next = Clock::now();
|
|
while (msSince(t0) < secs * 1000) {
|
|
{
|
|
std::lock_guard<std::mutex> l(mu);
|
|
queue.push_back(Clock::now());
|
|
max_q = std::max(max_q, queue.size());
|
|
}
|
|
cv.notify_one();
|
|
next += std::chrono::milliseconds(period_ms);
|
|
std::this_thread::sleep_until(next);
|
|
}
|
|
std::lock_guard<std::mutex> l(mu);
|
|
quit = true;
|
|
}
|
|
cv.notify_all();
|
|
size_t backlog;
|
|
{ std::lock_guard<std::mutex> l(mu); backlog = queue.size(); }
|
|
for (auto& w : ws) w.join();
|
|
const double wall = msSince(t0) / 1000;
|
|
s.finish();
|
|
const double cpu = cpuSecondsSelf() - cpu0;
|
|
|
|
std::lock_guard<std::mutex> l(mu);
|
|
std::printf("{\"summary\": \"pipeline\", \"kind\": \"%s\", \"workers\": %d, \"threads\": %d, "
|
|
"\"effort\": %d, \"distance\": %.1f, \"period_ms\": %d, \"secs\": %.1f, "
|
|
"\"frames\": %lld, \"frames_per_s\": %.3f, \"latency_ms_p50\": %.0f, "
|
|
"\"latency_ms_p95\": %.0f, \"latency_ms_max\": %.0f, \"max_queue\": %zu, "
|
|
"\"backlog_at_end\": %zu, \"cpu_s_per_frame\": %.3f, \"temp_c_mean\": %.1f, "
|
|
"\"temp_c_max\": %.1f, \"mhz_mean\": %.0f, \"mhz_min\": %.0f, \"cpu_pct_mean\": %.1f, "
|
|
"\"watts_mean\": %.2f}\n",
|
|
kind.c_str(), workers, threads, effort, distance, period_ms, wall,
|
|
done.load(), done.load() / wall, pct(lat, 0.5), pct(lat, 0.95), maxOf(lat),
|
|
max_q, backlog, done ? cpu / done.load() : -1, mean(s.temps), maxOf(s.temps),
|
|
mean(s.mhz), minOf(s.mhz), mean(s.load), mean(s.watts));
|
|
return 0;
|
|
}
|
|
|
|
int main(int argc, char** argv) {
|
|
if (argc < 4) {
|
|
std::fprintf(stderr, "usage: encode_bench matrix|pipeline|idle <frames> <out> ...\n");
|
|
return 2;
|
|
}
|
|
const std::string mode = argv[1], dir = argv[2], out = argv[3];
|
|
fs::create_directories(out);
|
|
|
|
if (mode == "idle") {
|
|
Sampler s;
|
|
const double secs = argc > 4 ? std::atof(argv[4]) : 60;
|
|
s.start(secs);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int>(secs * 1000)));
|
|
s.finish();
|
|
std::printf("{\"summary\": \"idle\", \"secs\": %.0f, \"temp_c_mean\": %.1f, "
|
|
"\"temp_c_max\": %.1f, \"mhz_mean\": %.0f, \"cpu_pct_mean\": %.1f, "
|
|
"\"watts_mean\": %.2f}\n",
|
|
secs, mean(s.temps), maxOf(s.temps), mean(s.mhz), mean(s.load), mean(s.watts));
|
|
return 0;
|
|
}
|
|
|
|
{
|
|
std::ifstream j(dir + "/BayerRG8_0.json");
|
|
std::string t{std::istreambuf_iterator<char>(j), {}};
|
|
W = jsonInt(t, "width");
|
|
H = jsonInt(t, "height");
|
|
}
|
|
Frames f;
|
|
f.rgb = load(dir + "/RGB8_0.bin");
|
|
f.bayer = load(dir + "/BayerRG8_0.bin");
|
|
f.b12 = load(dir + "/BayerRG12p_0.bin");
|
|
if (!W || f.rgb.size() != size_t(W) * H * 3 || f.bayer.size() != size_t(W) * H ||
|
|
f.b12.size() != size_t(W) * H * 3 / 2) {
|
|
std::fprintf(stderr, "frames missing or wrong size\n");
|
|
return 1;
|
|
}
|
|
|
|
if (mode == "matrix" || mode == "matrix12") {
|
|
matrix(f, out, mode == "matrix12");
|
|
return 0;
|
|
}
|
|
if (mode == "pipeline" && argc >= 9) {
|
|
return pipeline(f, out, argv[4], std::atoi(argv[5]), std::atoi(argv[6]),
|
|
std::atof(argv[7]), std::atoi(argv[8]),
|
|
argc > 9 ? std::atoi(argv[9]) : 4,
|
|
argc > 10 ? static_cast<float>(std::atof(argv[10])) : 0.8f);
|
|
}
|
|
std::fprintf(stderr, "bad arguments\n");
|
|
return 2;
|
|
}
|