libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
ntv2mediaio.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_NTV2
13
14#include <promeki/atomic.h>
15#include <promeki/audiobuffer.h>
16#include <promeki/audiodesc.h>
17#include <promeki/buffer.h>
18#include <promeki/clock.h>
20#include <promeki/frame.h>
21#include <promeki/framerate.h>
22#include <promeki/imagedesc.h>
23#include <promeki/mediadesc.h>
26#include <promeki/mutex.h>
27#include <promeki/namespace.h>
28#include <promeki/ntv2routing.h>
29#include <promeki/pacinggate.h>
30#include <promeki/queue.h>
32#include <promeki/string.h>
33#include <promeki/thread.h>
35
36PROMEKI_NAMESPACE_BEGIN
37
38class Ntv2Device;
39
73class Ntv2MediaIO : public DedicatedThreadMediaIO {
74 PROMEKI_OBJECT(Ntv2MediaIO, DedicatedThreadMediaIO)
75 public:
77 static inline const MediaIOStats::ID StatsFramesReceived{"Ntv2FramesReceived"};
79 static inline const MediaIOStats::ID StatsFramesDropped{"Ntv2FramesDropped"};
81 static inline const MediaIOStats::ID StatsAudioBytesReceived{"Ntv2AudioBytesReceived"};
83 static inline const MediaIOStats::ID StatsFramesPlayed{"Ntv2FramesPlayed"};
88 static inline const MediaIOStats::ID StatsFramesDroppedSink{"Ntv2FramesDroppedSink"};
96 static inline const MediaIOStats::ID StatsAncPacketsReceived{"Ntv2AncPacketsReceived"};
101 static inline const MediaIOStats::ID StatsAncPacketsSent{"Ntv2AncPacketsSent"};
112 static inline const MediaIOStats::ID StatsSignalLoss{"Ntv2SignalLoss"};
118 static inline const MediaIOStats::ID StatsSignalReacquired{"Ntv2SignalReacquired"};
120 static inline const MediaIOStats::ID StatsPacingTicksOnTime{"Ntv2PacingTicksOnTime"};
122 static inline const MediaIOStats::ID StatsPacingTicksLate{"Ntv2PacingTicksLate"};
124 static inline const MediaIOStats::ID StatsPacingTicksSkipped{"Ntv2PacingTicksSkipped"};
126 static inline const MediaIOStats::ID StatsPacingReanchors{"Ntv2PacingReanchors"};
140 static inline const MediaIOStats::ID StatsDeviceLost{"Ntv2DeviceLost"};
141
143 Ntv2MediaIO(ObjectBase *parent = nullptr);
144
146 ~Ntv2MediaIO() override;
147
167 Error proposeInput(const MediaDesc &offered, MediaDesc *preferred) const override;
168
169 protected:
170 Error executeCmd(MediaIOCommandOpen &cmd) override;
171 Error executeCmd(MediaIOCommandClose &cmd) override;
172 Error executeCmd(MediaIOCommandRead &cmd) override;
173 Error executeCmd(MediaIOCommandWrite &cmd) override;
174 Error executeCmd(MediaIOCommandStats &cmd) override;
194 Error executeCmd(MediaIOCommandSetClock &cmd) override;
195 void cancelBlockingWork() override;
196
197 private:
198 Error openSource(const MediaIO::Config &cfg, const MediaDesc &md);
199 void closeSource();
200 void captureLoop();
201
202 Error openSink(const MediaIO::Config &cfg, const MediaDesc &md);
203 void closeSink();
204 void playoutLoop();
205
206 // Apply the per-channel SDI signal routing for the
207 // resolved link standard. Single-link, quad-link 2SI,
208 // quad-link Squares, and 12G single-link all flow
209 // through @ref Ntv2Routing::sdiInputConnections /
210 // @ref Ntv2Routing::sdiOutputConnections — the helper
211 // applies each crosspoint via @c CNTV2Card::Connect and
212 // toggles the TSI / Squares enable bit beforehand when
213 // the standard requires it. Dual-link and SL_24G are
214 // not yet implemented; an empty connection list bubbles
215 // up as @c Error::NotSupported.
216 //
217 // The on-board CSC widgets bridge framebuffer-vs-wire
218 // colour-family mismatches inside the routing fabric
219 // when @c routingCfg.allowOnBoardCsc is @c true and the
220 // wire and framebuffer differ in colour family —
221 // wired up by the caller from
222 // @c MediaConfig::Ntv2DisableOnBoardCsc and the
223 // framebuffer / wire colour models.
224 Error routeSdiInput(const Ntv2Routing::Config &routingCfg);
225 Error routeSdiOutput(const Ntv2Routing::Config &routingCfg);
226
227 // Sink VPID stamping (Phase 6.4): resolves
228 // transfer / colorimetry / luminance / RGB range
229 // from the open-time @ref ImageDesc colour model +
230 // any @c MediaConfig::Ntv2Vpid*Override + per-frame
231 // @c Metadata::Video* keys when present, then writes
232 // the AJA per-channel VPID overrides via
233 // @c CNTV2Card::SetSDIOutVPID*. Called from
234 // @ref openSink with @p md = the resolved sink
235 // ImageDesc; the per-frame nudge path is currently
236 // not wired (HDR signalling is generally a session-
237 // level decision — switching mid-stream wants a
238 // device-wide re-init). Quietly skips when
239 // @c Ntv2VpidEnable is false.
240 Error applySinkVpid(const MediaIO::Config &cfg, const ImageDesc &md);
241
242 // Source VPID detection (Phase 6.4): reads the
243 // incoming SDI VPID via @c CNTV2Card::ReadSDIInVPID
244 // for the channel's primary input port, parses byte
245 // 4 (transfer / colorimetry / RGB range), and
246 // updates @ref _vpidLast* state. Called at open
247 // time (so the first captured Frame already carries
248 // the wire's colour claim) and during the periodic
249 // signal-loss poll (so mid-stream HDR transitions
250 // are picked up). No-op when @c Ntv2VpidEnable is
251 // false or the channel has no SDI input port.
252 void pollSourceVpid();
253
254 // Worker thread classes — capture and playout each have
255 // their own @ref Thread subclass so the library's
256 // OS-name / TID-tracking / clean-join machinery wraps
257 // them instead of bare @c std::thread. The bodies
258 // delegate straight back to captureLoop / playoutLoop
259 // on the owning MediaIO.
260 class CaptureWorker : public Thread {
261 public:
262 CaptureWorker(Ntv2MediaIO *owner) : _owner(owner) {}
263
264 protected:
265 void run() override { _owner->captureLoop(); }
266
267 private:
268 Ntv2MediaIO *_owner;
269 };
270
271 class PlayoutWorker : public Thread {
272 public:
273 PlayoutWorker(Ntv2MediaIO *owner) : _owner(owner) {}
274
275 protected:
276 void run() override { _owner->playoutLoop(); }
277
278 private:
279 Ntv2MediaIO *_owner;
280 };
281
282 // Device handle owned by the registry; non-null between
283 // openSource / openSink and the matching close.
284 // Released on close.
285 Ntv2Device *_device = nullptr;
286
287 // 1-based logical channel index — matches the SDK's
288 // NTV2_CHANNEL1.. numbering minus one for the enum
289 // cast.
290 int _channel = 0;
291
292 // 1-based audio system index, or 0 when audio is
293 // disabled for this channel.
294 int _audioSystem = 0;
295
296 // Direction flag set at openSource / openSink so the
297 // common close path can route to the right teardown.
298 bool _sinkMode = false;
299
300 // Per-port reservation list captured at open so the
301 // close path can hand it back to the device.
302 List<VideoPortRef> _reservedPorts;
303
304 // Capture pipeline state. The queue carries fully-
305 // built Frames (video + optional ANC) so the capture
306 // thread keeps ownership of payload assembly and the
307 // strand's executeCmd(Read) is a pure drain.
308 CaptureWorker _captureWorker{this};
309 Atomic<bool> _stopFlag{false};
310 Atomic<bool> _readCancelled{false};
311 static constexpr int VideoQueueDepth = 2;
312 Queue<Frame> _videoQueue;
313
314 // Playout pipeline state. The strand pushes onto
315 // @ref _writeQueue inside executeCmd(Write); the
316 // playout thread drains and submits via AutoCirculate.
317 // Queue depth is chosen larger than the card's pre-
318 // buffer (3 frames) so steady-state writers don't
319 // immediately back-pressure during the initial fill.
320 // Like the capture path, the queue carries full Frames
321 // so the playout thread can find any ANC payloads
322 // attached alongside the video.
323 PlayoutWorker _playoutWorker{this};
324 static constexpr int WriteQueueDepth = 4;
325 Queue<Frame> _writeQueue;
326
327 // ANC capture / insertion state. Buffers stay
328 // resident for the lifetime of the open so we don't
329 // pay an allocator round-trip on every frame. Empty
330 // when ANC is disabled (Ntv2WithAnc=false or card
331 // missing the custom-ANC engine).
332 bool _ancEnabled = false;
333 Buffer _ancF1Buf;
334 Buffer _ancF2Buf;
335 // Cached f2 start-line for interlaced playout — set
336 // at open from the resolved video standard.
337 uint16_t _f2StartLine = 0;
338
339 // Per-frame VBI poll timeout used by both worker loops'
340 // WaitForInputVerticalInterrupt /
341 // WaitForOutputVerticalInterrupt call. Configurable
342 // via MediaConfig::Ntv2VbiTimeoutMs.
343 int _vbiTimeoutMs = 50;
344
345 // Input-signal poll cadence (in VBIs) used by the
346 // capture worker — every Nth VBI the worker re-queries
347 // GetInputVideoFormat to catch a signal-loss or
348 // re-acquire event. Zero disables the poll. Set from
349 // @c MediaConfig::Ntv2SignalPollIntervalVbi at open.
350 int _signalPollIntervalVbi = 15;
351
352 // Latched signal-presence state. The capture worker
353 // toggles this and emits @c errorOccurredSignal +
354 // increments @c _signalLossCount on every transition
355 // from "present" to "absent"; on the inverse
356 // transition it logs a re-acquire info line +
357 // increments @c _signalReacquiredCount. Treat as
358 // strictly thread-confined to the capture worker —
359 // the stats getters read the counters via @ref Atomic.
360 bool _signalPresent = true;
361
362 // VPID (SMPTE ST 352) state. When @c _vpidEnabled is
363 // true the sink open path writes byte-4 overrides for
364 // transfer / colorimetry / luminance / RGB range, and
365 // the source open path reads input VPID and stamps
366 // the detected colour description on captured Frames.
367 // _vpidLast{Transfer,Colorimetry,Range} are the most
368 // recent decoded values from the source side and are
369 // re-stamped on every captured frame so consumers see
370 // the wire's claim — thread-confined to the capture
371 // worker (they're read/written only there).
372 bool _vpidEnabled = true;
373 int _vpidLastTransfer = 0; // NTV2VPIDTransferCharacteristics
374 int _vpidLastColorimetry = 0; // NTV2VPIDColorimetry
375 int _vpidLastRange = 0; // NTV2VPIDRGBRange
376 bool _vpidLastValid = false;
377
378 // Resolved descriptors / rate populated at open time.
379 ImageDesc _imageDesc;
380 FrameRate _frameRate;
381 AudioDesc _audioDesc;
382
383 // Device sample clock bound to the port group — kept
384 // alive past close in case downstream consumers still
385 // hold a Clock::Ptr to it.
386 Clock::Ptr _sourceClock;
387
388 // Sink-side external pacing (Phase 6). When a non-
389 // null Clock::Ptr is bound through executeCmd(SetClock)
390 // the playout worker waits on @ref _paceGate before
391 // submitting each frame to AutoCirculate. Without an
392 // external clock the gate stays unbound and the
393 // playout worker falls through to the card's self-
394 // pacing (which is the Phase-2 default).
395 PacingGate _paceGate;
396 // Read by the playout worker on every loop iteration
397 // to decide whether to wait on @ref _paceGate. Flipped
398 // by executeCmd(SetClock). Atomic so the playout
399 // thread reads the latest bind state without a mutex.
400 Atomic<bool> _paceClockExternal{false};
401
402 // Telemetry.
403 Atomic<int64_t> _framesReceived{0};
404 Atomic<int64_t> _framesDropped{0};
405 Atomic<int64_t> _audioBytesReceived{0};
406 Atomic<int64_t> _framesPlayed{0};
407 Atomic<int64_t> _framesDroppedSink{0};
408 Atomic<int64_t> _ancPacketsReceived{0};
409 Atomic<int64_t> _ancPacketsSent{0};
410 Atomic<int64_t> _signalLossCount{0};
411 Atomic<int64_t> _signalReacquiredCount{0};
412 Atomic<int64_t> _deviceLostCount{0};
413
414 // Set by either worker on the periodic signal-poll
415 // branch when @c CNTV2Card::IsOpen reports the
416 // device handle has gone away (driver unloaded /
417 // module reload / device hot-unplug). Read by both
418 // worker loops on every iteration so they exit cleanly
419 // instead of looping forever on a dead device handle.
420 // The MediaIO surfaces the event via
421 // @c errorOccurredSignal(Error::DeviceError) exactly
422 // once per loss.
423 Atomic<bool> _deviceLost{false};
424};
425
443class Ntv2Factory : public MediaIOFactory {
444 public:
445 Ntv2Factory() = default;
446
447 String name() const override { return String("Ntv2"); }
448 String displayName() const override { return String("AJA NTV2"); }
449 String description() const override {
450 return String("AJA NTV2 SDI / HDMI capture and playout via libajantv2");
451 }
452
453 bool canBeSource() const override { return true; }
454 bool canBeSink() const override { return true; }
455
456 StringList schemes() const override { return {String("ntv2")}; }
457 bool canHandlePath(const String &path) const override;
458 StringList enumerate() const override;
459 Error urlToConfig(const Url &url, Config *outConfig) const override;
460
461 Config::SpecMap configSpecs() const override;
462
463 MediaIO *create(const Config &config, ObjectBase *parent = nullptr) const override;
464};
465
466PROMEKI_NAMESPACE_END
467
468#endif // PROMEKI_ENABLE_NTV2