fix(capture): restore audio wallclock (throughput) + remove CPU codec options
- restore -use_wallclock_as_timestamps on audio input: without it ffmpeg's raw s16le reader stalled the graph (NVENC idle at 9%, ~half frames dropped). With it + long-GOP HEVC the encoder runs realtime and A/V length stays locked. - remove all CPU codec options (prores*, dnxh*, libx264/265) from recorder UI; GPU NVENC only (hevc_nvenc / h264_nvenc). 3x L4 cluster, no reason for CPU. - GPU codec defaults in env builders + proxy default h264_nvenc.
This commit is contained in:
parent
0ea22e1e53
commit
07eea02109
4 changed files with 21 additions and 34 deletions
|
|
@ -737,18 +737,20 @@ class CaptureManager {
|
|||
'-video_size', fcSize,
|
||||
'-framerate', fcFps,
|
||||
'-i', 'pipe:0',
|
||||
// Audio FIFO → ffmpeg input 1. The bridge writes EXACTLY the SDI-clock
|
||||
// paced samples (group 0 is the reference, same slot clock as video),
|
||||
// so we DERIVE audio PTS from the sample count at 48 kHz — NOT from
|
||||
// wall-clock arrival. Wall-clock timestamping made the audio stream's
|
||||
// length equal real elapsed time while video length = frame_count/fps;
|
||||
// when the encoder ran a hair under realtime the audio ended up ~1%
|
||||
// longer than video (heard as a pitch-up). Reading the raw stream at
|
||||
// its natural rate keeps both in the same SDI clock domain; the
|
||||
// master-output aresample=async=1 still soaks up any micro-jitter.
|
||||
// Audio FIFO → ffmpeg input 1. Wall-clock timestamps on the audio
|
||||
// input are REQUIRED for throughput: without them ffmpeg's audio
|
||||
// reader has no rate reference on the raw s16le FIFO and the demux
|
||||
// thread stalls the whole graph (NVENC sat idle at 9% while frames
|
||||
// dropped). With wallclock, audio is paced by arrival and the master
|
||||
// -af aresample=async=1 resamples it onto the video CFR timeline so
|
||||
// A/V length stays locked. The residual ~1% drift that wallclock used
|
||||
// to cause was actually the all-intra HEVC dropping frames (video
|
||||
// short); that's fixed by long-GOP HEVC for non-growing records, so
|
||||
// wallclock is safe again and necessary.
|
||||
// The FIFO carries the full 16ch the bridge publishes; channel
|
||||
// SELECTION (keep first N) is applied as an output filter so the
|
||||
// discrete broadcast channels are preserved, not downmixed.
|
||||
'-use_wallclock_as_timestamps', '1',
|
||||
'-thread_queue_size', '512',
|
||||
'-f', 's16le',
|
||||
'-ar', '48000',
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ function validateRecorderConfig(cfg, nodeHasGpu = null) {
|
|||
// NVENC requires a GPU on the target node. Only a hard error when we know the
|
||||
// node lacks one; unknown capability is left as a soft pass.
|
||||
if (GPU_CODECS.includes(codec) && nodeHasGpu === false) {
|
||||
return `Invalid combo: codec ${cfg.recording_codec} requires an NVIDIA GPU, but the target node reports no GPU. Choose a software codec (e.g. prores_hq, dnxhr_hq, h264) or assign a GPU node.`;
|
||||
return `Invalid combo: codec ${cfg.recording_codec} requires an NVIDIA GPU, but the target node reports no GPU. Assign this recorder to a GPU node.`;
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -253,7 +253,7 @@ function buildStandbyEnv(recorder) {
|
|||
`SOURCE_TYPE=${recorder.source_type}`,
|
||||
`SOURCE_CONFIG=${JSON.stringify(sourceConfig)}`,
|
||||
`DEVICE_INDEX=${deviceIndex}`,
|
||||
`RECORDING_CODEC=${recorder.recording_codec || 'prores_hq'}`,
|
||||
`RECORDING_CODEC=${recorder.recording_codec || 'hevc_nvenc'}`,
|
||||
`RECORDING_RESOLUTION=${recorder.recording_resolution || 'native'}`,
|
||||
`RECORDING_VIDEO_BITRATE=${recorder.recording_video_bitrate || ''}`,
|
||||
`RECORDING_FRAMERATE=${recorder.recording_framerate || ''}`,
|
||||
|
|
@ -262,7 +262,7 @@ function buildStandbyEnv(recorder) {
|
|||
`RECORDING_AUDIO_CHANNELS=${recorder.recording_audio_channels ?? 2}`,
|
||||
`RECORDING_CONTAINER=${recorder.recording_container || 'mov'}`,
|
||||
`PROXY_ENABLED=${recorder.proxy_enabled !== false ? 'true' : 'false'}`,
|
||||
`PROXY_CODEC=${recorder.proxy_codec || 'h264'}`,
|
||||
`PROXY_CODEC=${recorder.proxy_codec || 'h264_nvenc'}`,
|
||||
`PROXY_RESOLUTION=${recorder.proxy_resolution || '1920x1080'}`,
|
||||
`PROXY_VIDEO_BITRATE=${recorder.proxy_video_bitrate || '2M'}`,
|
||||
`PROXY_FRAMERATE=${recorder.proxy_framerate || ''}`,
|
||||
|
|
@ -468,9 +468,9 @@ router.post('/', async (req, res, next) => {
|
|||
recording_audio_codec: 'pcm_s24le',
|
||||
recording_audio_channels: 2,
|
||||
recording_container: 'mov',
|
||||
proxy_enabled: true,
|
||||
proxy_codec: 'h264',
|
||||
proxy_resolution: '1920x1080',
|
||||
proxy_enabled: true,
|
||||
proxy_codec: 'h264_nvenc',
|
||||
proxy_resolution: '1920x1080',
|
||||
proxy_video_bitrate: '2M',
|
||||
proxy_audio_codec: 'aac',
|
||||
proxy_audio_bitrate: '128k',
|
||||
|
|
@ -793,7 +793,7 @@ router.post('/:id/start', requireRecorderEdit, async (req, res, next) => {
|
|||
`DEVICE_INDEX=${deviceIndex}`,
|
||||
|
||||
// Recording codec controls
|
||||
`RECORDING_CODEC=${recorder.recording_codec || 'prores_hq'}`,
|
||||
`RECORDING_CODEC=${recorder.recording_codec || 'hevc_nvenc'}`,
|
||||
`RECORDING_RESOLUTION=${recorder.recording_resolution || 'native'}`,
|
||||
`RECORDING_VIDEO_BITRATE=${recorder.recording_video_bitrate || ''}`,
|
||||
`RECORDING_FRAMERATE=${recorder.recording_framerate || ''}`,
|
||||
|
|
@ -804,7 +804,7 @@ router.post('/:id/start', requireRecorderEdit, async (req, res, next) => {
|
|||
|
||||
// Proxy codec controls
|
||||
`PROXY_ENABLED=${recorder.proxy_enabled !== false ? 'true' : 'false'}`,
|
||||
`PROXY_CODEC=${recorder.proxy_codec || 'h264'}`,
|
||||
`PROXY_CODEC=${recorder.proxy_codec || 'h264_nvenc'}`,
|
||||
`PROXY_RESOLUTION=${recorder.proxy_resolution || '1920x1080'}`,
|
||||
`PROXY_VIDEO_BITRATE=${recorder.proxy_video_bitrate || '2M'}`,
|
||||
`PROXY_FRAMERATE=${recorder.proxy_framerate || ''}`,
|
||||
|
|
|
|||
|
|
@ -418,7 +418,6 @@ function NewRecorderModal({ open, onClose }) {
|
|||
{[
|
||||
{ id: 'hevc', label: 'HEVC Master (MOV)', codec: 'hevc_nvenc', bitrate: '25' },
|
||||
{ id: 'h264', label: 'H.264 Proxy-friendly (MP4)', codec: 'h264_nvenc', bitrate: '25' },
|
||||
{ id: 'dnxhr', label: 'DNxHR HQ (MOV)', codec: 'dnxhr_hq', bitrate: '145' },
|
||||
].map(p => (
|
||||
<button key={p.id}
|
||||
className={`btn ghost sm${recCodec === p.codec ? ' active' : ''}`}
|
||||
|
|
@ -437,15 +436,8 @@ function NewRecorderModal({ open, onClose }) {
|
|||
onChange={e => setRecCodec(e.target.value)} disabled={growingOn}
|
||||
style={{ appearance: 'auto', opacity: growingOn ? 0.6 : 1 }}>
|
||||
{growingOn && <option value="h264_growing">XDCAM HD422 (MXF OP1a, growing)</option>}
|
||||
<option value="hevc_nvenc">All-Intra HEVC (NVENC, GPU, growing)</option>
|
||||
<option value="hevc_nvenc">HEVC (NVENC, GPU)</option>
|
||||
<option value="h264_nvenc">H.264 (NVENC, GPU)</option>
|
||||
<option value="prores_hq">ProRes 422 HQ (4:2:2, CPU)</option>
|
||||
<option value="prores">ProRes 422</option>
|
||||
<option value="prores_lt">ProRes 422 LT</option>
|
||||
<option value="prores_proxy">ProRes 422 Proxy</option>
|
||||
<option value="dnxhr_hq">DNxHR HQ</option>
|
||||
<option value="libx264">H.264 (x264, CPU)</option>
|
||||
<option value="libx265">H.265 (x265, CPU)</option>
|
||||
</select>
|
||||
</div>
|
||||
{showBitrate ? (
|
||||
|
|
|
|||
|
|
@ -688,15 +688,8 @@ function RecorderConfigModal({ recorder, onClose, onSaved }) {
|
|||
onChange={e => setCodec(e.target.value)} disabled={growing || isRec}
|
||||
style={{ appearance: 'auto', opacity: growing ? 0.6 : 1 }}>
|
||||
{growing && <option value="h264_growing">XDCAM HD422 (MXF OP1a, growing)</option>}
|
||||
<option value="hevc_nvenc">All-Intra HEVC (NVENC, GPU, growing)</option>
|
||||
<option value="hevc_nvenc">HEVC (NVENC, GPU)</option>
|
||||
<option value="h264_nvenc">H.264 (NVENC, GPU)</option>
|
||||
<option value="prores_hq">ProRes 422 HQ (4:2:2, CPU)</option>
|
||||
<option value="prores">ProRes 422</option>
|
||||
<option value="prores_lt">ProRes 422 LT</option>
|
||||
<option value="prores_proxy">ProRes 422 Proxy</option>
|
||||
<option value="dnxhr_hq">DNxHR HQ</option>
|
||||
<option value="libx264">H.264 (x264, CPU)</option>
|
||||
<option value="libx265">H.265 (x265, CPU)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue