fc_pipe now muxes video + that frame's embedded audio into ONE streaming AVI
container on stdout, so ffmpeg reads a SINGLE input (-f avi -i pipe:0) instead
of a raw video pipe + a separate live audio FIFO. The two-live-pipe design
deadlocked ffmpeg (it stalled forever probing input 0); a single interleaved
stream removes that failure mode entirely.
fc_pipe.c:
- New AVI mode (argv[3] == "--avi"/"avi"). Writes RIFF('AVI ') + LIST(hdrl)
{ avih + strl(vids: strh+BITMAPINFOHEADER UYVY 16bpp) + strl(auds:
strh+WAVEFORMATEX PCM s16le 48k 2ch) } + LIST(movi) once, then per ring
entry a '00dc' video chunk followed by a '01wb' audio chunk (LE sizes,
even-pad). RIFF/movi sizes use the 0x7FFFFFFF streaming sentinel (pipe is
unseekable); dwFlags has NO index bits. Frame-coupled by construction: both
chunks come from the SAME ring entry in one read-loop iteration.
- dwScale/dwRate = fps_den/fps_num (video) and nBlockAlign/nAvgBytesPerSec
(audio). If a frame has audio_size 0, emits one frame-interval of silence
(round(48000*fps_den/fps_num) samples) so the audio timeline tracks video
and ffmpeg never starves on the audio demuxer.
- Legacy raw video-only mode retained when no avi flag is given. The old
split-stdout/audio-FIFO threaded path is removed (it was the deadlock).
fc_client.{h,c}:
- Add fc_consumer_info() / fc_stream_info_t to expose the slot header's
width/height/fps/audio params to fc_pipe for the AVI header.
capture-manager.js (_buildInputArgs deltacast/sdi framecache branch):
- Spawn fc_pipe with "--avi" (no audio FIFO). Remove the mkfifo + audio-FIFO
creation for this path.
- inputArgs: ONE input -thread_queue_size 512 -f avi [AUDIO_OFFSET_MS] -i pipe:0
(was: -f rawvideo -i pipe:0 AND -f s16le -ar 48000 -ac 2 -i <fifo>).
- audioInputIndex 0, audioFifo null. Growing VC-3/HEVC builders already map
[0:v] and audioMap 0🅰️0?; with one AVI input that resolves to 0:v / 0:a.
Validated on zampp3 against the LIVE deltacast-0-0 slot: fc_pipe --avi | ffmpeg
-f avi -i pipe:0 -> dnxhd/pcm_s24le MXF gives 360 video / 360 audio packets in
6.006s (no stall at 2 frames). A synthetic 1 kHz sine slot through the same
path yields mean_volume -9 dB / max -6 dB, proving the muxer carries real audio
end-to-end (the live SDI input currently carries no embedded audio, so the
bridge's silence fallback reads -91 dB — upstream of the muxer).
103 lines
4.2 KiB
C
103 lines
4.2 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);
|
|
|
|
/* Stream format info read from the slot header (set at slot creation by the
|
|
* bridge). Used by fc_pipe to emit a correct AVI/container header. */
|
|
typedef struct {
|
|
uint32_t width;
|
|
uint32_t height;
|
|
uint32_t fps_num;
|
|
uint32_t fps_den;
|
|
uint32_t pixel_format; /* FC_PIX_UYVY422 */
|
|
uint32_t frame_size; /* video bytes per frame (width*height*2 for UYVY422) */
|
|
uint32_t audio_rate; /* 48000 */
|
|
uint32_t audio_channels; /* 2 */
|
|
uint32_t audio_sample_bytes; /* 2 (s16le) */
|
|
} fc_stream_info_t;
|
|
|
|
/** Fill *info from the slot header. Returns 0 on success, -1 on error. */
|
|
int fc_consumer_info(fc_consumer_t *c, fc_stream_info_t *info);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|