Skip to content

Commit 1b0aeea

Browse files
committed
Implement NACK+RTX suppport
1 parent 86490ff commit 1b0aeea

File tree

1 file changed

+161
-4
lines changed

1 file changed

+161
-4
lines changed

libavformat/whip.c

Lines changed: 161 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
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
@@ -150,6 +151,11 @@
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+
196211
typedef 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

Comments
 (0)