458 lines
15 KiB
C++
458 lines
15 KiB
C++
#include "rtsp-media.h"
|
|
#include "sdp.h"
|
|
#include "sdp-options.h"
|
|
#include "sdp-a-fmtp.h"
|
|
#include "sdp-a-rtpmap.h"
|
|
#include "sys/path.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
static inline int scopy(struct rtsp_media_t* medias, char** dst, const char* src)
|
|
{
|
|
int n;
|
|
n = snprintf(medias->ptr + medias->offset, sizeof(medias->ptr) - medias->offset, "%s", src);
|
|
if (n < 0 || n >= (int)sizeof(medias->ptr) - medias->offset)
|
|
return -1;
|
|
*dst = medias->ptr + medias->offset;
|
|
medias->offset += n + 1; // with '\0'
|
|
return 0;
|
|
}
|
|
|
|
//static inline int vscopy(struct rtsp_media_t* medias, char** dst, const char* fmt, ...)
|
|
//{
|
|
// int n;
|
|
// va_list args;
|
|
// va_start(args, fmt);
|
|
// n = vsnprintf(medias->ptr + medias->offset, sizeof(medias->ptr) - medias->offset, fmt, args);
|
|
// va_end(args);
|
|
//
|
|
// if (n < 0 || n >= (int)sizeof(medias->ptr) - medias->offset)
|
|
// return -1;
|
|
// *dst = medias->ptr + medias->offset;
|
|
// medias->offset += n + 1; // with '\0'
|
|
// return 0;
|
|
//}
|
|
//
|
|
//static inline int rtsp_media_aggregate_control_enable(void *sdp)
|
|
//{
|
|
// const char* control;
|
|
//
|
|
// // rfc 2326 C.1.1 Control URL (p80)
|
|
// // If found at the session level, the attribute indicates the URL for aggregate control
|
|
// control = sdp_attribute_find(sdp, "control");
|
|
// return (control && *control) ? 1 : 0;
|
|
//}
|
|
|
|
// rfc 2326 C.1.1 Control URL (p81)
|
|
// look for a base URL in the following order:
|
|
// 1. The RTSP Content-Base field
|
|
// 2. The RTSP Content-Location field
|
|
// 3. The RTSP request URL
|
|
int rtsp_media_set_url(struct rtsp_media_t* m, const char* base, const char* location, const char* request)
|
|
{
|
|
int r;
|
|
char buffer[256] = { 0 };
|
|
|
|
// C.1.1 Control URL (p81)
|
|
// If this attribute contains only an asterisk (*), then the URL is
|
|
// treated as if it were an empty embedded URL, and thus inherits the entire base URL.
|
|
if (m->session_uri[0] && '*' != m->session_uri[0])
|
|
{
|
|
snprintf(buffer, sizeof(buffer)-1, "%s", m->session_uri);
|
|
r = path_resolve2(m->session_uri, sizeof(m->session_uri)-1, buffer, base, location, request);
|
|
}
|
|
else if('*' == m->session_uri[0])
|
|
{
|
|
r = snprintf(m->session_uri, sizeof(m->session_uri) - 1, "%s", request);
|
|
}
|
|
else
|
|
{
|
|
// keep session uri empty
|
|
r = 0;
|
|
}
|
|
|
|
if ('*' != m->uri[0])
|
|
{
|
|
snprintf(buffer, sizeof(buffer) - 1, "%s", m->uri);
|
|
r = path_resolve2(m->uri, sizeof(m->uri) - 1, buffer, base, location, request);
|
|
}
|
|
else
|
|
{
|
|
r = snprintf(m->uri, sizeof(m->uri) - 1, "%s", request);
|
|
}
|
|
|
|
return r >= 0 ? 0 : r;
|
|
}
|
|
|
|
// RFC 6184 RTP Payload Format for H.264 Video
|
|
// 8.2.1. Mapping of Payload Type Parameters to SDP
|
|
// m=video 49170 RTP/AVP 98
|
|
// a=rtpmap:98 H264/90000
|
|
// a=fmtp:98 profile-level-id=42A01E;
|
|
// packetization-mode=1;
|
|
// sprop-parameter-sets=<parameter sets data>
|
|
static void rtsp_media_onattr(void* param, const char* name, const char* value)
|
|
{
|
|
int i, n;
|
|
int payload = -1;
|
|
struct rtsp_media_t* media;
|
|
|
|
media = (struct rtsp_media_t*)param;
|
|
|
|
if (name)
|
|
{
|
|
if (0 == strcmp("rtpmap", name))
|
|
{
|
|
int rate = 0;
|
|
char channel[64];
|
|
char encoding[16 + sizeof(media->avformats[i].encoding)];
|
|
if (strlen(value) < sizeof(encoding)) // make sure encoding have enough memory space
|
|
{
|
|
channel[0] = '\0';
|
|
sdp_a_rtpmap(value, &payload, encoding, &rate, channel);
|
|
for (i = 0; i < media->avformat_count; i++)
|
|
{
|
|
if (media->avformats[i].fmt == payload)
|
|
{
|
|
media->avformats[i].rate = rate;
|
|
media->avformats[i].channel = *channel ? atoi(channel) : 1; // default 1-channel
|
|
snprintf(media->avformats[i].encoding, sizeof(media->avformats[i].encoding), "%s", encoding);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (0 == strcmp("fmtp", name))
|
|
{
|
|
n = (int)strlen(value);
|
|
payload = atoi(value);
|
|
for (i = 0; i < media->avformat_count && media->offset + n + 1 < sizeof(media->ptr); i++)
|
|
{
|
|
if (media->avformats[i].fmt != payload)
|
|
continue;
|
|
|
|
media->avformats[i].fmtp = media->ptr + media->offset;
|
|
strcpy(media->avformats[i].fmtp, value);
|
|
media->offset += n + 1;
|
|
//if(0 == strcmp("H264", media->avformats[i].encoding))
|
|
//{
|
|
// struct sdp_a_fmtp_h264_t h264;
|
|
// memset(&h264, 0, sizeof(h264));
|
|
// sdp_a_fmtp_h264(value, &payload, &h264);
|
|
// if(h264.flags & SDP_A_FMTP_H264_SPROP_PARAMETER_SETS)
|
|
// snprintf(media->avformats[i].ps, sizeof(media->avformats[i].ps), "%s", h264.sprop_parameter_sets);
|
|
//}
|
|
//else if (0 == strcmp("H265", media->avformats[i].encoding))
|
|
//{
|
|
// struct sdp_a_fmtp_h265_t h265;
|
|
// memset(&h265, 0, sizeof(h265));
|
|
// sdp_a_fmtp_h265(value, &payload, &h265);
|
|
// //if (h265.flags & SDP_A_FMTP_H265_SPROP_VPS)
|
|
// // snprintf(media->avformats[i].ps, sizeof(media->avformats[i].ps), "%s", h265.sprop_vps);
|
|
// //if (h265.flags & SDP_A_FMTP_H265_SPROP_SPS)
|
|
// // snprintf(media->avformats[i].ps, sizeof(media->avformats[i].ps), "%s", h265.sprop_sps);
|
|
// //if (h265.flags & SDP_A_FMTP_H265_SPROP_PPS)
|
|
// // snprintf(media->avformats[i].ps, sizeof(media->avformats[i].ps), "%s", h265.sprop_pps);
|
|
//}
|
|
//else if(0 == strcmp("mpeg4-generic", media->avformats[i].encoding))
|
|
//{
|
|
// struct sdp_a_fmtp_mpeg4_t mpeg4;
|
|
// memset(&mpeg4, 0, sizeof(mpeg4));
|
|
// sdp_a_fmtp_mpeg4(value, &payload, &mpeg4);
|
|
//}
|
|
break;
|
|
}
|
|
}
|
|
else if (0 == strcmp("etag", name))
|
|
{
|
|
// C.1.8 Entity Tag
|
|
}
|
|
else if (0 == strcmp("rtcp", name))
|
|
{
|
|
// rfc3605 Real Time Control Protocol (RTCP) attribute in Session Description Protocol (SDP)
|
|
// "a=rtcp:" port [nettype space addrtype space connection-address] CRLF
|
|
// a=rtcp:53020 IN IP6 2001:2345:6789:ABCD:EF01:2345:6789:ABCD
|
|
assert(media->nport <= 2 && sizeof(media->port)/sizeof(media->port[0]) >= 2);
|
|
if (1 == media->nport)
|
|
media->nport++;
|
|
media->port[1] = atoi(value);
|
|
|
|
// TODO: rtcp address
|
|
}
|
|
else if (0 == strcmp("rtcp-mux", name))
|
|
{
|
|
if (1 == media->nport)
|
|
media->nport++;
|
|
media->port[1] = media->port[0];
|
|
}
|
|
else if (0 == strcmp("rtcp-xr", name))
|
|
{
|
|
// rfc3611 RTP Control Protocol Extended Reports (RTCP XR)
|
|
// "a=rtcp-xr:" [xr-format *(SP xr-format)] CRLF
|
|
}
|
|
else if (0 == strcmp("ice-pwd", name))
|
|
{
|
|
scopy(media, &media->ice.pwd, value);
|
|
}
|
|
else if (0 == strcmp("ice-ufrag", name))
|
|
{
|
|
scopy(media, &media->ice.ufrag, value);
|
|
}
|
|
else if (0 == strcmp("ice-lite", name))
|
|
{
|
|
media->ice.lite = 1;
|
|
}
|
|
else if (0 == strcmp("ice-mismatch", name))
|
|
{
|
|
media->ice.mismatch = 1;
|
|
}
|
|
else if (0 == strcmp("ice-pacing", name))
|
|
{
|
|
media->ice.pacing = atoi(value);
|
|
}
|
|
else if (0 == strcmp("candidate", name))
|
|
{
|
|
if (media->ice.candidate_count + 1 < sizeof(media->ice.candidates) / sizeof(media->ice.candidates[0]) && media->offset + sizeof(*media->ice.candidates[0]) <= sizeof(media->ptr))
|
|
{
|
|
media->ice.candidates[media->ice.candidate_count] = (struct sdp_candidate_t*)(media->ptr + media->offset);
|
|
if (7 == sscanf(value, "%32s %hu %7s %u %63s %hu typ %7s%n",
|
|
media->ice.candidates[media->ice.candidate_count]->foundation,
|
|
&media->ice.candidates[media->ice.candidate_count]->component,
|
|
media->ice.candidates[media->ice.candidate_count]->transport,
|
|
&media->ice.candidates[media->ice.candidate_count]->priority,
|
|
media->ice.candidates[media->ice.candidate_count]->address,
|
|
&media->ice.candidates[media->ice.candidate_count]->port,
|
|
media->ice.candidates[media->ice.candidate_count]->candtype, &n))
|
|
{
|
|
sscanf(value + n, " raddr %63s rport %hu", media->ice.candidates[media->ice.candidate_count]->reladdr, &media->ice.candidates[media->ice.candidate_count]->relport);
|
|
media->offset += sizeof(*media->ice.candidates[0]);
|
|
++media->ice.candidate_count;
|
|
}
|
|
}
|
|
}
|
|
else if (0 == strcmp("remote-candidates", name))
|
|
{
|
|
while (media->ice.remote_count + 1 < sizeof(media->ice.remotes) / sizeof(media->ice.remotes[0]) && media->offset + sizeof(*media->ice.remotes[0]) <= sizeof(media->ptr))
|
|
{
|
|
media->ice.remotes[media->ice.remote_count] = (struct sdp_candidate_t*)(media->ptr + media->offset);
|
|
if (!value || 3 != sscanf(value, "%hu %63s %hu%n", &media->ice.remotes[media->ice.remote_count]->component, media->ice.remotes[media->ice.remote_count]->address, &media->ice.remotes[media->ice.remote_count]->port, &n))
|
|
break;
|
|
|
|
value += n;
|
|
++media->ice.remote_count;
|
|
media->offset += sizeof(*media->ice.remotes[0]);
|
|
}
|
|
}
|
|
else if (0 == strcmp("setup", name))
|
|
{
|
|
media->setup = sdp_option_setup_from(value);
|
|
}
|
|
else if (0 == strcmp("ssrc", name))
|
|
{
|
|
media->ssrc.ssrc = (uint32_t)strtoul(value, NULL, 10);
|
|
// TODO: ssrc attribute
|
|
}
|
|
else if (0 == strcmp("ssrc-group", name))
|
|
{
|
|
// TODO
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
v=0
|
|
o=mhandley 2890844526 2890842807 IN IP4 126.16.64.4
|
|
s=SDP Seminar
|
|
i=A Seminar on the session description protocol
|
|
u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps
|
|
e=mjh@isi.edu (Mark Handley)
|
|
c=IN IP4 224.2.17.12/127
|
|
t=2873397496 2873404696
|
|
a=recvonly
|
|
m=audio 3456 RTP/AVP 0
|
|
m=video 2232 RTP/AVP 31
|
|
m=whiteboard 32416 UDP WB
|
|
a=orient:portrait
|
|
*/
|
|
int rtsp_media_sdp(const char* s, int len, struct rtsp_media_t* medias, int count)
|
|
{
|
|
int i, j, n;
|
|
int formats[16];
|
|
const char* control;
|
|
const char* start, *stop;
|
|
const char* iceufrag, *icepwd, *setup;
|
|
const char* username, *session, *version;
|
|
const char* network, *addrtype, *address, *source;
|
|
struct rtsp_media_t* m;
|
|
struct rtsp_header_range_t range;
|
|
sdp_t* sdp;
|
|
|
|
sdp = sdp_parse(s, len);
|
|
if (!sdp)
|
|
return -1;
|
|
|
|
// rfc 2326 C.1.1 Control URL (p80)
|
|
// If found at the session level, the attribute indicates the URL for aggregate control
|
|
control = sdp_attribute_find(sdp, "control");
|
|
|
|
// C.1.5 Range of presentation
|
|
// The "a=range" attribute defines the total time range of the stored session.
|
|
memset(&range, 0, sizeof(range));
|
|
s = sdp_attribute_find(sdp, "range");
|
|
if(s) rtsp_header_range(s, &range);
|
|
|
|
// C.1.6 Time of availability
|
|
start = stop = NULL;
|
|
for (i = 0; i < sdp_timing_count(sdp); i++)
|
|
{
|
|
sdp_timing_get(sdp, i, &start, &stop);
|
|
}
|
|
|
|
// C.1.7 Connection Information
|
|
network = addrtype = source = NULL;
|
|
if (0 != sdp_connection_get(sdp, &network, &addrtype, &source) || 0 == strcmp("0.0.0.0", source))
|
|
sdp_origin_get(sdp, &username, &session, &version, &network, &addrtype, &source);
|
|
|
|
// session ice-ufrag/ice-pwd
|
|
iceufrag = sdp_attribute_find(sdp, "ice-ufrag");
|
|
icepwd = sdp_attribute_find(sdp, "ice-pwd");
|
|
setup = sdp_attribute_find(sdp, "setup");
|
|
|
|
for (i = 0; i < sdp_media_count(sdp) && i < count; i++)
|
|
{
|
|
m = medias + i;
|
|
memset(m, 0, sizeof(struct rtsp_media_t));
|
|
memcpy(&m->range, &range, sizeof(m->range));
|
|
if (control)
|
|
snprintf(m->session_uri, sizeof(m->session_uri), "%s", control);
|
|
if (start && stop)
|
|
{
|
|
m->start = strtoull(start, NULL, 10);
|
|
m->stop = strtoull(stop, NULL, 10);
|
|
}
|
|
|
|
if(0 == sdp_media_get_connection(sdp, i, &network, &addrtype, &address))
|
|
{
|
|
if (0 == strcmp("IP4", addrtype) && 0 == strcmp("0.0.0.0", address) && source && *source)
|
|
address = source;
|
|
snprintf(m->source, sizeof(m->source), "%s", source && *source ? source : "");
|
|
snprintf(m->network, sizeof(m->network), "%s", network);
|
|
snprintf(m->address, sizeof(m->address), "%s", address);
|
|
snprintf(m->addrtype, sizeof(m->addrtype), "%s", addrtype);
|
|
}
|
|
//media->cseq = rand();
|
|
|
|
m->nport = sdp_media_port(sdp, i, m->port, sizeof(m->port)/sizeof(m->port[0]));
|
|
snprintf(m->media, sizeof(m->media), "%s", sdp_media_type(sdp, i));
|
|
snprintf(m->proto, sizeof(m->proto), "%s", sdp_media_proto(sdp, i));
|
|
if (1 == m->nport && 0 == strncmp("RTP/", m->proto, 4))
|
|
m->port[m->nport++] = m->port[0] + 1;
|
|
|
|
// media control url
|
|
s = sdp_media_attribute_find(sdp, i, "control");
|
|
if(s)
|
|
snprintf(m->uri, sizeof(m->uri), "%s", s);
|
|
|
|
// media format
|
|
j = sizeof(m->avformats) / sizeof(m->avformats[0]);
|
|
assert(sizeof(formats) / sizeof(formats[0]) >= j);
|
|
n = sdp_media_formats(sdp, i, formats, j);
|
|
m->avformat_count = n > j ? j : n;
|
|
for (j = 0; j < m->avformat_count; j++)
|
|
m->avformats[j].fmt = formats[j];
|
|
|
|
// TODO: plan-B streams
|
|
m->mode = sdp_media_mode(sdp, i);
|
|
m->setup = setup ? sdp_option_setup_from(setup) : SDP_A_SETUP_NONE;
|
|
|
|
// update media encoding
|
|
sdp_media_attribute_list(sdp, i, NULL, rtsp_media_onattr, m);
|
|
|
|
// use default ice-ufrag/pwd
|
|
if(NULL == m->ice.ufrag && iceufrag)
|
|
scopy(medias, &m->ice.ufrag, iceufrag);
|
|
if(NULL == m->ice.pwd && icepwd)
|
|
scopy(medias, &m->ice.pwd, icepwd);
|
|
}
|
|
|
|
count = sdp_media_count(sdp);
|
|
sdp_destroy(sdp);
|
|
return count; // should check return value
|
|
}
|
|
|
|
/// @return -0-no media, >0-ok, <0-error
|
|
int rtsp_media_to_sdp(const struct rtsp_media_t* m, char* line, int bytes)
|
|
{
|
|
int i, n;
|
|
int setup = 0;
|
|
int port = m->port[0];
|
|
|
|
if (SDP_M_PROTO_TEST_TCP(sdp_option_proto_from(m->proto)))
|
|
{
|
|
// try to set tcp active for sender side
|
|
setup = (SDP_A_SETUP_NONE == m->setup || SDP_A_SETUP_ACTPASS == m->setup) ? SDP_A_SETUP_ACTIVE : m->setup;
|
|
//if (SDP_A_SETUP_PASSIVE == setup || SDP_A_SETUP_ACTPASS == setup)
|
|
// port = options->m[i].port[0];
|
|
}
|
|
|
|
n = snprintf(line, bytes, "m=%s %d %s", m->media, port, m->proto);
|
|
for (i = 0; i < m->avformat_count && n < bytes; i++)
|
|
{
|
|
if (m->avformats[i].fmt >= 96 && !m->avformats[i].encoding[0])
|
|
continue; // ignore empty encoding
|
|
n += snprintf(line + n, bytes - n, " %d", m->avformats[i].fmt);
|
|
}
|
|
n += snprintf(line + n, bytes > n ? bytes - n : 0, "\n");
|
|
|
|
for (i = 0; i < m->avformat_count && n >= 0 && n < bytes; i++)
|
|
{
|
|
if (!m->avformats[i].encoding[0])
|
|
continue;
|
|
|
|
if (SDP_M_MEDIA_VIDEO == sdp_option_media_from(m->media))
|
|
{
|
|
n += snprintf(line + n, bytes - n, "a=rtpmap:%d %s/%d\n", m->avformats[i].fmt, m->avformats[i].encoding, m->avformats[i].rate ? m->avformats[i].rate : 90000);
|
|
if(n >= 0 && n < bytes && m->avformats[i].fmtp && m->avformats[i].fmtp[0])
|
|
n += snprintf(line + n, bytes - n, "a=fmtp:%s\n", m->avformats[i].fmtp);
|
|
}
|
|
else if (SDP_M_MEDIA_AUDIO == sdp_option_media_from(m->media))
|
|
{
|
|
if(m->avformats[i].channel > 0)
|
|
n += snprintf(line + n, bytes - n, "a=rtpmap:%d %s/%d/%d\n", m->avformats[i].fmt, m->avformats[i].encoding, m->avformats[i].rate, m->avformats[i].channel);
|
|
else
|
|
n += snprintf(line + n, bytes - n, "a=rtpmap:%d %s/%d\n", m->avformats[i].fmt, m->avformats[i].encoding, m->avformats[i].rate);
|
|
|
|
if (n >= 0 && n < bytes && m->avformats[i].fmtp && m->avformats[i].fmtp[0])
|
|
n += snprintf(line + n, bytes - n, "a=fmtp:%s\n", m->avformats[i].fmtp);
|
|
}
|
|
}
|
|
|
|
//for (int j = 0; j < 128 && j < 8 * sizeof(m->payloads) / sizeof(m->payloads[0]); j++)
|
|
//{
|
|
// if(m->payloads[j/8] & (1<<(j%8)))
|
|
// n += snprintf(answer+n, sizeof(answer)-n, " %d", j);
|
|
//}
|
|
|
|
if (SDP_M_PROTO_TEST_TCP(sdp_option_proto_from(m->proto)))
|
|
{
|
|
n += snprintf(line + n, bytes - n, "a=setup:%s\n", sdp_option_setup_to(setup));
|
|
}
|
|
|
|
if (m->nport < 2 || m->port[0] == m->port[1])
|
|
{
|
|
n += snprintf(line + n, bytes - n, "a=rtcp-mux\n");
|
|
}
|
|
|
|
n += snprintf(line + n, bytes - n, "a=%s\n", sdp_option_mode_to(m->mode));
|
|
|
|
if (m->ssrc.ssrc)
|
|
{
|
|
n += snprintf(line + n, bytes - n, "a=ssrc:%u\n", m->ssrc.ssrc);
|
|
}
|
|
|
|
return n > 0 && n < bytes ? n : -1;
|
|
}
|