Re-engineer the framecache so each video frame carries its own SDI-embedded
audio through ONE transport, eliminating the "audio ahead of video" offset at
the root: there is no longer a second independent audio buffer/FIFO that can
race ahead of video.
slot.h (FC_VERSION 1 -> 2):
- Per ring entry data region is now [video frame_size][audio FC_MAX_AUDIO_BYTES].
- fc_frame_t: the former _pad u32 is REUSED as audio_size (header still 24B).
- Header gains audio_max_bytes / audio_rate / audio_channels (self-describing).
- New fc_entry_stride()/fc_frame_audio() helpers; shm size includes audio.
- Readers/writer check version == FC_VERSION and FAIL SAFE on mismatch so an
old reader against a new writer (or vice-versa) refuses rather than misparses.
slot.c: populate audio header fields; add fc_slot_write_av(); version gate in open.
fc_client.[ch]: fc_frame_ref_t gains audio/audio_size; copy buffer holds
video+audio; both copied from the SAME entry in one read -> frame-locked.
fc_pipe.c: now <slot_id> <wait_ms> <audio_fifo_path>; per ring entry writes
video -> stdout AND that frame's audio -> the audio FIFO IN LOCKSTEP from one
cursor read (no second buffer to drift). Auto-reattaches FIFO on EPIPE.
deltacast-bridge:
- SILENT-AUDIO FIX: audio_extract_init now configures ONLY pAudioChannels[0]
of group 0 as one stereo pair (Mode=STEREO, BufferFormat=AF_16, pData=buf),
leaving pAudioChannels[1] ZEROED, exactly like Deltacast's own FFmpeg fork
(libavdevice/videomaster_common.c init_audio_info). The prior JOINED code
ALSO set channel[1].Mode/BufferFormat=STEREO/AF_16, declaring a second pair
the signal does not carry -> VHD_SlotExtractAudio returned zero samples ->
-91 dB silent audio. DataSize is (re)set to capacity before each extract.
- VHD_SDI_SP_INTERFACE now set from the channel-detected interface
(VHD_SDI_CP_INTERFACE) before StartStream, per the same fork — required for
embedded-audio extraction on JOINED SDI streams.
- fc_writer.[ch]: add fc_writer_write_av(); struct/stride bumped to v2.
- video_thread (framecache path) extracts each frame's audio from the SAME
locked JOINED slot and writes BOTH via fc_writer_write_av. Silence fallback
at the source: a frame with no embedded audio gets one frame-interval of
silence so the audio timeline length always equals the video timeline length.
- The separate audio FIFO + audio_thread + apcm ring are retained ONLY for the
legacy (-DLEGACY_FIFO / framecache-unreachable) fallback; on the primary
framecache path the bridge no longer owns the audio FIFO.
capture-manager.js: deltacast/sdi framecache branch now CREATES the audio FIFO
and passes its path to fc_pipe (argv[3]); ffmpeg keeps two raw inputs
(rawvideo pipe:0 + s16le 48k input 1) but both are now fed frame-locked from
the same ring entry. Stale-audio pre-flush retained as harmless safety.
All changes versioned; mismatched binaries refuse to attach (fail safe).
86 lines
3.5 KiB
C
86 lines
3.5 KiB
C
/**
|
|
* fc_client.h — Consumer-side framecache client library.
|
|
*
|
|
* Usage:
|
|
* fc_consumer_t *c = fc_consumer_open("deltacast-zampp3-0");
|
|
* fc_frame_ref_t ref;
|
|
* while (fc_consumer_read(c, &ref, 2000) == FC_OK) {
|
|
* // ref.data valid until next fc_consumer_read call
|
|
* process_frame(ref.data, ref.size, ref.pts_us);
|
|
* }
|
|
* fc_consumer_close(c);
|
|
*
|
|
* Each consumer tracks its own read_cursor — multiple consumers on the same
|
|
* slot are fully independent and never block each other or the writer.
|
|
*
|
|
* If a consumer falls more than ring_depth frames behind the writer its cursor
|
|
* is snapped to the latest frame and FC_DROPPED is returned once.
|
|
*/
|
|
#pragma once
|
|
|
|
#include <stdint.h>
|
|
#include <stddef.h>
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* Return codes */
|
|
#define FC_OK 0 /* valid frame returned in ref */
|
|
#define FC_TIMEOUT 1 /* no new frame within timeout_ms — ref not populated */
|
|
#define FC_DROPPED 2 /* valid frame returned in ref, BUT one or more older
|
|
* frames were skipped first (consumer fell behind).
|
|
* ref IS populated — caller should USE the frame. */
|
|
#define FC_LAPPED 3 /* the copy was overwritten mid-read (writer lapped the
|
|
* consumer during memcpy). ref NOT populated — caller
|
|
* should call fc_consumer_read again. */
|
|
#define FC_ERROR -1
|
|
|
|
typedef struct fc_consumer fc_consumer_t;
|
|
|
|
typedef struct {
|
|
const uint8_t *data; /* pointer to a CONSUMER-OWNED copy of the frame —
|
|
* stable until the next fc_consumer_read() call.
|
|
* (Previously a zero-copy pointer into the shm ring,
|
|
* which the writer could overwrite mid-use when it
|
|
* lapped a slow consumer. We now copy into the
|
|
* consumer's own buffer and re-validate the cursor
|
|
* AFTER the copy, so a lapped frame is discarded
|
|
* rather than streamed corrupt.) */
|
|
uint32_t size; /* video bytes */
|
|
const uint8_t *audio; /* pointer to a CONSUMER-OWNED copy of this frame's
|
|
* embedded audio (s16le stereo 48k), stable until
|
|
* the next fc_consumer_read(). NULL if audio_size 0. */
|
|
uint32_t audio_size; /* audio bytes for this frame (0 = none) */
|
|
uint64_t pts_us; /* presentation timestamp (microseconds) */
|
|
uint64_t wall_us; /* wall clock at write time (microseconds) */
|
|
uint64_t seq; /* write_cursor value for this frame */
|
|
} fc_frame_ref_t;
|
|
|
|
/**
|
|
* Open a consumer handle for the named slot.
|
|
* Polls the slot shm file until it appears (up to wait_ms milliseconds).
|
|
* Returns NULL if slot not found within wait_ms or on error.
|
|
*/
|
|
fc_consumer_t *fc_consumer_open(const char *slot_id, uint64_t wait_ms);
|
|
|
|
/**
|
|
* Read the next frame.
|
|
* Blocks up to timeout_ms waiting for a new frame (via semaphore).
|
|
* Returns FC_OK, FC_TIMEOUT, FC_DROPPED, or FC_ERROR.
|
|
* On FC_OK or FC_DROPPED the ref fields are populated.
|
|
*/
|
|
int fc_consumer_read(fc_consumer_t *c, fc_frame_ref_t *ref, uint64_t timeout_ms);
|
|
|
|
/** Close the consumer handle. Does NOT destroy the slot. */
|
|
void fc_consumer_close(fc_consumer_t *c);
|
|
|
|
/** Current write_cursor of the slot (approximate — no lock). */
|
|
uint64_t fc_consumer_write_cursor(fc_consumer_t *c);
|
|
|
|
/** Frames dropped by this consumer since open. */
|
|
uint64_t fc_consumer_dropped(fc_consumer_t *c);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|