avformat/whip: add RTX support

See https://datatracker.ietf.org/doc/html/rfc4588

Parse sequence number from NACKs, then create RTX
packet and send it.

Signed-off-by: Jack Lau <jacklau1222gm@gmail.com>

avformat/whip: set NACK logs as DEBUG

Signed-off-by: Jack Lau <jacklau1222gm@gmail.com>
This commit is contained in:
Jack Lau
2026-01-20 20:30:18 +08:00
parent e7757d8f2e
commit d7820156f9

View File

@@ -264,6 +264,8 @@ typedef struct WHIPContext {
uint16_t audio_first_seq;
uint16_t video_first_seq;
uint16_t video_rtx_seq;
/* The PT(Payload Type) of stream, generated by the muxer. */
uint8_t audio_payload_type;
uint8_t video_payload_type;
@@ -1880,9 +1882,70 @@ end:
return ret;
}
/**
* See https://datatracker.ietf.org/doc/html/rfc4588#section-4
* Create RTX packet and send it out.
*/
static void handle_rtx_packet(AVFormatContext *s, uint16_t seq)
{
int ret = -1;
WHIPContext *whip = s->priv_data;
uint8_t *ori_buf, rtx_buf[MAX_UDP_BUFFER_SIZE] = { 0 };
int ori_size, rtx_size, cipher_size;
uint16_t ori_seq;
const RtpHistoryItem *it = rtp_history_find(whip, seq);
uint16_t latest_seq = whip->hist[(whip->hist_head - 1 + whip->hist_sz) % whip->hist_sz].seq;
if (!it) {
av_log(whip, AV_LOG_DEBUG,
"RTP history packet seq=%"PRIu16" not found, latest seq=%"PRIu16"\n",
seq, latest_seq);
return;
}
av_log(whip, AV_LOG_DEBUG,
"Found RTP history packet for RTX, seq=%"PRIu16", latest seq=%"PRIu16"\n",
seq, latest_seq);
ori_buf = it->buf;
ori_size = it->size;
/* RTX packet format: header + original seq (2 bytes) + payload */
if (ori_size + 2 > sizeof(rtx_buf)) {
av_log(whip, AV_LOG_WARNING, "RTX packet is too large, size=%d\n", ori_size);
goto end;
}
memcpy(rtx_buf, ori_buf, ori_size);
ori_seq = AV_RB16(rtx_buf + 2);
/* rewrite RTX packet header */
rtx_buf[1] = (rtx_buf[1] & 0x80) | whip->video_rtx_payload_type; /* keep M bit */
AV_WB16(rtx_buf + 2, whip->video_rtx_seq++);
AV_WB32(rtx_buf + 8, whip->video_rtx_ssrc);
/* shift payload 2 bytes to write the original seq number */
memmove(rtx_buf + 12 + 2, rtx_buf + 12, ori_size - 12);
AV_WB16(rtx_buf + 12, ori_seq);
rtx_size = ori_size + 2;
cipher_size = ff_srtp_encrypt(&whip->srtp_video_rtx_send,
rtx_buf, rtx_size,
whip->buf, sizeof(whip->buf));
if (cipher_size <= 0) {
av_log(whip, AV_LOG_WARNING,
"Failed to encrypt RTX packet, size=%d, cipher_size=%d\n",
rtx_size, cipher_size);
goto end;
}
ret = ffurl_write(whip->udp, whip->buf, cipher_size);
end:
if (ret < 0)
av_log(whip, AV_LOG_WARNING, "Failed to send RTX packet, skip this one\n");
}
static void handle_nack_rtx(AVFormatContext *s, int size)
{
int ret;
int ret, i = 0;
WHIPContext *whip = s->priv_data;
uint8_t *buf = NULL;
int rtcp_len, srtcp_len, header_len = 12/*RFC 4585 6.1*/;
@@ -1910,6 +1973,27 @@ static void handle_nack_rtx(AVFormatContext *s, int size)
av_log(whip, AV_LOG_WARNING, "NACK packet decrypt failed: %d\n", ret);
goto error;
}
while (header_len + i + 4 <= rtcp_len) {
/**
* See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1
* Handle multi NACKs in bundled packet.
*/
uint16_t pid = AV_RB16(&buf[12 + i]);
uint16_t blp = AV_RB16(&buf[14 + i]);
handle_rtx_packet(s, pid);
/* retransmit pid + any bit set in blp */
for (int bit = 0; bit < 16; bit++) {
uint16_t seq = pid + bit + 1;
if (!blp)
break;
if (!(blp & (1 << bit)))
continue;
handle_rtx_packet(s, seq);
}
i += 4;
}
goto end;
error:
av_log(whip, AV_LOG_WARNING, "Failed to handle NACK and RTX, Skip...\n");