* [FFmpeg-devel] [PATCH v2 0/6] avformat/whip: Add NACK and RTX support (depends on ignore_ipv6 patchset)
@ 2025-07-02 11:59 Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 1/6] avformat/whip: add whip_flags ignore_ipv6 to skip IPv6 ICE candidates Jack Lau
` (5 more replies)
0 siblings, 6 replies; 9+ messages in thread
From: Jack Lau @ 2025-07-02 11:59 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Jack Lau
Version 2 of https://ffmpeg.org/pipermail/ffmpeg-devel/2025-July/346052.html
This patchset fix the following issues:
* every option break into two new lines (now every of them is only one line)
* lack check HAVE_STRUCT_SOCKADDR_IN6 (add this check)
* change the variable name (pkt -> buf) to avoid misunderstanding
* add rtx_history_size option which can set the size of rtp packet history buffer
Jack Lau (5):
avformat/whip: add whip_flags ignore_ipv6 to skip IPv6 ICE candidates
avformat/whip: fix typos
avformat/whip: fix H264 profile_iop bit map for SDP
avformat/whip: implement NACK and RTX suppport
avformat/whip: reindent whip options
winlin (1):
WHIP: X509 cert serial number should be positive.
libavformat/tls_openssl.c | 3 +-
libavformat/whip.c | 410 ++++++++++++++++++++++++++++----------
2 files changed, 303 insertions(+), 110 deletions(-)
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 9+ messages in thread
* [FFmpeg-devel] [PATCH v2 1/6] avformat/whip: add whip_flags ignore_ipv6 to skip IPv6 ICE candidates
2025-07-02 11:59 [FFmpeg-devel] [PATCH v2 0/6] avformat/whip: Add NACK and RTX support (depends on ignore_ipv6 patchset) Jack Lau
@ 2025-07-02 11:59 ` Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 2/6] avformat/whip: fix typos Jack Lau
` (4 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Jack Lau @ 2025-07-02 11:59 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Jack Lau
mark this ignore_ipv6 flag could ignore any IPv6 ICE candidate,
preventing “No route to host” errors on devices without IPv6 connectivity.
Signed-off-by: Jack Lau <jacklau1222@qq.com>
---
libavformat/whip.c | 17 ++++++++++++++++-
1 file changed, 16 insertions(+), 1 deletion(-)
diff --git a/libavformat/whip.c b/libavformat/whip.c
index 5fdbd6949d..42a6b2ec70 100644
--- a/libavformat/whip.c
+++ b/libavformat/whip.c
@@ -193,9 +193,14 @@ enum WHIPState {
WHIP_STATE_FAILED,
};
+typedef enum WHIPFlags {
+ WHIP_FLAG_IGNORE_IPV6 = (1 << 0) // Ignore ipv6 candidate
+} WHIPFlags;
+
typedef struct WHIPContext {
AVClass *av_class;
+ uint32_t flags; // enum WHIPFlags
/* The state of the RTC connection. */
enum WHIPState state;
/* The callback return value for DTLS. */
@@ -879,6 +884,9 @@ static int parse_answer(AVFormatContext *s)
if (ptr && av_stristr(ptr, "host")) {
char protocol[17], host[129];
int priority, port;
+#if HAVE_STRUCT_SOCKADDR_IN6
+ struct in6_addr addr6;
+#endif
ret = sscanf(ptr, "%16s %d %128s %d typ host", protocol, &priority, host, &port);
if (ret != 4) {
av_log(whip, AV_LOG_ERROR, "WHIP: Failed %d to parse line %d %s from %s\n",
@@ -886,7 +894,12 @@ static int parse_answer(AVFormatContext *s)
ret = AVERROR(EIO);
goto end;
}
-
+#if HAVE_STRUCT_SOCKADDR_IN6
+ if (whip->flags & WHIP_FLAG_IGNORE_IPV6 && inet_pton(AF_INET6, host, &addr6) == 1) {
+ av_log(whip, AV_LOG_DEBUG, "Ignoring IPv6 ICE candidates %s, line %d %s \n", host, i, line);
+ continue;
+ }
+#endif
if (av_strcasecmp(protocol, "udp")) {
av_log(whip, AV_LOG_ERROR, "WHIP: Protocol %s is not supported by RTC, choose udp, line %d %s of %s\n",
protocol, i, line, whip->sdp_answer);
@@ -1898,6 +1911,8 @@ static const AVOption options[] = {
{ "authorization", "The optional Bearer token for WHIP Authorization", OFFSET(authorization), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
{ "cert_file", "The optional certificate file path for DTLS", OFFSET(cert_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
{ "key_file", "The optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
+ { "whip_flags", "Set flags affecting WHIP connection behavior", OFFSET(flags), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, 0, UINT_MAX, ENC, .unit = "flags" },
+ { "ignore_ipv6", "(Optional) Ignore any IPv6 ICE candidate", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_IGNORE_IPV6 }, 0, UINT_MAX, ENC, .unit = "flags" },
{ NULL },
};
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 9+ messages in thread
* [FFmpeg-devel] [PATCH v2 2/6] avformat/whip: fix typos
2025-07-02 11:59 [FFmpeg-devel] [PATCH v2 0/6] avformat/whip: Add NACK and RTX support (depends on ignore_ipv6 patchset) Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 1/6] avformat/whip: add whip_flags ignore_ipv6 to skip IPv6 ICE candidates Jack Lau
@ 2025-07-02 11:59 ` Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 3/6] avformat/whip: fix H264 profile_iop bit map for SDP Jack Lau
` (3 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Jack Lau @ 2025-07-02 11:59 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Jack Lau
Remove redundant "WHIP: " prefix in log context
since it already add whip context.
Fix grammers in whip options descriptions
Signed-off-by: Jack Lau <jacklau1222@qq.com>
---
libavformat/whip.c | 152 ++++++++++++++++++++++-----------------------
1 file changed, 76 insertions(+), 76 deletions(-)
diff --git a/libavformat/whip.c b/libavformat/whip.c
index 42a6b2ec70..932119a1c7 100644
--- a/libavformat/whip.c
+++ b/libavformat/whip.c
@@ -364,14 +364,14 @@ static int dtls_context_on_state(AVFormatContext *s, const char* type, const cha
if (state == DTLS_STATE_CLOSED) {
whip->dtls_closed = 1;
- av_log(whip, AV_LOG_VERBOSE, "WHIP: DTLS session closed, type=%s, desc=%s, elapsed=%dms\n",
+ av_log(whip, AV_LOG_VERBOSE, "DTLS session closed, type=%s, desc=%s, elapsed=%dms\n",
type ? type : "", desc ? desc : "", ELAPSED(whip->whip_starttime, av_gettime()));
goto error;
}
if (state == DTLS_STATE_FAILED) {
whip->state = WHIP_STATE_FAILED;
- av_log(whip, AV_LOG_ERROR, "WHIP: DTLS session failed, type=%s, desc=%s\n",
+ av_log(whip, AV_LOG_ERROR, "DTLS session failed, type=%s, desc=%s\n",
type ? type : "", desc ? desc : "");
whip->dtls_ret = AVERROR(EIO);
goto error;
@@ -380,7 +380,7 @@ static int dtls_context_on_state(AVFormatContext *s, const char* type, const cha
if (state == DTLS_STATE_FINISHED && whip->state < WHIP_STATE_DTLS_FINISHED) {
whip->state = WHIP_STATE_DTLS_FINISHED;
whip->whip_dtls_time = av_gettime();
- av_log(whip, AV_LOG_VERBOSE, "WHIP: DTLS handshake is done, elapsed=%dms\n",
+ av_log(whip, AV_LOG_VERBOSE, "DTLS handshake is done, elapsed=%dms\n",
ELAPSED(whip->whip_starttime, av_gettime()));
return ret;
}
@@ -409,7 +409,7 @@ static av_cold int initialize(AVFormatContext *s)
ret = certificate_key_init(s);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to init certificate and key\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to init certificate and key\n");
return ret;
}
@@ -418,13 +418,13 @@ static av_cold int initialize(AVFormatContext *s)
av_lfg_init(&whip->rnd, seed);
if (whip->pkt_size < ideal_pkt_size)
- av_log(whip, AV_LOG_WARNING, "WHIP: pkt_size=%d(<%d) is too small, may cause packet loss\n",
+ av_log(whip, AV_LOG_WARNING, "pkt_size=%d(<%d) is too small, may cause packet loss\n",
whip->pkt_size, ideal_pkt_size);
if (whip->state < WHIP_STATE_INIT)
whip->state = WHIP_STATE_INIT;
whip->whip_init_time = av_gettime();
- av_log(whip, AV_LOG_VERBOSE, "WHIP: Init state=%d, handshake_timeout=%dms, pkt_size=%d, seed=%d, elapsed=%dms\n",
+ av_log(whip, AV_LOG_VERBOSE, "Init state=%d, handshake_timeout=%dms, pkt_size=%d, seed=%d, elapsed=%dms\n",
whip->state, whip->handshake_timeout, whip->pkt_size, seed, ELAPSED(whip->whip_starttime, av_gettime()));
return 0;
@@ -457,7 +457,7 @@ static int parse_profile_level(AVFormatContext *s, AVCodecParameters *par)
return ret;
if (!par->extradata || par->extradata_size <= 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Unable to parse profile from empty extradata=%p, size=%d\n",
+ av_log(whip, AV_LOG_ERROR, "Unable to parse profile from empty extradata=%p, size=%d\n",
par->extradata, par->extradata_size);
return AVERROR(EINVAL);
}
@@ -471,12 +471,12 @@ static int parse_profile_level(AVFormatContext *s, AVCodecParameters *par)
if ((state & 0x1f) == H264_NAL_SPS) {
ret = ff_avc_decode_sps(sps, r, r1 - r);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to decode SPS, state=%x, size=%d\n",
+ av_log(whip, AV_LOG_ERROR, "Failed to decode SPS, state=%x, size=%d\n",
state, (int)(r1 - r));
return ret;
}
- av_log(whip, AV_LOG_VERBOSE, "WHIP: Parse profile=%d, level=%d from SPS\n",
+ av_log(whip, AV_LOG_VERBOSE, "Parse profile=%d, level=%d from SPS\n",
sps->profile_idc, sps->level_idc);
par->profile = sps->profile_idc;
par->level = sps->level_idc;
@@ -520,62 +520,62 @@ static int parse_codec(AVFormatContext *s)
switch (par->codec_type) {
case AVMEDIA_TYPE_VIDEO:
if (whip->video_par) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Only one video stream is supported by RTC\n");
+ av_log(whip, AV_LOG_ERROR, "Only one video stream is supported by RTC\n");
return AVERROR(EINVAL);
}
whip->video_par = par;
if (par->codec_id != AV_CODEC_ID_H264) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Unsupported video codec %s by RTC, choose h264\n",
+ av_log(whip, AV_LOG_ERROR, "Unsupported video codec %s by RTC, choose h264\n",
desc ? desc->name : "unknown");
return AVERROR_PATCHWELCOME;
}
if (par->video_delay > 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Unsupported B frames by RTC\n");
+ av_log(whip, AV_LOG_ERROR, "Unsupported B frames by RTC\n");
return AVERROR_PATCHWELCOME;
}
if ((ret = parse_profile_level(s, par)) < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to parse SPS/PPS from extradata\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to parse SPS/PPS from extradata\n");
return AVERROR(EINVAL);
}
if (par->profile == AV_PROFILE_UNKNOWN) {
- av_log(whip, AV_LOG_WARNING, "WHIP: No profile found in extradata, consider baseline\n");
+ av_log(whip, AV_LOG_WARNING, "No profile found in extradata, consider baseline\n");
return AVERROR(EINVAL);
}
if (par->level == AV_LEVEL_UNKNOWN) {
- av_log(whip, AV_LOG_WARNING, "WHIP: No level found in extradata, consider 3.1\n");
+ av_log(whip, AV_LOG_WARNING, "No level found in extradata, consider 3.1\n");
return AVERROR(EINVAL);
}
break;
case AVMEDIA_TYPE_AUDIO:
if (whip->audio_par) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Only one audio stream is supported by RTC\n");
+ av_log(whip, AV_LOG_ERROR, "Only one audio stream is supported by RTC\n");
return AVERROR(EINVAL);
}
whip->audio_par = par;
if (par->codec_id != AV_CODEC_ID_OPUS) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Unsupported audio codec %s by RTC, choose opus\n",
+ av_log(whip, AV_LOG_ERROR, "Unsupported audio codec %s by RTC, choose opus\n",
desc ? desc->name : "unknown");
return AVERROR_PATCHWELCOME;
}
if (par->ch_layout.nb_channels != 2) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Unsupported audio channels %d by RTC, choose stereo\n",
+ av_log(whip, AV_LOG_ERROR, "Unsupported audio channels %d by RTC, choose stereo\n",
par->ch_layout.nb_channels);
return AVERROR_PATCHWELCOME;
}
if (par->sample_rate != 48000) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Unsupported audio sample rate %d by RTC, choose 48000\n", par->sample_rate);
+ av_log(whip, AV_LOG_ERROR, "Unsupported audio sample rate %d by RTC, choose 48000\n", par->sample_rate);
return AVERROR_PATCHWELCOME;
}
break;
default:
- av_log(whip, AV_LOG_ERROR, "WHIP: Codec type '%s' for stream %d is not supported by RTC\n",
+ av_log(whip, AV_LOG_ERROR, "Codec type '%s' for stream %d is not supported by RTC\n",
av_get_media_type_string(par->codec_type), i);
return AVERROR_PATCHWELCOME;
}
@@ -603,7 +603,7 @@ static int generate_sdp_offer(AVFormatContext *s)
av_bprint_init(&bp, 1, MAX_SDP_SIZE);
if (whip->sdp_offer) {
- av_log(whip, AV_LOG_ERROR, "WHIP: SDP offer is already set\n");
+ av_log(whip, AV_LOG_ERROR, "SDP offer is already set\n");
ret = AVERROR(EINVAL);
goto end;
}
@@ -701,7 +701,7 @@ static int generate_sdp_offer(AVFormatContext *s)
}
if (!av_bprint_is_complete(&bp)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Offer exceed max %d, %s\n", MAX_SDP_SIZE, bp.str);
+ av_log(whip, AV_LOG_ERROR, "Offer exceed max %d, %s\n", MAX_SDP_SIZE, bp.str);
ret = AVERROR(EIO);
goto end;
}
@@ -715,7 +715,7 @@ static int generate_sdp_offer(AVFormatContext *s)
if (whip->state < WHIP_STATE_OFFER)
whip->state = WHIP_STATE_OFFER;
whip->whip_offer_time = av_gettime();
- av_log(whip, AV_LOG_VERBOSE, "WHIP: Generated state=%d, offer: %s\n", whip->state, whip->sdp_offer);
+ av_log(whip, AV_LOG_VERBOSE, "Generated state=%d, offer: %s\n", whip->state, whip->sdp_offer);
end:
av_bprint_finalize(&bp, NULL);
@@ -750,7 +750,7 @@ static int exchange_sdp(AVFormatContext *s)
}
if (!whip->sdp_offer || !strlen(whip->sdp_offer)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: No offer to exchange\n");
+ av_log(whip, AV_LOG_ERROR, "No offer to exchange\n");
ret = AVERROR(EINVAL);
goto end;
}
@@ -759,7 +759,7 @@ static int exchange_sdp(AVFormatContext *s)
if (whip->authorization)
ret += snprintf(buf + ret, sizeof(buf) - ret, "Authorization: Bearer %s\r\n", whip->authorization);
if (ret <= 0 || ret >= sizeof(buf)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to generate headers, size=%d, %s\n", ret, buf);
+ av_log(whip, AV_LOG_ERROR, "Failed to generate headers, size=%d, %s\n", ret, buf);
ret = AVERROR(EINVAL);
goto end;
}
@@ -778,7 +778,7 @@ static int exchange_sdp(AVFormatContext *s)
ret = ffurl_open_whitelist(&whip_uc, s->url, AVIO_FLAG_READ_WRITE, &s->interrupt_callback,
&opts, s->protocol_whitelist, s->protocol_blacklist, NULL);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to request url=%s, offer: %s\n", s->url, whip->sdp_offer);
+ av_log(whip, AV_LOG_ERROR, "Failed to request url=%s, offer: %s\n", s->url, whip->sdp_offer);
goto end;
}
@@ -798,21 +798,21 @@ static int exchange_sdp(AVFormatContext *s)
break;
}
if (ret <= 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to read response from url=%s, offer is %s, answer is %s\n",
+ av_log(whip, AV_LOG_ERROR, "Failed to read response from url=%s, offer is %s, answer is %s\n",
s->url, whip->sdp_offer, whip->sdp_answer);
goto end;
}
av_bprintf(&bp, "%.*s", ret, buf);
if (!av_bprint_is_complete(&bp)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Answer exceed max size %d, %.*s, %s\n", MAX_SDP_SIZE, ret, buf, bp.str);
+ av_log(whip, AV_LOG_ERROR, "Answer exceed max size %d, %.*s, %s\n", MAX_SDP_SIZE, ret, buf, bp.str);
ret = AVERROR(EIO);
goto end;
}
}
if (!av_strstart(bp.str, "v=", NULL)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Invalid answer: %s\n", bp.str);
+ av_log(whip, AV_LOG_ERROR, "Invalid answer: %s\n", bp.str);
ret = AVERROR(EINVAL);
goto end;
}
@@ -825,7 +825,7 @@ static int exchange_sdp(AVFormatContext *s)
if (whip->state < WHIP_STATE_ANSWER)
whip->state = WHIP_STATE_ANSWER;
- av_log(whip, AV_LOG_VERBOSE, "WHIP: Got state=%d, answer: %s\n", whip->state, whip->sdp_answer);
+ av_log(whip, AV_LOG_VERBOSE, "Got state=%d, answer: %s\n", whip->state, whip->sdp_answer);
end:
ffurl_closep(&whip_uc);
@@ -856,7 +856,7 @@ static int parse_answer(AVFormatContext *s)
WHIPContext *whip = s->priv_data;
if (!whip->sdp_answer || !strlen(whip->sdp_answer)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: No answer to parse\n");
+ av_log(whip, AV_LOG_ERROR, "No answer to parse\n");
ret = AVERROR(EINVAL);
goto end;
}
@@ -889,7 +889,7 @@ static int parse_answer(AVFormatContext *s)
#endif
ret = sscanf(ptr, "%16s %d %128s %d typ host", protocol, &priority, host, &port);
if (ret != 4) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed %d to parse line %d %s from %s\n",
+ av_log(whip, AV_LOG_ERROR, "Failed %d to parse line %d %s from %s\n",
ret, i, line, whip->sdp_answer);
ret = AVERROR(EIO);
goto end;
@@ -901,7 +901,7 @@ static int parse_answer(AVFormatContext *s)
}
#endif
if (av_strcasecmp(protocol, "udp")) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Protocol %s is not supported by RTC, choose udp, line %d %s of %s\n",
+ av_log(whip, AV_LOG_ERROR, "Protocol %s is not supported by RTC, choose udp, line %d %s of %s\n",
protocol, i, line, whip->sdp_answer);
ret = AVERROR(EIO);
goto end;
@@ -919,19 +919,19 @@ static int parse_answer(AVFormatContext *s)
}
if (!whip->ice_pwd_remote || !strlen(whip->ice_pwd_remote)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: No remote ice pwd parsed from %s\n", whip->sdp_answer);
+ av_log(whip, AV_LOG_ERROR, "No remote ice pwd parsed from %s\n", whip->sdp_answer);
ret = AVERROR(EINVAL);
goto end;
}
if (!whip->ice_ufrag_remote || !strlen(whip->ice_ufrag_remote)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: No remote ice ufrag parsed from %s\n", whip->sdp_answer);
+ av_log(whip, AV_LOG_ERROR, "No remote ice ufrag parsed from %s\n", whip->sdp_answer);
ret = AVERROR(EINVAL);
goto end;
}
if (!whip->ice_protocol || !whip->ice_host || !whip->ice_port) {
- av_log(whip, AV_LOG_ERROR, "WHIP: No ice candidate parsed from %s\n", whip->sdp_answer);
+ av_log(whip, AV_LOG_ERROR, "No ice candidate parsed from %s\n", whip->sdp_answer);
ret = AVERROR(EINVAL);
goto end;
}
@@ -939,7 +939,7 @@ static int parse_answer(AVFormatContext *s)
if (whip->state < WHIP_STATE_NEGOTIATED)
whip->state = WHIP_STATE_NEGOTIATED;
whip->whip_answer_time = av_gettime();
- av_log(whip, AV_LOG_VERBOSE, "WHIP: SDP state=%d, offer=%luB, answer=%luB, ufrag=%s, pwd=%luB, transport=%s://%s:%d, elapsed=%dms\n",
+ av_log(whip, AV_LOG_VERBOSE, "SDP state=%d, offer=%luB, answer=%luB, ufrag=%s, pwd=%luB, transport=%s://%s:%d, elapsed=%dms\n",
whip->state, strlen(whip->sdp_offer), strlen(whip->sdp_answer), whip->ice_ufrag_remote, strlen(whip->ice_pwd_remote),
whip->ice_protocol, whip->ice_host, whip->ice_port, ELAPSED(whip->whip_starttime, av_gettime()));
@@ -990,7 +990,7 @@ static int ice_create_request(AVFormatContext *s, uint8_t *buf, int buf_size, in
/* The username is the concatenation of the two ICE ufrag */
ret = snprintf(username, sizeof(username), "%s:%s", whip->ice_ufrag_remote, whip->ice_ufrag_local);
if (ret <= 0 || ret >= sizeof(username)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to build username %s:%s, max=%lu, ret=%d\n",
+ av_log(whip, AV_LOG_ERROR, "Failed to build username %s:%s, max=%lu, ret=%d\n",
whip->ice_ufrag_remote, whip->ice_ufrag_local, sizeof(username), ret);
ret = AVERROR(EIO);
goto end;
@@ -1059,7 +1059,7 @@ static int ice_create_response(AVFormatContext *s, char *tid, int tid_size, uint
WHIPContext *whip = s->priv_data;
if (tid_size != 12) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Invalid transaction ID size. Expected 12, got %d\n", tid_size);
+ av_log(whip, AV_LOG_ERROR, "Invalid transaction ID size. Expected 12, got %d\n", tid_size);
return AVERROR(EINVAL);
}
@@ -1162,7 +1162,7 @@ static int ice_handle_binding_request(AVFormatContext *s, char *buf, int buf_siz
return ret;
if (buf_size < ICE_STUN_HEADER_SIZE) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Invalid STUN message, expected at least %d, got %d\n",
+ av_log(whip, AV_LOG_ERROR, "Invalid STUN message, expected at least %d, got %d\n",
ICE_STUN_HEADER_SIZE, buf_size);
return AVERROR(EINVAL);
}
@@ -1173,13 +1173,13 @@ static int ice_handle_binding_request(AVFormatContext *s, char *buf, int buf_siz
/* Build the STUN binding response. */
ret = ice_create_response(s, tid, sizeof(tid), whip->buf, sizeof(whip->buf), &size);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to create STUN binding response, size=%d\n", size);
+ av_log(whip, AV_LOG_ERROR, "Failed to create STUN binding response, size=%d\n", size);
return ret;
}
ret = ffurl_write(whip->udp, whip->buf, size);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to send STUN binding response, size=%d\n", size);
+ av_log(whip, AV_LOG_ERROR, "Failed to send STUN binding response, size=%d\n", size);
return ret;
}
@@ -1209,7 +1209,7 @@ static int udp_connect(AVFormatContext *s)
ret = ffurl_open_whitelist(&whip->udp, url, AVIO_FLAG_WRITE, &s->interrupt_callback,
&opts, s->protocol_whitelist, s->protocol_blacklist, NULL);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to connect udp://%s:%d\n", whip->ice_host, whip->ice_port);
+ av_log(whip, AV_LOG_ERROR, "Failed to connect udp://%s:%d\n", whip->ice_host, whip->ice_port);
goto end;
}
@@ -1220,7 +1220,7 @@ static int udp_connect(AVFormatContext *s)
if (whip->state < WHIP_STATE_UDP_CONNECTED)
whip->state = WHIP_STATE_UDP_CONNECTED;
whip->whip_udp_time = av_gettime();
- av_log(whip, AV_LOG_VERBOSE, "WHIP: UDP state=%d, elapsed=%dms, connected to udp://%s:%d\n",
+ av_log(whip, AV_LOG_VERBOSE, "UDP state=%d, elapsed=%dms, connected to udp://%s:%d\n",
whip->state, ELAPSED(whip->whip_starttime, av_gettime()), whip->ice_host, whip->ice_port);
end:
@@ -1238,7 +1238,7 @@ static int ice_dtls_handshake(AVFormatContext *s)
char buf[256], *cert_buf = NULL, *key_buf = NULL;
if (whip->state < WHIP_STATE_UDP_CONNECTED || !whip->udp) {
- av_log(whip, AV_LOG_ERROR, "WHIP: UDP not connected, state=%d, udp=%p\n", whip->state, whip->udp);
+ av_log(whip, AV_LOG_ERROR, "UDP not connected, state=%d, udp=%p\n", whip->state, whip->udp);
return AVERROR(EINVAL);
}
@@ -1247,13 +1247,13 @@ static int ice_dtls_handshake(AVFormatContext *s)
/* Build the STUN binding request. */
ret = ice_create_request(s, whip->buf, sizeof(whip->buf), &size);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to create STUN binding request, size=%d\n", size);
+ av_log(whip, AV_LOG_ERROR, "Failed to create STUN binding request, size=%d\n", size);
goto end;
}
ret = ffurl_write(whip->udp, whip->buf, size);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to send STUN binding request, size=%d\n", size);
+ av_log(whip, AV_LOG_ERROR, "Failed to send STUN binding request, size=%d\n", size);
goto end;
}
@@ -1268,7 +1268,7 @@ next_packet:
now = av_gettime();
if (now - starttime >= whip->handshake_timeout * 1000) {
- av_log(whip, AV_LOG_ERROR, "WHIP: DTLS handshake timeout=%dms, cost=%dms, elapsed=%dms, state=%d\n",
+ av_log(whip, AV_LOG_ERROR, "DTLS handshake timeout=%dms, cost=%dms, elapsed=%dms, state=%d\n",
whip->handshake_timeout, ELAPSED(starttime, now), ELAPSED(whip->whip_starttime, now), whip->state);
ret = AVERROR(ETIMEDOUT);
goto end;
@@ -1283,7 +1283,7 @@ next_packet:
av_usleep(5 * 1000);
continue;
}
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to read message\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to read message\n");
goto end;
}
@@ -1296,7 +1296,7 @@ next_packet:
if (whip->state < WHIP_STATE_ICE_CONNECTED) {
whip->state = WHIP_STATE_ICE_CONNECTED;
whip->whip_ice_time = av_gettime();
- av_log(whip, AV_LOG_VERBOSE, "WHIP: ICE STUN ok, state=%d, url=udp://%s:%d, location=%s, username=%s:%s, res=%dB, elapsed=%dms\n",
+ av_log(whip, AV_LOG_VERBOSE, "ICE STUN ok, state=%d, url=udp://%s:%d, location=%s, username=%s:%s, res=%dB, elapsed=%dms\n",
whip->state, whip->ice_host, whip->ice_port, whip->whip_resource_url ? whip->whip_resource_url : "",
whip->ice_ufrag_remote, whip->ice_ufrag_local, ret, ELAPSED(whip->whip_starttime, av_gettime()));
@@ -1396,20 +1396,20 @@ static int setup_srtp(AVFormatContext *s)
/* Setup SRTP context for outgoing packets */
if (!av_base64_encode(buf, sizeof(buf), send_key, sizeof(send_key))) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to encode send key\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to encode send key\n");
ret = AVERROR(EIO);
goto end;
}
ret = ff_srtp_set_crypto(&whip->srtp_audio_send, suite, buf);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to set crypto for audio send\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to set crypto for audio send\n");
goto end;
}
ret = ff_srtp_set_crypto(&whip->srtp_video_send, suite, buf);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to set crypto for video send\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to set crypto for video send\n");
goto end;
}
@@ -1421,21 +1421,21 @@ static int setup_srtp(AVFormatContext *s)
/* Setup SRTP context for incoming packets */
if (!av_base64_encode(buf, sizeof(buf), recv_key, sizeof(recv_key))) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to encode recv key\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to encode recv key\n");
ret = AVERROR(EIO);
goto end;
}
ret = ff_srtp_set_crypto(&whip->srtp_recv, suite, buf);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to set crypto for recv\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to set crypto for recv\n");
goto end;
}
if (whip->state < WHIP_STATE_SRTP_FINISHED)
whip->state = WHIP_STATE_SRTP_FINISHED;
whip->whip_srtp_time = av_gettime();
- av_log(whip, AV_LOG_VERBOSE, "WHIP: SRTP setup done, state=%d, suite=%s, key=%luB, elapsed=%dms\n",
+ av_log(whip, AV_LOG_VERBOSE, "SRTP setup done, state=%d, suite=%s, key=%luB, elapsed=%dms\n",
whip->state, suite, sizeof(send_key), ELAPSED(whip->whip_starttime, av_gettime()));
end:
@@ -1474,13 +1474,13 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size)
/* Encrypt by SRTP and send out. */
cipher_size = ff_srtp_encrypt(srtp, buf, buf_size, whip->buf, sizeof(whip->buf));
if (cipher_size <= 0 || cipher_size < buf_size) {
- av_log(whip, AV_LOG_WARNING, "WHIP: Failed to encrypt packet=%dB, cipher=%dB\n", buf_size, cipher_size);
+ av_log(whip, AV_LOG_WARNING, "Failed to encrypt packet=%dB, cipher=%dB\n", buf_size, cipher_size);
return 0;
}
ret = ffurl_write(whip->udp, whip->buf, cipher_size);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to write packet=%dB, ret=%d\n", cipher_size, ret);
+ av_log(whip, AV_LOG_ERROR, "Failed to write packet=%dB, ret=%d\n", cipher_size, ret);
return ret;
}
@@ -1509,7 +1509,7 @@ static int create_rtp_muxer(AVFormatContext *s)
const AVOutputFormat *rtp_format = av_guess_format("rtp", NULL, NULL);
if (!rtp_format) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to guess rtp muxer\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to guess rtp muxer\n");
ret = AVERROR(ENOSYS);
goto end;
}
@@ -1577,7 +1577,7 @@ static int create_rtp_muxer(AVFormatContext *s)
ret = avformat_write_header(rtp_ctx, &opts);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to write rtp header\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to write rtp header\n");
goto end;
}
@@ -1589,7 +1589,7 @@ static int create_rtp_muxer(AVFormatContext *s)
if (whip->state < WHIP_STATE_READY)
whip->state = WHIP_STATE_READY;
- av_log(whip, AV_LOG_INFO, "WHIP: Muxer state=%d, buffer_size=%d, max_packet_size=%d, "
+ av_log(whip, AV_LOG_INFO, "Muxer state=%d, buffer_size=%d, max_packet_size=%d, "
"elapsed=%dms(init:%d,offer:%d,answer:%d,udp:%d,ice:%d,dtls:%d,srtp:%d)\n",
whip->state, buffer_size, max_packet_size, ELAPSED(whip->whip_starttime, av_gettime()),
ELAPSED(whip->whip_starttime, whip->whip_init_time),
@@ -1631,7 +1631,7 @@ static int dispose_session(AVFormatContext *s)
if (whip->authorization)
ret += snprintf(buf + ret, sizeof(buf) - ret, "Authorization: Bearer %s\r\n", whip->authorization);
if (ret <= 0 || ret >= sizeof(buf)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to generate headers, size=%d, %s\n", ret, buf);
+ av_log(whip, AV_LOG_ERROR, "Failed to generate headers, size=%d, %s\n", ret, buf);
ret = AVERROR(EINVAL);
goto end;
}
@@ -1642,7 +1642,7 @@ static int dispose_session(AVFormatContext *s)
ret = ffurl_open_whitelist(&whip_uc, whip->whip_resource_url, AVIO_FLAG_READ_WRITE, &s->interrupt_callback,
&opts, s->protocol_whitelist, s->protocol_blacklist, NULL);
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to DELETE url=%s\n", whip->whip_resource_url);
+ av_log(whip, AV_LOG_ERROR, "Failed to DELETE url=%s\n", whip->whip_resource_url);
goto end;
}
@@ -1653,12 +1653,12 @@ static int dispose_session(AVFormatContext *s)
break;
}
if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to read response from DELETE url=%s\n", whip->whip_resource_url);
+ av_log(whip, AV_LOG_ERROR, "Failed to read response from DELETE url=%s\n", whip->whip_resource_url);
goto end;
}
}
- av_log(whip, AV_LOG_INFO, "WHIP: Dispose resource %s ok\n", whip->whip_resource_url);
+ av_log(whip, AV_LOG_INFO, "Dispose resource %s ok\n", whip->whip_resource_url);
end:
ffurl_closep(&whip_uc);
@@ -1804,18 +1804,18 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt)
if (ret > 0) {
if (is_dtls_packet(whip->buf, ret)) {
if ((ret = ffurl_write(whip->dtls_uc, whip->buf, ret)) < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to handle DTLS message\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to handle DTLS message\n");
goto end;
}
}
} else if (ret != AVERROR(EAGAIN)) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to read from UDP socket\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to read from UDP socket\n");
goto end;
}
if (whip->h264_annexb_insert_sps_pps && st->codecpar->codec_id == AV_CODEC_ID_H264) {
if ((ret = h264_annexb_insert_sps_pps(s, pkt)) < 0) {
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to insert SPS/PPS before IDR\n");
+ av_log(whip, AV_LOG_ERROR, "Failed to insert SPS/PPS before IDR\n");
goto end;
}
}
@@ -1823,10 +1823,10 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt)
ret = ff_write_chained(rtp_ctx, 0, pkt, s, 0);
if (ret < 0) {
if (ret == AVERROR(EINVAL)) {
- av_log(whip, AV_LOG_WARNING, "WHIP: Ignore failed to write packet=%dB, ret=%d\n", pkt->size, ret);
+ av_log(whip, AV_LOG_WARNING, "Ignore failed to write packet=%dB, ret=%d\n", pkt->size, ret);
ret = 0;
} else
- av_log(whip, AV_LOG_ERROR, "WHIP: Failed to write packet, size=%d\n", pkt->size);
+ av_log(whip, AV_LOG_ERROR, "Failed to write packet, size=%d\n", pkt->size);
goto end;
}
@@ -1847,7 +1847,7 @@ static av_cold void whip_deinit(AVFormatContext *s)
ret = dispose_session(s);
if (ret < 0)
- av_log(whip, AV_LOG_WARNING, "WHIP: Failed to dispose resource, ret=%d\n", ret);
+ av_log(whip, AV_LOG_WARNING, "Failed to dispose resource, ret=%d\n", ret);
for (i = 0; i < s->nb_streams; i++) {
AVFormatContext* rtp_ctx = s->streams[i]->priv_data;
@@ -1894,7 +1894,7 @@ static int whip_check_bitstream(AVFormatContext *s, AVStream *st, const AVPacket
extradata_isom = st->codecpar->extradata_size > 0 && st->codecpar->extradata[0] == 1;
if (pkt->size >= 5 && AV_RB32(b) != 0x0000001 && (AV_RB24(b) != 0x000001 || extradata_isom)) {
ret = ff_stream_add_bitstream_filter(st, "h264_mp4toannexb", NULL);
- av_log(whip, AV_LOG_VERBOSE, "WHIP: Enable BSF h264_mp4toannexb, packet=[%x %x %x %x %x ...], extradata_isom=%d\n",
+ av_log(whip, AV_LOG_VERBOSE, "Enable BSF h264_mp4toannexb, packet=[%x %x %x %x %x ...], extradata_isom=%d\n",
b[0], b[1], b[2], b[3], b[4], extradata_isom);
} else
whip->h264_annexb_insert_sps_pps = 1;
@@ -1908,11 +1908,11 @@ static int whip_check_bitstream(AVFormatContext *s, AVStream *st, const AVPacket
static const AVOption options[] = {
{ "handshake_timeout", "Timeout in milliseconds for ICE and DTLS handshake.", OFFSET(handshake_timeout), AV_OPT_TYPE_INT, { .i64 = 5000 }, -1, INT_MAX, ENC },
{ "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, ENC },
- { "authorization", "The optional Bearer token for WHIP Authorization", OFFSET(authorization), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
- { "cert_file", "The optional certificate file path for DTLS", OFFSET(cert_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
- { "key_file", "The optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
+ { "authorization", "Optional Bearer token for WHIP Authorization", OFFSET(authorization), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
+ { "cert_file", "Optional certificate file path for DTLS", OFFSET(cert_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
+ { "key_file", "Optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
{ "whip_flags", "Set flags affecting WHIP connection behavior", OFFSET(flags), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, 0, UINT_MAX, ENC, .unit = "flags" },
- { "ignore_ipv6", "(Optional) Ignore any IPv6 ICE candidate", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_IGNORE_IPV6 }, 0, UINT_MAX, ENC, .unit = "flags" },
+ { "ignore_ipv6", "Ignore any IPv6 ICE candidate", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_IGNORE_IPV6 }, 0, UINT_MAX, ENC, .unit = "flags" },
{ NULL },
};
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 9+ messages in thread
* [FFmpeg-devel] [PATCH v2 3/6] avformat/whip: fix H264 profile_iop bit map for SDP
2025-07-02 11:59 [FFmpeg-devel] [PATCH v2 0/6] avformat/whip: Add NACK and RTX support (depends on ignore_ipv6 patchset) Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 1/6] avformat/whip: add whip_flags ignore_ipv6 to skip IPv6 ICE candidates Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 2/6] avformat/whip: fix typos Jack Lau
@ 2025-07-02 11:59 ` Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 4/6] WHIP: X509 cert serial number should be positive Jack Lau
` (2 subsequent siblings)
5 siblings, 0 replies; 9+ messages in thread
From: Jack Lau @ 2025-07-02 11:59 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Jack Lau
AVCodecParameters::profile only contains constraint_set1_flag
(AV_PROFILE_H264_CONSTRAINED 1<<9).
So add H264 constraints flag fully parse refer to hlsenc
write_codec_attr
Signed-off-by: Jack Lau <jacklau1222@qq.com>
---
libavformat/whip.c | 47 ++++++++++++++++------------------------------
1 file changed, 16 insertions(+), 31 deletions(-)
diff --git a/libavformat/whip.c b/libavformat/whip.c
index 932119a1c7..e287a3062f 100644
--- a/libavformat/whip.c
+++ b/libavformat/whip.c
@@ -210,6 +210,7 @@ typedef struct WHIPContext {
/* Parameters for the input audio and video codecs. */
AVCodecParameters *audio_par;
AVCodecParameters *video_par;
+ uint8_t constraint_set_flags;
/**
* The h264_mp4toannexb Bitstream Filter (BSF) bypasses the AnnexB packet;
@@ -445,45 +446,30 @@ static av_cold int initialize(AVFormatContext *s)
static int parse_profile_level(AVFormatContext *s, AVCodecParameters *par)
{
int ret = 0;
- const uint8_t *r = par->extradata, *r1, *end = par->extradata + par->extradata_size;
- H264SPS seq, *const sps = &seq;
- uint32_t state;
+ const uint8_t *r = par->extradata;
WHIPContext *whip = s->priv_data;
if (par->codec_id != AV_CODEC_ID_H264)
return ret;
- if (par->profile != AV_PROFILE_UNKNOWN && par->level != AV_LEVEL_UNKNOWN)
- return ret;
-
if (!par->extradata || par->extradata_size <= 0) {
av_log(whip, AV_LOG_ERROR, "Unable to parse profile from empty extradata=%p, size=%d\n",
par->extradata, par->extradata_size);
return AVERROR(EINVAL);
}
- while (1) {
- r = avpriv_find_start_code(r, end, &state);
- if (r >= end)
- break;
-
- r1 = ff_nal_find_startcode(r, end);
- if ((state & 0x1f) == H264_NAL_SPS) {
- ret = ff_avc_decode_sps(sps, r, r1 - r);
- if (ret < 0) {
- av_log(whip, AV_LOG_ERROR, "Failed to decode SPS, state=%x, size=%d\n",
- state, (int)(r1 - r));
- return ret;
- }
-
- av_log(whip, AV_LOG_VERBOSE, "Parse profile=%d, level=%d from SPS\n",
- sps->profile_idc, sps->level_idc);
- par->profile = sps->profile_idc;
- par->level = sps->level_idc;
- }
+ if (AV_RB32(r) == 0x00000001 && (r[4] & 0x1F) == 7)
+ r = &r[5];
+ else if (AV_RB24(r) == 0x000001 && (r[3] & 0x1F) == 7)
+ r = &r[4];
+ else if (r[0] == 0x01) // avcC
+ r = &r[1];
+ else
+ return AVERROR(EINVAL);
- r = r1;
- }
+ if (par->profile == AV_PROFILE_UNKNOWN) par->profile = r[0];
+ whip->constraint_set_flags = r[1];
+ if (par->level == AV_LEVEL_UNKNOWN) par->level = r[2];
return ret;
}
@@ -594,7 +580,7 @@ static int parse_codec(AVFormatContext *s)
*/
static int generate_sdp_offer(AVFormatContext *s)
{
- int ret = 0, profile, level, profile_iop;
+ int ret = 0, profile, level;
const char *acodec_name = NULL, *vcodec_name = NULL;
AVBPrint bp;
WHIPContext *whip = s->priv_data;
@@ -662,11 +648,10 @@ static int generate_sdp_offer(AVFormatContext *s)
}
if (whip->video_par) {
- profile_iop = profile = whip->video_par->profile;
+ profile = whip->video_par->profile;
level = whip->video_par->level;
if (whip->video_par->codec_id == AV_CODEC_ID_H264) {
vcodec_name = "H264";
- profile_iop &= AV_PROFILE_H264_CONSTRAINED;
profile &= (~AV_PROFILE_H264_CONSTRAINED);
}
@@ -694,7 +679,7 @@ static int generate_sdp_offer(AVFormatContext *s)
vcodec_name,
whip->video_payload_type,
profile,
- profile_iop,
+ whip->constraint_set_flags,
level,
whip->video_ssrc,
whip->video_ssrc);
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 9+ messages in thread
* [FFmpeg-devel] [PATCH v2 4/6] WHIP: X509 cert serial number should be positive.
2025-07-02 11:59 [FFmpeg-devel] [PATCH v2 0/6] avformat/whip: Add NACK and RTX support (depends on ignore_ipv6 patchset) Jack Lau
` (2 preceding siblings ...)
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 3/6] avformat/whip: fix H264 profile_iop bit map for SDP Jack Lau
@ 2025-07-02 11:59 ` Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 5/6] avformat/whip: implement NACK and RTX suppport Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 6/6] avformat/whip: reindent whip options Jack Lau
5 siblings, 0 replies; 9+ messages in thread
From: Jack Lau @ 2025-07-02 11:59 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: winlin, Jack Lau
From: winlin <winlinvip@gmail.com>
See RFC5280 4.1.2.2
Co-authored-by: winlin <winlinvip@gmail.com>
Signed-off-by: Jack Lau <jacklau1222@qq.com>
---
libavformat/tls_openssl.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/libavformat/tls_openssl.c b/libavformat/tls_openssl.c
index 2a3905891d..4733faec9c 100644
--- a/libavformat/tls_openssl.c
+++ b/libavformat/tls_openssl.c
@@ -316,7 +316,8 @@ static int openssl_gen_certificate(EVP_PKEY *pkey, X509 **cert, char **fingerpri
goto enomem_end;
}
- serial = (int)av_get_random_seed();
+ // According to RFC5280 4.1.2.2, The serial number MUST be a positive integer
+ serial = (int)(av_get_random_seed() & 0x7FFFFFFF);
if (ASN1_INTEGER_set(X509_get_serialNumber(*cert), serial) != 1) {
av_log(NULL, AV_LOG_ERROR, "TLS: Failed to set serial, %s\n", ERR_error_string(ERR_get_error(), NULL));
goto einval_end;
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 9+ messages in thread
* [FFmpeg-devel] [PATCH v2 5/6] avformat/whip: implement NACK and RTX suppport
2025-07-02 11:59 [FFmpeg-devel] [PATCH v2 0/6] avformat/whip: Add NACK and RTX support (depends on ignore_ipv6 patchset) Jack Lau
` (3 preceding siblings ...)
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 4/6] WHIP: X509 cert serial number should be positive Jack Lau
@ 2025-07-02 11:59 ` Jack Lau
2025-07-02 13:46 ` Steven Liu
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 6/6] avformat/whip: reindent whip options Jack Lau
5 siblings, 1 reply; 9+ messages in thread
From: Jack Lau @ 2025-07-02 11:59 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Sergio Garcia Murillo, Jack Lau
RTP retransmission described in RFC4588 (RTX) is an effective packet
loss recovery technique for real-time applications with relaxed delay bounds.
This patch provides a minimal implementation for RTX and RTCP NACK (RFC3940)
and its associated SDP signaling and negotiation.
Co-authored-by: Sergio Garcia Murillo <sergio.garcia.murillo@gmail.com>
Signed-off-by: Jack Lau <jacklau1222@qq.com>
---
libavformat/whip.c | 198 ++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 195 insertions(+), 3 deletions(-)
diff --git a/libavformat/whip.c b/libavformat/whip.c
index e287a3062f..fa6e3855d3 100644
--- a/libavformat/whip.c
+++ b/libavformat/whip.c
@@ -114,6 +114,7 @@
/* Referring to Chrome's definition of RTP payload types. */
#define WHIP_RTP_PAYLOAD_TYPE_H264 106
#define WHIP_RTP_PAYLOAD_TYPE_OPUS 111
+#define WHIP_RTP_PAYLOAD_TYPE_RTX 105
/**
* The STUN message header, which is 20 bytes long, comprises the
@@ -150,6 +151,11 @@
#define WHIP_SDP_SESSION_ID "4489045141692799359"
#define WHIP_SDP_CREATOR_IP "127.0.0.1"
+/**
+ * Retransmission / NACK support
+*/
+#define HISTORY_SIZE_DEFAULT 512
+
/* Calculate the elapsed time from starttime to endtime in milliseconds. */
#define ELAPSED(starttime, endtime) ((int)(endtime - starttime) / 1000)
@@ -194,9 +200,19 @@ enum WHIPState {
};
typedef enum WHIPFlags {
- WHIP_FLAG_IGNORE_IPV6 = (1 << 0) // Ignore ipv6 candidate
+ WHIP_FLAG_IGNORE_IPV6 = (1 << 0), // Ignore ipv6 candidate
+ WHIP_FLAG_DISABLE_RTX = (1 << 1) // Enable NACK and RTX
} WHIPFlags;
+typedef struct RtpHistoryItem {
+ /* original RTP seq */
+ uint16_t seq;
+ /* length in bytes */
+ int size;
+ /* malloc-ed copy */
+ uint8_t* buf;
+} RtpHistoryItem;
+
typedef struct WHIPContext {
AVClass *av_class;
@@ -285,6 +301,7 @@ typedef struct WHIPContext {
/* The SRTP send context, to encrypt outgoing packets. */
SRTPContext srtp_audio_send;
SRTPContext srtp_video_send;
+ SRTPContext srtp_video_rtx_send;
SRTPContext srtp_rtcp_send;
/* The SRTP receive context, to decrypt incoming packets. */
SRTPContext srtp_recv;
@@ -309,6 +326,14 @@ typedef struct WHIPContext {
/* The certificate and private key used for DTLS handshake. */
char* cert_file;
char* key_file;
+
+ /* RTX and NACK */
+ uint8_t rtx_payload_type;
+ uint32_t video_rtx_ssrc;
+ uint16_t rtx_seq;
+ int history_size;
+ RtpHistoryItem *history; /* ring buffer */
+ int hist_head;
} WHIPContext;
/**
@@ -606,6 +631,16 @@ static int generate_sdp_offer(AVFormatContext *s)
whip->audio_payload_type = WHIP_RTP_PAYLOAD_TYPE_OPUS;
whip->video_payload_type = WHIP_RTP_PAYLOAD_TYPE_H264;
+ /* RTX and NACK init */
+ whip->rtx_payload_type = WHIP_RTP_PAYLOAD_TYPE_RTX;
+ whip->video_rtx_ssrc = av_lfg_get(&whip->rnd);
+ whip->rtx_seq = 0;
+ whip->hist_head = 0;
+ whip->history_size = FFMAX(64, whip->history_size);
+ whip->history = av_calloc(whip->history_size, sizeof(*whip->history));
+ if (!whip->history)
+ return AVERROR(ENOMEM);
+
av_bprintf(&bp, ""
"v=0\r\n"
"o=FFmpeg %s 2 IN IP4 %s\r\n"
@@ -656,7 +691,7 @@ static int generate_sdp_offer(AVFormatContext *s)
}
av_bprintf(&bp, ""
- "m=video 9 UDP/TLS/RTP/SAVPF %u\r\n"
+ "m=video 9 UDP/TLS/RTP/SAVPF %u %u\r\n"
"c=IN IP4 0.0.0.0\r\n"
"a=ice-ufrag:%s\r\n"
"a=ice-pwd:%s\r\n"
@@ -669,9 +704,16 @@ static int generate_sdp_offer(AVFormatContext *s)
"a=rtcp-rsize\r\n"
"a=rtpmap:%u %s/90000\r\n"
"a=fmtp:%u level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=%02x%02x%02x\r\n"
+ "a=rtcp-fb:%u nack\r\n"
+ "a=rtpmap:%u rtx/90000\r\n"
+ "a=fmtp:%u apt=%u\r\n"
+ "a=ssrc-group:FID %u %u\r\n"
+ "a=ssrc:%u cname:FFmpeg\r\n"
+ "a=ssrc:%u msid:FFmpeg video\r\n"
"a=ssrc:%u cname:FFmpeg\r\n"
"a=ssrc:%u msid:FFmpeg video\r\n",
whip->video_payload_type,
+ whip->rtx_payload_type,
whip->ice_ufrag_local,
whip->ice_pwd_local,
whip->dtls_fingerprint,
@@ -681,8 +723,16 @@ static int generate_sdp_offer(AVFormatContext *s)
profile,
whip->constraint_set_flags,
level,
+ whip->video_payload_type,
+ whip->rtx_payload_type,
+ whip->rtx_payload_type,
+ whip->video_payload_type,
+ whip->video_ssrc,
+ whip->video_rtx_ssrc,
whip->video_ssrc,
- whip->video_ssrc);
+ whip->video_ssrc,
+ whip->video_rtx_ssrc,
+ whip->video_rtx_ssrc);
}
if (!av_bprint_is_complete(&bp)) {
@@ -1398,6 +1448,12 @@ static int setup_srtp(AVFormatContext *s)
goto end;
}
+ ret = ff_srtp_set_crypto(&whip->srtp_video_rtx_send, suite, buf);
+ if (ret < 0) {
+ av_log(whip, AV_LOG_ERROR, "WHIP: Failed to set crypto for video rtx send\n");
+ goto end;
+ }
+
ret = ff_srtp_set_crypto(&whip->srtp_rtcp_send, suite, buf);
if (ret < 0) {
av_log(whip, AV_LOG_ERROR, "Failed to set crypto for rtcp send\n");
@@ -1427,6 +1483,37 @@ end:
return ret;
}
+
+/**
+ * RTX history helpers
+ */
+ static void rtp_history_store(WHIPContext *whip, const uint8_t *buf, int size)
+{
+ int pos = whip->hist_head % whip->history_size;
+ RtpHistoryItem *it = &whip->history[pos];
+ /* free older entry */
+ av_free(it->buf);
+ it->buf = av_malloc(size);
+ if (!it->buf)
+ return;
+
+ memcpy(it->buf, buf, size);
+ it->size = size;
+ it->seq = AV_RB16(buf + 2);
+
+ whip->hist_head = ++pos;
+}
+
+static const RtpHistoryItem *rtp_history_find(const WHIPContext *whip, uint16_t seq)
+{
+ for (int i = 0; i < whip->history_size; i++) {
+ const RtpHistoryItem *it = &whip->history[i];
+ if (it->buf && it->seq == seq)
+ return it;
+ }
+ return NULL;
+}
+
/**
* Callback triggered by the RTP muxer when it creates and sends out an RTP packet.
*
@@ -1463,6 +1550,10 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size)
return 0;
}
+ /* Store only ORIGINAL video packets (non-RTX, non-RTCP) */
+ if (!is_rtcp && is_video)
+ rtp_history_store(whip, buf, buf_size);
+
ret = ffurl_write(whip->udp, whip->buf, cipher_size);
if (ret < 0) {
av_log(whip, AV_LOG_ERROR, "Failed to write packet=%dB, ret=%d\n", cipher_size, ret);
@@ -1471,6 +1562,45 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size)
return ret;
}
+/**
+ * See https://datatracker.ietf.org/doc/html/rfc4588
+ * Build and send a single RTX packet
+ */
+static int send_rtx_packet(AVFormatContext *s, const uint8_t *orig_pkt_buf, int orig_size)
+{
+ WHIPContext *whip = s->priv_data;
+ int new_size, cipher_size;
+ if (whip->flags & WHIP_FLAG_DISABLE_RTX)
+ return 0;
+
+ /* allocate new buffer: header + 2 + payload */
+ if (orig_size + 2 > sizeof(whip->buf))
+ return 0;
+
+ memcpy(whip->buf, orig_pkt_buf, orig_size);
+
+ uint8_t *hdr = whip->buf;
+ uint16_t orig_seq = AV_RB16(hdr + 2);
+
+ /* rewrite header */
+ hdr[1] = (hdr[1] & 0x80) | whip->rtx_payload_type; /* keep M bit */
+ AV_WB16(hdr + 2, whip->rtx_seq++);
+ AV_WB32(hdr + 8, whip->video_rtx_ssrc);
+
+ /* shift payload 2 bytes */
+ memmove(hdr + 12 + 2, hdr + 12, orig_size - 12);
+ AV_WB16(hdr + 12, orig_seq);
+
+ new_size = orig_size + 2;
+
+ /* Encrypt by SRTP and send out. */
+ cipher_size = ff_srtp_encrypt(&whip->srtp_video_rtx_send, whip->buf, new_size, whip->buf, sizeof(whip->buf));
+ if (cipher_size <= 0 || cipher_size < new_size) {
+ av_log(whip, AV_LOG_WARNING, "WHIP: Failed to encrypt packet=%dB, cipher=%dB\n", new_size, cipher_size);
+ return 0;
+ }
+ return ffurl_write(whip->udp, whip->buf, cipher_size);
+}
/**
* Creates dedicated RTP muxers for each stream in the AVFormatContext to build RTP
@@ -1793,6 +1923,66 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt)
goto end;
}
}
+ /**
+ * Handle RTCP NACK
+ * Refer to RFC 4585, Section 6.2.1
+ * The Generic NACK message is identified by PT=RTPFB and FMT=1.
+ * TODO: disable retransmisstion when "-tune zerolatency"
+ */
+ if (media_is_rtcp(whip->buf, ret)) {
+ int ptr = 0;
+ uint8_t pt = whip->buf[ptr + 1];
+ uint8_t fmt = (whip->buf[ptr] & 0x1f);
+ if (ptr + 4 <= ret && pt == 205 && fmt == 1) {
+ /**
+ * Refer to RFC 3550, Section 6.4.1.
+ * The length of this RTCP packet in 32-bit words minus one,
+ * including the header and any padding.
+ */
+ int rtcp_len = (AV_RB16(&whip->buf[ptr + 2]) + 1) * 4;
+ /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */
+ int srtcp_len = rtcp_len + 4 + 10;
+ if (srtcp_len == ret && rtcp_len >= 12) {
+ int i = 0;
+ uint8_t *buf = av_malloc(srtcp_len);
+ memcpy(buf, whip->buf, srtcp_len);
+ int ret = ff_srtp_decrypt(&whip->srtp_recv, buf, &srtcp_len);
+ if (ret < 0)
+ av_log(whip, AV_LOG_ERROR, "WHIP: SRTCP decrypt failed: %d\n", ret);
+ while (12 + i < rtcp_len && !ret) {
+ /**
+ * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1
+ * Handle multi NACKs in bundled packet.
+ */
+ uint16_t pid = AV_RB16(&buf[ptr + 12 + i]);
+ uint16_t blp = AV_RB16(&buf[ptr + 14 + i]);
+
+ /* retransmit pid + any bit set in blp */
+ for (int bit = -1; bit < 16; bit++) {
+ uint16_t seq = (bit < 0) ? pid : pid + bit + 1;
+ if (bit >= 0 && !(blp & (1 << bit)))
+ continue;
+
+ const RtpHistoryItem *it = rtp_history_find(whip, seq);
+ if (it) {
+ av_log(whip, AV_LOG_VERBOSE,
+ "WHIP: NACK, packet found: size: %d, seq=%d, rtx size=%d, lateset stored packet seq:%d\n",
+ it->size, seq, ret, whip->history[whip->hist_head-1].seq);
+ ret = send_rtx_packet(s, it->buf, it->size);
+ if (ret <= 0 && !(whip->flags & WHIP_FLAG_DISABLE_RTX))
+ av_log(whip, AV_LOG_ERROR, "WHIP: Failed to send RTX packet\n");
+ } else {
+ av_log(whip, AV_LOG_VERBOSE,
+ "WHIP: NACK, packet not found, seq=%d, latest stored packet seq: %d, latest rtx seq: %d\n",
+ seq, whip->history[whip->hist_head-1].seq, whip->rtx_seq);
+ }
+ }
+ i = i + 4;
+ }
+ av_free(buf);
+ }
+ }
+ }
} else if (ret != AVERROR(EAGAIN)) {
av_log(whip, AV_LOG_ERROR, "Failed to read from UDP socket\n");
goto end;
@@ -1898,6 +2088,8 @@ static const AVOption options[] = {
{ "key_file", "Optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
{ "whip_flags", "Set flags affecting WHIP connection behavior", OFFSET(flags), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, 0, UINT_MAX, ENC, .unit = "flags" },
{ "ignore_ipv6", "Ignore any IPv6 ICE candidate", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_IGNORE_IPV6 }, 0, UINT_MAX, ENC, .unit = "flags" },
+ { "disable_rtx", "Disable RFC 4588 RTX", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_DISABLE_RTX }, 0, UINT_MAX, ENC, .unit = "flags" },
+ { "rtx_history_size", "Packet history size", OFFSET(history_size), AV_OPT_TYPE_INT, { .i64 = HISTORY_SIZE_DEFAULT }, 64, 2048, ENC },
{ NULL },
};
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 9+ messages in thread
* [FFmpeg-devel] [PATCH v2 6/6] avformat/whip: reindent whip options
2025-07-02 11:59 [FFmpeg-devel] [PATCH v2 0/6] avformat/whip: Add NACK and RTX support (depends on ignore_ipv6 patchset) Jack Lau
` (4 preceding siblings ...)
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 5/6] avformat/whip: implement NACK and RTX suppport Jack Lau
@ 2025-07-02 11:59 ` Jack Lau
5 siblings, 0 replies; 9+ messages in thread
From: Jack Lau @ 2025-07-02 11:59 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Jack Lau
Signed-off-by: Jack Lau <jacklau1222@qq.com>
---
libavformat/whip.c | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/libavformat/whip.c b/libavformat/whip.c
index fa6e3855d3..0c44ef6c73 100644
--- a/libavformat/whip.c
+++ b/libavformat/whip.c
@@ -2081,13 +2081,13 @@ static int whip_check_bitstream(AVFormatContext *s, AVStream *st, const AVPacket
#define OFFSET(x) offsetof(WHIPContext, x)
#define ENC AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
- { "handshake_timeout", "Timeout in milliseconds for ICE and DTLS handshake.", OFFSET(handshake_timeout), AV_OPT_TYPE_INT, { .i64 = 5000 }, -1, INT_MAX, ENC },
- { "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, ENC },
- { "authorization", "Optional Bearer token for WHIP Authorization", OFFSET(authorization), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
- { "cert_file", "Optional certificate file path for DTLS", OFFSET(cert_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
- { "key_file", "Optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
- { "whip_flags", "Set flags affecting WHIP connection behavior", OFFSET(flags), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, 0, UINT_MAX, ENC, .unit = "flags" },
- { "ignore_ipv6", "Ignore any IPv6 ICE candidate", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_IGNORE_IPV6 }, 0, UINT_MAX, ENC, .unit = "flags" },
+ { "handshake_timeout", "Timeout in milliseconds for ICE and DTLS handshake.", OFFSET(handshake_timeout), AV_OPT_TYPE_INT, { .i64 = 5000 }, -1, INT_MAX, ENC },
+ { "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, ENC },
+ { "authorization", "Optional Bearer token for WHIP Authorization", OFFSET(authorization), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
+ { "cert_file", "Optional certificate file path for DTLS", OFFSET(cert_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
+ { "key_file", "Optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
+ { "whip_flags", "Set flags affecting WHIP connection behavior", OFFSET(flags), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, 0, 0, ENC, .unit = "flags" },
+ { "ignore_ipv6", "Ignore any IPv6 ICE candidate", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_IGNORE_IPV6 }, 0, UINT_MAX, ENC, .unit = "flags" },
{ "disable_rtx", "Disable RFC 4588 RTX", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_DISABLE_RTX }, 0, UINT_MAX, ENC, .unit = "flags" },
{ "rtx_history_size", "Packet history size", OFFSET(history_size), AV_OPT_TYPE_INT, { .i64 = HISTORY_SIZE_DEFAULT }, 64, 2048, ENC },
{ NULL },
--
2.49.0
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [FFmpeg-devel] [PATCH v2 5/6] avformat/whip: implement NACK and RTX suppport
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 5/6] avformat/whip: implement NACK and RTX suppport Jack Lau
@ 2025-07-02 13:46 ` Steven Liu
2025-07-02 14:10 ` Jack Lau
0 siblings, 1 reply; 9+ messages in thread
From: Steven Liu @ 2025-07-02 13:46 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Cc: Sergio Garcia Murillo, Jack Lau
Jack Lau <jacklau1222gm-at-gmail.com@ffmpeg.org> 于2025年7月2日周三 20:09写道:
>
> RTP retransmission described in RFC4588 (RTX) is an effective packet
> loss recovery technique for real-time applications with relaxed delay bounds.
>
> This patch provides a minimal implementation for RTX and RTCP NACK (RFC3940)
> and its associated SDP signaling and negotiation.
>
> Co-authored-by: Sergio Garcia Murillo <sergio.garcia.murillo@gmail.com>
> Signed-off-by: Jack Lau <jacklau1222@qq.com>
> ---
> libavformat/whip.c | 198 ++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 195 insertions(+), 3 deletions(-)
>
> diff --git a/libavformat/whip.c b/libavformat/whip.c
> index e287a3062f..fa6e3855d3 100644
> --- a/libavformat/whip.c
> +++ b/libavformat/whip.c
> @@ -114,6 +114,7 @@
> /* Referring to Chrome's definition of RTP payload types. */
> #define WHIP_RTP_PAYLOAD_TYPE_H264 106
> #define WHIP_RTP_PAYLOAD_TYPE_OPUS 111
> +#define WHIP_RTP_PAYLOAD_TYPE_RTX 105
>
> /**
> * The STUN message header, which is 20 bytes long, comprises the
> @@ -150,6 +151,11 @@
> #define WHIP_SDP_SESSION_ID "4489045141692799359"
> #define WHIP_SDP_CREATOR_IP "127.0.0.1"
>
> +/**
> + * Retransmission / NACK support
> +*/
> +#define HISTORY_SIZE_DEFAULT 512
> +
> /* Calculate the elapsed time from starttime to endtime in milliseconds. */
> #define ELAPSED(starttime, endtime) ((int)(endtime - starttime) / 1000)
>
> @@ -194,9 +200,19 @@ enum WHIPState {
> };
>
> typedef enum WHIPFlags {
> - WHIP_FLAG_IGNORE_IPV6 = (1 << 0) // Ignore ipv6 candidate
> + WHIP_FLAG_IGNORE_IPV6 = (1 << 0), // Ignore ipv6 candidate
> + WHIP_FLAG_DISABLE_RTX = (1 << 1) // Enable NACK and RTX
> } WHIPFlags;
>
> +typedef struct RtpHistoryItem {
> + /* original RTP seq */
> + uint16_t seq; /* original RTP seq */
> + /* length in bytes */
> + int size; /* length in bytes */
> + /* malloc-ed copy */
move the comments.
> + uint8_t* buf; /* malloc-ed copy */
> +} RtpHistoryItem;
for example:
171 typedef struct mkv_cuepoint {
172 uint64_t pts;
173 int stream_idx;
174 int64_t cluster_pos; ///< offset of the
cluster containing the block relative to the segment
175 int64_t relative_pos; ///< relative offset from
the position of the cluster containing the block
176 int64_t duration; ///< duration of the
block according to time base
177 } mkv_cuepoint;
> +
> typedef struct WHIPContext {
> AVClass *av_class;
>
> @@ -285,6 +301,7 @@ typedef struct WHIPContext {
> /* The SRTP send context, to encrypt outgoing packets. */
> SRTPContext srtp_audio_send;
> SRTPContext srtp_video_send;
> + SRTPContext srtp_video_rtx_send;
> SRTPContext srtp_rtcp_send;
> /* The SRTP receive context, to decrypt incoming packets. */
> SRTPContext srtp_recv;
> @@ -309,6 +326,14 @@ typedef struct WHIPContext {
> /* The certificate and private key used for DTLS handshake. */
> char* cert_file;
> char* key_file;
> +
> + /* RTX and NACK */
> + uint8_t rtx_payload_type;
> + uint32_t video_rtx_ssrc;
> + uint16_t rtx_seq;
> + int history_size;
> + RtpHistoryItem *history; /* ring buffer */
> + int hist_head;
> } WHIPContext;
>
> /**
> @@ -606,6 +631,16 @@ static int generate_sdp_offer(AVFormatContext *s)
> whip->audio_payload_type = WHIP_RTP_PAYLOAD_TYPE_OPUS;
> whip->video_payload_type = WHIP_RTP_PAYLOAD_TYPE_H264;
>
> + /* RTX and NACK init */
> + whip->rtx_payload_type = WHIP_RTP_PAYLOAD_TYPE_RTX;
> + whip->video_rtx_ssrc = av_lfg_get(&whip->rnd);
> + whip->rtx_seq = 0;
> + whip->hist_head = 0;
> + whip->history_size = FFMAX(64, whip->history_size);
> + whip->history = av_calloc(whip->history_size, sizeof(*whip->history));
> + if (!whip->history)
> + return AVERROR(ENOMEM);
> +
> av_bprintf(&bp, ""
> "v=0\r\n"
> "o=FFmpeg %s 2 IN IP4 %s\r\n"
> @@ -656,7 +691,7 @@ static int generate_sdp_offer(AVFormatContext *s)
> }
>
> av_bprintf(&bp, ""
> - "m=video 9 UDP/TLS/RTP/SAVPF %u\r\n"
> + "m=video 9 UDP/TLS/RTP/SAVPF %u %u\r\n"
> "c=IN IP4 0.0.0.0\r\n"
> "a=ice-ufrag:%s\r\n"
> "a=ice-pwd:%s\r\n"
> @@ -669,9 +704,16 @@ static int generate_sdp_offer(AVFormatContext *s)
> "a=rtcp-rsize\r\n"
> "a=rtpmap:%u %s/90000\r\n"
> "a=fmtp:%u level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=%02x%02x%02x\r\n"
> + "a=rtcp-fb:%u nack\r\n"
> + "a=rtpmap:%u rtx/90000\r\n"
> + "a=fmtp:%u apt=%u\r\n"
> + "a=ssrc-group:FID %u %u\r\n"
> + "a=ssrc:%u cname:FFmpeg\r\n"
> + "a=ssrc:%u msid:FFmpeg video\r\n"
> "a=ssrc:%u cname:FFmpeg\r\n"
> "a=ssrc:%u msid:FFmpeg video\r\n",
> whip->video_payload_type,
> + whip->rtx_payload_type,
> whip->ice_ufrag_local,
> whip->ice_pwd_local,
> whip->dtls_fingerprint,
> @@ -681,8 +723,16 @@ static int generate_sdp_offer(AVFormatContext *s)
> profile,
> whip->constraint_set_flags,
> level,
> + whip->video_payload_type,
> + whip->rtx_payload_type,
> + whip->rtx_payload_type,
> + whip->video_payload_type,
> + whip->video_ssrc,
> + whip->video_rtx_ssrc,
> whip->video_ssrc,
> - whip->video_ssrc);
> + whip->video_ssrc,
> + whip->video_rtx_ssrc,
> + whip->video_rtx_ssrc);
> }
>
> if (!av_bprint_is_complete(&bp)) {
> @@ -1398,6 +1448,12 @@ static int setup_srtp(AVFormatContext *s)
> goto end;
> }
>
> + ret = ff_srtp_set_crypto(&whip->srtp_video_rtx_send, suite, buf);
> + if (ret < 0) {
> + av_log(whip, AV_LOG_ERROR, "WHIP: Failed to set crypto for video rtx send\n");
Since av_log(whip, has been set, the "WHIP:" prefix might be
unnecessary. Same comment to bellow.
> + goto end;
> + }
> +
> ret = ff_srtp_set_crypto(&whip->srtp_rtcp_send, suite, buf);
> if (ret < 0) {
> av_log(whip, AV_LOG_ERROR, "Failed to set crypto for rtcp send\n");
> @@ -1427,6 +1483,37 @@ end:
> return ret;
> }
>
> +
> +/**
> + * RTX history helpers
> + */
> + static void rtp_history_store(WHIPContext *whip, const uint8_t *buf, int size)
return value.
> +{
> + int pos = whip->hist_head % whip->history_size;
> + RtpHistoryItem *it = &whip->history[pos];
> + /* free older entry */
> + av_free(it->buf);
> + it->buf = av_malloc(size);
> + if (!it->buf)
> + return;
return AVERROR(ENOMEM)
> +
> + memcpy(it->buf, buf, size);
> + it->size = size;
> + it->seq = AV_RB16(buf + 2);
> +
> + whip->hist_head = ++pos;
> +}
> +
> +static const RtpHistoryItem *rtp_history_find(const WHIPContext *whip, uint16_t seq)
> +{
> + for (int i = 0; i < whip->history_size; i++) {
> + const RtpHistoryItem *it = &whip->history[i];
> + if (it->buf && it->seq == seq)
> + return it;
> + }
> + return NULL;
> +}
> +
> /**
> * Callback triggered by the RTP muxer when it creates and sends out an RTP packet.
> *
> @@ -1463,6 +1550,10 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size)
> return 0;
> }
>
> + /* Store only ORIGINAL video packets (non-RTX, non-RTCP) */
> + if (!is_rtcp && is_video)
> + rtp_history_store(whip, buf, buf_size);
ret = rtp_history_store(whip, buf, buf_size);
if (ret < 0)
process failed;
> +
> ret = ffurl_write(whip->udp, whip->buf, cipher_size);
> if (ret < 0) {
> av_log(whip, AV_LOG_ERROR, "Failed to write packet=%dB, ret=%d\n", cipher_size, ret);
> @@ -1471,6 +1562,45 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size)
>
> return ret;
> }
> +/**
> + * See https://datatracker.ietf.org/doc/html/rfc4588
> + * Build and send a single RTX packet
> + */
> +static int send_rtx_packet(AVFormatContext *s, const uint8_t *orig_pkt_buf, int orig_size)
> +{
> + WHIPContext *whip = s->priv_data;
> + int new_size, cipher_size;
> + if (whip->flags & WHIP_FLAG_DISABLE_RTX)
> + return 0;
> +
> + /* allocate new buffer: header + 2 + payload */
> + if (orig_size + 2 > sizeof(whip->buf))
> + return 0;
> +
> + memcpy(whip->buf, orig_pkt_buf, orig_size);
> +
> + uint8_t *hdr = whip->buf;
> + uint16_t orig_seq = AV_RB16(hdr + 2);
> +
> + /* rewrite header */
> + hdr[1] = (hdr[1] & 0x80) | whip->rtx_payload_type; /* keep M bit */
> + AV_WB16(hdr + 2, whip->rtx_seq++);
> + AV_WB32(hdr + 8, whip->video_rtx_ssrc);
> +
> + /* shift payload 2 bytes */
> + memmove(hdr + 12 + 2, hdr + 12, orig_size - 12);
> + AV_WB16(hdr + 12, orig_seq);
> +
> + new_size = orig_size + 2;
> +
> + /* Encrypt by SRTP and send out. */
> + cipher_size = ff_srtp_encrypt(&whip->srtp_video_rtx_send, whip->buf, new_size, whip->buf, sizeof(whip->buf));
> + if (cipher_size <= 0 || cipher_size < new_size) {
> + av_log(whip, AV_LOG_WARNING, "WHIP: Failed to encrypt packet=%dB, cipher=%dB\n", new_size, cipher_size);
> + return 0;
> + }
> + return ffurl_write(whip->udp, whip->buf, cipher_size);
> +}
>
> /**
> * Creates dedicated RTP muxers for each stream in the AVFormatContext to build RTP
> @@ -1793,6 +1923,66 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt)
> goto end;
> }
> }
> + /**
> + * Handle RTCP NACK
> + * Refer to RFC 4585, Section 6.2.1
> + * The Generic NACK message is identified by PT=RTPFB and FMT=1.
> + * TODO: disable retransmisstion when "-tune zerolatency"
> + */
> + if (media_is_rtcp(whip->buf, ret)) {
> + int ptr = 0;
> + uint8_t pt = whip->buf[ptr + 1];
> + uint8_t fmt = (whip->buf[ptr] & 0x1f);
> + if (ptr + 4 <= ret && pt == 205 && fmt == 1) {
> + /**
> + * Refer to RFC 3550, Section 6.4.1.
> + * The length of this RTCP packet in 32-bit words minus one,
> + * including the header and any padding.
> + */
> + int rtcp_len = (AV_RB16(&whip->buf[ptr + 2]) + 1) * 4;
> + /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */
> + int srtcp_len = rtcp_len + 4 + 10;
> + if (srtcp_len == ret && rtcp_len >= 12) {
> + int i = 0;
> + uint8_t *buf = av_malloc(srtcp_len);
if (!buf)
process AVEROR(ENOMEM)
> + memcpy(buf, whip->buf, srtcp_len);
> + int ret = ff_srtp_decrypt(&whip->srtp_recv, buf, &srtcp_len);
> + if (ret < 0)
if error here, should free buf, otherwise will memleak.
> + av_log(whip, AV_LOG_ERROR, "WHIP: SRTCP decrypt failed: %d\n", ret);
> + while (12 + i < rtcp_len && !ret) {
> + /**
> + * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1
> + * Handle multi NACKs in bundled packet.
> + */
> + uint16_t pid = AV_RB16(&buf[ptr + 12 + i]);
> + uint16_t blp = AV_RB16(&buf[ptr + 14 + i]);
> +
> + /* retransmit pid + any bit set in blp */
> + for (int bit = -1; bit < 16; bit++) {
> + uint16_t seq = (bit < 0) ? pid : pid + bit + 1;
> + if (bit >= 0 && !(blp & (1 << bit)))
> + continue;
> +
> + const RtpHistoryItem *it = rtp_history_find(whip, seq);
> + if (it) {
> + av_log(whip, AV_LOG_VERBOSE,
> + "WHIP: NACK, packet found: size: %d, seq=%d, rtx size=%d, lateset stored packet seq:%d\n",
> + it->size, seq, ret, whip->history[whip->hist_head-1].seq);
> + ret = send_rtx_packet(s, it->buf, it->size);
> + if (ret <= 0 && !(whip->flags & WHIP_FLAG_DISABLE_RTX))
> + av_log(whip, AV_LOG_ERROR, "WHIP: Failed to send RTX packet\n");
> + } else {
> + av_log(whip, AV_LOG_VERBOSE,
> + "WHIP: NACK, packet not found, seq=%d, latest stored packet seq: %d, latest rtx seq: %d\n",
> + seq, whip->history[whip->hist_head-1].seq, whip->rtx_seq);
> + }
> + }
> + i = i + 4;
> + }
> + av_free(buf);
> + }
> + }
> + }
> } else if (ret != AVERROR(EAGAIN)) {
> av_log(whip, AV_LOG_ERROR, "Failed to read from UDP socket\n");
> goto end;
> @@ -1898,6 +2088,8 @@ static const AVOption options[] = {
> { "key_file", "Optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
> { "whip_flags", "Set flags affecting WHIP connection behavior", OFFSET(flags), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, 0, UINT_MAX, ENC, .unit = "flags" },
> { "ignore_ipv6", "Ignore any IPv6 ICE candidate", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_IGNORE_IPV6 }, 0, UINT_MAX, ENC, .unit = "flags" },
> + { "disable_rtx", "Disable RFC 4588 RTX", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_DISABLE_RTX }, 0, UINT_MAX, ENC, .unit = "flags" },
> + { "rtx_history_size", "Packet history size", OFFSET(history_size), AV_OPT_TYPE_INT, { .i64 = HISTORY_SIZE_DEFAULT }, 64, 2048, ENC },
> { NULL },
> };
>
> --
> 2.49.0
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [FFmpeg-devel] [PATCH v2 5/6] avformat/whip: implement NACK and RTX suppport
2025-07-02 13:46 ` Steven Liu
@ 2025-07-02 14:10 ` Jack Lau
0 siblings, 0 replies; 9+ messages in thread
From: Jack Lau @ 2025-07-02 14:10 UTC (permalink / raw)
To: FFmpeg development discussions and patches
Hi
> On Jul 2, 2025, at 21:46, Steven Liu <lingjiujianke-at-gmail.com@ffmpeg.org> wrote:
>
> Jack Lau <jacklau1222gm-at-gmail.com@ffmpeg.org <mailto:jacklau1222gm-at-gmail.com@ffmpeg.org>> 于2025年7月2日周三 20:09写道:
>>
>> RTP retransmission described in RFC4588 (RTX) is an effective packet
>> loss recovery technique for real-time applications with relaxed delay bounds.
>>
>> This patch provides a minimal implementation for RTX and RTCP NACK (RFC3940)
>> and its associated SDP signaling and negotiation.
>>
>> Co-authored-by: Sergio Garcia Murillo <sergio.garcia.murillo@gmail.com>
>> Signed-off-by: Jack Lau <jacklau1222@qq.com>
>> ---
>> libavformat/whip.c | 198 ++++++++++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 195 insertions(+), 3 deletions(-)
>>
>> diff --git a/libavformat/whip.c b/libavformat/whip.c
>> index e287a3062f..fa6e3855d3 100644
>> --- a/libavformat/whip.c
>> +++ b/libavformat/whip.c
>> @@ -114,6 +114,7 @@
>> /* Referring to Chrome's definition of RTP payload types. */
>> #define WHIP_RTP_PAYLOAD_TYPE_H264 106
>> #define WHIP_RTP_PAYLOAD_TYPE_OPUS 111
>> +#define WHIP_RTP_PAYLOAD_TYPE_RTX 105
>>
>> /**
>> * The STUN message header, which is 20 bytes long, comprises the
>> @@ -150,6 +151,11 @@
>> #define WHIP_SDP_SESSION_ID "4489045141692799359"
>> #define WHIP_SDP_CREATOR_IP "127.0.0.1"
>>
>> +/**
>> + * Retransmission / NACK support
>> +*/
>> +#define HISTORY_SIZE_DEFAULT 512
>> +
>> /* Calculate the elapsed time from starttime to endtime in milliseconds. */
>> #define ELAPSED(starttime, endtime) ((int)(endtime - starttime) / 1000)
>>
>> @@ -194,9 +200,19 @@ enum WHIPState {
>> };
>>
>> typedef enum WHIPFlags {
>> - WHIP_FLAG_IGNORE_IPV6 = (1 << 0) // Ignore ipv6 candidate
>> + WHIP_FLAG_IGNORE_IPV6 = (1 << 0), // Ignore ipv6 candidate
>> + WHIP_FLAG_DISABLE_RTX = (1 << 1) // Enable NACK and RTX
>> } WHIPFlags;
>>
>> +typedef struct RtpHistoryItem {
>> + /* original RTP seq */
>> + uint16_t seq; /* original RTP seq */
>> + /* length in bytes */
>> + int size; /* length in bytes */
>> + /* malloc-ed copy */
> move the comments.
>> + uint8_t* buf; /* malloc-ed copy */
>> +} RtpHistoryItem;
>
> for example:
>
>
> 171 typedef struct mkv_cuepoint {
> 172 uint64_t pts;
> 173 int stream_idx;
> 174 int64_t cluster_pos; ///< offset of the
> cluster containing the block relative to the segment
> 175 int64_t relative_pos; ///< relative offset from
> the position of the cluster containing the block
> 176 int64_t duration; ///< duration of the
> block according to time base
> 177 } mkv_cuepoint;
>
>
>
>> +
>> typedef struct WHIPContext {
>> AVClass *av_class;
>>
>> @@ -285,6 +301,7 @@ typedef struct WHIPContext {
>> /* The SRTP send context, to encrypt outgoing packets. */
>> SRTPContext srtp_audio_send;
>> SRTPContext srtp_video_send;
>> + SRTPContext srtp_video_rtx_send;
>> SRTPContext srtp_rtcp_send;
>> /* The SRTP receive context, to decrypt incoming packets. */
>> SRTPContext srtp_recv;
>> @@ -309,6 +326,14 @@ typedef struct WHIPContext {
>> /* The certificate and private key used for DTLS handshake. */
>> char* cert_file;
>> char* key_file;
>> +
>> + /* RTX and NACK */
>> + uint8_t rtx_payload_type;
>> + uint32_t video_rtx_ssrc;
>> + uint16_t rtx_seq;
>> + int history_size;
>> + RtpHistoryItem *history; /* ring buffer */
>> + int hist_head;
>> } WHIPContext;
>>
>> /**
>> @@ -606,6 +631,16 @@ static int generate_sdp_offer(AVFormatContext *s)
>> whip->audio_payload_type = WHIP_RTP_PAYLOAD_TYPE_OPUS;
>> whip->video_payload_type = WHIP_RTP_PAYLOAD_TYPE_H264;
>>
>> + /* RTX and NACK init */
>> + whip->rtx_payload_type = WHIP_RTP_PAYLOAD_TYPE_RTX;
>> + whip->video_rtx_ssrc = av_lfg_get(&whip->rnd);
>> + whip->rtx_seq = 0;
>> + whip->hist_head = 0;
>> + whip->history_size = FFMAX(64, whip->history_size);
>> + whip->history = av_calloc(whip->history_size, sizeof(*whip->history));
>> + if (!whip->history)
>> + return AVERROR(ENOMEM);
>> +
>> av_bprintf(&bp, ""
>> "v=0\r\n"
>> "o=FFmpeg %s 2 IN IP4 %s\r\n"
>> @@ -656,7 +691,7 @@ static int generate_sdp_offer(AVFormatContext *s)
>> }
>>
>> av_bprintf(&bp, ""
>> - "m=video 9 UDP/TLS/RTP/SAVPF %u\r\n"
>> + "m=video 9 UDP/TLS/RTP/SAVPF %u %u\r\n"
>> "c=IN IP4 0.0.0.0\r\n"
>> "a=ice-ufrag:%s\r\n"
>> "a=ice-pwd:%s\r\n"
>> @@ -669,9 +704,16 @@ static int generate_sdp_offer(AVFormatContext *s)
>> "a=rtcp-rsize\r\n"
>> "a=rtpmap:%u %s/90000\r\n"
>> "a=fmtp:%u level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=%02x%02x%02x\r\n"
>> + "a=rtcp-fb:%u nack\r\n"
>> + "a=rtpmap:%u rtx/90000\r\n"
>> + "a=fmtp:%u apt=%u\r\n"
>> + "a=ssrc-group:FID %u %u\r\n"
>> + "a=ssrc:%u cname:FFmpeg\r\n"
>> + "a=ssrc:%u msid:FFmpeg video\r\n"
>> "a=ssrc:%u cname:FFmpeg\r\n"
>> "a=ssrc:%u msid:FFmpeg video\r\n",
>> whip->video_payload_type,
>> + whip->rtx_payload_type,
>> whip->ice_ufrag_local,
>> whip->ice_pwd_local,
>> whip->dtls_fingerprint,
>> @@ -681,8 +723,16 @@ static int generate_sdp_offer(AVFormatContext *s)
>> profile,
>> whip->constraint_set_flags,
>> level,
>> + whip->video_payload_type,
>> + whip->rtx_payload_type,
>> + whip->rtx_payload_type,
>> + whip->video_payload_type,
>> + whip->video_ssrc,
>> + whip->video_rtx_ssrc,
>> whip->video_ssrc,
>> - whip->video_ssrc);
>> + whip->video_ssrc,
>> + whip->video_rtx_ssrc,
>> + whip->video_rtx_ssrc);
>> }
>>
>> if (!av_bprint_is_complete(&bp)) {
>> @@ -1398,6 +1448,12 @@ static int setup_srtp(AVFormatContext *s)
>> goto end;
>> }
>>
>> + ret = ff_srtp_set_crypto(&whip->srtp_video_rtx_send, suite, buf);
>> + if (ret < 0) {
>> + av_log(whip, AV_LOG_ERROR, "WHIP: Failed to set crypto for video rtx send\n");
> Since av_log(whip, has been set, the "WHIP:" prefix might be
> unnecessary. Same comment to bellow.
>
>> + goto end;
>> + }
>> +
>> ret = ff_srtp_set_crypto(&whip->srtp_rtcp_send, suite, buf);
>> if (ret < 0) {
>> av_log(whip, AV_LOG_ERROR, "Failed to set crypto for rtcp send\n");
>> @@ -1427,6 +1483,37 @@ end:
>> return ret;
>> }
>>
>> +
>> +/**
>> + * RTX history helpers
>> + */
>> + static void rtp_history_store(WHIPContext *whip, const uint8_t *buf, int size)
> return value.
>> +{
>> + int pos = whip->hist_head % whip->history_size;
>> + RtpHistoryItem *it = &whip->history[pos];
>> + /* free older entry */
>> + av_free(it->buf);
>> + it->buf = av_malloc(size);
>> + if (!it->buf)
>> + return;
> return AVERROR(ENOMEM)
>> +
>> + memcpy(it->buf, buf, size);
>> + it->size = size;
>> + it->seq = AV_RB16(buf + 2);
>> +
>> + whip->hist_head = ++pos;
>> +}
>> +
>> +static const RtpHistoryItem *rtp_history_find(const WHIPContext *whip, uint16_t seq)
>> +{
>> + for (int i = 0; i < whip->history_size; i++) {
>> + const RtpHistoryItem *it = &whip->history[i];
>> + if (it->buf && it->seq == seq)
>> + return it;
>> + }
>> + return NULL;
>> +}
>> +
>> /**
>> * Callback triggered by the RTP muxer when it creates and sends out an RTP packet.
>> *
>> @@ -1463,6 +1550,10 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size)
>> return 0;
>> }
>>
>> + /* Store only ORIGINAL video packets (non-RTX, non-RTCP) */
>> + if (!is_rtcp && is_video)
>> + rtp_history_store(whip, buf, buf_size);
> ret = rtp_history_store(whip, buf, buf_size);
> if (ret < 0)
> process failed;
>
>> +
>> ret = ffurl_write(whip->udp, whip->buf, cipher_size);
>> if (ret < 0) {
>> av_log(whip, AV_LOG_ERROR, "Failed to write packet=%dB, ret=%d\n", cipher_size, ret);
>> @@ -1471,6 +1562,45 @@ static int on_rtp_write_packet(void *opaque, const uint8_t *buf, int buf_size)
>>
>> return ret;
>> }
>> +/**
>> + * See https://datatracker.ietf.org/doc/html/rfc4588
>> + * Build and send a single RTX packet
>> + */
>> +static int send_rtx_packet(AVFormatContext *s, const uint8_t *orig_pkt_buf, int orig_size)
>> +{
>> + WHIPContext *whip = s->priv_data;
>> + int new_size, cipher_size;
>> + if (whip->flags & WHIP_FLAG_DISABLE_RTX)
>> + return 0;
>> +
>> + /* allocate new buffer: header + 2 + payload */
>> + if (orig_size + 2 > sizeof(whip->buf))
>> + return 0;
>> +
>> + memcpy(whip->buf, orig_pkt_buf, orig_size);
>> +
>> + uint8_t *hdr = whip->buf;
>> + uint16_t orig_seq = AV_RB16(hdr + 2);
>> +
>> + /* rewrite header */
>> + hdr[1] = (hdr[1] & 0x80) | whip->rtx_payload_type; /* keep M bit */
>> + AV_WB16(hdr + 2, whip->rtx_seq++);
>> + AV_WB32(hdr + 8, whip->video_rtx_ssrc);
>> +
>> + /* shift payload 2 bytes */
>> + memmove(hdr + 12 + 2, hdr + 12, orig_size - 12);
>> + AV_WB16(hdr + 12, orig_seq);
>> +
>> + new_size = orig_size + 2;
>> +
>> + /* Encrypt by SRTP and send out. */
>> + cipher_size = ff_srtp_encrypt(&whip->srtp_video_rtx_send, whip->buf, new_size, whip->buf, sizeof(whip->buf));
>> + if (cipher_size <= 0 || cipher_size < new_size) {
>> + av_log(whip, AV_LOG_WARNING, "WHIP: Failed to encrypt packet=%dB, cipher=%dB\n", new_size, cipher_size);
>> + return 0;
>> + }
>> + return ffurl_write(whip->udp, whip->buf, cipher_size);
>> +}
>>
>> /**
>> * Creates dedicated RTP muxers for each stream in the AVFormatContext to build RTP
>> @@ -1793,6 +1923,66 @@ static int whip_write_packet(AVFormatContext *s, AVPacket *pkt)
>> goto end;
>> }
>> }
>> + /**
>> + * Handle RTCP NACK
>> + * Refer to RFC 4585, Section 6.2.1
>> + * The Generic NACK message is identified by PT=RTPFB and FMT=1.
>> + * TODO: disable retransmisstion when "-tune zerolatency"
>> + */
>> + if (media_is_rtcp(whip->buf, ret)) {
>> + int ptr = 0;
>> + uint8_t pt = whip->buf[ptr + 1];
>> + uint8_t fmt = (whip->buf[ptr] & 0x1f);
>> + if (ptr + 4 <= ret && pt == 205 && fmt == 1) {
>> + /**
>> + * Refer to RFC 3550, Section 6.4.1.
>> + * The length of this RTCP packet in 32-bit words minus one,
>> + * including the header and any padding.
>> + */
>> + int rtcp_len = (AV_RB16(&whip->buf[ptr + 2]) + 1) * 4;
>> + /* SRTCP index(4 bytes) + HMAC (SRTP_AES128_CM_SHA1_80 10bytes) */
>> + int srtcp_len = rtcp_len + 4 + 10;
>> + if (srtcp_len == ret && rtcp_len >= 12) {
>> + int i = 0;
>> + uint8_t *buf = av_malloc(srtcp_len);
> if (!buf)
> process AVEROR(ENOMEM)
Thanks for your detailed reviews! I totally agree the top of ideas.
>> + memcpy(buf, whip->buf, srtcp_len);
>> + int ret = ff_srtp_decrypt(&whip->srtp_recv, buf, &srtcp_len);
>> + if (ret < 0)
> if error here, should free buf, otherwise will memleak.
But I think this is not an issue,
>
>> + av_log(whip, AV_LOG_ERROR, "WHIP: SRTCP decrypt failed: %d\n", ret);
>> + while (12 + i < rtcp_len && !ret) {
>> + /**
>> + * See https://datatracker.ietf.org/doc/html/rfc4585#section-6.1
>> + * Handle multi NACKs in bundled packet.
>> + */
>> + uint16_t pid = AV_RB16(&buf[ptr + 12 + i]);
>> + uint16_t blp = AV_RB16(&buf[ptr + 14 + i]);
>> +
>> + /* retransmit pid + any bit set in blp */
>> + for (int bit = -1; bit < 16; bit++) {
>> + uint16_t seq = (bit < 0) ? pid : pid + bit + 1;
>> + if (bit >= 0 && !(blp & (1 << bit)))
>> + continue;
>> +
>> + const RtpHistoryItem *it = rtp_history_find(whip, seq);
>> + if (it) {
>> + av_log(whip, AV_LOG_VERBOSE,
>> + "WHIP: NACK, packet found: size: %d, seq=%d, rtx size=%d, lateset stored packet seq:%d\n",
>> + it->size, seq, ret, whip->history[whip->hist_head-1].seq);
>> + ret = send_rtx_packet(s, it->buf, it->size);
>> + if (ret <= 0 && !(whip->flags & WHIP_FLAG_DISABLE_RTX))
>> + av_log(whip, AV_LOG_ERROR, "WHIP: Failed to send RTX packet\n");
>> + } else {
>> + av_log(whip, AV_LOG_VERBOSE,
>> + "WHIP: NACK, packet not found, seq=%d, latest stored packet seq: %d, latest rtx seq: %d\n",
>> + seq, whip->history[whip->hist_head-1].seq, whip->rtx_seq);
>> + }
>> + }
>> + i = i + 4;
>> + }
>> + av_free(buf);
Because we have this free function after the loop so there’s no memory leak.
>> + }
>> + }
>> + }
>> } else if (ret != AVERROR(EAGAIN)) {
>> av_log(whip, AV_LOG_ERROR, "Failed to read from UDP socket\n");
>> goto end;
>> @@ -1898,6 +2088,8 @@ static const AVOption options[] = {
>> { "key_file", "Optional private key file path for DTLS", OFFSET(key_file), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, ENC },
>> { "whip_flags", "Set flags affecting WHIP connection behavior", OFFSET(flags), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, 0, UINT_MAX, ENC, .unit = "flags" },
>> { "ignore_ipv6", "Ignore any IPv6 ICE candidate", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_IGNORE_IPV6 }, 0, UINT_MAX, ENC, .unit = "flags" },
>> + { "disable_rtx", "Disable RFC 4588 RTX", 0, AV_OPT_TYPE_CONST, { .i64 = WHIP_FLAG_DISABLE_RTX }, 0, UINT_MAX, ENC, .unit = "flags" },
>> + { "rtx_history_size", "Packet history size", OFFSET(history_size), AV_OPT_TYPE_INT, { .i64 = HISTORY_SIZE_DEFAULT }, 64, 2048, ENC },
>> { NULL },
>> };
>>
>> --
>> 2.49.0
>>
>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel@ffmpeg.org <mailto:ffmpeg-devel@ffmpeg.org>
>> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>>
>> To unsubscribe, visit link above, or email
>> ffmpeg-devel-request@ffmpeg.org <mailto:ffmpeg-devel-request@ffmpeg.org> with subject "unsubscribe”.
I’ll submit new version of this patch later.
Thanks
Jack
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org <mailto:ffmpeg-devel@ffmpeg.org>
> https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> To unsubscribe, visit link above, or email
> ffmpeg-devel-request@ffmpeg.org <mailto:ffmpeg-devel-request@ffmpeg.org> with subject "unsubscribe".
_______________________________________________
ffmpeg-devel mailing list
ffmpeg-devel@ffmpeg.org
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
To unsubscribe, visit link above, or email
ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2025-07-02 14:10 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-07-02 11:59 [FFmpeg-devel] [PATCH v2 0/6] avformat/whip: Add NACK and RTX support (depends on ignore_ipv6 patchset) Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 1/6] avformat/whip: add whip_flags ignore_ipv6 to skip IPv6 ICE candidates Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 2/6] avformat/whip: fix typos Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 3/6] avformat/whip: fix H264 profile_iop bit map for SDP Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 4/6] WHIP: X509 cert serial number should be positive Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 5/6] avformat/whip: implement NACK and RTX suppport Jack Lau
2025-07-02 13:46 ` Steven Liu
2025-07-02 14:10 ` Jack Lau
2025-07-02 11:59 ` [FFmpeg-devel] [PATCH v2 6/6] avformat/whip: reindent whip options Jack Lau
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git