0%

SAT2IP 开源工程分析

开源工程

  1. minisatip
  2. libsatip
  3. tvheadend

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

涉及RTSPHTTP,忽略HTTPRTSP函数为new_rtspread_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

涉及RTPRTCP协议

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;
}
  1. 获取CustomList
  2. 根据satip:X_SATIPM3U获取
  3. 指定URL:http://www.satip.info/Playlists/%s.m3u获取