fix(audio): per-port A/V start barrier so video+audio FIFOs begin at the same instant (fixes fixed A/V offset)
This commit is contained in:
parent
20d913fbad
commit
bebfe7a43d
1 changed files with 65 additions and 2 deletions
|
|
@ -50,6 +50,19 @@ static void on_signal(int s) { (void)s; atomic_store(&g_stop, 1); }
|
||||||
* rather than stopping the port, so the thread survives ffmpeg restarts. */
|
* rather than stopping the port, so the thread survives ffmpeg restarts. */
|
||||||
static atomic_int g_port_stop[MAX_PORTS];
|
static atomic_int g_port_stop[MAX_PORTS];
|
||||||
|
|
||||||
|
/* ── Per-port A/V start barrier ──────────────────────────────────────────
|
||||||
|
* Video and audio are two independent FIFOs that ffmpeg opens at slightly
|
||||||
|
* different times (input 0 video, then input 1 audio). Whichever thread's
|
||||||
|
* reader connects first would otherwise start pumping data immediately,
|
||||||
|
* giving its stream a head start → a fixed A/V offset that never recovers
|
||||||
|
* (raw streams carry no timestamps, so ffmpeg PTS-zeroes whatever each stream
|
||||||
|
* delivers first). To start both streams from the SAME instant, each thread
|
||||||
|
* publishes "my FIFO reader is connected" and then BOTH spin until the other
|
||||||
|
* side is also connected before writing their first real sample/frame.
|
||||||
|
* Reset to 0 each time a fresh pair of readers connects. */
|
||||||
|
static atomic_int g_video_ready[MAX_PORTS];
|
||||||
|
static atomic_int g_audio_ready[MAX_PORTS];
|
||||||
|
|
||||||
/* ── Stream type by port index (non-contiguous SDK enum) ────────────── */
|
/* ── Stream type by port index (non-contiguous SDK enum) ────────────── */
|
||||||
static ULONG rx_streamtype(unsigned port) {
|
static ULONG rx_streamtype(unsigned port) {
|
||||||
switch (port) {
|
switch (port) {
|
||||||
|
|
@ -252,6 +265,28 @@ static void *audio_thread(void *arg) {
|
||||||
#endif
|
#endif
|
||||||
fcntl(fd, F_SETPIPE_SZ, 1024 * 1024);
|
fcntl(fd, F_SETPIPE_SZ, 1024 * 1024);
|
||||||
|
|
||||||
|
/* A/V start barrier: announce ready, then wait (up to ~5s) for the
|
||||||
|
* video reader so both streams begin at the same instant. While
|
||||||
|
* waiting we drain+discard hardware audio slots so the board audio
|
||||||
|
* queue doesn't overflow and the first sample we write lines up with
|
||||||
|
* the first video frame. */
|
||||||
|
atomic_store(&g_audio_ready[ps->port], 1);
|
||||||
|
{
|
||||||
|
int waited = 0;
|
||||||
|
while (!atomic_load(&g_video_ready[ps->port])
|
||||||
|
&& !atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])
|
||||||
|
&& waited < 5000) {
|
||||||
|
if (have_vhd_audio) {
|
||||||
|
HANDLE ds = NULL;
|
||||||
|
if (VHD_LockSlotHandle(stream, &ds) == VHDERR_NOERROR)
|
||||||
|
VHD_UnlockSlotHandle(ds); /* discard pre-roll audio */
|
||||||
|
}
|
||||||
|
struct timespec t = {0, 1000000L}; nanosleep(&t, NULL); waited++;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "[audio:%u] A/V barrier released (video_ready=%d)\n",
|
||||||
|
ps->port, atomic_load(&g_video_ready[ps->port]));
|
||||||
|
}
|
||||||
|
|
||||||
/* Reset wall-clock baseline after potentially blocking on open().
|
/* Reset wall-clock baseline after potentially blocking on open().
|
||||||
* Only used for the SILENCE fallback path (no hardware audio). */
|
* Only used for the SILENCE fallback path (no hardware audio). */
|
||||||
struct timespec next;
|
struct timespec next;
|
||||||
|
|
@ -343,6 +378,8 @@ static void *audio_thread(void *arg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fd);
|
close(fd);
|
||||||
|
/* Reader gone — clear ready so the next session re-syncs at the barrier. */
|
||||||
|
atomic_store(&g_audio_ready[ps->port], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream) {
|
if (stream) {
|
||||||
|
|
@ -383,6 +420,26 @@ static void *video_thread(void *arg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A/V start barrier: announce ready, then wait (up to ~5s) for the
|
||||||
|
* audio reader so both streams begin at the same instant. While
|
||||||
|
* waiting we drain+discard incoming video slots so the board queue
|
||||||
|
* doesn't overflow and so the FIRST frame we actually write is the one
|
||||||
|
* captured at the moment audio also starts. */
|
||||||
|
atomic_store(&g_video_ready[ps->port], 1);
|
||||||
|
{
|
||||||
|
int waited = 0;
|
||||||
|
while (!atomic_load(&g_audio_ready[ps->port])
|
||||||
|
&& !atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])
|
||||||
|
&& waited < 5000) {
|
||||||
|
HANDLE ds = NULL;
|
||||||
|
if (VHD_LockSlotHandle(ps->video_stream, &ds) == VHDERR_NOERROR)
|
||||||
|
VHD_UnlockSlotHandle(ds); /* discard pre-roll frame */
|
||||||
|
struct timespec t = {0, 1000000L}; nanosleep(&t, NULL); waited++;
|
||||||
|
}
|
||||||
|
fprintf(stderr, "[video:%u] A/V barrier released (audio_ready=%d)\n",
|
||||||
|
ps->port, atomic_load(&g_audio_ready[ps->port]));
|
||||||
|
}
|
||||||
|
|
||||||
HANDLE slot = NULL;
|
HANDLE slot = NULL;
|
||||||
int fatal = 0;
|
int fatal = 0;
|
||||||
while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) {
|
while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) {
|
||||||
|
|
@ -424,6 +481,8 @@ static void *video_thread(void *arg) {
|
||||||
}
|
}
|
||||||
|
|
||||||
close(fd);
|
close(fd);
|
||||||
|
/* Reader gone — clear ready so the next session re-syncs at the barrier. */
|
||||||
|
atomic_store(&g_video_ready[ps->port], 0);
|
||||||
if (fatal) break;
|
if (fatal) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -610,8 +669,12 @@ int main(int argc, char *argv[]) {
|
||||||
memset(ps, 0, sizeof(ps));
|
memset(ps, 0, sizeof(ps));
|
||||||
int active_count = 0;
|
int active_count = 0;
|
||||||
|
|
||||||
/* Initialise per-port stop flags. */
|
/* Initialise per-port stop + A/V barrier flags. */
|
||||||
for (int pi = 0; pi < MAX_PORTS; pi++) atomic_store(&g_port_stop[pi], 0);
|
for (int pi = 0; pi < MAX_PORTS; pi++) {
|
||||||
|
atomic_store(&g_port_stop[pi], 0);
|
||||||
|
atomic_store(&g_video_ready[pi], 0);
|
||||||
|
atomic_store(&g_audio_ready[pi], 0);
|
||||||
|
}
|
||||||
|
|
||||||
for (int pi = 0; pi < port_count; pi++) {
|
for (int pi = 0; pi < port_count; pi++) {
|
||||||
if (!locked[pi]) continue;
|
if (!locked[pi]) continue;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue