Skip to content

Commit 54e45e4

Browse files
yangrtcwinlinvip
authored andcommitted
WHIP: Generate and exchange offer server to get answer.
1. Generate random ice ufrag, pwd and ssrc. 2. Use HTTP POST to send offer to server and read answer. 3. Logging offer and answer in verbose level.
1 parent 8a9c829 commit 54e45e4

File tree

1 file changed

+255
-0
lines changed

1 file changed

+255
-0
lines changed

libavformat/rtcenc.c

+255
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,42 @@
2929
#include "mux.h"
3030
#include "libavutil/opt.h"
3131
#include "libavcodec/avcodec.h"
32+
#include "libavutil/avstring.h"
33+
#include "url.h"
34+
#include "libavutil/random_seed.h"
3235

3336
typedef struct RTCContext {
3437
AVClass *av_class;
3538

3639
/* Input audio and video codec parameters */
3740
AVCodecParameters *audio_par;
3841
AVCodecParameters *video_par;
42+
43+
/* The ICE username and pwd fragment generated by the muxer. */
44+
char ice_ufrag_local[9];
45+
char ice_pwd_local[33];
46+
/* The SSRC of the audio and video stream, generated by the muxer. */
47+
uint32_t audio_ssrc;
48+
uint32_t video_ssrc;
49+
/* The PT(Payload Type) of stream, generated by the muxer. */
50+
uint8_t audio_payload_type;
51+
uint8_t video_payload_type;
52+
/**
53+
* The SDP offer generated by the muxer according to the codec parameters,
54+
* DTLS and ICE information.
55+
* */
56+
char *sdp_offer;
57+
/* The SDP answer received from the WebRTC server. */
58+
char *sdp_answer;
59+
/* The HTTP URL context is the transport layer for the WHIP protocol. */
60+
URLContext *whip_uc;
3961
} RTCContext;
4062

4163
/**
4264
* Only support video(h264) and audio(opus) for now. Note that only baseline
4365
* and constrained baseline of h264 are supported.
66+
*
67+
* @return 0 if OK, AVERROR_xxx on error
4468
*/
4569
static int check_codec(AVFormatContext *s)
4670
{
@@ -103,13 +127,240 @@ static int check_codec(AVFormatContext *s)
103127
return 0;
104128
}
105129

