perf: prefer VideoToolbox decoding with software fallback

This commit is contained in:
2026-10-17 01:11:58 +02:00
parent 599c1492ee
commit fcad96de62
8 changed files with 513 additions and 36 deletions
+85 -1
View File
@@ -35,6 +35,33 @@ extern "C" {
extern char **environ;
// Encoded H.264 output cannot identify its input decoder; observe received
// source frames only in the check build, before CPU-readable mapping.
#ifdef CAST_MEDIA_CHECK
extern "C" void cast_media_check_decode_counts(uint64_t *, uint64_t *);
#endif
static std::pair<uint64_t, uint64_t> decodeCounts() {
uint64_t hardware = 0, software = 0;
#ifdef CAST_MEDIA_CHECK
cast_media_check_decode_counts(&hardware, &software);
#endif
return {hardware, software};
}
static std::pair<uint64_t, uint64_t> decodeUsage(std::pair<uint64_t, uint64_t> before) {
const auto after = decodeCounts();
const std::pair counts{after.first - before.first, after.second - before.second};
#ifdef CAST_MEDIA_CHECK
assert(counts.first + counts.second > 0);
#ifdef TEREBI_SOFTWARE_DECODE
assert(counts.first == 0);
#endif
std::cout << " decoder_videotoolbox_frames=" << counts.first << " decoder_software_frames=" << counts.second;
#else
std::cout << " decoder_frames=unavailable (compile with CAST_MEDIA_CHECK)";
#endif
return counts;
}
static void ok(int result) { assert(result >= 0); }
static std::vector<uint8_t> readFile(const std::string &path) {
std::ifstream file(path, std::ios::binary); assert(file.good());
@@ -690,6 +717,7 @@ static void codecWork(const std::filesystem::path &directory) {
Upstream upstream(readFile(source), readFile(audio)); upstream.start();
const auto videoURL = upstream.url("/source.ts"), audioURL = upstream.url("/audio.wav");
std::string url; double duration = 0;
const auto beforeDecode = decodeCounts();
const auto began = std::chrono::steady_clock::now();
auto *media = start({videoURL.c_str(), "X-Video: video-secret\r\n", audioURL.c_str(),
"X-Audio: audio-secret\r\n", false, false, false, 0}, url, duration);
@@ -723,7 +751,8 @@ static void codecWork(const std::filesystem::path &directory) {
cast_media_destroy(media);
std::cout << "codec-work prepare_ms=" << std::chrono::duration<double, std::milli>(prepared - began).count()
<< " sequential_60s_ms=" << sequentialMS << " sequential_cpu_ms=" << sequentialCPU
<< " seek_sequence_ms=" << randomMS << std::endl;
<< " seek_sequence_ms=" << randomMS;
decodeUsage(beforeDecode); std::cout << std::endl;
// A long video gap must not turn bounded encoder lookahead into HTTP 503,
// and resuming video after audio-only segments still needs an independent IDR.
const auto sparsePath = (directory / "sparse.mp4").string();
@@ -752,6 +781,55 @@ static void codecWork(const std::filesystem::path &directory) {
}
cast_media_destroy(media);
}
// Twelve-second, 30 fps video-only fixtures exercise each codec without
// regenerating sources. Separate PCM audio also keeps the AAC lane covered.
static void decodeWork(const std::filesystem::path &directory, const std::string &fixturePath) {
#ifndef CAST_MEDIA_CHECK
assert(false && "Compile --decode-work with CAST_MEDIA_CHECK to observe actual decoding");
#endif
AVFormatContext *format = nullptr;
ok(avformat_open_input(&format, fixturePath.c_str(), nullptr, nullptr)); ok(avformat_find_stream_info(format, nullptr));
const int videoIndex = av_find_best_stream(format, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0); assert(videoIndex >= 0);
const auto *video = format->streams[videoIndex]->codecpar;
const std::string codec = avcodec_get_name(video->codec_id);
avformat_close_input(&format);
Upstream upstream(readFile(fixturePath), wav()); upstream.start();
const auto videoURL = upstream.url("/source.ts"), audioURL = upstream.url("/audio.wav");
const auto output = (directory / "decode-output.ts").string();
const auto beforeDecode = decodeCounts();
double wallMS = 0, cpuMS = 0;
for (bool random : {false, true}) {
std::string url; double duration = 0;
auto *media = start({videoURL.c_str(), "X-Video: video-secret\r\n", audioURL.c_str(),
"X-Audio: audio-secret\r\n", false, false, false, 0}, url, duration);
assert(std::abs(duration - 12) < .1);
const auto base = url.substr(0, url.rfind('/') + 1);
std::map<int, std::pair<double, double>> spans;
for (int at = 0; at < 2; ++at) {
const int id = random ? 1 - at : at;
const auto began = std::chrono::steady_clock::now(); const auto cpu = std::clock();
const auto response = request(base + "segment/" + std::to_string(id) + ".ts");
wallMS += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - began).count();
cpuMS += 1000.0 * (std::clock() - cpu) / CLOCKS_PER_SEC;
assert(response.status == 200); writeFile(output, response.bytes);
const auto rows = samples(output, 1);
int videos = 0;
for (const auto &[key, value] : rows) {
assert(key.second >= id * 6 * 90000 && key.second < (id + 1) * 6 * 90000);
if (key.first) ++videos;
}
assert(videos == 180);
spans[id] = audioSpan(response, output);
assert(request(base + "segment/" + std::to_string(id) + ".ts").bytes == response.bytes);
}
assert(std::abs(spans.at(1).first - spans.at(0).second) < 1.0 / 48000);
cast_media_destroy(media);
}
assert(upstream.foreignRequests == 0);
std::cout << "decode-work codec=" << codec << " relay_24s_ms=" << wallMS << " relay_cpu_ms=" << cpuMS;
decodeUsage(beforeDecode);
std::cout << "\ncast-media: codec decode, independent IDRs, AAC continuity, sequential and random seeks passed\n";
}
int main(int argc, char **argv) {
bool liveCheck = false, seekCheck = false, hlsCheck = false, remuxCheck = false;
for (int argument = 1; argument < argc; ++argument) {
@@ -759,6 +837,12 @@ int main(int argc, char **argv) {
if (std::string(argv[argument]) == "--seek-work") seekCheck = true;
if (std::string(argv[argument]) == "--hls-work") hlsCheck = true;
if (std::string(argv[argument]) == "--remux-work") remuxCheck = true;
if (std::string(argv[argument]) == "--decode-work") {
assert(argument + 1 < argc && "--decode-work requires a 12s/30fps fixture path");
char temp[] = "/tmp/terebi-decode-check.XXXXXX"; assert(mkdtemp(temp));
decodeWork(temp, argv[++argument]); std::filesystem::remove_all(temp);
return 0;
}
if (std::string(argv[argument]) == "--codec-work") {
char temp[] = "/tmp/terebi-codec-check.XXXXXX"; assert(mkdtemp(temp));
codecWork(temp); std::filesystem::remove_all(temp);