libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
framesync.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_PROAV
13#include <promeki/namespace.h>
14#include <promeki/string.h>
15#include <promeki/frame.h>
16#include <promeki/audiodesc.h>
17#if PROMEKI_ENABLE_SRC
19#endif
22#include <promeki/framecount.h>
23#include <promeki/framenumber.h>
24#include <promeki/framerate.h>
25#include <promeki/duration.h>
26#include <promeki/error.h>
27#include <promeki/result.h>
28#include <promeki/mutex.h>
30#include <promeki/list.h>
32#include <promeki/atomic.h>
33#include <promeki/clock.h>
34
35PROMEKI_NAMESPACE_BEGIN
36
37class SyntheticClock;
38
93class FrameSync {
94 public:
99 enum class InputOverflowPolicy {
104 DropOldest,
105
110 Block,
111 };
112
121 struct PullResult {
123 Frame frame;
124
126 FrameNumber frameIndex{0};
127
130 FrameCount framesRepeated{0};
131
134 FrameCount framesDropped{0};
135
137 Duration error;
138 };
139
141 FrameSync();
142
147 explicit FrameSync(const String &name);
148
150 ~FrameSync();
151
152 FrameSync(const FrameSync &) = delete;
153 FrameSync &operator=(const FrameSync &) = delete;
154 FrameSync(FrameSync &&) = delete;
155 FrameSync &operator=(FrameSync &&) = delete;
156
158 void setName(const String &name) { _name = name; }
159
161 const String &name() const { return _name; }
162
170 void setTargetFrameRate(const FrameRate &fps);
171
173 const FrameRate &targetFrameRate() const { return _targetFrameRate; }
174
182 void setTargetAudioDesc(const AudioDesc &desc);
183
185 const AudioDesc &targetAudioDesc() const { return _targetAudioDesc; }
186
194 void setClock(const Clock::Ptr &clock);
195
197 Clock::Ptr clock() const { return _clock; }
198
209 void setInputQueueCapacity(int capacity);
210
212 int inputQueueCapacity() const { return _queueCapacity; }
213
220 void setInputOverflowPolicy(InputOverflowPolicy policy);
221
223 InputOverflowPolicy inputOverflowPolicy() const { return _overflowPolicy; }
224
232 void reset();
233
244 void reset(int64_t originNs);
245
257 Error pushFrame(const Frame &frame);
258
267 void pushEndOfStream();
268
290 Result<PullResult> pullFrame(bool blockOnEmpty = true);
291
295 void interrupt();
296
307 void clearInterrupt();
308
326 void resetSourceRateEstimator();
327
328 // ---- Stats ----
329
331 FrameCount framesIn() const { return FrameCount(_framesIn.value()); }
332
334 FrameCount framesOut() const { return FrameCount(_framesOut.value()); }
335
337 FrameCount framesRepeated() const { return FrameCount(_framesRepeated.value()); }
338
340 FrameCount framesDropped() const { return FrameCount(_framesDropped.value()); }
341
343 FrameCount overflowDrops() const { return FrameCount(_overflowDrops.value()); }
344
346 Duration accumulatedError() const { return Duration::fromNanoseconds(_accumulatedErrorNs); }
347
349 double currentResampleRatio() const { return _currentResampleRatio; }
350
352 double currentSourceAudioRate() const { return _sourceAudioRateHz; }
353
357 double currentSourceVideoRate() const { return _sourceVideoRateHz; }
358
359 private:
360 struct QueuedFrame {
361 Frame frame;
362 int64_t videoTsNs = 0; // source video timestamp
363 bool hasVideoTs = false;
364 int64_t audioTsNs = 0; // first audio timestamp
365 bool hasAudioTs = false;
366 };
367
368 // Initial setup done under the mutex on demand.
369 void ensureInitialised();
370 void resetLocked(bool setExplicitOrigin, int64_t originNs);
371
372 // Pull-path helpers (all assume _mutex is NOT held).
373 void selectVideo(int64_t sourceTimeNs, int64_t nextSourceTimeNs, VideoPayload::Ptr &outVideo,
374 int64_t &outRepeated, int64_t &outDropped);
375 PcmAudioPayload::Ptr produceAudio(int64_t targetSamples);
376 void updateSourceAudioRate(const PcmAudioPayload &audio, int64_t audioTsNs);
377 void updateSourceVideoRate(int64_t videoTsNs);
378
379 // Periodic debug log.
380 void periodicDebugLog(int64_t nowNs);
381
382 // Configuration.
383 String _name;
384 FrameRate _targetFrameRate;
385 AudioDesc _targetAudioDesc;
386 Clock::Ptr _clock;
387 SyntheticClock *_syntheticClock = nullptr; // cached downcast
388 int _queueCapacity = 8;
389 InputOverflowPolicy _overflowPolicy = InputOverflowPolicy::DropOldest;
390
391 // Shared state (all guarded by _mutex unless noted).
392 mutable Mutex _mutex;
393 WaitCondition _cv;
394
395 // Input queue.
396 List<QueuedFrame> _queue;
397 bool _eos = false;
398 bool _interrupted = false;
399
400 // Timeline state.
401 bool _started = false;
402 bool _explicitOrigin = false;
403 int64_t _originNs = 0;
404 int64_t _framePeriodNs = 0;
405 FrameCount _frameCount{0};
406
407 // Source-origin anchoring — captured on first pushed frame.
408 bool _sourceOriginValid = false;
409 int64_t _sourceVideoOriginNs = 0;
410 int64_t _sourceAudioOriginNs = 0;
411
412 // Held "current" video payload (used for repeats when
413 // no new input qualifies for this pull). Carries the
414 // source payload with its original metadata.
415 VideoPayload::Ptr _heldVideo;
416 int64_t _heldVideoSourceTsNs = 0;
417 bool _hasHeldVideo = false;
418
419 // FrameSyncDrop/FrameSyncRepeat metadata state.
420 // _pendingFrameSyncDrops accumulates input drops across
421 // pulls that emit a repeat — the spec requires
422 // FrameSyncDrop to be zero on repeat outputs, so any
423 // drops that occur while the output is stuck on a
424 // repeat are deferred to the next fresh emit.
425 // _frameSyncRepeatIndex is the position of the current
426 // output within a repeat sequence (0 on fresh emit;
427 // 1, 2, ... on successive repeats).
428 int64_t _pendingFrameSyncDrops = 0;
429 int64_t _frameSyncRepeatIndex = 0;
430
431 // Audio resampler pipeline. Only present when libsamplerate
432 // (PROMEKI_ENABLE_SRC) is compiled in; without it the audio
433 // path falls back to a 1:1 pass-through (no rate conversion).
434#if PROMEKI_ENABLE_SRC
435 AudioResampler::UPtr _resampler;
436#endif
437 List<PcmAudioPayload::Ptr> _audioInput; // pending input audio, FIFO
438 int64_t _audioSamplesConsumed = 0; // of current front audio
439
440 // Rate tracking.
441 double _sourceAudioRateHz = 0.0; // LPF'd source rate
442 double _sourceVideoRateHz = 0.0; // LPF'd source video rate
443 double _currentResampleRatio = 1.0;
444 int64_t _lastAudioTsForRateNs = 0;
445 int64_t _lastAudioTsSamples = 0; // samples covered by last push
446 int64_t _lastVideoTsForRateNs = 0;
447
448 // Error / logging.
449 int64_t _accumulatedErrorNs = 0;
450 int64_t _lastPeriodicLogNs = 0;
451 FrameCount _frameCountAtLastLog{0};
452 FrameCount _lastEmitFrameCount = FrameCount::unknown(); // debug only
453
454 // PLL-style deadline bias. The per-pull measured
455 // wake-error feeds this LPF; the next deadline is
456 // shifted earlier by this amount so any systematic
457 // bias (sleep latency, clock interpolation rate
458 // mismatch, per-pull work time) self-corrects.
459 int64_t _deadlineBiasNs = 0;
460
461 // Atomic stats (readable from any thread without lock).
462 Atomic<int64_t> _framesIn;
463 Atomic<int64_t> _framesOut;
464 Atomic<int64_t> _framesRepeated;
465 Atomic<int64_t> _framesDropped;
466 Atomic<int64_t> _overflowDrops;
467};
468
469PROMEKI_NAMESPACE_END
470
471#endif // PROMEKI_ENABLE_PROAV