libpromeki 1.0.0-alpha
PROfessional MEdia toolKIt
 
Loading...
Searching...
No Matches
rtpmediaio.h
Go to the documentation of this file.
1
8#pragma once
9
10
11#include <promeki/config.h>
12#if PROMEKI_ENABLE_NETWORK
13#include <promeki/atomic.h>
14#include <promeki/audiobuffer.h>
15#include <promeki/audiodesc.h>
16#include <promeki/clockdomain.h>
18#include <promeki/duration.h>
19#include <promeki/enums_anc.h>
20#include <promeki/enums_audio.h>
21#include <promeki/enums_jxs.h>
22#include <promeki/enums_rtp.h>
23#include <promeki/eui64.h>
24#include <promeki/macaddress.h>
25#include <promeki/frame.h>
26#include <promeki/histogram.h>
27#include <promeki/imagedesc.h>
29#include <promeki/mutex.h>
30#include <promeki/namespace.h>
31#include <promeki/ntptime.h>
32#include <promeki/pacinggate.h>
34#include <promeki/phcclock.h>
36#include <promeki/st2110tx.h>
37#include <promeki/pixelformat.h>
38#include <promeki/queue.h>
48#include <promeki/rtppacket.h>
53#include <promeki/rtptxthread.h>
55#include <promeki/sdpsession.h>
57#include <promeki/string.h>
58#include <promeki/timestamp.h>
61
62PROMEKI_NAMESPACE_BEGIN
63
64class RtpSession;
65class RtpPayload;
66class UdpSocketTransport;
67class Thread;
68
386class RtpMediaIO : public DedicatedThreadMediaIO {
387 PROMEKI_OBJECT(RtpMediaIO, DedicatedThreadMediaIO)
388 public:
390 static inline const MediaIOStats::ID StatsFramesSent{"FramesSent"};
392 static inline const MediaIOStats::ID StatsPacketsSent{"PacketsSent"};
394 static inline const MediaIOStats::ID StatsBytesSent{"BytesSent"};
396 static inline const MediaIOStats::ID StatsFramesReceived{"FramesReceived"};
398 static inline const MediaIOStats::ID StatsPacketsReceived{"PacketsReceived"};
400 static inline const MediaIOStats::ID StatsBytesReceived{"BytesReceived"};
402 static inline const MediaIOStats::ID StatsTxVideoFrameIntervalUs{"TxVideoFrameIntervalUs"};
404 static inline const MediaIOStats::ID StatsTxVideoSendDurationUs{"TxVideoSendDurationUs"};
406 static inline const MediaIOStats::ID StatsRxVideoPacketIntervalUs{"RxVideoPacketIntervalUs"};
408 static inline const MediaIOStats::ID StatsRxVideoFrameIntervalUs{"RxVideoFrameIntervalUs"};
410 static inline const MediaIOStats::ID StatsRxVideoFrameAssembleUs{"RxVideoFrameAssembleUs"};
422 static inline const MediaIOStats::ID StatsAudioSilencePacketsEmitted{"AudioSilencePacketsEmitted"};
431 static inline const MediaIOStats::ID StatsAudioSilenceSamplesEmitted{"AudioSilenceSamplesEmitted"};
444 static inline const MediaIOStats::ID StatsAudioConformanceLevel{"AudioConformanceLevel"};
453 static inline const MediaIOStats::ID StatsAudioWireFormat{"AudioWireFormat"};
454
455 // ----------------------------------------------------------
456 // Reader-side per-stream RFC 3550 §A counters,
457 // aggregated across every active reader stream
458 // (video + audio + data). All published by
459 // @c executeCmd(MediaIOCommandStats); see
460 // @c devplan/network/rtp-rx.md for the full
461 // ReaderStream::Stats block these correspond to.
462 // ----------------------------------------------------------
463
465 static inline const MediaIOStats::ID StatsRxExtendedHighestSeq{"RxExtendedHighestSeq"};
467 static inline const MediaIOStats::ID StatsRxPacketsExpected{"RxPacketsExpected"};
469 static inline const MediaIOStats::ID StatsRxCumulativeLost{"RxCumulativeLost"};
471 static inline const MediaIOStats::ID StatsRxFractionLost{"RxFractionLost"};
473 static inline const MediaIOStats::ID StatsRxDuplicatePackets{"RxDuplicatePackets"};
475 static inline const MediaIOStats::ID StatsRxReorderedPackets{"RxReorderedPackets"};
477 static inline const MediaIOStats::ID StatsRxInterarrivalJitter{"RxInterarrivalJitter"};
478
480 static inline const MediaIOStats::ID StatsRxSsrcChanges{"RxSsrcChanges"};
481
483 static inline const MediaIOStats::ID StatsRxReorderEmittedInOrder{"RxReorderEmittedInOrder"};
485 static inline const MediaIOStats::ID StatsRxReorderEmittedOnDeadline{"RxReorderEmittedOnDeadline"};
487 static inline const MediaIOStats::ID StatsRxReorderDroppedOverflow{"RxReorderDroppedOverflow"};
489 static inline const MediaIOStats::ID StatsRxReorderDroppedDuplicate{"RxReorderDroppedDuplicate"};
490
492 static inline const MediaIOStats::ID StatsRxVideoQueueDepth{"RxVideoQueueDepth"};
494 static inline const MediaIOStats::ID StatsRxAudioQueueDepth{"RxAudioQueueDepth"};
496 static inline const MediaIOStats::ID StatsRxDataQueueDepth{"RxDataQueueDepth"};
498 static inline const MediaIOStats::ID StatsRxReaderQueueDepth{"RxReaderQueueDepth"};
499
504 static inline const MediaIOStats::ID StatsRxFramesReassembled{"RxFramesReassembled"};
508 static inline const MediaIOStats::ID StatsRxFramesDroppedValidate{"RxFramesDroppedValidate"};
513 static inline const MediaIOStats::ID StatsRxFramesWaitingParamSets{"RxFramesWaitingParamSets"};
519 static inline const MediaIOStats::ID StatsRxFramesDroppedSsrcReset{"RxFramesDroppedSsrcReset"};
520
524 static inline const MediaIOStats::ID StatsRxSrObserved{"RxSrObserved"};
529 static inline const MediaIOStats::ID StatsRxLastSrAgeUs{"RxLastSrAgeUs"};
534 static inline const MediaIOStats::ID StatsRxFirstSrLatencyUs{"RxFirstSrLatencyUs"};
535
542 static inline const MediaIOParamsID ParamSdp{"Sdp"};
543
545 RtpMediaIO(ObjectBase *parent = nullptr);
546
548 ~RtpMediaIO() override;
549
562 uint64_t objectId() const { return _objectId; }
563
577 static String buildDefaultCname(int64_t pid, uint64_t objectId, const String &host);
578
597 static String pickEgressHostForCname(const SocketAddress &destination);
598
633 static String formatAes67Ptime(int packetSamples, int sampleRateHz);
634
654 static int parseAes67PtimeUs(const String &ptimeMs);
655
656 protected:
657 Error executeCmd(MediaIOCommandOpen &cmd) override;
658 Error executeCmd(MediaIOCommandClose &cmd) override;
659 Error executeCmd(MediaIOCommandRead &cmd) override;
660 Error executeCmd(MediaIOCommandWrite &cmd) override;
661 Error getParam(MediaIOParamsID id, Variant &out) override;
662 Error executeCmd(MediaIOCommandStats &cmd) override;
682 Error executeCmd(MediaIOCommandSetClock &cmd) override;
683
684 // Wakes the reader-side executeCmd(Read) loop so close()
685 // can drain a strand parked on _readerQueue.pop().
686 void cancelBlockingWork() override;
687
688 private:
689
690 // describe() / proposeInput overrides intentionally
691 // omitted for v1: each RFC 9134 / RFC 2435 / RFC 4175
692 // payload type has its own accepted-shape constraints
693 // (e.g. JPEG payload = 8-bit YUV422 only; raw payload
694 // = configurable subsampling / bit depth; L16 audio =
695 // 16-bit BE PCM at fixed channel counts). Encoding
696 // those rules per-payload is its own follow-up — for
697 // now the planner's open() fallback inspects the SDP
698 // (when configured) to learn the live shape, and
699 // bridges insert as needed via the configured payload
700 // type's own runtime checks.
701
710 // Per-stream packetizer + TX worker classes. Each
711 // active stream gets one of each: the packetizer
712 // pulls Frames off the strand-side @c PayloadQueue
713 // and emits payload-bytes-only packets onto the
714 // sink-side queue; the TX thread pops the latter,
715 // stamps the full RTP header, and dispatches to
716 // the wire. Packetizer and TX talk only through
717 // their own per-stream @c Queue, so stream-level
718 // jitter (heavy IDR encode, audio cadence) never
719 // bleeds across stream boundaries. Concrete
720 // subclasses live as nested classes inside
721 // @c rtpmediaio.cpp because they need access to
722 // RtpMediaIO state (parameter-set cache, pacing
723 // mode, SDP sprop refresh, etc.).
724 class VideoPacketizerThread;
725 class AudioPacketizerThread;
726 class DataPacketizerThread;
727 class VideoTxThread;
728 class AudioTxThread;
729 class DataTxThread;
730
763 struct Stream {
764 Stream() = default;
765 Stream(Stream &&o) noexcept
766 : transport(o.transport),
767 transportSecondary(o.transportSecondary),
768 session(o.session),
769 payload(o.payload),
770 destination(std::move(o.destination)),
771 destinationSecondary(std::move(o.destinationSecondary)),
772 localAddressSecondary(std::move(o.localAddressSecondary)),
773 interfaceSecondary(std::move(o.interfaceSecondary)),
774 payloadType(o.payloadType),
775 clockRate(o.clockRate),
776 dscp(o.dscp),
777 ssrc(o.ssrc),
778 maxUdp(o.maxUdp),
779 tsDelayUs(o.tsDelayUs),
780 ptpTraceable(o.ptpTraceable),
781 tsMode(o.tsMode),
782 mediaType(std::move(o.mediaType)),
783 rtpmap(std::move(o.rtpmap)),
784 fmtp(std::move(o.fmtp)),
785 mid(std::move(o.mid)),
786 active(o.active),
787 clockDomain(o.clockDomain),
788 tsRefClkMode(o.tsRefClkMode),
789 ptpGrandmaster(o.ptpGrandmaster),
790 ptpDomain(o.ptpDomain),
791 refClockLocalMac(std::move(o.refClockLocalMac)),
792 mediaClkOffset(o.mediaClkOffset),
793 mediaClkMode(o.mediaClkMode),
794 senderType(o.senderType),
795 trOffsetUs(o.trOffsetUs),
796 cmax(o.cmax),
797 jxsPacketMode(o.jxsPacketMode),
798 jxsTransMode(o.jxsTransMode),
799 jxsProfile(o.jxsProfile),
800 jxsLevel(o.jxsLevel),
801 jxsSublevel(o.jxsSublevel),
802 mediaClock(std::move(o.mediaClock)) {
803 // Null pointers on the moved-
804 // from instance so a stray
805 // reset can't double-delete.
806 o.transport = nullptr;
807 o.transportSecondary = nullptr;
808 o.session = nullptr;
809 o.payload = nullptr;
810 }
811 Stream(const Stream &) = delete;
812 Stream &operator=(const Stream &) = delete;
813 Stream &operator=(Stream &&) = delete;
814 UdpSocketTransport *transport = nullptr;
815 UdpSocketTransport *transportSecondary = nullptr;
816 RtpSession *session = nullptr;
817 RtpPayload *payload = nullptr;
818 SocketAddress destination;
819 SocketAddress destinationSecondary;
820 SocketAddress localAddressSecondary;
821 String interfaceSecondary;
822 uint8_t payloadType = 0;
823 uint32_t clockRate = 90000;
824 int dscp = 0;
825 uint32_t ssrc = 0;
826 int maxUdp = 0;
827 int tsDelayUs = 0;
828 bool ptpTraceable = false;
829 RtpTsMode tsMode = RtpTsMode::Samp;
830 String mediaType;
831 String rtpmap;
832 String fmtp;
833 String mid;
834 bool active = false;
835 ClockDomain clockDomain;
836 RtpRefClockMode tsRefClkMode = RtpRefClockMode::None;
837 EUI64 ptpGrandmaster;
838 uint8_t ptpDomain = 0;
839 MacAddress refClockLocalMac;
840 int32_t mediaClkOffset = 0;
858 RtpMediaClkMode mediaClkMode = RtpMediaClkMode::Auto;
859
870 RtpSenderType senderType = RtpSenderType::Auto;
871
877 int trOffsetUs = 0;
878
884 int cmax = 0;
885
896 JxsPacketMode jxsPacketMode = JxsPacketMode::Codestream;
897
906 JxsTransMode jxsTransMode = JxsTransMode::SequentialOnly;
907
911 JxsProfile jxsProfile = JxsProfile::Unspecified;
912
916 JxsLevel jxsLevel = JxsLevel::Unspecified;
917
921 JxsSublevel jxsSublevel = JxsSublevel::Unspecified;
942 RtpMediaClock mediaClock;
943 };
944
959 struct WriterStream : Stream {
960 WriterStream() = default;
961 WriterStream(WriterStream &&o) noexcept
962 : Stream(std::move(o)),
963 packetizer(o.packetizer),
964 tx(o.tx),
965 packetsSent(o.packetsSent.value()),
966 bytesSent(o.bytesSent.value()),
967 senderOctets(o.senderOctets.value()),
968 txFrameInterval(std::move(o.txFrameInterval)),
969 txSendDuration(std::move(o.txSendDuration)),
970 txLastSendStart(o.txLastSendStart),
971 txHasLastSend(o.txHasLastSend) {
972 o.packetizer = nullptr;
973 o.tx = nullptr;
974 }
975 WriterStream(const WriterStream &) = delete;
976 WriterStream &operator=(const WriterStream &) = delete;
977 WriterStream &operator=(WriterStream &&) = delete;
978 RtpPacketizerThread *packetizer = nullptr;
979 RtpTxThread *tx = nullptr;
980 Atomic<int64_t> packetsSent{0};
981 Atomic<int64_t> bytesSent{0};
982 Atomic<int64_t> senderOctets{0};
983 Histogram txFrameInterval;
984 Histogram txSendDuration;
985 TimeStamp txLastSendStart;
986 bool txHasLastSend = false;
987 };
988
1005 struct ReaderStream : Stream {
1006 ReaderStream() = default;
1007 ReaderStream(ReaderStream &&o) noexcept
1008 : Stream(std::move(o)),
1009 packetsReceived(o.packetsReceived.value()),
1010 bytesReceived(o.bytesReceived.value()),
1011 framesReceived(o.framesReceived),
1012 packetsLost(o.packetsLost.value()),
1013 readerImageDesc(std::move(o.readerImageDesc)),
1014 readerAudioDesc(std::move(o.readerAudioDesc)),
1015 reasmTimestamp(o.reasmTimestamp),
1016 reasmHasTimestamp(o.reasmHasTimestamp),
1017 reasmLastSeq(o.reasmLastSeq),
1018 reasmHaveLastSeq(o.reasmHaveLastSeq),
1019 reasmPackets(std::move(o.reasmPackets)),
1020 rxPacketInterval(std::move(o.rxPacketInterval)),
1021 rxFrameInterval(std::move(o.rxFrameInterval)),
1022 rxFrameAssembleTime(std::move(o.rxFrameAssembleTime)),
1023 rxLastPacketTime(o.rxLastPacketTime),
1024 rxLastFrameTime(o.rxLastFrameTime),
1025 rxFrameStartTime(o.rxFrameStartTime),
1026 rxHasLastPacket(o.rxHasLastPacket),
1027 rxHasLastFrame(o.rxHasLastFrame),
1028 rxHasFrameStart(o.rxHasFrameStart),
1029 streamClock(std::move(o.streamClock)),
1030 lastSrArrivedAt(o.lastSrArrivedAt),
1031 hasSr(o.hasSr),
1032 seqTracker(std::move(o.seqTracker)),
1033 reorderBuffer(std::move(o.reorderBuffer)),
1034 reorderQueue(std::move(o.reorderQueue)),
1035 depacketizer(std::move(o.depacketizer)),
1036 ssrcChanges(o.ssrcChanges.value()),
1037 framesReassembled(o.framesReassembled.value()),
1038 framesDroppedValidate(o.framesDroppedValidate.value()),
1039 framesWaitingParamSets(o.framesWaitingParamSets.value()),
1040 framesDroppedSsrcReset(o.framesDroppedSsrcReset.value()) {}
1041 ReaderStream(const ReaderStream &) = delete;
1042 ReaderStream &operator=(const ReaderStream &) = delete;
1043 ReaderStream &operator=(ReaderStream &&) = delete;
1044 Atomic<int64_t> packetsReceived{0};
1045 Atomic<int64_t> bytesReceived{0};
1046 FrameCount framesReceived{0};
1047 Atomic<int64_t> packetsLost{0};
1048 ImageDesc readerImageDesc;
1049 AudioDesc readerAudioDesc;
1050 uint32_t reasmTimestamp = 0;
1051 bool reasmHasTimestamp = false;
1052 uint16_t reasmLastSeq = 0;
1053 bool reasmHaveLastSeq = false;
1054 RtpPacket::List reasmPackets;
1055 Histogram rxPacketInterval;
1056 Histogram rxFrameInterval;
1057 Histogram rxFrameAssembleTime;
1058 TimeStamp rxLastPacketTime;
1059 TimeStamp rxLastFrameTime;
1060 TimeStamp rxFrameStartTime;
1061 bool rxHasLastPacket = false;
1062 bool rxHasLastFrame = false;
1063 bool rxHasFrameStart = false;
1077 RtpStreamClock streamClock;
1085 TimeStamp lastSrArrivedAt;
1088 bool hasSr = false;
1089
1101 UniquePtr<RtpSeqTracker> seqTracker;
1102
1107 UniquePtr<RtpSeqReorderBuffer> reorderBuffer;
1108
1116 UniquePtr<RtpPacket::Queue> reorderQueue;
1117
1126 UniquePtr<RtpDepacketizerThread> depacketizer;
1127
1140 Atomic<uint32_t> resetEpoch{0};
1141
1152 Atomic<int64_t> lastPacketArrivalNs{0};
1153
1161 bool wireSilenceEosSignaled = false;
1162
1173 Atomic<int64_t> ssrcChanges{0};
1174
1184 Atomic<int64_t> framesReassembled{0};
1185
1195 Atomic<int64_t> framesDroppedValidate{0};
1196
1206 Atomic<int64_t> framesWaitingParamSets{0};
1207
1215 Atomic<int64_t> framesDroppedSsrcReset{0};
1216 };
1217
1234 struct VideoStream : WriterStream {
1235 VideoStream() = default;
1236 VideoStream(VideoStream &&o) noexcept
1237 : WriterStream(std::move(o)),
1238 imageDesc(std::move(o.imageDesc)),
1239 cachedSps(std::move(o.cachedSps)),
1240 cachedPps(std::move(o.cachedPps)),
1241 cachedVps(std::move(o.cachedVps)) {}
1242 VideoStream(const VideoStream &) = delete;
1243 VideoStream &operator=(const VideoStream &) = delete;
1244 VideoStream &operator=(VideoStream &&) = delete;
1253 ImageDesc imageDesc;
1254 Buffer cachedSps;
1255 Buffer cachedPps;
1256 Buffer cachedVps;
1257 };
1258
1269 struct DataStream : WriterStream {
1270 DataStream() = default;
1271 DataStream(DataStream &&o) noexcept = default;
1272 DataStream(const DataStream &) = delete;
1273 DataStream &operator=(const DataStream &) = delete;
1274 DataStream &operator=(DataStream &&) = delete;
1275
1281 AncTransmissionModel ancTransmissionModel =
1282 AncTransmissionModel::Unsignalled;
1283
1288 Duration ancTrOffset;
1289
1296 int ancTotalLines = 0;
1297
1300 int ancVpidCode = 0;
1301 };
1302
1321 struct AudioStream : WriterStream {
1322 AudioStream() = default;
1323 AudioStream(AudioStream &&o) noexcept
1324 : WriterStream(std::move(o)),
1325 storageDesc(std::move(o.storageDesc)),
1326 packetSamples(o.packetSamples),
1327 packetBytes(o.packetBytes),
1328 packetTimeUs(o.packetTimeUs),
1329 prerollSamples(o.prerollSamples),
1330 wireFormat(o.wireFormat),
1331 conformanceLevel(o.conformanceLevel),
1332 silencePacketsEmitted(
1333 o.silencePacketsEmitted.value()),
1334 silenceSamplesEmitted(
1335 o.silenceSamplesEmitted.value()) {}
1336 AudioStream(const AudioStream &) = delete;
1337 AudioStream &operator=(const AudioStream &) = delete;
1338 AudioStream &operator=(AudioStream &&) = delete;
1348 AudioDesc storageDesc;
1350 size_t packetSamples = 0;
1357 size_t packetBytes = 0;
1359 int packetTimeUs = 0;
1368 size_t prerollSamples = 0;
1381 AudioWireFormat wireFormat = AudioWireFormat::L16;
1392 AudioConformanceLevel conformanceLevel = AudioConformanceLevel::None;
1401 Atomic<int64_t> silencePacketsEmitted{0};
1406 Atomic<int64_t> silenceSamplesEmitted{0};
1407 };
1408
1419 struct VideoReaderStream : ReaderStream {
1420 VideoReaderStream() = default;
1421 VideoReaderStream(VideoReaderStream &&o) noexcept
1422 : ReaderStream(std::move(o)),
1423 payloadQueue(std::move(o.payloadQueue)) {}
1424 VideoReaderStream(const VideoReaderStream &) = delete;
1425 VideoReaderStream &operator=(const VideoReaderStream &) = delete;
1426 VideoReaderStream &operator=(VideoReaderStream &&) = delete;
1437 UniquePtr<Queue<RxVideoFrame>> payloadQueue;
1438 };
1439
1456 struct AudioReaderStream : ReaderStream {
1457 AudioReaderStream() = default;
1458 AudioReaderStream(AudioReaderStream &&o) noexcept
1459 : ReaderStream(std::move(o)),
1460 fifo(std::move(o.fifo)),
1461 payloadQueue(std::move(o.payloadQueue)) {}
1462 AudioReaderStream(const AudioReaderStream &) = delete;
1463 AudioReaderStream &operator=(const AudioReaderStream &) = delete;
1464 AudioReaderStream &operator=(AudioReaderStream &&) = delete;
1474 AudioBuffer fifo;
1478 UniquePtr<Queue<RxAudioChunk>> payloadQueue;
1479 };
1480
1494 struct DataReaderStream : ReaderStream {
1495 DataReaderStream() = default;
1496 DataReaderStream(DataReaderStream &&o) noexcept
1497 : ReaderStream(std::move(o)),
1498 payloadQueue(std::move(o.payloadQueue)),
1499 ancPayloadQueue(std::move(o.ancPayloadQueue)) {}
1500 DataReaderStream(const DataReaderStream &) = delete;
1501 DataReaderStream &operator=(const DataReaderStream &) = delete;
1502 DataReaderStream &operator=(DataReaderStream &&) = delete;
1508 UniquePtr<Queue<RxDataMessage>> payloadQueue;
1514 UniquePtr<Queue<RxAncFrame>> ancPayloadQueue;
1515 };
1516
1524 static constexpr size_t VideoPayloadQueueDepth = 4;
1525 static constexpr size_t AudioPayloadQueueDepth = 100;
1526 static constexpr size_t DataPayloadQueueDepth = 8;
1527
1528 Error configureVideoStream(const MediaIO::Config &cfg, const MediaDesc &mediaDesc);
1529 Error configureAudioStream(const MediaIO::Config &cfg, const MediaDesc &mediaDesc);
1530 Error configureDataStream(const MediaIO::Config &cfg);
1531
1538 void applyClockReferenceConfig(const MediaIO::Config &cfg);
1539
1540 Error openStream(WriterStream &s, bool enableMulticastLoopback);
1541 Error openReaderStream(ReaderStream &s, bool enableMulticastLoopback);
1542
1577 size_t computeStreamPacketBudget(const ReaderStream &s) const;
1578
1612 Error injectParameterSets(const uint8_t *data, size_t size, Buffer &healed);
1613
1628 bool paceVideoFrame();
1629
1630 // sendVideo / sendAudio / sendData were per-stream
1631 // strand-side helpers in the previous architecture.
1632 // They are gone in Phase 2; their packetization
1633 // logic lives in the per-stream
1634 // @c VideoPacketizerThread / @c AudioPacketizerThread
1635 // / @c DataPacketizerThread (declared as nested
1636 // classes inside @c rtpmediaio.cpp), and their wire-
1637 // pacing logic lives in the matching
1638 // @c VideoTxThread / @c AudioTxThread /
1639 // @c DataTxThread.
1640
1658 void refreshStreamClock(ReaderStream &s);
1659
1679 TimeStamp ntpToSteady(const NtpTime &ntp) const;
1680
1681 // Reader path.
1682 Error applySdp(const SdpSession &sdp, MediaIO::Config &cfg, MediaDesc &mediaDesc);
1683 Error openAllReaders();
1684 void pushReaderFrame(Frame frame);
1685
1686 void buildSdp();
1687 Error writeSdpFile(const String &path);
1688
1694 void resetStreamCommon(Stream &s);
1699 void resetWriterStream(WriterStream &s);
1705 void resetReaderStream(ReaderStream &s);
1706 void resetAll();
1707
1719 RtcpSchedulerContext buildRtcpSchedulerContext();
1720
1721 // Per-mode stream lists — writer-mode populates the
1722 // first three, reader-mode the last three. Reader
1723 // and writer modes are mutually exclusive on a
1724 // single @c RtpMediaIO instance, so only one set is
1725 // populated at any given time. Each kind is a list
1726 // (instead of a single slot) so the routing, SDP
1727 // builder, RTCP scheduler, and per-stream threads
1728 // can iterate uniformly regardless of how many
1729 // streams of each kind a session carries. Today's
1730 // @c configureVideoStream / @c configureAudioStream /
1731 // @c configureDataStream populate one entry per kind
1732 // on the matching list (per @c _readerMode); the
1733 // collective shape is ready for multi-stream config
1734 // that lands later.
1735 List<VideoStream> _videos;
1736 List<AudioStream> _audios;
1737 List<DataStream> _datas;
1738 List<VideoReaderStream> _videoReaders;
1739 List<AudioReaderStream> _audioReaders;
1740 List<DataReaderStream> _dataReaders;
1741
1742 // Transport-global config
1743 SocketAddress _localAddress;
1744 String _sessionName;
1745 String _sessionOrigin;
1746 String _multicastInterface;
1747 int _multicastTTL = 0;
1748 int _recvBufferBytes = 0;
1749 int _sendBufferBytes = 0;
1750 Enum _pacingMode;
1751 Enum _dataFormat;
1752
1753 // ST 2110-10 / RFC 4570 / RFC 5888 session-level state.
1754 String _rtpSourceAddress;
1755 bool _rtpDontFragment = true;
1756
1757 // Runtime
1758 FrameRate _frameRate;
1759 FrameCount _frameCount{0};
1760 FrameCount _framesSent{0};
1761
1762 // Mode
1763 bool _readerMode = false;
1764
1765 // RFC 4175 wire-format PixelFormat. When the input
1766 // pixel format doesn't match what RFC 4175 expects
1767 // on the wire (e.g. YUYV vs UYVY), the
1768 // VideoPacketizerThread calls
1769 // UncompressedVideoPayload::convert() to the wire
1770 // format before packing. Invalid means no conversion
1771 // needed.
1772 PixelFormat _videoWirePixelFormat;
1773
1774 // Reader runtime
1775 Queue<Frame> _readerQueue;
1776 int _readerMaxDepth = 4;
1777 int _readerJitterMs = 50;
1778 int _wireSilenceTimeoutMs = 0;
1779 bool _videoWatchdogEnabled = false;
1780 FrameCount _readerFramesReceived{0};
1781 // Set by cancelBlockingWork() so the executeCmd(Read)
1782 // pop loop can break out of its short-timeout polling
1783 // when MediaIO::close is unwinding the strand. Cleared
1784 // at every Open so a closed-then-reopened RtpMediaIO
1785 // doesn't carry the previous instance's cancellation
1786 // forward.
1787 Atomic<bool> _readCancelled{false};
1788
1805 TimeStamp _readerSteadyAnchor;
1806 NtpTime _readerNtpAnchor;
1807 bool _readerHasAnchor = false;
1814 TimeStamp _openedAt;
1821 UniquePtr<RtpAggregatorThread> _aggregator;
1822
1823 // SDP — the active session description built at
1824 // open time. Reader mode leaves this empty (the
1825 // reader consumes an externally-supplied SDP via
1826 // RtpSdp); writer mode populates it so the
1827 // get(ParamSdp) param and the RtpSaveSdpPath
1828 // export path can serve it.
1829 SdpSession _sdpSession;
1830 String _sdpPath;
1831
1832 // RTCP — one scheduler thread per RtpMediaIO. Wakes
1833 // every @c _rtcpIntervalMs and emits an SR + SDES
1834 // compound on every active writer stream's
1835 // RtpSession plus an RR for every active reader
1836 // stream. The wallclock anchor captured at open
1837 // time is shared across all streams so a single
1838 // receiver-side observation of any stream's first
1839 // SR is sufficient for cross-stream correlation.
1840 // Disabled in reader mode (we are not a sender
1841 // there) and when @c MediaConfig::RtpRtcpEnabled
1842 // is @c false. The scheduler itself lives in
1843 // @c rtcpscheduler.h; this io populates an
1844 // @c RtcpSchedulerContext at @c executeCmd(Open)
1845 // time and hands it to the scheduler's constructor.
1846 UniquePtr<RtcpScheduler> _rtcpScheduler;
1847 bool _rtcpEnabled = true;
1848 int _rtcpIntervalMs = 5000;
1849 String _rtcpCname;
1850
1851 // Process-local monotonic counter that gives every
1852 // RtpMediaIO a distinct id within one process — see
1853 // objectId() and buildDefaultCname(). Starts at 0,
1854 // first instance gets 1.
1855 static Atomic<uint64_t> _nextObjectId;
1856 uint64_t _objectId = 0;
1857
1858 // SR-anchor seeding gate. At @c openStream time
1859 // every active session is anchored with
1860 // @c (NtpTime::now(), 0) so an SR can be emitted
1861 // even before the first frame arrives (better than
1862 // a structurally-invalid SR for late receivers).
1863 // The very first @c executeCmd(Write) refines the
1864 // anchor from the Frame's @ref Frame::captureTime —
1865 // we use a compare-exchange on this flag so even
1866 // future changes that take the Write path off the
1867 // single-threaded strand still seed exactly once
1868 // per opening.
1869 Atomic<bool> _anchorSeeded;
1870
1871 // PHC-backed wallclock source (Phase D2). When
1872 // @ref MediaConfig::RtpPtpDevicePath is non-empty,
1873 // @c executeCmd(Open) opens a @ref PhcClock at that
1874 // path, binds it as the @ref ClockDomain::Ptp
1875 // wallclock provider, and routes SR-anchor seeding
1876 // through the @c setRtpAnchor(ClockDomain, ...)
1877 // overload so the emitted NTP timestamps reflect the
1878 // PTP timescale instead of @c CLOCK_REALTIME. Null
1879 // otherwise; the legacy @c NtpTime::now() path stays
1880 // in effect. Lifetime tied to the open/close cycle
1881 // of this @c RtpMediaIO — destructor calls
1882 // @c unbindDomain before @c PhcClock destructs.
1883 UniquePtr<PhcClock> _phcClock;
1884 // Auto-resolved @c ts-refclk:traceable signal when
1885 // @c _phcClock reports a sub-microsecond
1886 // @c sysOffsetPrecise sample. Combined with
1887 // @c MediaConfig::RtpPtpTraceable in the SDP builder.
1888 bool _phcAutoTraceable = false;
1889
1890 // External writer-mode video pacing — null clock means
1891 // the upstream pump's natural cadence is the only
1892 // timing source. Set via
1893 // executeCmd(MediaIOCommandSetClock); read on the
1894 // dedicated worker thread in executeCmd(Write) (same
1895 // thread as the setter, no synchronization required).
1896 // Audio is not paced separately by the gate — AES67
1897 // packet timing is governed by the per-packet RTP
1898 // timestamp stride that the audio FIFO maintains.
1899 PacingGate _videoGate;
1900
1901 // Out-of-band parameter sets for H.264 / HEVC writers,
1902 // populated lazily by injectParameterSets() the first
1903 // time a complete set passes through the bitstream
1904 // (typically frame 0). Stored as the base64-encoded
1905 // SDP-ready string — for H.264 it's
1906 // @c "<sps-base64>,<pps-base64>" (matches RFC 6184
1907 // @c sprop-parameter-sets); for HEVC it's the three
1908 // separate sprop-vps / -sps / -pps strings (the bool
1909 // tracks whether they have been populated). Empty
1910 // until the encoder's first IDR / IRAP flows through;
1911 // once populated, buildSdp() embeds them in the
1912 // @c a=fmtp line so a receiver that reads the SDP
1913 // (e.g. ffplay) can populate its decoder's extradata
1914 // before the first packet arrives. ffmpeg's H.264 RTP
1915 // demuxer requires this — without it, a receiver that
1916 // joins after the first IDR fails its initial codec
1917 // probe and never recovers, even with in-band
1918 // parameter sets repeated on every IDR.
1919 String _h264SpropParameterSets;
1920 String _h265SpropVps;
1921 String _h265SpropSps;
1922 String _h265SpropPps;
1923
1937 void refreshSdpSprop();
1938};
1939
1944class RtpFactory : public MediaIOFactory {
1945 public:
1946 RtpFactory() = default;
1947
1948 String name() const override { return String("Rtp"); }
1949 String displayName() const override { return String("RTP Stream"); }
1950 String description() const override {
1951 return String("RTP video + audio + metadata reader / writer "
1952 "(MJPEG / JPEG XS / H.264 / H.265 / raw / L16 / JSON)");
1953 }
1954 // An SDP file on disk implies the Rtp reader; writers
1955 // never open via a filesystem path, but extension-based
1956 // dispatch still uses this list so `-i foo.sdp` in
1957 // mediaplay picks the Rtp backend automatically.
1958 StringList extensions() const override {
1959 return {String("sdp")};
1960 }
1961
1962 bool canBeSource() const override { return true; }
1963 bool canBeSink() const override { return true; }
1964
1965 bool canHandleDevice(IODevice *device) const override;
1966 Config::SpecMap configSpecs() const override;
1967
1968 MediaIO *create(const Config &config, ObjectBase *parent = nullptr) const override;
1969};
1970
1971PROMEKI_NAMESPACE_END
1972
1973#endif // PROMEKI_ENABLE_NETWORK