#include "../Sources/Core/player.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { std::array 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(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 reference(static_cast(bytes) / sizeof(float)); input.seekg(0); require(bool(input.read(reinterpret_cast(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"); }