130+
/**
131+
* Generate SDP offer according to the codec parameters, DTLS and ICE information.
132+
* The below is an example of SDP offer:
133+
*
134+
* v=0
135+
* o=FFmpeg 4489045141692799359 2 IN IP4 127.0.0.1
136+
* s=FFmpegPublishSession
137+
* t=0 0
138+
* a=group:BUNDLE 0 1
139+
* a=extmap-allow-mixed
140+
* a=msid-semantic: WMS
141+
*
142+
* m=audio 9 UDP/TLS/RTP/SAVPF 111
143+
* c=IN IP4 0.0.0.0
144+
* a=ice-ufrag:a174B
145+
* a=ice-pwd:wY8rJ3gNLxL3eWZs6UPOxy
146+
* a=fingerprint:sha-256 EE:FE:A2:E5:6A:21:78:60:71:2C:21:DC:1A:2C:98:12:0C:E8:AD:68:07:61:1B:0E:FC:46:97:1E:BC:97:4A:54
147+
* a=setup:actpass
148+
* a=mid:0
149+
* a=sendonly
150+
* a=msid:FFmpeg audio
151+
* a=rtcp-mux
152+
* a=rtpmap:111 opus/48000/2
153+
* a=ssrc:4267647086 cname:FFmpeg
154+
* a=ssrc:4267647086 msid:FFmpeg audio
155+
*
156+
* m=video 9 UDP/TLS/RTP/SAVPF 106
157+
* c=IN IP4 0.0.0.0
158+
* a=ice-ufrag:a174B
159+
* a=ice-pwd:wY8rJ3gNLxL3eWZs6UPOxy
160+
* a=fingerprint:sha-256 EE:FE:A2:E5:6A:21:78:60:71:2C:21:DC:1A:2C:98:12:0C:E8:AD:68:07:61:1B:0E:FC:46:97:1E:BC:97:4A:54
161+
* a=setup:actpass
162+
* a=mid:1
163+
* a=sendonly
164+
* a=msid:FFmpeg video
165+
* a=rtcp-mux
166+
* a=rtcp-rsize
167+
* a=rtpmap:106 H264/90000
168+
* a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
169+
* a=ssrc:107169110 cname:FFmpeg
170+
* a=ssrc:107169110 msid:FFmpeg video
171+
*
172+
* Note that we don't use av_sdp_create to generate SDP offer because it doesn't
173+
* support DTLS and ICE information.
174+
*
175+
* @return 0 if OK, AVERROR_xxx on error
176+
*/
177+
static int generate_sdp_offer(AVFormatContext *s)
178+
{
179+
int profile_iop;
180+
RTCContext *rtc = s->priv_data;
181+
182+
if (rtc->sdp_offer) {
183+
av_log(s, AV_LOG_ERROR, "SDP offer is already set\n");
184+
return AVERROR(EINVAL);
185+
}
186+
187+
snprintf(rtc->ice_ufrag_local, sizeof(rtc->ice_ufrag_local), "%08x",
188+
av_get_random_seed());
189+
snprintf(rtc->ice_pwd_local, sizeof(rtc->ice_pwd_local), "%08x%08x%08x%08x",
190+
av_get_random_seed(), av_get_random_seed(), av_get_random_seed(),
191+
av_get_random_seed());
192+
193+
rtc->audio_ssrc = av_get_random_seed();
194+
rtc->video_ssrc = av_get_random_seed();
195+
196+
rtc->audio_payload_type = 111;
197+
rtc->video_payload_type = 106;
198+
199+
profile_iop = rtc->video_par->profile & FF_PROFILE_H264_CONSTRAINED ? 0xe0 : 0x00;
200+
rtc->sdp_offer = av_asprintf(
201+
"v=0\r\n"
202+
"o=FFmpeg 4489045141692799359 2 IN IP4 127.0.0.1\r\n"
203+
"s=FFmpegPublishSession\r\n"
204+
"t=0 0\r\n"
205+
"a=group:BUNDLE 0 1\r\n"
206+
"a=extmap-allow-mixed\r\n"
207+
"a=msid-semantic: WMS\r\n"
208+
""
209+
"m=audio 9 UDP/TLS/RTP/SAVPF %u\r\n"
210+
"c=IN IP4 0.0.0.0\r\n"
211+
"a=ice-ufrag:%s\r\n"
212+
"a=ice-pwd:%s\r\n"
213+
"a=fingerprint:sha-256 EE:FE:A2:E5:6A:21:78:60:71:2C:21:DC:1A:2C:98:12:0C:E8:AD:68:07:61:1B:0E:FC:46:97:1E:BC:97:4A:54\r\n"
214+
"a=setup:active\r\n"
215+
"a=mid:0\r\n"
216+
"a=sendonly\r\n"
217+
"a=msid:FFmpeg audio\r\n"
218+
"a=rtcp-mux\r\n"
219+
"a=rtpmap:%u opus/%d/%d\r\n"
220+
"a=ssrc:%u cname:FFmpeg\r\n"
221+
"a=ssrc:%u msid:FFmpeg audio\r\n"
222+
""
223+
"m=video 9 UDP/TLS/RTP/SAVPF %u\r\n"
224+
"c=IN IP4 0.0.0.0\r\n"
225+
"a=ice-ufrag:%s\r\n"
226+
"a=ice-pwd:%s\r\n"
227+
"a=fingerprint:sha-256 EE:FE:A2:E5:6A:21:78:60:71:2C:21:DC:1A:2C:98:12:0C:E8:AD:68:07:61:1B:0E:FC:46:97:1E:BC:97:4A:54\r\n"
228+
"a=setup:active\r\n"
229+
"a=mid:1\r\n"
230+
"a=sendonly\r\n"
231+
"a=msid:FFmpeg video\r\n"
232+
"a=rtcp-mux\r\n"
233+
"a=rtcp-rsize\r\n"
234+
"a=rtpmap:%u H264/90000\r\n"
235+
"a=fmtp:%u level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=%02x%02x%02x\r\n"
236+
"a=ssrc:%u cname:FFmpeg\r\n"
237+
"a=ssrc:%u msid:FFmpeg video\r\n",
238+
rtc->audio_payload_type,
239+
rtc->ice_ufrag_local,
240+
rtc->ice_pwd_local,
241+
rtc->audio_payload_type,
242+
rtc->audio_par->sample_rate,
243+
rtc->audio_par->ch_layout.nb_channels,
244+
rtc->audio_ssrc,
245+
rtc->audio_ssrc,
246+
rtc->video_payload_type,
247+
rtc->ice_ufrag_local,
248+
rtc->ice_pwd_local,
249+
rtc->video_payload_type,
250+
rtc->video_payload_type,
251+
rtc->video_par->profile & (~FF_PROFILE_H264_CONSTRAINED),
252+
profile_iop,
253+
rtc->video_par->level,
254+
rtc->video_ssrc,
255+
rtc->video_ssrc
256+
);
257+
av_log(s, AV_LOG_VERBOSE, "Generated offer: %s", rtc->sdp_offer);
258+
259+
return 0;
260+
}
261+
262+
/**
263+
* Exchange SDP offer with WebRTC peer to get the answer.
264+
* The below is an example of SDP answer:
265+
*
266+
* v=0
267+
* o=SRS/6.0.42(Bee) 107408542208384 2 IN IP4 0.0.0.0
268+
* s=SRSPublishSession
269+
* t=0 0
270+
* a=ice-lite
271+
* a=group:BUNDLE 0 1
272+
* a=msid-semantic: WMS live/show
273+
*
274+
* m=audio 9 UDP/TLS/RTP/SAVPF 111
275+
* c=IN IP4 0.0.0.0
276+
* a=ice-ufrag:ex9061f9
277+
* a=ice-pwd:bi8k19m9n836187b00d1gm3946234w85
278+
* a=fingerprint:sha-256 68:DD:7A:95:27:BD:0A:99:F4:7A:83:21:2F:50:15:2A:1D:1F:8A:D8:96:24:42:2D:A1:83:99:BF:F1:E2:11:A2
279+
* a=setup:passive
280+
* a=mid:0
281+
* a=recvonly
282+
* a=rtcp-mux
283+
* a=rtcp-rsize
284+
* a=rtpmap:111 opus/48000/2
285+
* a=candidate:0 1 udp 2130706431 172.20.10.7 8000 typ host generation 0
286+
*
287+
* m=video 9 UDP/TLS/RTP/SAVPF 106
288+
* c=IN IP4 0.0.0.0
289+
* a=ice-ufrag:ex9061f9
290+
* a=ice-pwd:bi8k19m9n836187b00d1gm3946234w85
291+
* a=fingerprint:sha-256 68:DD:7A:95:27:BD:0A:99:F4:7A:83:21:2F:50:15:2A:1D:1F:8A:D8:96:24:42:2D:A1:83:99:BF:F1:E2:11:A2
292+
* a=setup:passive
293+
* a=mid:1
294+
* a=recvonly
295+
* a=rtcp-mux
296+
* a=rtcp-rsize
297+
* a=rtpmap:106 H264/90000
298+
* a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01e
299+
* a=candidate:0 1 udp 2130706431 172.20.10.7 8000 typ host generation 0
300+
*
301+
* @return 0 if OK, AVERROR_xxx on error
302+
*/
303+
static int exchange_sdp(AVFormatContext *s)
304+
{
305+
int ret;
306+
char headers[MAX_URL_SIZE], buf[MAX_URL_SIZE];
307+
char *p;
308+
RTCContext *rtc = s->priv_data;
309+
310+
ret = ffurl_alloc(&rtc->whip_uc, s->url, AVIO_FLAG_READ_WRITE, &s->interrupt_callback);
311+
if (ret < 0) {
312+
av_log(s, AV_LOG_ERROR, "Failed to alloc HTTP context: %s", s->url);
313+
return ret;
314+
}
315+
316+
snprintf(headers, sizeof(headers),
317+
"Cache-Control: no-cache\r\n"
318+
"Content-Type: application/sdp\r\n");
319+
av_opt_set(rtc->whip_uc->priv_data, "headers", headers, 0);
320+
av_opt_set(rtc->whip_uc->priv_data, "chunked_post", "0", 0);
321+
av_opt_set_bin(rtc->whip_uc->priv_data, "post_data", rtc->sdp_offer, (int)strlen(rtc->sdp_offer), 0);
322+
323+
ret = ffurl_connect(rtc->whip_uc, NULL);
324+
if (ret < 0) {
325+
av_log(s, AV_LOG_ERROR, "Failed to request url=%s, offer: %s", s->url, rtc->sdp_offer);
326+
return ret;
327+
}
328+
329+
for (;;) {
330+
ret = ffurl_read(rtc->whip_uc, buf, sizeof(buf));
331+
if (ret == AVERROR_EOF) {
332+
/* Reset the error because we read all response as answer util EOF. */
333+
ret = 0;
334+
break;
335+
}
336+
if (ret <= 0) {
337+
av_log(s, AV_LOG_ERROR, "Failed to read response from url=%s, offer is %s, answer is %s",
338+
s->url, rtc->sdp_offer, rtc->sdp_answer);
339+
return ret;
340+
}
341+
342+
p = rtc->sdp_answer;
343+
rtc->sdp_answer = av_asprintf("%s%.*s", p ? p : "", ret, buf);
344+
av_free(p);
345+
}
346+
av_log(s, AV_LOG_VERBOSE, "Got answer: %s", rtc->sdp_answer);
347+
348+
return ret;
349+
}
350+
106351
static int rtc_init(AVFormatContext *s)
107352
{
108353
int ret;
109354

110355
if ((ret = check_codec(s)) < 0)
111356
return ret;
112357

358+
if ((ret = generate_sdp_offer(s)) < 0)
359+
return ret;
360+
361+
if ((ret = exchange_sdp(s)) < 0)
362+
return ret;
363+
113364
return 0;
114365
}
115366

@@ -130,6 +381,10 @@ static int rtc_write_trailer(AVFormatContext *s)
130381

131382
static void rtc_deinit(AVFormatContext *s)
132383
{
384+
RTCContext *rtc = s->priv_data;
385+
av_freep(&rtc->sdp_offer);
386+
av_freep(&rtc->sdp_answer);
387+
ffurl_closep(&rtc->whip_uc);
133388
}
134389

135390
static const AVOption options[] = {

0 commit comments

Comments
 (0)