fix: count repeated frame steps and retain the forward decoder cursor
This commit is contained in:
@@ -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
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user