fix: count repeated frame steps and retain the forward decoder cursor

This commit is contained in:
2026-10-10 18:35:13 +02:00
parent 2bb7d9b4ee
commit 131acbe41b
6 changed files with 130 additions and 21 deletions
+40
View File
@@ -80,6 +80,45 @@ size_t firstActive(const float *samples, size_t frames, float threshold = .002f)
require(first < frames, "tone is present");
return first;
}
void playStepped(const std::string &path, const std::vector<float> &reference) {
capturedFrames = 0;
std::unique_ptr<Player, decltype(&player_destroy)> player(player_create(), player_destroy);
require(bool(player), "create stepped audio player");
auto wait = [&](auto ready) {
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(12);
while (std::chrono::steady_clock::now() < deadline) {
auto state = player_get_state(player.get());
require(state.status != PLAYER_ERROR, state.error);
if (ready(state)) return state;
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
require(false, "stepped audio operation completes");
return PlayerState{};
};
require(player_open(player.get(), path.c_str()), "open video with PCM audio");
wait([](auto s) { return s.status == PLAYER_PAUSED; });
require(player_step_frame(player.get(), -1), "establish first frame");
wait([](auto s) { return !s.seek_pending; });
for (int i = 0; i < 32; ++i) require(player_step_frame(player.get(), 1), "queue forward audio frame steps");
for (int i = 0; i < 19; ++i) require(player_step_frame(player.get(), -1), "queue backward audio frame steps");
auto selected = wait([](auto s) { return !s.seek_pending; });
require(std::abs(selected.position - 13.0 / 24) < .0001, "resume starts at selected video frame");
require(capturedFrames == 0, "frame stepping never starts audio output");
require(player_play(player.get()), "resume audio after repeated steps");
auto ended = wait([](auto s) { return s.status == PLAYER_ENDED; });
require(std::abs(ended.position - 2) < .0001, "stepped audio reaches original media end");
player.reset(); // Stop/join before inspecting callback-owned storage.
constexpr size_t sourceStart = 26000; // 13 / 24 seconds at 48 kHz.
const size_t remaining = reference.size() / 2 - sourceStart;
const size_t offset = firstActive(capture.data(), capturedFrames);
require(capturedFrames >= offset + remaining, "all remaining PCM reaches the callback");
float maximumError = 0;
for (size_t i = 0; i < remaining * 2; ++i)
maximumError = std::max(maximumError,
std::abs(capture[offset * 2 + i] - reference[sourceStart * 2 + i]));
std::printf("stepped resume: max remaining PCM error %.8f\n", maximumError);
require(maximumError < .00003f, "resume audio matches selected-frame suffix, without stale or dropped samples");
}
}
// Linked only into the check build. No allocation, locks, or file I/O in callback.
@@ -115,6 +154,7 @@ int main(int argc, char **argv) {
std::printf("%s: max sample error %.8f\n", name, maximumError);
require(maximumError < .00003f, "continuous callback PCM matches reference (no inserted/dropped samples)");
if (std::string(name) == "tone480.wav") {
playStepped(directory + "/tone-step.mov", reference);
play(path, .25f);
size_t quietStart = firstActive(capture.data(), capturedFrames, .002f * .25f);
require(quietStart >= referenceStart, "fractional-volume alignment");
+23 -1
View File
@@ -121,9 +121,31 @@ static void frameSteps(Player *player, MTKView *view, const std::string &directo
auto ended = until(player, [](auto s) { return s.status == PLAYER_ENDED; });
require(std::abs(ended.position - 4) < .0001, "last stepped frame retains its presentation duration");
step(player, -1, 94.0 / 24);
require(player_open(player, (directory + "/av.mp4").c_str()), "repeated stepping open");
until(player, [](auto s) { return s.status == PLAYER_PAUSED; });
step(player, -1, 0);
revision = player_get_state(player).revision;
for (int i = 0; i < 64; ++i)
require(player_step_frame(player, 1), "held forward steps accepted without queue overflow");
frameCompleted(player, revision, 64.0 / 24);
revision = player_get_state(player).revision;
for (int i = 0; i < 64; ++i)
require(player_step_frame(player, -1), "held backward steps accepted without queue overflow");
frameCompleted(player, revision, 0);
revision = player_get_state(player).revision;
for (int i = 0; i < 40; ++i) require(player_step_frame(player, 1), "queue forward step run");
require(player_seek(player, 1.5), "seek separates repeated step runs");
for (int i = 0; i < 32; ++i) require(player_step_frame(player, -1), "queue reverse step run");
for (int i = 0; i < 24; ++i) require(player_step_frame(player, 1), "queue direction reversal");
frameCompleted(player, revision, 28.0 / 24);
// A new media timeline supersedes the entire pending repeated run.
for (int i = 0; i < 4096; ++i) require(player_step_frame(player, 1), "queue long repeated run");
require(player_open(player, (directory + "/audio.wav").c_str()), "open supersedes repeated steps");
auto replacement = until(player, [](auto s) { return s.status == PLAYER_PAUSED && !s.has_video && !s.seek_pending; });
require(replacement.position == 0 && replacement.has_audio, "stale steps do not affect replacement media");
require(player_close(player), "close stepped media accepted");
until(player, [](auto s) { return s.status == PLAYER_EMPTY; });
std::puts("PASS exact VFR/B-frame steps/queued steps/seek reference/pause/resume/boundaries");
std::puts("PASS exact VFR/B-frame steps/repeat bursts/seek ordering/pause/resume/boundaries/timeline replacement");
}
int main(int argc, char **argv) {