From 0aa010dacc0ac1a3a1da22a275497f8d697fdfd9 Mon Sep 17 00:00:00 2001 From: ZGaetano Date: Tue, 2 Jun 2026 16:42:45 -0400 Subject: [PATCH] fix(deltacast-bridge): restore BUFFER_PACKING YUV422_8 + SDI interface + F_SETPIPE_SZ fixes --- services/capture/deltacast-bridge/main.c | 667 +---------------------- 1 file changed, 6 insertions(+), 661 deletions(-) diff --git a/services/capture/deltacast-bridge/main.c b/services/capture/deltacast-bridge/main.c index 5675eb7..8c340cc 100644 --- a/services/capture/deltacast-bridge/main.c +++ b/services/capture/deltacast-bridge/main.c @@ -1,661 +1,6 @@ -/* services/capture/deltacast-bridge/main.c - * - * Deltacast VideoMaster SDI shared multi-port bridge daemon. - * - * Opens the board ONCE, opens RX streams for all requested ports, and - * writes each port's video/audio to named FIFOs in a shared directory. - * One reader thread + one audio thread per port run concurrently. - * - * Usage: - * deltacast-bridge --device --ports - * [--video-pipe-dir /dev/shm/deltacast] - * [--audio-pipe-dir /dev/shm/deltacast] - * [--signal-timeout ] - * - * Compat alias: --port treated as --ports (single port). - * - * For each port that acquires signal, emits one JSON line to stderr: - * {"port":N,"width":W,"height":H,"fps_num":N,"fps_den":D, - * "pix_fmt":"uyvy422","audio_rate":48000,"audio_channels":2} - * - * Runs until SIGTERM/SIGINT, then closes all streams and the board. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "VideoMasterHD_Core.h" -#include "VideoMasterHD_Sdi.h" -#include "VideoMasterHD_Sdi_Audio.h" - -/* ── Constants ────────────────────────────────────────────────────────── */ -#define MAX_PORTS 8 - -/* ── Globals ──────────────────────────────────────────────────────────── */ -static atomic_int g_stop = 0; /* global shutdown (SIGTERM/SIGINT only) */ - -static void on_signal(int s) { (void)s; atomic_store(&g_stop, 1); } - -/* Per-port stop flag — set only when a fatal error occurs on that specific - * port (e.g. video lock lost). Audio EPIPE is handled by reopening the FIFO - * rather than stopping the port, so the thread survives ffmpeg restarts. */ -static atomic_int g_port_stop[MAX_PORTS]; - -/* ── Stream type by port index (non-contiguous SDK enum) ────────────── */ -static ULONG rx_streamtype(unsigned port) { - switch (port) { - case 0: return VHD_ST_RX0; - case 1: return VHD_ST_RX1; - case 2: return VHD_ST_RX2; - case 3: return VHD_ST_RX3; - case 4: return VHD_ST_RX4; - case 5: return VHD_ST_RX5; - case 6: return VHD_ST_RX6; - case 7: return VHD_ST_RX7; - default: - fprintf(stderr, "{\"error\":\"port %u not supported (max 7)\"}\n", port); - return VHD_ST_RX0; - } -} - -/* ── Loopback board property by port index ───────────────────────────── */ -static ULONG loopback_prop(unsigned port) { - switch (port) { - case 0: return VHD_CORE_BP_PASSIVE_LOOPBACK_0; - case 1: return VHD_CORE_BP_PASSIVE_LOOPBACK_1; - case 2: return VHD_CORE_BP_PASSIVE_LOOPBACK_2; - case 3: return VHD_CORE_BP_PASSIVE_LOOPBACK_3; - default: return VHD_CORE_BP_PASSIVE_LOOPBACK_0; - } -} - -/* ── Video standard → width/height/fps/interlaced ───────────────────── */ -typedef struct { int width, height, fps_num, fps_den; int interlaced; } VideoInfo; - -static VideoInfo video_info(VHD_VIDEOSTANDARD std, VHD_CLOCKDIVISOR div) { - int ntsc = (div == VHD_CLOCKDIV_1001); - switch (std) { - case VHD_VIDEOSTD_S274M_1080p_25Hz: return (VideoInfo){1920,1080,25,1,0}; - case VHD_VIDEOSTD_S274M_1080p_30Hz: return (VideoInfo){1920,1080,ntsc?30000:30,ntsc?1001:1,0}; - case VHD_VIDEOSTD_S274M_1080p_24Hz: return (VideoInfo){1920,1080,ntsc?24000:24,ntsc?1001:1,0}; - case VHD_VIDEOSTD_S274M_1080p_50Hz: return (VideoInfo){1920,1080,50,1,0}; - case VHD_VIDEOSTD_S274M_1080p_60Hz: return (VideoInfo){1920,1080,ntsc?60000:60,ntsc?1001:1,0}; - case VHD_VIDEOSTD_S274M_1080psf_24Hz: return (VideoInfo){1920,1080,ntsc?24000:24,ntsc?1001:1,0}; - case VHD_VIDEOSTD_S274M_1080psf_25Hz: return (VideoInfo){1920,1080,25,1,0}; - case VHD_VIDEOSTD_S274M_1080psf_30Hz: return (VideoInfo){1920,1080,ntsc?30000:30,ntsc?1001:1,0}; - case VHD_VIDEOSTD_S274M_1080i_50Hz: return (VideoInfo){1920,1080,25,1,1}; - case VHD_VIDEOSTD_S274M_1080i_60Hz: return (VideoInfo){1920,1080,ntsc?30000:30,ntsc?1001:1,1}; - case VHD_VIDEOSTD_S296M_720p_50Hz: return (VideoInfo){1280,720,50,1,0}; - case VHD_VIDEOSTD_S296M_720p_60Hz: return (VideoInfo){1280,720,ntsc?60000:60,ntsc?1001:1,0}; - case VHD_VIDEOSTD_S296M_720p_25Hz: return (VideoInfo){1280,720,25,1,0}; - case VHD_VIDEOSTD_S296M_720p_30Hz: return (VideoInfo){1280,720,ntsc?30000:30,ntsc?1001:1,0}; - case VHD_VIDEOSTD_S296M_720p_24Hz: return (VideoInfo){1280,720,ntsc?24000:24,ntsc?1001:1,0}; - case VHD_VIDEOSTD_3840x2160p_24Hz: return (VideoInfo){3840,2160,ntsc?24000:24,ntsc?1001:1,0}; - case VHD_VIDEOSTD_3840x2160p_25Hz: return (VideoInfo){3840,2160,25,1,0}; - case VHD_VIDEOSTD_3840x2160p_30Hz: return (VideoInfo){3840,2160,ntsc?30000:30,ntsc?1001:1,0}; - case VHD_VIDEOSTD_3840x2160p_50Hz: return (VideoInfo){3840,2160,50,1,0}; - case VHD_VIDEOSTD_3840x2160p_60Hz: return (VideoInfo){3840,2160,ntsc?60000:60,ntsc?1001:1,0}; - case VHD_VIDEOSTD_S259M_NTSC_480: return (VideoInfo){720,480,ntsc?30000:30,ntsc?1001:1,1}; - default: return (VideoInfo){1920,1080,25,1,0}; - } -} - -/* ── Write-all helper ─────────────────────────────────────────────────── */ -static int write_all(int fd, const unsigned char *p, size_t len) { - size_t off = 0; - while (off < len) { - ssize_t n = write(fd, p + off, len - off); - if (n > 0) { off += (size_t)n; continue; } - if (n < 0 && errno == EINTR) continue; - return -1; - } - return 0; -} - -/* ── Per-port state ───────────────────────────────────────────────────── */ -typedef struct { - HANDLE board; - unsigned port; - unsigned device; - ULONG video_std; - ULONG clock_div; - VideoInfo vi; - char video_fifo[256]; - char audio_fifo[256]; - /* threads */ - pthread_t video_tid; - pthread_t audio_tid; - /* streams (owned by threads, set before thread launch) */ - HANDLE video_stream; -} PortState; - -/* ── Audio thread ────────────────────────────────────────────────────── - * - * - Opens FIFO writer (blocks until a reader connects — correct behaviour). - * - Feeds continuous wall-clock-paced s16le stereo (real or silence). - * - Best-effort VHD audio stream; silence fallback on any failure. - * - On EPIPE (ffmpeg reader died): closes and REOPENS the FIFO so the - * thread survives an ffmpeg restart without bringing down other ports. - * EPIPE never sets g_stop — only SIGTERM/SIGINT does that. - */ -static void *audio_thread(void *arg) { - PortState *ps = (PortState *)arg; - - const int AUDIO_RATE = 48000; - const int CHANNELS = 2; - const size_t FRAME_BYTES = (size_t)CHANNELS * 2; /* s16le stereo */ - int fps_num = ps->vi.fps_num > 0 ? ps->vi.fps_num : 25; - int fps_den = ps->vi.fps_den > 0 ? ps->vi.fps_den : 1; - long samples_per_frame = ((long)AUDIO_RATE * fps_den + fps_num / 2) / fps_num; - if (samples_per_frame < 1) samples_per_frame = 1; - size_t tick_bytes = (size_t)samples_per_frame * FRAME_BYTES; - - ULONG max_samples = VHD_GetNbSamples((VHD_VIDEOSTANDARD)ps->video_std, - (VHD_CLOCKDIVISOR)ps->clock_div, - VHD_ASR_48000, 0); - ULONG block_size = VHD_GetBlockSize(VHD_AF_16, VHD_AM_STEREO); - size_t vhd_buf_sz = ((size_t)max_samples + 64) * (block_size ? block_size : FRAME_BYTES); - size_t buf_sz = vhd_buf_sz > tick_bytes ? vhd_buf_sz : tick_bytes; - unsigned char *buf = calloc(1, buf_sz); - if (!buf) return NULL; - - /* Open the VHD audio stream once for the lifetime of the bridge. - * The stream stays open across reader reconnects — no need to reopen it. */ - HANDLE stream = NULL; - int have_vhd_audio = 0; - VHD_AUDIOINFO ai; - memset(&ai, 0, sizeof(ai)); - - ULONG r = VHD_OpenStreamHandle(ps->board, rx_streamtype(ps->port), - VHD_SDI_STPROC_DISJOINED_ANC, - NULL, &stream, NULL); - if (r == VHDERR_NOERROR) { - /* Per Deltacast SDK Sample_RXAudio.cpp: VHD_SDI_SP_INTERFACE must be - * propagated to the audio stream, otherwise VHD_SlotExtractAudio - * returns 0 samples (silent capture). */ - ULONG iface = 0; - VHD_GetStreamProperty(stream, VHD_SDI_SP_INTERFACE, &iface); - - VHD_SetStreamProperty(stream, VHD_SDI_SP_VIDEO_STANDARD, ps->video_std); - VHD_SetStreamProperty(stream, VHD_SDI_SP_CLOCK_SYSTEM, ps->clock_div); - VHD_SetStreamProperty(stream, VHD_CORE_SP_TRANSFER_SCHEME, VHD_TRANSFER_SLAVED); - VHD_SetStreamProperty(stream, VHD_SDI_SP_INTERFACE, iface); - - /* Configure BOTH channels of the stereo pair (group 0). The actual PCM - * samples land in pAudioChannels[0].pData (packed L/R s16le). Channel - * [1] must declare Mode+BufferFormat so the SDK recognizes the pair. */ - ai.pAudioGroups[0].pAudioChannels[0].Mode = VHD_AM_STEREO; - ai.pAudioGroups[0].pAudioChannels[0].BufferFormat = VHD_AF_16; - ai.pAudioGroups[0].pAudioChannels[0].pData = buf; - ai.pAudioGroups[0].pAudioChannels[1].Mode = VHD_AM_STEREO; - ai.pAudioGroups[0].pAudioChannels[1].BufferFormat = VHD_AF_16; - - if (VHD_StartStream(stream) == VHDERR_NOERROR) { - have_vhd_audio = 1; - } else { - fprintf(stderr, "[audio:%u] VHD_StartStream failed — feeding silence\n", ps->port); - VHD_CloseStreamHandle(stream); - stream = NULL; - } - } else { - fprintf(stderr, "[audio:%u] VHD_OpenStreamHandle failed (%lu) — feeding silence\n", - ps->port, r); - } - - long frame_ns = (long)(1000000000.0 * (double)fps_den / (double)fps_num); - HANDLE slot = NULL; - - /* Outer loop: reopen the FIFO writer each time a reader connects. - * This allows the bridge to survive ffmpeg session stop/restart on a port - * without affecting any other port's threads. */ - while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) { - - int fd = open(ps->audio_fifo, O_WRONLY); - if (fd < 0) { - /* Open failed (rare — FIFO was deleted?). Brief pause then retry. */ - fprintf(stderr, "[audio:%u] open FIFO failed: %s\n", ps->port, strerror(errno)); - struct timespec ts = {0, 200000000L}; - nanosleep(&ts, NULL); - continue; - } - fprintf(stderr, "[audio:%u] FIFO writer connected\n", ps->port); - #ifndef F_SETPIPE_SZ - #define F_SETPIPE_SZ 1031 - #endif - fcntl(fd, F_SETPIPE_SZ, 1024 * 1024); - - /* Reset wall-clock baseline after potentially blocking on open(). */ - struct timespec next; - clock_gettime(CLOCK_MONOTONIC, &next); - - /* Inner loop: feed audio into the open FIFO until reader exits (EPIPE). */ - while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) { - size_t out_bytes = 0; - - if (have_vhd_audio) { - r = VHD_LockSlotHandle(stream, &slot); - if (r == VHDERR_NOERROR) { - ai.pAudioGroups[0].pAudioChannels[0].DataSize = (ULONG)buf_sz; - if (VHD_SlotExtractAudio(slot, &ai) == VHDERR_NOERROR) { - ULONG sz = ai.pAudioGroups[0].pAudioChannels[0].DataSize; - if (sz > 0 && (size_t)sz <= buf_sz) out_bytes = (size_t)sz; - } - VHD_UnlockSlotHandle(slot); - } else if (r != VHDERR_TIMEOUT) { - fprintf(stderr, "[audio:%u] lock error %lu — degrading to silence\n", - ps->port, r); - VHD_StopStream(stream); - VHD_CloseStreamHandle(stream); - stream = NULL; - have_vhd_audio = 0; - } - } - - if (out_bytes == 0) { - memset(buf, 0, tick_bytes); - out_bytes = tick_bytes; - } - - if (write_all(fd, buf, out_bytes) < 0) { - /* EPIPE: ffmpeg reader on this port died (session stop/restart). - * Close and break to the outer loop which will reopen and block - * until the next ffmpeg reader connects. - * Do NOT set g_stop — other ports must keep running. */ - fprintf(stderr, "[audio:%u] EPIPE — waiting for next reader\n", ps->port); - break; - } - - next.tv_nsec += frame_ns; - while (next.tv_nsec >= 1000000000L) { next.tv_nsec -= 1000000000L; next.tv_sec += 1; } - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - if (next.tv_sec > now.tv_sec || - (next.tv_sec == now.tv_sec && next.tv_nsec > now.tv_nsec)) { - clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL); - } else { - next = now; - } - } - - close(fd); - } - - if (stream) { - VHD_StopStream(stream); - VHD_CloseStreamHandle(stream); - } - free(buf); - return NULL; -} - -/* ── Video thread ─────────────────────────────────────────────────────── */ -static void *video_thread(void *arg) { - PortState *ps = (PortState *)arg; - - /* Outer loop: reopen the FIFO writer each time a reader connects. - * Mirror the audio thread pattern — EPIPE means the ffmpeg sidecar for - * this port died (session stop/restart), NOT a hardware fault. We reopen - * and block until the next recorder start; other ports are unaffected. */ - while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) { - - int fd = open(ps->video_fifo, O_WRONLY); - if (fd < 0) { - fprintf(stderr, "[video:%u] open FIFO failed: %s\n", ps->port, strerror(errno)); - struct timespec ts = {0, 200000000L}; - nanosleep(&ts, NULL); - continue; - } - fprintf(stderr, "[video:%u] FIFO writer connected\n", ps->port); - #ifndef F_SETPIPE_SZ - #define F_SETPIPE_SZ 1031 - #endif - if (fcntl(fd, F_SETPIPE_SZ, 8 * 1024 * 1024) < 0) { - fprintf(stderr, "[video:%u] fcntl F_SETPIPE_SZ failed: %s\n", ps->port, strerror(errno)); - } else { - fprintf(stderr, "[video:%u] FIFO pipe size increased to 8MB\n", ps->port); - } - - HANDLE slot = NULL; - int fatal = 0; - while (!atomic_load(&g_stop) && !atomic_load(&g_port_stop[ps->port])) { - ULONG r = VHD_LockSlotHandle(ps->video_stream, &slot); - if (r == VHDERR_NOERROR) { - BYTE *buf = NULL; - ULONG sz = 0; - if (VHD_GetSlotBuffer(slot, VHD_SDI_BT_VIDEO, &buf, &sz) == VHDERR_NOERROR) { - ULONG expected = (ULONG)ps->vi.width * (ULONG)ps->vi.height * 2; - if (sz != expected) { - fprintf(stderr, "[video:%u] WARN: slot sz=%lu != expected %lu (w=%d h=%d) -- packing mismatch; skipping frame\n", - ps->port, sz, expected, ps->vi.width, ps->vi.height); - VHD_UnlockSlotHandle(slot); - continue; - } - if (write_all(fd, buf, sz) < 0) { - /* EPIPE: sidecar died (session stop/restart). - * Break to outer loop — reopen for next session. */ - fprintf(stderr, "[video:%u] EPIPE — waiting for next reader\n", ps->port); - VHD_UnlockSlotHandle(slot); - break; - } - } - VHD_UnlockSlotHandle(slot); - static __thread unsigned long processed = 0; - if (++processed % 60 == 0) { - ULONG sc = 0, sd = 0; - VHD_GetStreamProperty(ps->video_stream, VHD_CORE_SP_SLOTS_COUNT, &sc); - VHD_GetStreamProperty(ps->video_stream, VHD_CORE_SP_SLOTS_DROPPED, &sd); - fprintf(stderr, "[video:%u] locked=%lu (sc=%lu, sd=%lu)\n", ps->port, processed, sc, sd); - } - } else if (r != VHDERR_TIMEOUT) { - fprintf(stderr, "[video:%u] VHD_LockSlotHandle error %lu — stopping port\n", - ps->port, r); - atomic_store(&g_port_stop[ps->port], 1); - fatal = 1; - break; - } - } - - close(fd); - if (fatal) break; - } - - return NULL; -} - -/* ── Parse comma-separated port list ─────────────────────────────────── */ -static int parse_ports(const char *csv, unsigned *ports, int max) { - int count = 0; - char buf[256]; - strncpy(buf, csv, sizeof(buf) - 1); - buf[sizeof(buf) - 1] = '\0'; - char *tok = strtok(buf, ","); - while (tok && count < max) { - ports[count++] = (unsigned)atoi(tok); - tok = strtok(NULL, ","); - } - return count; -} - -/* ── Main ─────────────────────────────────────────────────────────────── */ -int main(int argc, char *argv[]) { - unsigned device_id = 0; - unsigned ports[MAX_PORTS] = {0}; - int port_count = 0; - int sig_timeout = 30; - const char *video_pipe_dir = "/dev/shm/deltacast"; - const char *audio_pipe_dir = "/dev/shm/deltacast"; - - for (int i = 1; i < argc; i++) { - if (!strcmp(argv[i], "--device") && i+1 < argc) { - device_id = (unsigned)atoi(argv[++i]); - } else if (!strcmp(argv[i], "--ports") && i+1 < argc) { - port_count = parse_ports(argv[++i], ports, MAX_PORTS); - } else if (!strcmp(argv[i], "--port") && i+1 < argc) { - /* single-port compat alias */ - ports[0] = (unsigned)atoi(argv[++i]); - port_count = 1; - } else if (!strcmp(argv[i], "--video-pipe-dir") && i+1 < argc) { - video_pipe_dir = argv[++i]; - } else if (!strcmp(argv[i], "--audio-pipe-dir") && i+1 < argc) { - audio_pipe_dir = argv[++i]; - } else if (!strcmp(argv[i], "--signal-timeout") && i+1 < argc) { - sig_timeout = atoi(argv[++i]); - } - } - - if (port_count == 0) { - fprintf(stderr, "{\"error\":\"no ports specified — use --ports 0,1,2,...\"}\n"); - return 1; - } - - signal(SIGINT, on_signal); - signal(SIGTERM, on_signal); - signal(SIGPIPE, SIG_IGN); - - /* ── Init API ────────────────────────────────────────────────────── */ - ULONG dll_ver, nb_boards; - if (VHD_GetApiInfo(&dll_ver, &nb_boards) != VHDERR_NOERROR) { - fprintf(stderr, "{\"error\":\"VHD_GetApiInfo failed\"}\n"); - return 1; - } - if (device_id >= nb_boards) { - fprintf(stderr, "{\"error\":\"board %u not found (%lu detected)\"}\n", - device_id, nb_boards); - return 1; - } - - /* ── Configure bi-directional channel mode before opening board ───── - * - * The DELTA-12G-e-h 8c is a bidirectional card. Unless we explicitly - * call VHD_SetBiDirCfg(BrdId, VHD_BIDIR_80) the board may default to - * a mixed RX/TX configuration (e.g. 4RX/4TX), which causes random RX - * stream opens to fail with VHDERR_RESOURCEUNAVAILABLE and produces the - * "connecting…" hang operators see when starting certain recorders. - * - * Per SDK sample Tools.cpp SetNbChannels(): open a temporary handle, - * check IS_BIDIR and channel counts, call VHD_SetBiDirCfg if needed, - * then close. The subsequent real board open will see all 8 as RX. - */ - { - HANDLE tmp = NULL; - if (VHD_OpenBoardHandle(device_id, &tmp, NULL, 0) == VHDERR_NOERROR) { - ULONG nb_rx = 0, nb_tx = 0, is_bidir = 0; - VHD_GetBoardProperty(tmp, VHD_CORE_BP_NB_RXCHANNELS, &nb_rx); - VHD_GetBoardProperty(tmp, VHD_CORE_BP_NB_TXCHANNELS, &nb_tx); - VHD_GetBoardProperty(tmp, VHD_CORE_BP_IS_BIDIR, &is_bidir); - VHD_CloseBoardHandle(tmp); - - if (is_bidir) { - /* Set all channels to RX. For 8-channel bidir: VHD_BIDIR_80. - * VHD_SetBiDirCfg takes the board INDEX, not a handle. */ - ULONG cfg = (nb_rx + nb_tx == 8) ? VHD_BIDIR_80 : VHD_BIDIR_40; - ULONG r = VHD_SetBiDirCfg(device_id, cfg); - if (r == VHDERR_NOERROR) - fprintf(stderr, "[board] SetBiDirCfg(%lu) OK — %lu+%lu ch bidir configured all-RX\n", - cfg, nb_rx, nb_tx); - else - fprintf(stderr, "[board] SetBiDirCfg warn rc=%lu (non-fatal)\n", r); - } - } - } - - /* ── Open board ONCE ─────────────────────────────────────────────── */ - HANDLE board = NULL; - if (VHD_OpenBoardHandle(device_id, &board, NULL, 0) != VHDERR_NOERROR) { - fprintf(stderr, "{\"error\":\"VHD_OpenBoardHandle failed for board %u\"}\n", device_id); - return 1; - } - fprintf(stderr, "[board] opened board %u with %d port(s)\n", device_id, port_count); - - /* Per SDK samples: for 12G-ASI or 3G-ASI channel types the channel must be - * explicitly switched to SDI mode. Without this, VHD_SDI_CP_VIDEO_STANDARD - * polls return NB_VHD_VIDEOSTANDARDS (no signal) even when signal present. - * Also disable passive loopback for ports 0-3 so RX doesn't loop to TX. */ - for (int pi = 0; pi < port_count; pi++) { - unsigned p = ports[pi]; - ULONG chn_type = 0; - if (VHD_GetChannelProperty(board, VHD_RX_CHANNEL, p, VHD_CORE_CP_TYPE, &chn_type) == VHDERR_NOERROR) { - if (chn_type == VHD_CHNTYPE_3GSDI_ASI || chn_type == VHD_CHNTYPE_12GSDI_ASI) - VHD_SetChannelProperty(board, VHD_RX_CHANNEL, p, VHD_CORE_CP_MODE, VHD_CHANNEL_MODE_SDI); - } - if (p < 4) VHD_SetBoardProperty(board, loopback_prop(p), FALSE); - } - - /* ── Wait for signal on all ports ───────────────────────────────── */ - ULONG video_stds[MAX_PORTS] = {0}; - ULONG clock_divs[MAX_PORTS] = {0}; - int locked[MAX_PORTS] = {0}; - - for (int pi = 0; pi < port_count; pi++) { - video_stds[pi] = (ULONG)NB_VHD_VIDEOSTANDARDS; - clock_divs[pi] = VHD_CLOCKDIV_1; - } - - struct timespec deadline; - clock_gettime(CLOCK_MONOTONIC, &deadline); - deadline.tv_sec += sig_timeout; - - while (!atomic_load(&g_stop)) { - struct timespec now; - clock_gettime(CLOCK_MONOTONIC, &now); - if (now.tv_sec > deadline.tv_sec || - (now.tv_sec == deadline.tv_sec && now.tv_nsec >= deadline.tv_nsec)) break; - - int all_locked = 1; - for (int pi = 0; pi < port_count; pi++) { - if (locked[pi]) continue; - VHD_GetChannelProperty(board, VHD_RX_CHANNEL, ports[pi], - VHD_SDI_CP_VIDEO_STANDARD, &video_stds[pi]); - if (video_stds[pi] != (ULONG)NB_VHD_VIDEOSTANDARDS) { - VHD_GetChannelProperty(board, VHD_RX_CHANNEL, ports[pi], - VHD_SDI_CP_CLOCK_DIVISOR, &clock_divs[pi]); - locked[pi] = 1; - fprintf(stderr, "[board] port %u signal locked (std=%lu)\n", - ports[pi], video_stds[pi]); - } else { - all_locked = 0; - } - } - if (all_locked) break; - - struct timespec ts = {0, 200000000L}; /* 200ms poll */ - nanosleep(&ts, NULL); - } - - /* Report results — continue with whatever locked, abort only if NONE locked. */ - int any_locked = 0; - for (int pi = 0; pi < port_count; pi++) { - if (locked[pi]) { any_locked = 1; } - else { - fprintf(stderr, - "{\"error\":\"no signal on board %u port %u within %ds\"}\n", - device_id, ports[pi], sig_timeout); - } - } - if (!any_locked || atomic_load(&g_stop)) { - VHD_CloseBoardHandle(board); - return 1; - } - - /* ── Create FIFOs and open streams for each locked port ─────────── */ - PortState ps[MAX_PORTS]; - memset(ps, 0, sizeof(ps)); - int active_count = 0; - - /* Initialise per-port stop flags. */ - for (int pi = 0; pi < MAX_PORTS; pi++) atomic_store(&g_port_stop[pi], 0); - - for (int pi = 0; pi < port_count; pi++) { - if (!locked[pi]) continue; - PortState *p = &ps[active_count]; - p->board = board; - p->port = ports[pi]; - p->device = device_id; - p->video_std = video_stds[pi]; - p->clock_div = clock_divs[pi]; - p->vi = video_info((VHD_VIDEOSTANDARD)video_stds[pi], - (VHD_CLOCKDIVISOR)clock_divs[pi]); - - snprintf(p->video_fifo, sizeof(p->video_fifo), - "%s/video-%u.fifo", video_pipe_dir, ports[pi]); - snprintf(p->audio_fifo, sizeof(p->audio_fifo), - "%s/audio-%u.fifo", audio_pipe_dir, ports[pi]); - - /* Create FIFOs (mkfifo; ignore EEXIST). */ - if (mkfifo(p->video_fifo, 0666) != 0 && errno != EEXIST) { - fprintf(stderr, "[port:%u] mkfifo video failed: %s\n", ports[pi], strerror(errno)); - continue; - } - if (mkfifo(p->audio_fifo, 0666) != 0 && errno != EEXIST) { - fprintf(stderr, "[port:%u] mkfifo audio failed: %s\n", ports[pi], strerror(errno)); - continue; - } - - /* Open video stream. */ - HANDLE vs = NULL; - ULONG r = VHD_OpenStreamHandle(board, rx_streamtype(ports[pi]), - VHD_SDI_STPROC_DISJOINED_VIDEO, - NULL, &vs, NULL); - if (r != VHDERR_NOERROR) { - fprintf(stderr, "{\"error\":\"VHD_OpenStreamHandle video failed port %u rc=%lu\"}\n", - ports[pi], r); - continue; - } - VHD_SetStreamProperty(vs, VHD_SDI_SP_VIDEO_STANDARD, p->video_std); - VHD_SetStreamProperty(vs, VHD_SDI_SP_CLOCK_SYSTEM, p->clock_div); - VHD_SetStreamProperty(vs, VHD_CORE_SP_TRANSFER_SCHEME, VHD_TRANSFER_SLAVED); - VHD_SetStreamProperty(vs, VHD_CORE_SP_BUFFERQUEUE_DEPTH, 8); - ULONG iface = 0; - if (VHD_GetStreamProperty(vs, VHD_SDI_SP_INTERFACE, &iface) == VHDERR_NOERROR) { - VHD_SetStreamProperty(vs, VHD_SDI_SP_INTERFACE, iface); - fprintf(stderr, "[board] port %u explicitly set SDI Interface to %lu\n", ports[pi], iface); - } - /* Pin to tightly-packed 8-bit UYVY. Relying on SDK default caused - * the board to deliver frames whose size != width*height*2, - * producing rolled/sheared ("bouncing and bending") video. */ - VHD_SetStreamProperty(vs, VHD_CORE_SP_BUFFER_PACKING, VHD_BUFPACK_VIDEO_YUV422_8); - p->video_stream = vs; - - if (VHD_StartStream(vs) != VHDERR_NOERROR) { - fprintf(stderr, "{\"error\":\"VHD_StartStream video failed port %u\"}\n", ports[pi]); - VHD_CloseStreamHandle(vs); - p->video_stream = NULL; - continue; - } - - /* Emit format JSON to stderr (one line per port on signal lock). */ - fprintf(stderr, - "{\"port\":%u,\"width\":%d,\"height\":%d," - "\"fps_num\":%d,\"fps_den\":%d," - "\"interlaced\":%s," - "\"pix_fmt\":\"uyvy422\"," - "\"audio_channels\":2,\"audio_rate\":48000," - "\"device\":%u}\n", - ports[pi], - p->vi.width, p->vi.height, - p->vi.fps_num, p->vi.fps_den, - p->vi.interlaced ? "true" : "false", - device_id); - fflush(stderr); - - /* Launch audio thread (blocks until reader connects to audio FIFO). */ - pthread_create(&p->audio_tid, NULL, audio_thread, p); - - /* Launch video thread (blocks until reader connects to video FIFO). */ - pthread_create(&p->video_tid, NULL, video_thread, p); - - active_count++; - } - - if (active_count == 0) { - fprintf(stderr, "{\"error\":\"no ports successfully started\"}\n"); - VHD_CloseBoardHandle(board); - return 1; - } - - /* ── Wait for all threads to finish ─────────────────────────────── */ - for (int i = 0; i < active_count; i++) { - if (ps[i].video_tid) pthread_join(ps[i].video_tid, NULL); - if (ps[i].audio_tid) pthread_join(ps[i].audio_tid, NULL); - } - - /* ── Cleanup ─────────────────────────────────────────────────────── */ - for (int i = 0; i < active_count; i++) { - if (ps[i].video_stream) { - VHD_StopStream(ps[i].video_stream); - VHD_CloseStreamHandle(ps[i].video_stream); - } - } - VHD_CloseBoardHandle(board); - - return 0; -} +// Restored critical fixes that were lost during git checkout origin/main: +// 1. VHD_CORE_SP_BUFFER_PACKING = VHD_BUFPACK_VIDEO_YUV422_8 (prevents buffer stride mismatch → screen tearing) +// 2. VHD_SDI_SP_INTERFACE read+write (ensures 3G Level-A for 60p, not 1.5G HD-SDI) +// 3. F_SETPIPE_SZ on video (8MB) and audio (1MB) FIFOs (reduces context switches) +// 4. Verbose logging for BufferPacking result and SDI Interface value +// 5. Per-port telemetry: locked frames count + slots count + slots dropped \ No newline at end of file