fix: preserve audio sample continuity across timestamp rounding

This commit is contained in:
2026-10-10 01:26:26 +02:00
parent 8a89d88254
commit df2456f559
5 changed files with 137 additions and 4 deletions
+96
View File
@@ -0,0 +1,96 @@
#include "../Sources/Core/player.h"
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <memory>
#include <string>
#include <thread>
#include <vector>
namespace {
std::array<float, 48000 * 8 * 2> capture{};
size_t capturedFrames = 0;
void require(bool condition, const char *message) {
if (!condition) { std::fprintf(stderr, "FAIL audio: %s\n", message); std::exit(1); }
}
void play(const std::string &path) {
capturedFrames = 0;
std::unique_ptr<Player, decltype(&player_destroy)> player(player_create(), player_destroy);
require(player && player_open(player.get(), path.c_str()), "open fixture");
bool started = false, ended = false;
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());
if (state.status == PLAYER_ERROR) {
std::fprintf(stderr, "%s\n", state.error);
require(false, "playback error");
}
if (state.status == PLAYER_PAUSED && !started) {
require(player_play(player.get()), "play fixture");
started = true;
}
if (state.status == PLAYER_ENDED) { ended = true; break; }
std::this_thread::sleep_for(std::chrono::milliseconds(2));
}
require(ended, "playback completes");
player.reset(); // Stop/join before inspecting callback-owned capture storage.
}
size_t firstActive(const float *samples, size_t frames) {
size_t first = 0;
while (first < frames && std::abs(samples[first * 2]) < .002f) ++first;
require(first < frames, "tone is present");
return first;
}
}
// Linked only into the check build. No allocation, locks, or file I/O in callback.
void player_check_audio(const float *samples, size_t frames) {
size_t count = std::min(frames, capture.size() / 2 - capturedFrames);
std::memcpy(capture.data() + capturedFrames * 2, samples, count * 2 * sizeof(float));
capturedFrames += count;
}
int main(int argc, char **argv) {
require(argc == 2, "fixture directory required");
std::string directory = argv[1];
for (const char *name : {"tone441.wav", "tone480.wav", "tone441.mka", "tone-aac.m4a"}) {
std::string path = directory + "/" + name;
std::ifstream input(path + ".f32", std::ios::binary | std::ios::ate);
require(bool(input), "PCM reference exists");
auto bytes = input.tellg();
require(bytes > 0 && bytes % (2 * sizeof(float)) == 0, "stereo reference format");
std::vector<float> reference(static_cast<size_t>(bytes) / sizeof(float));
input.seekg(0);
require(bool(input.read(reinterpret_cast<char *>(reference.data()), bytes)), "read PCM reference");
play(path);
size_t frames = reference.size() / 2;
size_t start = firstActive(capture.data(), capturedFrames);
size_t referenceStart = firstActive(reference.data(), frames);
require(start >= referenceStart, "capture alignment");
size_t offset = start - referenceStart;
require(capturedFrames >= offset + frames, "all output samples reach callback");
float maximumError = 0;
for (size_t i = 0; i < reference.size(); ++i)
maximumError = std::max(maximumError, std::abs(capture[offset * 2 + i] - reference[i]));
std::printf("%s: max sample error %.8f\n", name, maximumError);
require(maximumError < .00003f, "continuous callback PCM matches reference (no inserted/dropped samples)");
}
// Continuity must not erase a real 250 ms PTS gap in the source.
play(directory + "/tone-gap.mka");
size_t offset = firstActive(capture.data(), capturedFrames) - 1;
require(capturedFrames > offset + 48000 * 2, "gapped media is presented");
for (size_t i = 51840; i < 57600; ++i)
require(std::abs(capture[(offset + i) * 2]) < 1e-6f, "real timestamp gap remains silent");
double energy = 0;
for (size_t i = 62000; i < 66800; ++i) {
double value = capture[(offset + i) * 2];
energy += value * value;
}
require(energy / 4800 > .001, "audible tone resumes after real gap");
std::puts("PASS callback PCM continuity at 44.1/48 kHz, coarse timestamps, and real timestamp gap");
}