开源工程
SAT>IP Server Minisatip
SAT>IP Server
流程具体见 SAT>IP 协议简介 及 SAT>IP 协议笔记
流程图如下:
Main
readBootID();
if ((ssdp = udp_bind(NULL, 1900)) < 1)
FAIL("SSDP: Could not bind on udp port 1900");
if ((ssdp1 = udp_bind(opts.disc_host, 1900)) < 1)
FAIL("SSDP: Could not bind on %s udp port 1900", opts.disc_host);
if ((rtsp = tcp_listen(NULL, opts.rtsp_port)) < 1)
FAIL("RTSP: Could not listen on port %d", opts.rtsp_port);
if ((http = tcp_listen(NULL, opts.http_port)) < 1)
FAIL("Could not listen on http port %d", opts.http_port);
si = sockets_add(ssdp, NULL, -1, TYPE_UDP, (socket_action)ssdp_reply, NULL,
(socket_action)ssdp_discovery);
si1 = sockets_add(ssdp1, NULL, -1, TYPE_UDP, (socket_action)ssdp_reply,
NULL, (socket_action)ssdp_discovery);
if (si < 0 || si1 < 0)
FAIL("sockets_add failed for ssdp");
sockets_timeout(si, 60 * 1000);
set_sockets_rtime(si, -60 * 1000);
if (0 > sockets_add(rtsp, NULL, -1, TYPE_SERVER, (socket_action)new_rtsp,
NULL, (socket_action)close_http))
FAIL("sockets_add failed for rtsp");
if (0 > sockets_add(http, NULL, -1, TYPE_SERVER, (socket_action)new_http,
NULL, (socket_action)close_http))
FAIL("sockets_add failed for http");
if (0 > (sock_signal = sockets_add(SOCK_TIMEOUT, NULL, -1, TYPE_UDP, NULL,
NULL, (socket_action)signal_thread)))
FAIL("sockets_add failed for signal thread");
if (!opts.no_threads)
{
set_socket_thread(sock_signal, start_new_thread("signal"));
sockets_timeout(sock_signal, 300); // 300 ms
}
else
sockets_timeout(sock_signal, 1000); // 1 sec
if (0 > (sock_bw = sockets_add(SOCK_TIMEOUT, NULL, -1, TYPE_UDP, NULL,
NULL, (socket_action)calculate_bw)))
FAIL("sockets_add failed for BW calculation");
Discovery
函数ssdp_discovery
以及ssdp_reply
int ssdp_discovery(sockets *s)
{
char *reply = "NOTIFY * HTTP/1.1\r\n"
"HOST: %s:1900\r\n"
"CACHE-CONTROL: max-age=1800\r\n"
"LOCATION: http://%s/%s\r\n"
"NT: %s\r\n"
"NTS: ssdp:alive\r\n"
"SERVER: Linux/1.0 UPnP/1.1 %s/%s\r\n"
"USN: uuid:%s%s\r\n"
"BOOTID.UPNP.ORG: %d\r\n"
"CONFIGID.UPNP.ORG: 0\r\n"
"DEVICEID.SES.COM: %d\r\n\r\n\0";
char buf[500], mac[15] = "00000000000000";
char nt[3][50];
char uuid1[] = "11223344-9999-0000-b7ae";
socklen_t salen;
int i;
s->wtime = getTick();
if (uuidi == 0)
{
uuidi = 1;
get_mac_address(mac);
sprintf(uuid, "%s-%s", uuid1, mac);
fill_sockaddr(&ssdp_sa, opts.disc_host, 1900);
}
strcpy(nt[0], "::upnp:rootdevice");
sprintf(nt[1], "::uuid:%s", uuid);
strcpy(nt[2], "::urn:ses-com:device:SatIPServer:1");
if (s->type != TYPE_UDP)
return 0;
LOGM("ssdp_discovery: bootid: %d deviceid: %d http: %s", opts.bootid,
opts.device_id, opts.http_host);
for (i = 0; i < 3; i++)
{
sprintf(buf, reply, opts.disc_host, opts.http_host, opts.xml_path,
nt[i] + 2, app_name, version, uuid, i == 1 ? "" : nt[i],
opts.bootid, opts.device_id);
salen = sizeof(ssdp_sa);
LOGM("Discovery packet %d:\n%s", i + 1, buf);
int wb = sendto(s->sock, buf, strlen(buf), MSG_NOSIGNAL, (const struct sockaddr *)&ssdp_sa, salen);
if (wb != strlen(buf))
LOG("incomplete ssdp_discovery: wrote %d out of %d: error %d: %s", wb, strlen(buf), errno, strerror(errno));
}
s->rtime = getTick();
return 0;
}
int ssdp;
int ssdp_reply(sockets *s)
{
char *reply = "HTTP/1.1 200 OK\r\n"
"CACHE-CONTROL: max-age=1800\r\n"
"DATE: %s\r\n"
"EXT:\r\n"
"LOCATION: http://%s/%s\r\n"
"SERVER: Linux/1.0 UPnP/1.1 %s/%s\r\n"
"ST: urn:ses-com:device:SatIPServer:1\r\n"
"USN: uuid:%s::urn:ses-com:device:SatIPServer:1\r\n"
"BOOTID.UPNP.ORG: %d\r\n"
"CONFIGID.UPNP.ORG: 0\r\n"
"DEVICEID.SES.COM: %d\r\n\r\n\0";
char *device_id_conflict = "M-SEARCH * HTTP/1.1\r\n"
"HOST: %s:1900\r\n"
"MAN: \"ssdp:discover\"\r\n"
"ST: urn:ses-com:device:SatIPServer:1\r\n"
"USER-AGENT: Linux/1.0 UPnP/1.1 %s/%s\r\n"
"DEVICEID.SES.COM: %d\r\n\r\n\0";
socklen_t salen;
char *man, *man_sd, *didsescom, *ruuid, *rdid;
char buf[500];
char ra[50];
int did = 0;
if (uuidi == 0)
ssdp_discovery(s);
s->rtime = s->wtime; // consider the timeout of the discovery operation
salen = sizeof(s->sa);
ruuid = strcasestr((const char *)s->buf, "uuid:");
if (ruuid && strncmp(uuid, strip(ruuid + 5), strlen(uuid)) == 0)
{
LOGM("Dropping packet from the same UUID as mine (from %s:%d)",
get_socket_rhost(s->id, ra, sizeof(ra)),
get_socket_rport(s->id));
return 0;
}
// not my uuid
#ifdef AXE
axe_set_network_led(1);
#endif
LOGM("Received SSDP packet from %s:%d -> handle %d",
get_socket_rhost(s->id, ra, sizeof(ra)), get_socket_rport(s->id),
s->sock);
LOGM("%s", s->buf);
if (strncasecmp((const char *)s->buf, "NOTIFY", 6) == 0)
{
rdid = strcasestr((const char *)s->buf, "DEVICEID.SES.COM:");
if (rdid && opts.device_id == map_int(strip(rdid + 17), NULL))
{
snprintf(buf, sizeof(buf), device_id_conflict, getlocalip(),
app_name, version, opts.device_id);
LOG(
"A new device joined the network with the same Device ID: %s, asking to change DEVICEID.SES.COM",
get_socket_rhost(s->id, ra, sizeof(ra)));
int wb = sendto(ssdp, buf, strlen(buf), MSG_NOSIGNAL,
(const struct sockaddr *)&s->sa, salen);
if (wb != strlen(buf))
LOG("incomplete ssdp_reply notify: wrote %d out of %d: error %d: %s", wb, strlen(buf), errno, strerror(errno));
}
return 0;
}
man = strcasestr((const char *)s->buf, "MAN");
man_sd = strcasestr((const char *)s->buf, "ssdp:discover");
if ((didsescom = strcasestr((const char *)s->buf, "DEVICEID.SES.COM:")))
did = map_int(didsescom + 17, NULL);
if (man && man_sd && didsescom && (s->rtime < 15000) && did == opts.device_id) // SSDP Device ID clash, only first 5 seconds after the announcement
{
opts.device_id++;
s[si].timeout_ms = 1800 * 1000;
s[si].rtime = -s[si].timeout_ms;
LOG(
"Device ID conflict, changing our device id to %d, destination SAT>IP server %s",
opts.device_id, get_socket_rhost(s->id, ra, sizeof(ra)));
readBootID();
}
else
did = opts.device_id;
if (strncmp((const char *)s->buf, "HTTP/1", 6) == 0)
LOG_AND_RETURN(0, "ssdp_reply: the message is a reply, ignoring....");
sprintf(buf, reply, get_current_timestamp(), opts.http_host, opts.xml_path,
app_name, version, uuid, opts.bootid, did);
LOGM("ssdp_reply fd: %d -> %s:%d, bootid: %d deviceid: %d http: %s", ssdp,
get_socket_rhost(s->id, ra, sizeof(ra)), get_socket_rport(s->id),
opts.bootid, did, opts.http_host);
//use ssdp (unicast) even if received to multicast address
LOGM("%s", buf);
int wb = sendto(ssdp, buf, strlen(buf), MSG_NOSIGNAL, (const struct sockaddr *)&s->sa, salen);
if (wb != strlen(buf))
LOG("incomplete ssdp_reply: wrote %d out of %d: error %d: %s", wb, strlen(buf), errno, strerror(errno));
return 0;
}
Description
ssdp
信息中的LOCATION
字段,对于minisatip
可以通过参数来指定
case XML_OPT:
while (*optarg > 0 && *optarg == '/')
optarg++;
if (*optarg > 0)
opts.xml_path = optarg;
else
LOG("Not a valid path for the xml file");
break;
case PLAYLIST_OPT:
{
if (strlen(optarg) < 1000)
{
opts.playlist = malloc1(strlen(optarg) + 200);
if (opts.playlist)
sprintf(opts.playlist, "<satip:X_SATIPM3U xmlns:satip=\"urn:ses-com:satip\">%s</satip:X_SATIPM3U>\r\n", >
}
else
LOG("playlist length is too big %d bytes", strlen(optarg));
break;
}
另外从函数read_http
也可以看到xml
的组成成分及生成过程
minisatip
同时支持SAT>IP Client
,文件satipc.c
即为相关代码,函数satip_getxml
为获取LOCATION
过程
Control
涉及RTSP
和HTTP
,忽略HTTP
。RTSP
函数为new_rtsp
及read_rtsp
read_rtsp
为服务端处理rtsp
#define RTSP_SETUP 1
#define RTSP_PLAY 2
#define RTSP_OPTIONS 3
#define RTSP_TEARDOWN 4
#define RTSP_DESCRIBE 5
if ((strncasecmp(arg[0], "PLAY", 4) == 0) || (strncasecmp(arg[0], "GET", 3) == 0) || (strncasecmp(arg[0], "SETUP", 5) == 0))
{
char ra[100];
int rv;
if (!(sid = get_sid(s->sid)))
{
http_response(s, 454, NULL, NULL, cseq, 0);
return 0;
}
if (useragent)
strncpy(sid->useragent, useragent, sizeof(sid->useragent) - 1);
if ((strncasecmp(arg[0], "PLAY", 4) == 0) || (strncasecmp(arg[0], "GET", 3) == 0))
if ((rv = start_play(sid, s)) < 0)
{
http_response(s, -rv, NULL, NULL, cseq, 0);
return 0;
}
buf[0] = 0;
if (transport)
{
int s_timeout;
if (sid->timeout == 1)
sid->timeout = opts.timeout_sec;
s_timeout = ((sid->timeout > 20000) ? sid->timeout : opts.timeout_sec) / 1000;
get_stream_rhost(sid->sid, ra, sizeof(ra));
switch (sid->type)
{
case STREAM_RTSP_UDP:
if (atoi(ra) < 224)
snprintf(buf, sizeof(buf),
"Transport: RTP/AVP;unicast;destination=%s;source=%s;client_port=%d-%d;server_port=%d-%d\r\nSession: %010d;timeout=%d\r\ncom.ses.streamID: %d",
ra, get_sock_shost(s->sock),
get_stream_rport(sid->sid),
get_stream_rport(sid->sid) + 1,
// opts.start_rtp, opts.start_rtp + 1,
get_sock_sport(sid->rsock),
get_sock_sport(sid->rtcp), get_session_id(s->sid),
s_timeout, sid->sid + 1);
else
snprintf(buf, sizeof(buf),
"Transport: RTP/AVP;multicast;destination=%s;port=%d-%d\r\nSession: %010d;timeout=%d\r\ncom.ses.streamID: %d",
ra, get_stream_rport(sid->sid),
ntohs(sid->sa.sin_port) + 1,
get_session_id(s->sid), s_timeout, sid->sid + 1);
break;
case STREAM_RTSP_TCP:
snprintf(buf, sizeof(buf),
"Transport: RTP/AVP/TCP;interleaved=0-1\r\nSession: %010d;timeout=%d\r\ncom.ses.streamID: %d",
get_session_id(s->sid), s_timeout, sid->sid + 1);
break;
}
}
if (strncasecmp(arg[0], "PLAY", 4) == 0)
{
char *qm = strchr(arg[1], '?');
if (qm)
*qm = 0;
if (buf[0])
strcat(buf, "\r\n");
snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf) - 1,
"RTP-Info: url=%s;seq=%jd;rtptime=%jd\r\nRange: npt=0.000-",
arg[1], getTick(), (getTickUs() / 1000000));
}
if (buf[0] == 0 && sid->type == STREAM_HTTP)
snprintf(buf, sizeof(buf), "Content-Type: video/mp2t\r\nConnection: close");
http_response(s, 200, buf, NULL, cseq, 0);
}
else if (strncmp(arg[0], "TEARDOWN", 8) == 0)
{
buf[0] = 0;
if (get_sid(s->sid))
sprintf(buf, "Session: %010d", get_session_id(s->sid));
close_stream(s->sid);
s->flush_enqued_data = 1;
http_response(s, 200, buf, NULL, cseq, 0);
}
else
{
if (strncmp(arg[0], "DESCRIBE", 8) == 0)
{
char sbuf[1000];
char *rv;
rv = describe_streams(s, arg[1], sbuf, sizeof(sbuf));
if (!rv)
{
http_response(s, 404, NULL, NULL, cseq, 0);
return 0;
}
snprintf(buf, sizeof(buf),
"Content-type: application/sdp\r\nContent-Base: rtsp://%s/",
get_sock_shost(s->sock));
http_response(s, 200, buf, sbuf, cseq, 0);
}
else if (strncmp(arg[0], "OPTIONS", 7) == 0)
{
// if(!get_sid(s->sid))
// http_response(s, 454, public, NULL, cseq, 0);
// else
http_response(s, 200, public, NULL, cseq, 0);
}
}
客户端发送rtsp
命令可以参看文件satipc.c
函数satipc_reply
Media Transport
涉及RTP
及RTCP
协议
RTP
处理流程read_dmx
-> process_dmx
-> process_packet
-> flush_streami
-> send_rtp
RTCP
处理流程setup_stream
-> stream_timeout
-> send_rtcp
SAT>IP Client Vlc
主要是SAT>IP Client
怎么获取Channel List
,以vlc
为例
modules/services_discovery/upnp.cpp
函数void MediaServerList::parseNewServer( IXML_Document *doc, const std::string &location )
if ( !strncmp( SATIP_SERVER_DEVICE_TYPE, psz_device_type,
strlen( SATIP_SERVER_DEVICE_TYPE ) - 1 ) )
{
SD::MediaServerDesc* p_server = NULL;
vlc_url_t url;
vlc_UrlParse( &url, psz_base_url );
char *psz_satip_channellist = config_GetPsz(m_sd, "satip-channelist");
if( !psz_satip_channellist ) {
break;
}
/* a user may have provided a custom playlist url */
if (strncmp(psz_satip_channellist, "CustomList", 10) == 0) {
char *psz_satip_playlist_url = config_GetPsz( m_sd, "satip-channellist-url" );
if ( psz_satip_playlist_url ) {
p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn, psz_friendly_name, psz_satip_playlist_url, iconUrl );
if( likely( p_server ) ) {
p_server->satIpHost = url.psz_host;
p_server->isSatIp = true;
if( !addServer( p_server ) ) {
delete p_server;
}
}
/* to comply with the SAT>IP specification, we don't fall back on another channel list if this path failed */
free( psz_satip_playlist_url );
vlc_UrlClean( &url );
continue;
}
}
/* If requested by the user, check for a SAT>IP m3u list, which may be provided by some rare devices */
if (strncmp(psz_satip_channellist, "ServerList", 10) == 0) {
const char* psz_m3u_url = xml_getChildElementValue( p_device_element, "satip:X_SATIPM3U" );
if ( psz_m3u_url ) {
if ( strncmp( "http", psz_m3u_url, 4) )
{
char* psz_url = NULL;
if ( UpnpResolveURL2( psz_base_url, psz_m3u_url, &psz_url ) == UPNP_E_SUCCESS )
{
p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn, psz_friendly_name, psz_url, iconUrl );
free(psz_url);
}
} else {
p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn, psz_friendly_name, psz_m3u_url, iconUrl );
}
if ( unlikely( !p_server ) )
{
free( psz_satip_channellist );
break;
}
p_server->satIpHost = url.psz_host;
p_server->isSatIp = true;
if ( !addServer( p_server ) )
delete p_server;
} else {
msg_Warn( m_sd, "SAT>IP server '%s' did not provide a playlist", url.psz_host);
}
/* to comply with the SAT>IP specifications, we don't fallback on another channel list if this path failed */
free(psz_satip_channellist);
vlc_UrlClean( &url );
continue;
}
/* Normally, fetch a playlist from the web,
* which will be processed by a lua script a bit later */
char *psz_url;
if (asprintf( &psz_url, "http://www.satip.info/Playlists/%s.m3u",
psz_satip_channellist ) < 0 ) {
vlc_UrlClean( &url );
free( psz_satip_channellist );
continue;
}
p_server = new(std::nothrow) SD::MediaServerDesc( psz_udn,
psz_friendly_name, psz_url, iconUrl );
if( likely( p_server ) ) {
p_server->satIpHost = url.psz_host;
p_server->isSatIp = true;
if( !addServer( p_server ) ) {
delete p_server;
}
}
free( psz_url );
free( psz_satip_channellist );
vlc_UrlClean( &url );
continue;
}
- 获取
CustomList
- 根据
satip:X_SATIPM3U
获取 - 指定
URL:http://www.satip.info/Playlists/%s.m3u
获取