114114/* Referring to Chrome's definition of RTP payload types. */
115115#define WHIP_RTP_PAYLOAD_TYPE_H264 106
116116#define WHIP_RTP_PAYLOAD_TYPE_OPUS 111
117+ #define WHIP_RTP_PAYLOAD_TYPE_RTX 105
117118
118119/**
119120 * The STUN message header, which is 20 bytes long, comprises the
150151#define WHIP_SDP_SESSION_ID "4489045141692799359"
151152#define WHIP_SDP_CREATOR_IP "127.0.0.1"
152153
154+ /**
155+ * Retransmission / NACK support
156+ */
157+ #define HISTORY_SIZE_DEFAULT 512
158+
153159/* Calculate the elapsed time from starttime to endtime in milliseconds. */
154160#define ELAPSED (starttime , endtime ) ((int)(endtime - starttime) / 1000)
155161
@@ -193,6 +199,15 @@ enum WHIPState {
193199 WHIP_STATE_FAILED ,
194200};
195201
202+ typedef struct RtpHistoryItem {
203+ /* original RTP seq */
204+ uint16_t seq ;
205+ /* length in bytes */
206+ int size ;
207+ /* malloc-ed copy */
208+ uint8_t * pkt ;
209+ } RtpHistoryItem ;
210+
196211typedef struct WHIPContext {
197212 AVClass * av_class ;
198213
@@ -303,6 +318,15 @@ typedef struct WHIPContext {
303318 /* The certificate and private key used for DTLS handshake. */
304319 char * cert_file ;
305320 char * key_file ;
321+
322+ /* RTX / NACK */
323+ uint8_t rtx_payload_type ;
324+ uint32_t video_rtx_ssrc ;
325+ uint16_t rtx_seq ;
326+ int history_size ;
327+ RtpHistoryItem * history ; /* ring buffer */
328+ int hist_head ;
329+ int enable_nack_rtx ;
306330} WHIPContext ;
307331
308332/**
@@ -615,6 +639,17 @@ static int generate_sdp_offer(AVFormatContext *s)
615639 whip -> audio_payload_type = WHIP_RTP_PAYLOAD_TYPE_OPUS ;
616640 whip -> video_payload_type = WHIP_RTP_PAYLOAD_TYPE_H264 ;
617641
642+ /* RTX / NACK init */
643+ whip -> rtx_payload_type = WHIP_RTP_PAYLOAD_TYPE_RTX ;
644+ whip -> video_rtx_ssrc = av_lfg_get (& whip -> rnd );
645+ whip -> rtx_seq = 0 ;
646+ whip -> hist_head = 0 ;
647+ whip -> history_size = FFMAX (64 , whip -> history_size );
648+ whip -> history = av_calloc (whip -> history_size , sizeof (* whip -> history ));
649+ if (!whip -> history )
650+ return AVERROR (ENOMEM );
651+ whip -> enable_nack_rtx = 1 ;
652+
618653 av_bprintf (& bp , ""
619654 "v=0\r\n"
620655 "o=FFmpeg %s 2 IN IP4 %s\r\n"
@@ -666,7 +701,7 @@ static int generate_sdp_offer(AVFormatContext *s)
666701 }
667702
668703 av_bprintf (& bp , ""
669- "m=video 9 UDP/TLS/RTP/SAVPF %u\r\n"
704+ "m=video 9 UDP/TLS/RTP/SAVPF %u %u \r\n"
670705 "c=IN IP4 0.0.0.0\r\n"
671706 "a=ice-ufrag:%s\r\n"
672707 "a=ice-pwd:%s\r\n"
@@ -679,9 +714,16 @@ static int generate_sdp_offer(AVFormatContext *s)
679714 "a=rtcp-rsize\r\n"
680715 "a=rtpmap:%u %s/90000\r\n"
681716 "a=fmtp:%u level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=%02x%02x%02x\r\n"
717+ "a=rtcp-fb:%u nack\r\n"
718+ "a=rtpmap:%u rtx/90000\r\n"
719+ "a=fmtp:%u apt=%u\r\n"
720+ "a=ssrc:%u cname:FFmpeg\r\n"
721+ "a=ssrc:%u msid:FFmpeg video\r\n"
682722 "a=ssrc:%u cname:FFmpeg\r\n"
683- "a=ssrc:%u msid:FFmpeg video\r\n" ,
723+ "a=ssrc:%u msid:FFmpeg video\r\n"
724+ "a=ssrc-group:FID %u %u\r\n" ,
684725 whip -> video_payload_type ,
726+ whip -> rtx_payload_type ,
685727 whip -> ice_ufrag_local ,
686728 whip -> ice_pwd_local ,
687729 whip -> dtls_fingerprint ,
@@ -691,8 +733,16 @@ static int generate_sdp_offer(AVFormatContext *s)
691733 profile ,
692734 profile_iop ,
693735 level ,
736+ whip -> video_payload_type ,
737+ whip -> rtx_payload_type ,
738+ whip -> rtx_payload_type ,
739+ whip -> video_payload_type ,
740+ whip -> video_ssrc ,
694741 whip -> video_ssrc ,
695- whip -> video_ssrc );
742+ whip -> video_rtx_ssrc ,
743+ whip -> video_rtx_ssrc ,
744+ whip -> video_ssrc ,
745+ whip -> video_rtx_ssrc );
696746 }
697747
698748 if (!av_bprint_is_complete (& bp )) {
@@ -1421,6 +1471,37 @@ static int setup_srtp(AVFormatContext *s)
14211471 return ret ;
14221472}
14231473
1474+
1475+ /**
1476+ * RTX history helpers
1477+ */
1478+ static void rtp_history_store (WHIPContext * whip , const uint8_t * pkt , int size )
1479+ {
1480+ int pos = whip -> hist_head % whip -> history_size ;
1481+ RtpHistoryItem * it = & whip -> history [pos ];
1482+ /* free older entry */
1483+ av_free (it -> pkt );
1484+ it -> pkt = av_malloc (size );
1485+ if (!it -> pkt )
1486+ return ;
1487+
1488+ memcpy (it -> pkt , pkt , size );
1489+ it -> size = size ;
1490+ it -> seq = AV_RB16 (pkt + 2 );
1491+
1492+ whip -> hist_head ++ ;
1493+ }
1494+
1495+ static const RtpHistoryItem * rtp_history_find (const WHIPContext * whip , uint16_t seq )
1496+ {
1497+ for (int i = 0 ; i < whip -> history_size ; i ++ ) {
1498+ const RtpHistoryItem * it = & whip -> history [i ];
1499+ if (it -> pkt && it -> seq == seq )
1500+ return it ;
1501+ }
1502+ return NULL ;
1503+ }
1504+
14241505/**
14251506 * Callback triggered by the RTP muxer when it creates and sends out an RTP packet.
14261507 *
@@ -1457,6 +1538,10 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size)
14571538 return 0 ;
14581539 }
14591540
1541+ /* Store only ORIGINAL video packets (non-RTX, non-RTCP) */
1542+ if (!is_rtcp && is_video )
1543+ rtp_history_store (whip , buf , buf_size );
1544+
14601545 ret = ffurl_write (whip -> udp , whip -> buf , cipher_size );
14611546 if (ret < 0 ) {
14621547 av_log (whip , AV_LOG_ERROR , "WHIP: Failed to write packet=%dB, ret=%d\n" , cipher_size , ret );
@@ -1465,6 +1550,45 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size)
14651550
14661551 return ret ;
14671552}
1553+ /**
1554+ * Build and send a single RTX packet
1555+ */
1556+ static int send_rtx_packet (AVFormatContext * s , const uint8_t * orig_pkt , int orig_size )
1557+ {
1558+ WHIPContext * whip = s -> priv_data ;
1559+ int new_size , cipher_size ;
1560+ /* skip if no RTX PT configured */
1561+ if (!whip -> enable_nack_rtx )
1562+ return 0 ;
1563+
1564+ /* allocate new buffer: header + 2 + payload */
1565+ if (orig_size + 2 > sizeof (whip -> buf ))
1566+ return 0 ;
1567+
1568+ memcpy (whip -> buf , orig_pkt , orig_size );
1569+
1570+ uint8_t * hdr = whip -> buf ;
1571+ uint16_t orig_seq = AV_RB16 (hdr + 2 );
1572+
1573+ /* rewrite header */
1574+ hdr [1 ] = (hdr [1 ] & 0x80 ) | whip -> rtx_payload_type ; /* keep M bit */
1575+ AV_WB16 (hdr + 2 , whip -> rtx_seq ++ );
1576+ AV_WB32 (hdr + 8 , whip -> video_rtx_ssrc );
1577+
1578+ /* shift payload 2 bytes */
1579+ memmove (hdr + 12 + 2 , hdr + 12 , orig_size - 12 );
1580+ AV_WB16 (hdr + 12 , orig_seq );
1581+
1582+ new_size = orig_size + 2 ;
1583+
1584+ /* Encrypt by SRTP and send out. */
1585+ cipher_size = ff_srtp_encrypt (& whip -> srtp_video_send , whip -> buf , new_size , whip -> buf , sizeof (whip -> buf ));
1586+ if (cipher_size <= 0 || cipher_size < new_size ) {
1587+ av_log (whip , AV_LOG_WARNING , "WHIP: Failed to encrypt packet=%dB, cipher=%dB\n" , new_size , cipher_size );
1588+ return 0 ;
1589+ }
1590+ return ffurl_write (whip -> udp , whip -> buf , cipher_size );
1591+ }
14681592
14691593/**
14701594 * Creates dedicated RTP muxers for each stream in the AVFormatContext to build RTP
@@ -1789,6 +1913,38 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt)
17891913 goto end ;
17901914 }
17911915 }
1916+ /* Handle RTCP NACK ( RTPFB / FMT=1 ) */
1917+ if (media_is_rtcp (whip -> buf , ret )) {
1918+ int ptr = 0 ;
1919+ while (ptr + 4 <= ret ) {
1920+ uint8_t pt = whip -> buf [ptr + 1 ];
1921+ int len = (AV_RB16 (& whip -> buf [ptr + 2 ]) + 1 ) * 4 ;
1922+ if (ptr + len > ret ) break ;
1923+
1924+ if (pt == 205 ) { /* RTPFB */
1925+ uint8_t fmt = (whip -> buf [ptr ] & 0x1f );
1926+ if (fmt == 1 && len >= 12 ) {
1927+ uint16_t pid = AV_RB16 (& whip -> buf [ptr + 12 - 4 ]);
1928+ uint16_t blp = AV_RB16 (& whip -> buf [ptr + 14 - 4 ]);
1929+
1930+ /* retransmit pid + any bit set in blp */
1931+ for (int bit = -1 ; bit < 16 ; bit ++ ) {
1932+ uint16_t seq = (bit < 0 ) ? pid : pid + bit + 1 ;
1933+ if (bit >= 0 && !(blp & (1 << bit )))
1934+ continue ;
1935+
1936+ const RtpHistoryItem * it = rtp_history_find (whip , seq );
1937+ if (it )
1938+ send_rtx_packet (s , it -> pkt , it -> size );
1939+
1940+ }
1941+ }
1942+
1943+ }
1944+ ptr += len ;
1945+ }
1946+
1947+ }
17921948 } else if (ret != AVERROR (EAGAIN )) {
17931949 av_log (whip , AV_LOG_ERROR , "WHIP: Failed to read from UDP socket\n" );
17941950 goto end ;
@@ -1891,7 +2047,8 @@ static const AVOption options[] = {
18912047 { "pkt_size" , "The maximum size, in bytes, of RTP packets that send out" , OFFSET (pkt_size ), AV_OPT_TYPE_INT , { .i64 = 1200 }, -1 , INT_MAX , DEC },
18922048 { "authorization" , "The optional Bearer token for WHIP Authorization" , OFFSET (authorization ), AV_OPT_TYPE_STRING , { .str = NULL }, 0 , 0 , DEC },
18932049 { "cert_file" , "The optional certificate file path for DTLS" , OFFSET (cert_file ), AV_OPT_TYPE_STRING , { .str = NULL }, 0 , 0 , DEC },
1894- { "key_file" , "The optional private key file path for DTLS" , OFFSET (key_file ), AV_OPT_TYPE_STRING , { .str = NULL }, 0 , 0 , DEC },
2050+ { "key_file" , "The optional private key file path for DTLS" , OFFSET (key_file ), AV_OPT_TYPE_STRING , { .str = NULL }, 0 , 0 , DEC },
2051+ { "rtx_history" , "Packet history size" , OFFSET (history_size ), AV_OPT_TYPE_INT , { .i64 = HISTORY_SIZE_DEFAULT }, 64 , 2048 , DEC },
18952052 { NULL },
18962053};
18972054
0 commit comments