36d086af4e
Four encoder quality improvements, all sender-side: - CRF rate control (default 22, --crf to override): targets a constant visual quality level instead of a fixed bitrate. Static desktop content uses 300-800 kbps (vs. forced 4000+), and the saved bits go to sharp text and clean motion when they appear. The VBV max rate (the --bitrate value, now a cap rather than a target) bounds bursts so the receiver's UDP buffers stay safe. Round-trip test bitrate dropped from 1390 kb/s to 47 kb/s on synthetic frames — the encoder uses only what it needs. - 5-second GOP (was 1 second): 80% fewer keyframe bits freed for detail frames. Screen content changes incrementally, not wholesale; PLI feedback recovers from loss in one frame time regardless of GOP length. - faster preset (was veryfast): better sub-pixel estimation and RDO on more decisions. The desktop handles it trivially at 1080p. - Screen-content x264 tuning: aq-mode=2 (auto-variance AQ moves bits away from flat areas toward text edges) and psy-rd=1.5 (preserves texture sharpness). Combined with the earlier veryfast upgrade and sender-side downscaling, this is roughly 2x the perceived quality at the same average bandwidth compared to the original ultrafast ABR encoder. meson test 5/5 in both configurations, valgrind clean.
166 lines
5.3 KiB
C++
166 lines
5.3 KiB
C++
#include "screencast/app/cli.h"
|
|
|
|
#include <charconv>
|
|
#include <cstdio>
|
|
#include <string_view>
|
|
|
|
namespace sc {
|
|
namespace {
|
|
|
|
void print_usage() {
|
|
std::fputs(
|
|
"usage: screencast --send [--target monitor|window] [--peer HOST[:PORT]] [--bitrate MAX_KBPS] [--crf 0-51]\n"
|
|
" screencast --receive [--port PORT] [--signaling-port PORT] [--fullscreen] [--swdecode]\n"
|
|
" screencast --discover [--timeout SECONDS]\n"
|
|
" screencast waybar [--toggle] # for waybar widgets\n"
|
|
"\n"
|
|
"--send without --peer discovers a receiver on the LAN and requires\n"
|
|
"that exactly one is found.\n",
|
|
stderr);
|
|
}
|
|
|
|
bool parse_int(std::string_view text, int& value) {
|
|
const auto [pointer, error] = std::from_chars(text.data(), text.data() + text.size(), value);
|
|
return error == std::errc{} && pointer == text.data() + text.size();
|
|
}
|
|
|
|
bool next_argument(int argc, const char* const argv[], int& index, std::string_view& value) {
|
|
if (index + 1 >= argc) {
|
|
print_usage();
|
|
return false;
|
|
}
|
|
value = argv[++index];
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::optional<Command> parse_cli(int argc, const char* const argv[]) {
|
|
enum class Mode {
|
|
None,
|
|
Send,
|
|
Receive,
|
|
Discover,
|
|
Waybar,
|
|
};
|
|
|
|
Mode mode = Mode::None;
|
|
SendCommand send;
|
|
ReceiveCommand receive;
|
|
DiscoverCommand discover;
|
|
WaybarCommand waybar;
|
|
|
|
for (int index = 1; index < argc; ++index) {
|
|
const std::string_view argument = argv[index];
|
|
|
|
if (argument == "--send") {
|
|
if (mode != Mode::None) {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
mode = Mode::Send;
|
|
} else if (argument == "--receive") {
|
|
if (mode != Mode::None) {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
mode = Mode::Receive;
|
|
} else if (argument == "--discover") {
|
|
if (mode != Mode::None) {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
mode = Mode::Discover;
|
|
} else if (argument == "waybar") {
|
|
if (mode != Mode::None) {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
mode = Mode::Waybar;
|
|
} else if (argument == "--toggle") {
|
|
waybar.toggle = true;
|
|
} else if (argument == "--target") {
|
|
std::string_view value;
|
|
if (!next_argument(argc, argv, index, value)) {
|
|
return std::nullopt;
|
|
}
|
|
if (value != "monitor" && value != "window") {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
send.target = value;
|
|
} else if (argument == "--peer") {
|
|
std::string_view value;
|
|
if (!next_argument(argc, argv, index, value)) {
|
|
return std::nullopt;
|
|
}
|
|
if (mode == Mode::Receive) {
|
|
receive.peer_address = value;
|
|
} else {
|
|
send.peer_address = value;
|
|
}
|
|
} else if (argument == "--bitrate") {
|
|
std::string_view value;
|
|
if (!next_argument(argc, argv, index, value) || !parse_int(value, send.bitrate_kbps) ||
|
|
send.bitrate_kbps <= 0) {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
} else if (argument == "--crf") {
|
|
std::string_view value;
|
|
if (!next_argument(argc, argv, index, value) || !parse_int(value, send.crf) || send.crf < 0 ||
|
|
send.crf > 51) {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
} else if (argument == "--port") {
|
|
std::string_view value;
|
|
int port = 0;
|
|
if (!next_argument(argc, argv, index, value) || !parse_int(value, port) || port <= 0 || port > 65535) {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
receive.local_rtp_port = port;
|
|
} else if (argument == "--signaling-port") {
|
|
std::string_view value;
|
|
int port = 0;
|
|
if (!next_argument(argc, argv, index, value) || !parse_int(value, port) || port <= 0 || port > 65535) {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
receive.signaling_port = port;
|
|
} else if (argument == "--fullscreen") {
|
|
receive.fullscreen = true;
|
|
} else if (argument == "--swdecode") {
|
|
receive.software_decode = true;
|
|
} else if (argument == "--timeout") {
|
|
std::string_view value;
|
|
if (!next_argument(argc, argv, index, value) || !parse_int(value, discover.timeout_seconds) ||
|
|
discover.timeout_seconds <= 0) {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
} else {
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
if (mode == Mode::Send) {
|
|
return Command{std::move(send)};
|
|
}
|
|
if (mode == Mode::Receive) {
|
|
return Command{std::move(receive)};
|
|
}
|
|
if (mode == Mode::Discover) {
|
|
return Command{std::move(discover)};
|
|
}
|
|
if (mode == Mode::Waybar) {
|
|
return Command{std::move(waybar)};
|
|
}
|
|
print_usage();
|
|
return std::nullopt;
|
|
}
|
|
|
|
} // namespace sc
|