11#include <promeki/config.h>
12#if PROMEKI_ENABLE_PROAV
35PROMEKI_NAMESPACE_BEGIN
48struct InspectorDiscontinuity {
65 ImageDataDecodeFailure,
82 MissingVideoTimestamp,
85 MissingAudioTimestamp,
92 AudioTimestampReanchor,
98 VideoTimestampReanchor,
105 AudioChannelMismatch,
115 AudioDataDecodeFailure,
125 AudioDataLengthAnomaly,
133 String previousValue;
148struct InspectorEvent {
151 FrameNumber frameIndex{0};
156 bool pictureDecoded =
false;
160 bool pictureDecoderEnabled =
false;
163 uint32_t pictureFrameNumber = 0;
166 uint32_t pictureStreamId = 0;
169 Timecode pictureTimecode;
175 bool ltcDecoded =
false;
177 bool ltcDecoderEnabled =
false;
179 Timecode ltcTimecode;
185 int64_t ltcSampleStart = 0;
194 bool avSyncValid =
false;
238 int64_t avSyncOffsetSamples = 0;
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;
270 bool audioSamplesTestEnabled =
false;
273 bool audioSamplesValid =
false;
280 int64_t audioSamplesThisFrame = 0;
288 bool audioPtsJitterValid =
false;
295 int64_t audioPtsJitterNs = 0;
298 bool videoPtsJitterValid =
false;
302 int64_t videoPtsJitterNs = 0;
309 bool avPtsDriftValid =
false;
316 int64_t avPtsDriftNs = 0;
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;
369 bool audioDataDecoderEnabled =
false;
372 bool audioDataDecoded =
false;
376 List<AudioChannelMarker> audioChannelMarkers;
382 List<InspectorDiscontinuity> discontinuities;
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{};
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;
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;
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;
508 bool hasLastEvent =
false;
510 InspectorEvent lastEvent;
622class InspectorMediaIO :
public SharedThreadMediaIO {
623 PROMEKI_OBJECT(InspectorMediaIO, SharedThreadMediaIO)
626 using EventCallback = Function<void(
const InspectorEvent &)>;
629 InspectorMediaIO(ObjectBase *parent =
nullptr);
632 ~InspectorMediaIO()
override;
645 void setEventCallback(EventCallback cb);
654 InspectorSnapshot snapshot()
const;
657 Error executeCmd(MediaIOCommandOpen &cmd)
override;
658 Error executeCmd(MediaIOCommandClose &cmd)
override;
659 Error executeCmd(MediaIOCommandWrite &cmd)
override;
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();
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;
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;
729 ImageDataDecoder _imageDataDecoder;
730 AudioDataDecoder _audioDataDecoder;
731 LtcDecoder::UPtr _ltcDecoder;
732 bool _decodersInitialized =
false;
742 struct VideoFrameRecord {
746 uint64_t frame48 = 0;
749 uint8_t streamId = 0;
752 int64_t videoWallNs = 0;
758 static constexpr size_t kVideoFrameHistoryMax = 64;
759 List<VideoFrameRecord> _videoFrameHistory;
771 List<AudioDataDecoder::StreamState> _audioDataStreamStates;
785 List<uint8_t> _audioDataChannelActive;
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;
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;
883 bool _hasPreviousVideoTimestamp =
false;
884 bool _hasPreviousAudioTimestamp =
false;
885 int64_t _previousVideoTimestampNs = 0;
886 int64_t _previousAudioTimestampNs = 0;
889 bool _videoPtsAnchored =
false;
890 int64_t _videoPtsAnchorNs = 0;
891 FrameNumber _videoPtsAnchorFrame{0};
896 bool _avBaselineSet =
false;
897 int64_t _avBaselineOffsetNs = 0;
901 bool _hasLastAudioPtsForAv =
false;
902 int64_t _lastAudioPtsForAvNs = 0;
905 FrameNumber _frameIndex{0};
908 mutable Mutex _stateMutex;
909 InspectorSnapshot _stats;
912 double _lastLogWallSec = 0.0;
913 FrameCount _framesSinceLastLog{0};
916 EventCallback _callback;
927 FILE *_statsFile =
nullptr;
928 String _statsFilePath;
929 bool _statsWriteError =
false;
940 FILE *_ancDataFile =
nullptr;
941 String _ancDataFilePath;
942 bool _ancDataWriteError =
false;
947 FrameCount _ancDataFrameIndex{0};
952 AncTranslator _ancTranslator;
959class InspectorFactory :
public MediaIOFactory {
961 InspectorFactory() =
default;
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.");
969 bool canBeSink()
const override {
return true; }
971 Config::SpecMap configSpecs()
const override;
972 MediaIO *create(
const Config &config, ObjectBase *parent =
nullptr)
const override;