Skip to content

Commit

Permalink
WHIP: Generate offer and exchange with server to get answer.
Browse files Browse the repository at this point in the history
  • Loading branch information
yangrtc authored and winlinvip committed Apr 21, 2023
1 parent 26d4cea commit cf69b01
Showing 1 changed file with 260 additions and 0 deletions.
260 changes: 260 additions & 0 deletions libavformat/rtcenc.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,42 @@
#include "mux.h"
#include "libavutil/opt.h"
#include "libavcodec/avcodec.h"
#include "libavutil/avstring.h"
#include "url.h"
#include "libavutil/random_seed.h"

typedef struct RTCContext {
AVClass *av_class;

/* Input audio and video codec parameters */
AVCodecParameters *audio_par;
AVCodecParameters *video_par;

/* The ICE username and pwd fragment generated by the muxer. */
char ice_ufrag_local[9];
char ice_pwd_local[33];
/* The SSRC of the audio and video stream, generated by the muxer. */
uint32_t audio_ssrc;
uint32_t video_ssrc;
/* The PT(Payload Type) of stream, generated by the muxer. */
uint8_t audio_pt;
uint8_t video_pt;
/**
* The SDP offer generated by the muxer according to the codec parameters,
* DTLS and ICE information.
* */
char *sdp_offer;
/* The SDP answer received from the WebRTC server. */
char *sdp_answer;
/* The HTTP URL context is the transport layer for the WHIP protocol. */
URLContext *whip_uc;
} RTCContext;

/**
* Only support video(h264) and audio(opus) for now. Note that only baseline
* and constrained baseline of h264 are supported.
*
* @return 0 if OK, AVERROR_xxx on error
*/
static int check_codec(AVFormatContext *s)
{
Expand Down Expand Up @@ -112,13 +136,245 @@ static int check_codec(AVFormatContext *s)
return 0;
}

/**
* Generate SDP offer according to the codec parameters, DTLS and ICE information.
* The below is an example of SDP offer:
*
* v=0
* o=FFmpeg 4489045141692799359 2 IN IP4 127.0.0.1
* s=FFmpegPublishSession
* t=0 0
* a=group:BUNDLE 0 1
* a=extmap-allow-mixed
* a=msid-semantic: WMS
*
* m=audio 9 UDP/TLS/RTP/SAVPF 111
* c=IN IP4 0.0.0.0
* a=ice-ufrag:a174B
* a=ice-pwd:wY8rJ3gNLxL3eWZs6UPOxy
* 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
* a=setup:actpass
* a=mid:0
* a=sendonly
* a=msid:FFmpeg audio
* a=rtcp-mux
* a=rtpmap:111 opus/48000/2
* a=ssrc:4267647086 cname:FFmpeg
* a=ssrc:4267647086 msid:FFmpeg audio
*
* m=video 9 UDP/TLS/RTP/SAVPF 106
* c=IN IP4 0.0.0.0
* a=ice-ufrag:a174B
* a=ice-pwd:wY8rJ3gNLxL3eWZs6UPOxy
* 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
* a=setup:actpass
* a=mid:1
* a=sendonly
* a=msid:FFmpeg video
* a=rtcp-mux
* a=rtcp-rsize
* a=rtpmap:106 H264/90000
* a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
* a=ssrc:107169110 cname:FFmpeg
* a=ssrc:107169110 msid:FFmpeg video
*
* Note that we don't use av_sdp_create to generate SDP offer because it doesn't
* support DTLS and ICE information.
*
* @return 0 if OK, AVERROR_xxx on error
*/
static int generate_sdp_offer(AVFormatContext *s)
{
int profile_iop;
RTCContext *rtc = s->priv_data;

if (rtc->sdp_offer) {
av_log(s, AV_LOG_ERROR, "SDP offer is already set\n");
return AVERROR(EINVAL);
}

/* Generate a random ICE ufrag and password by av_get_random_seed. */
snprintf(rtc->ice_ufrag_local, sizeof(rtc->ice_ufrag_local), "%08x",
av_get_random_seed());
snprintf(rtc->ice_pwd_local, sizeof(rtc->ice_pwd_local), "%08x%08x%08x%08x",
av_get_random_seed(), av_get_random_seed(), av_get_random_seed(),
av_get_random_seed());

/* Generate audio and video SSRCs. */
rtc->audio_ssrc = av_get_random_seed();
rtc->video_ssrc = av_get_random_seed();

/* Set up the PT(Payload Type). */
rtc->audio_pt = 111;
rtc->video_pt = 106;

profile_iop = rtc->video_par->profile & FF_PROFILE_H264_CONSTRAINED ? 0xe0 : 0x00;
rtc->sdp_offer = av_asprintf(
"v=0\r\n"
"o=FFmpeg 4489045141692799359 2 IN IP4 127.0.0.1\r\n"
"s=FFmpegPublishSession\r\n"
"t=0 0\r\n"
"a=group:BUNDLE 0 1\r\n"
"a=extmap-allow-mixed\r\n"
"a=msid-semantic: WMS\r\n"
""
"m=audio 9 UDP/TLS/RTP/SAVPF %u\r\n"
"c=IN IP4 0.0.0.0\r\n"
"a=ice-ufrag:%s\r\n"
"a=ice-pwd:%s\r\n"
"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"
"a=setup:active\r\n"
"a=mid:0\r\n"
"a=sendonly\r\n"
"a=msid:FFmpeg audio\r\n"
"a=rtcp-mux\r\n"
"a=rtpmap:%u opus/48000/2\r\n"
"a=ssrc:%u cname:FFmpeg\r\n"
"a=ssrc:%u msid:FFmpeg audio\r\n"
""
"m=video 9 UDP/TLS/RTP/SAVPF %u\r\n"
"c=IN IP4 0.0.0.0\r\n"
"a=ice-ufrag:%s\r\n"
"a=ice-pwd:%s\r\n"
"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"
"a=setup:active\r\n"
"a=mid:1\r\n"
"a=sendonly\r\n"
"a=msid:FFmpeg video\r\n"
"a=rtcp-mux\r\n"
"a=rtcp-rsize\r\n"
"a=rtpmap:%u H264/90000\r\n"
"a=fmtp:%u level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=%02x%02x%02x\r\n"
"a=ssrc:%u cname:FFmpeg\r\n"
"a=ssrc:%u msid:FFmpeg video\r\n",
rtc->audio_pt,
rtc->ice_ufrag_local,
rtc->ice_pwd_local,
rtc->audio_pt,
rtc->audio_ssrc,
rtc->audio_ssrc,
rtc->video_pt,
rtc->ice_ufrag_local,
rtc->ice_pwd_local,
rtc->video_pt,
rtc->video_pt,
rtc->video_par->profile & (~FF_PROFILE_H264_CONSTRAINED),
profile_iop,
rtc->video_par->level,
rtc->video_ssrc,
rtc->video_ssrc
);
av_log(s, AV_LOG_VERBOSE, "Generated offer: %s", rtc->sdp_offer);

return 0;
}

