perf: retain Cast codecs across sequential VOD segments

This commit is contained in:
2026-10-17 00:56:20 +02:00
parent caa10c063f
commit 599c1492ee
4 changed files with 182 additions and 36 deletions
+85
View File
@@ -14,6 +14,7 @@ extern "C" {
#include <condition_variable>
#include <cmath>
#include <cstring>
#include <ctime>
#include <filesystem>
#include <fstream>
#include <future>
@@ -673,6 +674,84 @@ static void remuxWork(const std::filesystem::path &directory) {
cast_media_destroy(media);
}
}
// Exercise the HTTP relay, not an isolated codec: delayed video, resampled AAC,
// independent segment decoding, cache eviction, and random-to-sequential seeks.
static void codecWork(const std::filesystem::path &directory) {
const auto source = (directory / "codec.mp4").string(), audio = (directory / "codec.wav").string();
const char *args[] = {"ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
"-f", "lavfi", "-i", "testsrc2=size=1280x720:rate=30:duration=61",
"-f", "lavfi", "-i", "sine=frequency=440:sample_rate=44100:duration=61",
"-c:v", "libx264", "-preset", "ultrafast", "-bf", "2", "-g", "210",
"-an", source.c_str(), "-vn", "-c:a", "pcm_s16le", audio.c_str(), nullptr};
pid_t child = 0;
assert(posix_spawnp(&child, args[0], nullptr, nullptr, const_cast<char *const *>(args), environ) == 0);
int status = 0; assert(waitpid(child, &status, 0) == child);
assert(WIFEXITED(status) && WEXITSTATUS(status) == 0);
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 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);
const auto prepared = std::chrono::steady_clock::now();
const auto base = url.substr(0, url.rfind('/') + 1), output = (directory / "codec-output.ts").string();
std::map<int, std::pair<double, double>> spans;
double sequentialMS = 0, randomMS = 0, sequentialCPU = 0;
for (int id : {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 0, 1, 10}) {
const auto before = std::chrono::steady_clock::now();
const auto cpuBefore = std::clock();
const auto response = request(base + "segment/" + std::to_string(id) + ".ts");
const auto elapsed = std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - before).count();
const double cpuMS = 1000.0 * (std::clock() - cpuBefore) / CLOCKS_PER_SEC;
if (spans.size() < 10) { sequentialMS += elapsed; sequentialCPU += cpuMS; }
else randomMS += elapsed;
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 < std::min(61, (id + 1) * 6) * 90000);
if (key.first) ++videos;
}
assert(videos == (id == 10 ? 30 : 180));
auto span = audioSpan(response, output);
if (spans.contains(id - 1)) assert(std::abs(span.first - spans.at(id - 1).second) < 1.0 / 48000);
if (spans.contains(id + 1)) assert(std::abs(spans.at(id + 1).first - span.second) < 1.0 / 48000);
spans[id] = span;
assert(request(base + "segment/" + std::to_string(id) + ".ts").bytes == response.bytes);
}
assert(upstream.foreignRequests == 0);
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;
// 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();
const char *sparseArgs[] = {"ffmpeg", "-hide_banner", "-loglevel", "error", "-y",
"-i", source.c_str(), "-t", "25", "-vf", "select='lt(t,3)+gte(t,18)'",
"-fps_mode", "vfr", "-c:v", "libx264", "-preset", "ultrafast", "-bf", "2",
"-an", sparsePath.c_str(), nullptr};
assert(posix_spawnp(&child, sparseArgs[0], nullptr, nullptr, const_cast<char *const *>(sparseArgs), environ) == 0);
assert(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
Upstream sparse(readFile(sparsePath), readFile(audio)); sparse.start();
const auto sparseVideo = sparse.url("/source.ts"), sparseAudio = sparse.url("/audio.wav");
media = start({sparseVideo.c_str(), "X-Video: video-secret\r\n", sparseAudio.c_str(),
"X-Audio: audio-secret\r\n", false, false, false, 0}, url, duration);
const auto sparseBase = url.substr(0, url.rfind('/') + 1);
double audioEnd = NAN;
for (int id = 0; id < 5; ++id) {
const auto response = request(sparseBase + "segment/" + std::to_string(id) + ".ts");
const auto span = audioSpan(response, output);
if (id) assert(std::abs(span.first - audioEnd) < 1.0 / 48000);
audioEnd = span.second;
if (id == 0 || id >= 3) {
const auto rows = samples(output, 1);
const auto videos = std::count_if(rows.begin(), rows.end(), [](const auto &row) { return row.first.first == 1; });
assert(videos == (id == 0 ? 90 : id == 3 ? 180 : 30));
}
}
cast_media_destroy(media);
}
int main(int argc, char **argv) {
bool liveCheck = false, seekCheck = false, hlsCheck = false, remuxCheck = false;
for (int argument = 1; argument < argc; ++argument) {
@@ -680,6 +759,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]) == "--codec-work") {
char temp[] = "/tmp/terebi-codec-check.XXXXXX"; assert(mkdtemp(temp));
codecWork(temp); std::filesystem::remove_all(temp);
std::cout << "cast-media: sequential codec reuse, independent IDRs, AAC continuity and random seeks passed\n";
return 0;
}
}
char temp[] = "/tmp/terebi-cast-check.XXXXXX"; assert(mkdtemp(temp)); std::filesystem::path directory(temp);
if (remuxCheck) {