libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
inspectormediaio.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 <cstdio>
14#include <functional>
15#include <promeki/function.h>
16#include <promeki/namespace.h>
22#include <promeki/imagedesc.h>
23#include <promeki/audiodesc.h>
25#include <promeki/array.h>
26#include <promeki/audiobuffer.h>
27#include <promeki/buffer.h>
28#include <promeki/framerate.h>
29#include <promeki/timecode.h>
30#include <promeki/mutex.h>
31#include <promeki/list.h>
32#include <promeki/ltcdecoder.h>
33#include <promeki/string.h>
34
35PROMEKI_NAMESPACE_BEGIN
36
48struct InspectorDiscontinuity {
50 enum Kind {
52 FrameNumberJump,
54 StreamIdChange,
57 PictureTcJump,
60 LtcTcJump,
65 ImageDataDecodeFailure,
68 LtcDecodeFailure,
78 SyncOffsetChange,
82 MissingVideoTimestamp,
85 MissingAudioTimestamp,
92 AudioTimestampReanchor,
98 VideoTimestampReanchor,
105 AudioChannelMismatch,
115 AudioDataDecodeFailure,
125 AudioDataLengthAnomaly,
126
129 KindCount
130 };
131
132 Kind kind;
133 String previousValue;
134 String currentValue;
135 String description;
136};
137
148struct InspectorEvent {
151 FrameNumber frameIndex{0};
152
153 // ---- Picture data band decode ----
154
156 bool pictureDecoded = false;
160 bool pictureDecoderEnabled = false;
163 uint32_t pictureFrameNumber = 0;
166 uint32_t pictureStreamId = 0;
169 Timecode pictureTimecode;
170
171 // ---- Audio LTC decode ----
172
175 bool ltcDecoded = false;
177 bool ltcDecoderEnabled = false;
179 Timecode ltcTimecode;
185 int64_t ltcSampleStart = 0;
186
187 // ---- A/V Sync (marker-based, baseline-anchored) ----
188
194 bool avSyncValid = false;
238 int64_t avSyncOffsetSamples = 0;
239
240 // ---- Timestamps (per-essence MediaTimeStamps) ----
241
243 bool timestampTestEnabled = false;
245 bool videoTimestampValid = false;
247 bool audioTimestampValid = false;
250 int64_t videoTimestampNs = 0;
252 int64_t audioTimestampNs = 0;
256 bool videoTimestampDeltaValid = false;
258 bool audioTimestampDeltaValid = false;
262 int64_t videoTimestampDeltaNs = 0;
265 int64_t audioTimestampDeltaNs = 0;
266
267 // ---- Audio sample count ----
268
270 bool audioSamplesTestEnabled = false;
273 bool audioSamplesValid = false;
280 int64_t audioSamplesThisFrame = 0;
281
282 // ---- Per-essence PTS jitter (continuous-stream model) ----
283
288 bool audioPtsJitterValid = false;
295 int64_t audioPtsJitterNs = 0;
298 bool videoPtsJitterValid = false;
302 int64_t videoPtsJitterNs = 0;
303
304 // ---- A/V cross-essence PTS drift ----
305
309 bool avPtsDriftValid = false;
316 int64_t avPtsDriftNs = 0;
317
318 // ---- Per-channel audio data marker decode ----
319
325 struct AudioChannelMarker {
333 bool decoded = false;
337 bool channelMatches = false;
340 uint8_t streamId = 0;
345 uint8_t encodedChannel = 0;
348 uint64_t frameNumber = 0;
354 uint32_t packetsDecoded = 0;
358 uint32_t packetsCorrupt = 0;
364 int64_t streamSampleStart = -1;
365 };
366
369 bool audioDataDecoderEnabled = false;
372 bool audioDataDecoded = false;
376 List<AudioChannelMarker> audioChannelMarkers;
377
378 // ---- Continuity ----
379
382 List<InspectorDiscontinuity> discontinuities;
383};
384
394struct InspectorSnapshot {
396 FrameCount framesProcessed{0};
398 FrameCount framesWithPictureData{0};
400 FrameCount framesWithLtc{0};
402 FrameCount framesWithVideoTimestamp{0};
404 FrameCount framesWithAudioTimestamp{0};
406 int64_t videoDeltaSamples = 0;
408 int64_t audioDeltaSamples = 0;
410 int64_t videoDeltaMinNs = 0;
412 int64_t videoDeltaMaxNs = 0;
414 double videoDeltaAvgNs = 0.0;
416 int64_t audioDeltaMinNs = 0;
418 int64_t audioDeltaMaxNs = 0;
420 double audioDeltaAvgNs = 0.0;
423 double actualFps = 0.0;
425 int64_t audioSamplesFrames = 0;
427 int64_t audioSamplesMin = 0;
429 int64_t audioSamplesMax = 0;
431 double audioSamplesAvg = 0.0;
436 int64_t audioSamplesTotal = 0;
439 int64_t audioSamplesSpanNs = 0;
443 double measuredAudioSampleRate = 0.0;
446 int64_t totalDiscontinuities = 0;
455 Array<int64_t, static_cast<size_t>(InspectorDiscontinuity::KindCount)>
456 discontinuitiesByKind{};
457
458 // ---- Per-essence PTS jitter accumulators (jitter is
459 // measured against the prediction; see
460 // @ref InspectorEvent::audioPtsJitterNs and
461 // @ref InspectorEvent::videoPtsJitterNs) ----
462
464 int64_t audioPtsJitterSamples = 0;
466 int64_t audioPtsJitterMinNs = 0;
468 int64_t audioPtsJitterMaxNs = 0;
470 double audioPtsJitterAvgNs = 0.0;
474 int64_t audioReanchorCount = 0;
475
477 int64_t videoPtsJitterSamples = 0;
479 int64_t videoPtsJitterMinNs = 0;
481 int64_t videoPtsJitterMaxNs = 0;
483 double videoPtsJitterAvgNs = 0.0;
485 int64_t videoReanchorCount = 0;
486
487 // ---- A/V cross-stream PTS drift accumulators ----
488
491 bool avBaselineSet = false;
496 int64_t avBaselineOffsetNs = 0;
498 int64_t avPtsDriftSamples = 0;
500 int64_t avPtsDriftMinNs = 0;
502 int64_t avPtsDriftMaxNs = 0;
504 double avPtsDriftAvgNs = 0.0;
505
508 bool hasLastEvent = false;
510 InspectorEvent lastEvent;
511};
512
622class InspectorMediaIO : public SharedThreadMediaIO {
623 PROMEKI_OBJECT(InspectorMediaIO, SharedThreadMediaIO)
624 public:
626 using EventCallback = Function<void(const InspectorEvent &)>;
627
629 InspectorMediaIO(ObjectBase *parent = nullptr);
630
632 ~InspectorMediaIO() override;
633
645 void setEventCallback(EventCallback cb);
646
654 InspectorSnapshot snapshot() const;
655
656 protected:
657 Error executeCmd(MediaIOCommandOpen &cmd) override;
658 Error executeCmd(MediaIOCommandClose &cmd) override;
659 Error executeCmd(MediaIOCommandWrite &cmd) override;
660
661 private:
662 void decompressImages(Frame &frame);
663 void initDecoders(const Frame &frame);
664 void ingestAudio(const Frame &frame, InspectorEvent &event);
665 void runImageDataCheck(const Frame &frame, InspectorEvent &event);
666 void runAudioDataCheck(const Frame &frame, InspectorEvent &event);
667 void runLtcCheck(InspectorEvent &event);
668 void runAvSyncCheck(const Frame &frame, InspectorEvent &event);
669 void runContinuityCheck(InspectorEvent &event);
670 void runTimestampCheck(const Frame &frame, InspectorEvent &event);
671 void runAudioSamplesCheck(const Frame &frame, InspectorEvent &event);
672 void runCaptureStats(const Frame &frame, const InspectorEvent &event);
673 void runAncDataCheck(const Frame &frame, InspectorEvent &event);
674 Error openCaptureStatsFile(const String &configured);
675 void closeCaptureStatsFile();
676 Error openAncDataFile(const String &configured);
677 void closeAncDataFile();
678 void emitPeriodicLogIfDue();
679 void resetState();
680
681 // ---- Configuration (resolved at open time) ----
682 bool _decodeImageData = false;
683 bool _decodeAudioData = false;
684 bool _decodeLtc = false;
685 bool _checkAvSync = false;
686 bool _checkContinuity = false;
687 bool _checkTimestamp = false;
688 bool _checkAudioSamples = false;
689 bool _checkCaptureStats = false;
690 bool _checkAncData = false;
691 bool _dropFrames = true;
692 int _imageDataRepeatLines = 16;
693 int _ltcChannel = 0;
694 int _syncOffsetToleranceSamples = 0;
695 int64_t _audioPtsToleranceNs = 5'000'000;
696 int64_t _videoPtsToleranceNs = 5'000'000;
697 double _logIntervalSec = 1.0;
698 bool _isOpen = false;
702 double _videoFrameDurationNs = 0.0;
714 FrameRate _frameRate;
724 bool _frameRateConfirmed = false;
725
726 // ---- Decoder state (constructed lazily on the first frame
727 // because we need the actual ImageDesc and AudioDesc to
728 // build them) ----
729 ImageDataDecoder _imageDataDecoder;
730 AudioDataDecoder _audioDataDecoder;
731 LtcDecoder::UPtr _ltcDecoder;
732 bool _decodersInitialized = false;
733
734 // ---- Video frame history (for marker-based A/V sync) ----
735 //
736 // Every successful @ref runImageDataCheck pushes a
737 // record into this ring so the audio-side codeword
738 // decode can look up the matching video frame's
739 // wall-clock anchor by 48-bit frame number. Bounded
740 // at @ref kVideoFrameHistoryMax; older entries are
741 // evicted from the front when the ring fills.
742 struct VideoFrameRecord {
746 uint64_t frame48 = 0;
749 uint8_t streamId = 0;
752 int64_t videoWallNs = 0;
753 };
758 static constexpr size_t kVideoFrameHistoryMax = 64;
759 List<VideoFrameRecord> _videoFrameHistory;
760
761 // ---- AudioData per-channel rolling accumulators ----
762 //
763 // Decoupled from the LTC-side @ref _audioStream so the
764 // two decoders don't fight over the same FIFO and so
765 // the data check can survive bursty audio that splits
766 // a codeword across multiple chunks. One @ref
767 // AudioDataDecoder::StreamState per channel — owns the
768 // per-channel rolling buffer and the absolute stream
769 // sample anchor reported back through @ref
770 // AudioDataDecoder::DecodedItem::streamSampleStart.
771 List<AudioDataDecoder::StreamState> _audioDataStreamStates;
772 // Per-channel "have I ever seen a clean decode on
773 // this channel?" latch. Anomaly discontinuities
774 // (decode failure / length anomaly) only fire on
775 // channels that have produced at least one valid
776 // codeword — without this gate, channels that simply
777 // don't carry PcmMarker (LTC, tones, silence) would
778 // generate spurious anomalies whenever findSync's
779 // 4-transition search happens to land on a samplesPerBit
780 // inside the decoder's bandwidth gate.
781 // Stored as @c uint8_t (0/1) rather than @c bool —
782 // @c List<bool> wraps @c List<bool> whose
783 // proxy references break the lvalue interfaces our
784 // @c List wrapper exposes.
785 List<uint8_t> _audioDataChannelActive;
786
787 // ---- Audio stream (continuous-stream view of incoming
788 // audio that decouples per-frame chunk boundaries from
789 // analyses that want a real stream) ----
790
796 AudioBuffer _audioStream;
798 bool _audioStreamReady = false;
800 double _audioSampleRate = 0.0;
804 bool _audioStreamAnchored = false;
808 int64_t _audioStreamStartNs = 0;
810 int64_t _audioCumulativeIn = 0;
816 int64_t _audioCumulativeAnalyzed = 0;
821 Buffer _audioDrainScratch;
822
823 // ---- Continuity tracking ----
824 bool _hasPreviousPicture = false;
825 uint32_t _previousFrameNumber = 0;
826 uint32_t _previousStreamId = 0;
827 Timecode _previousPictureTc;
838 FrameNumber _previousFrameIndexAtDecode{0};
839 bool _hasPreviousLtc = false;
840 Timecode _previousLtcTc;
849 Timecode::Mode _inferredPictureMode;
852 bool _hasPreviousSyncOffset = false;
853 int64_t _previousSyncOffset = 0;
867 bool _avSyncBaselineSet = false;
868 int64_t _avSyncBaselinePhase = 0;
879 double _samplesPerFrame = 0.0;
880
881 // ---- Timestamp test state (previous-frame anchors
882 // needed to compute frame-to-frame deltas) ----
883 bool _hasPreviousVideoTimestamp = false;
884 bool _hasPreviousAudioTimestamp = false;
885 int64_t _previousVideoTimestampNs = 0;
886 int64_t _previousAudioTimestampNs = 0;
887
888 // ---- Video PTS prediction anchor (for jitter / re-anchor) ----
889 bool _videoPtsAnchored = false;
890 int64_t _videoPtsAnchorNs = 0;
891 FrameNumber _videoPtsAnchorFrame{0};
892
893 // ---- A/V cross-PTS baseline for drift measurement. Set
894 // on the first frame that carries valid PTSs on both
895 // essences. ----
896 bool _avBaselineSet = false;
897 int64_t _avBaselineOffsetNs = 0;
901 bool _hasLastAudioPtsForAv = false;
902 int64_t _lastAudioPtsForAvNs = 0;
903
904 // ---- Per-frame counter ----
905 FrameNumber _frameIndex{0};
906
907 // ---- Stats accumulator ----
908 mutable Mutex _stateMutex;
909 InspectorSnapshot _stats;
910
911 // ---- Periodic log timing (wall time) ----
912 double _lastLogWallSec = 0.0;
913 FrameCount _framesSinceLastLog{0};
914
915 // ---- Per-frame callback ----
916 EventCallback _callback;
917
918 // ---- CaptureStats test output ----
919 //
920 // TSV with one row per frame. @c _statsFile is opened
921 // at @c open() time when the test is enabled and closed
922 // at @c close(). @c _statsFilePath holds the resolved
923 // path (caller-supplied or auto-generated inside
924 // @c Dir::temp()). @c _statsWriteError latches on the
925 // first write failure so the inspector stops retrying
926 // rather than spamming the log.
927 FILE *_statsFile = nullptr;
928 String _statsFilePath;
929 bool _statsWriteError = false;
930
931 // ---- AncData test output ----
932 //
933 // JSON Lines (one self-contained JSON object per frame)
934 // covering every ANC packet on each incoming Frame. Same
935 // lifecycle and error-latching semantics as the
936 // @c CaptureStats file. Each row records the frame
937 // index, per-packet format / transport / line / parsed
938 // payload (via @ref AncTranslator) when a parser is
939 // registered, plus an opaque hex dump fallback when not.
940 FILE *_ancDataFile = nullptr;
941 String _ancDataFilePath;
942 bool _ancDataWriteError = false;
947 FrameCount _ancDataFrameIndex{0};
952 AncTranslator _ancTranslator;
953};
954
959class InspectorFactory : public MediaIOFactory {
960 public:
961 InspectorFactory() = default;
962
963 String name() const override { return String("Inspector"); }
964 String displayName() const override { return String("Frame Inspector"); }
965 String description() const override {
966 return String("Sink that inspects each frame for continuity, sync, and decode "
967 "tests, then drops it.");
968 }
969 bool canBeSink() const override { return true; }
970
971 Config::SpecMap configSpecs() const override;
972 MediaIO *create(const Config &config, ObjectBase *parent = nullptr) const override;
973};
974
975PROMEKI_NAMESPACE_END
976
977#endif // PROMEKI_ENABLE_PROAV