/**
* Exchange SDP offer with WebRTC peer to get the answer.
* The below is an example of SDP answer:
*
* v=0
* o=SRS/6.0.42(Bee) 107408542208384 2 IN IP4 0.0.0.0
* s=SRSPublishSession
* t=0 0
* a=ice-lite
* a=group:BUNDLE 0 1
* a=msid-semantic: WMS live/show
*
* m=audio 9 UDP/TLS/RTP/SAVPF 111
* c=IN IP4 0.0.0.0
* a=ice-ufrag:ex9061f9
* a=ice-pwd:bi8k19m9n836187b00d1gm3946234w85
* 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
* a=setup:passive
* a=mid:0
* a=recvonly
* a=rtcp-mux
* a=rtcp-rsize
* a=rtpmap:111 opus/48000/2
* a=candidate:0 1 udp 2130706431 172.20.10.7 8000 typ host generation 0
*
* m=video 9 UDP/TLS/RTP/SAVPF 106
* c=IN IP4 0.0.0.0
* a=ice-ufrag:ex9061f9
* a=ice-pwd:bi8k19m9n836187b00d1gm3946234w85
* 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
* a=setup:passive
* a=mid:1
* a=recvonly
* a=rtcp-mux
* a=rtcp-rsize
* a=rtpmap:106 H264/90000
* a=fmtp:106 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01e
* a=candidate:0 1 udp 2130706431 172.20.10.7 8000 typ host generation 0
*
* @return 0 if OK, AVERROR_xxx on error
*/
static int exchange_sdp(AVFormatContext *s)
{
int ret;
char headers[MAX_URL_SIZE], buf[MAX_URL_SIZE];
char *p;
RTCContext *rtc = s->priv_data;

ret = ffurl_alloc(&rtc->whip_uc, s->url, AVIO_FLAG_READ_WRITE, &s->interrupt_callback);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Failed to alloc HTTP context: %s", s->url);
return ret;
}

/* Set the options to disable chunked_post and enable post for the WHIP IO. */
av_opt_set(rtc->whip_uc->priv_data, "chunked_post", "0", 0);
/* Set the HTTP header Content-Type for the SDP offer. */
snprintf(headers, sizeof(headers),
"Cache-Control: no-cache\r\n"
"Content-Type: application/sdp\r\n");
av_opt_set(rtc->whip_uc->priv_data, "headers", headers, 0);
/* Set the offer as the post data, to send when connection HTTP. */
av_opt_set_bin(rtc->whip_uc->priv_data, "post_data", rtc->sdp_offer, (int)strlen(rtc->sdp_offer), 0);

/* Open the HTTP URL with POST and send the SDP offer. */
ret = ffurl_connect(rtc->whip_uc, NULL);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Failed to open WHIP URL: %s", s->url);
return ret;
}

/* Read the answer from response */
for (;;) {
ret = ffurl_read(rtc->whip_uc, buf, sizeof(buf));
if (ret == AVERROR_EOF) {
/* Reset the error because we read all response as answer util EOF. */
ret = 0;
break;
}
if (ret <= 0) {
av_log(s, AV_LOG_ERROR, "Failed to read response from URL: %s", s->url);
return ret;
}

p = rtc->sdp_answer;
rtc->sdp_answer = av_asprintf("%s%.*s", p ? p : "", ret, buf);
av_free(p);
}
av_log(s, AV_LOG_VERBOSE, "Got answer: %s", rtc->sdp_answer);

return ret;
}

static int rtc_init(AVFormatContext *s)
{
int ret;

if ((ret = check_codec(s)) < 0)
return ret;

if ((ret = generate_sdp_offer(s)) < 0)
return ret;

if ((ret = exchange_sdp(s)) < 0)
return ret;

return 0;
}

Expand All @@ -139,6 +395,10 @@ static int rtc_write_trailer(AVFormatContext *s)

static void rtc_deinit(AVFormatContext *s)
{
RTCContext *rtc = s->priv_data;
av_freep(&rtc->sdp_offer);
av_freep(&rtc->sdp_answer);
ffurl_closep(&rtc->whip_uc);
}

static const AVOption options[] = {
Expand Down

0 comments on commit cf69b01

Please sign in to comment.