root/modules/rtp_in/rtp_in.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. RT_LoadPrefs
  2. RP_cleanup
  3. RP_Thread
  4. RP_RegisterMimeTypes
  5. RP_CanHandleURL
  6. Satip_GetServerIP
  7. RP_ConnectServiceEx
  8. RP_ConnectService
  9. RP_FlushCommands
  10. RP_CloseService
  11. RP_GetServiceDesc
  12. RP_ConnectChannel
  13. RP_DisconnectChannel
  14. gf_rtp_switch_quality
  15. RP_ServiceCommand
  16. RP_ChannelGetSLP
  17. RP_ChannelReleaseSLP
  18. RP_CanHandleURLInService
  19. RTP_Load
  20. RTP_Delete
  21. QueryInterfaces
  22. LoadInterface
  23. ShutdownInterface

/*
 *                      GPAC - Multimedia Framework C SDK
 *
 *                      Authors: Jean Le Feuvre
 *                      Copyright (c) Telecom ParisTech 2000-2012
 *                                      All rights reserved
 *
 *  This file is part of GPAC / RTP input module
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  GPAC is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "rtp_in.h"

#ifndef GPAC_DISABLE_STREAMING

#define RTP_DEFAULT_TIMEOUT 10000
#define RTSP_DEFAULT_TIMEOUT 2000

static void RT_LoadPrefs(GF_InputService *plug, RTPClient *rtp)
{
        const char *sOpt;

        sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "DefaultPort");
        if (sOpt) {
                rtp->default_port = atoi(sOpt);
        } else {
                rtp->default_port = 554;
        }
        if ((rtp->default_port == 80) || (rtp->default_port == 8080))
                gf_modules_set_option((GF_BaseInterface *)plug, "Streaming", "RTPoverRTSP", "yes");

        sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "RTPoverRTSP");
        if (sOpt && !stricmp(sOpt, "yes")) {
                rtp->transport_mode = 1;
        } else if (sOpt && !stricmp(sOpt, "OnlyCritical")) {
                rtp->transport_mode = 2;
        } else {
                rtp->transport_mode = 0;
        }

        /*
                get heneral network config for UDP
        */
        /*if UDP not available don't try it*/
        sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Network", "UDPNotAvailable");
        if (sOpt && !stricmp(sOpt, "yes")) {
                if (!rtp->transport_mode) rtp->transport_mode = 1;
                /*turn it off*/
                gf_modules_set_option((GF_BaseInterface *)plug, "Network", "UDPNotAvailable", "no");
        }

        if (!rtp->transport_mode) {
                sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Network", "UDPTimeout");
                if (sOpt ) {
                        rtp->udp_time_out = atoi(sOpt);
                } else {
                        rtp->udp_time_out = RTP_DEFAULT_TIMEOUT;
                }
        }

        sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "RTSPTimeout");
        if (sOpt ) {
                rtp->time_out = atoi(sOpt);
        } else {
                rtp->time_out = RTSP_DEFAULT_TIMEOUT;
        }

        /*packet drop emulation*/
        sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "FirstPacketDrop");
        if (sOpt) {
                rtp->first_packet_drop = atoi(sOpt);
        } else {
                rtp->first_packet_drop = 0;
        }
        sOpt = gf_modules_get_option((GF_BaseInterface *)plug, "Streaming", "PacketDropFrequency");
        if (sOpt) {
                rtp->frequency_drop = atoi(sOpt);
        } else {
                rtp->frequency_drop = 0;
        }

//      rtp->handle_announce = 0;
}

static void RP_cleanup(RTPClient *rtp)
{
        RTSPSession *sess;

        while (gf_list_count(rtp->channels)) {
                RTPStream *ch = (RTPStream *)gf_list_get(rtp->channels, 0);
                gf_list_rem(rtp->channels, 0);
                RP_DeleteStream(ch);
        }

        while ( (sess = (RTSPSession *)gf_list_last(rtp->sessions)) ) {
                gf_list_rem_last(rtp->sessions);
                RP_DelSession(sess);
        }

        if (rtp->session_desc) gf_odf_desc_del(rtp->session_desc);
        rtp->session_desc = NULL;

        if (rtp->sdp_temp) {
                gf_free(rtp->sdp_temp->remote_url);
                gf_free(rtp->sdp_temp);
        }
        rtp->sdp_temp = NULL;
}

u32 RP_Thread(void *param)
{
        u32 i;
        GF_NetworkCommand com;
        RTSPSession *sess;
        RTPStream *ch;
        RTPClient *rtp = (RTPClient *)param;

        rtp->th_state = 1;
        com.command_type = GF_NET_CHAN_BUFFER_QUERY;
        while (rtp->th_state) {
                gf_mx_p(rtp->mx);

                /*fecth data on udp*/
                i=0;
                while ((ch = (RTPStream *)gf_list_enum(rtp->channels, &i))) {
                        if ((ch->flags & RTP_EOS) || (ch->status!=RTP_Running) ) continue;
                        /*for interleaved channels don't read too fast, query the buffer occupancy*/
                        if (ch->flags & RTP_INTERLEAVED) {
                                com.base.on_channel = ch->channel;
                                gf_service_command(rtp->service, &com, GF_OK);
                                /*if no buffering, use a default value (3 sec of data should do it)*/
                                if (!com.buffer.max) com.buffer.max = 3000;
                                if (com.buffer.occupancy <= com.buffer.max) ch->rtsp->flags |= RTSP_TCP_FLUSH;
                        } else {
                                RP_ReadStream(ch);
                        }
                }

                /*and process commands / flush TCP*/
                i=0;
                while ((sess = (RTSPSession *)gf_list_enum(rtp->sessions, &i))) {
                        RP_ProcessCommands(sess);

                        if (sess->connect_error) {
                                gf_service_connect_ack(sess->owner->service, NULL, sess->connect_error);
                                sess->connect_error = GF_OK;
                        }

                }

                gf_mx_v(rtp->mx);

                gf_sleep(1);
        }

        if (rtp->dnload) gf_service_download_del(rtp->dnload);
        rtp->dnload = NULL;

        rtp->th_state = 2;
        return 0;
}

static const char * sdp_mime = "application/sdp";

static const char * sdp_exts = "sdp";

static const char * sdp_desc = "OnDemand Media/Multicast Session";

static u32 RP_RegisterMimeTypes(const GF_InputService *plug) {
        if (!plug)
                return 0;
        gf_service_register_mime(plug, sdp_mime, sdp_exts, sdp_desc);
        return 1;
}

static Bool RP_CanHandleURL(GF_InputService *plug, const char *url)
{
        char *sExt;
        if (!plug || !url)
                return GF_FALSE;
        sExt = strrchr(url, '.');

        if (sExt && gf_service_check_mime_register(plug, sdp_mime, sdp_exts, sdp_desc, sExt)) return GF_TRUE;

        /*local */
        if (strstr(url, "data:application/sdp")) return GF_TRUE;
        /*embedded data*/
        if (strstr(url, "data:application/mpeg4-od-au;base64") ||
                strstr(url, "data:application/mpeg4-bifs-au;base64") ||
                strstr(url, "data:application/mpeg4-es-au;base64")) return GF_TRUE;

        /*we need rtsp/tcp , rtsp/udp or direct RTP sender (no control)*/
        if (!strnicmp(url, "rtsp://", 7) || !strnicmp(url, "rtspu://", 8) || !strnicmp(url, "rtp://", 6)  || !strnicmp(url, "satip://", 6))
                return GF_TRUE;
        /*we don't check extensions*/
        return GF_FALSE;
}

//simplified version of RTSP_UnpackURL for SAT>IP
static void Satip_GetServerIP(const char *sURL, char *Server)
{
        char schema[10], *test, text[1024], *retest;
        u32 i, len;
        Bool is_ipv6;

        strcpy(Server, "");

        //extract the schema
        i = 0;
        while (i <= strlen(sURL)) {
                if (sURL[i] == ':')
                        goto found;
                schema[i] = sURL[i];
                i += 1;
        }
        return;

found:
        schema[i] = 0;
        if (stricmp(schema, "satip")) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTP] Wrong SATIP schema %s - not setting up\n", schema));
                return;
        }
        test = strstr(sURL, "://");
        test += 3;
        //check for service
        retest = strstr(test, "/");

        //check for port
        retest = strrchr(test, ':');
        /*IPV6 address*/
        if (retest && strchr(retest, ']')) retest = NULL;

        if (retest && strstr(retest, "/")) {
                retest += 1;
                i = 0;
                while (i<strlen(retest)) {
                        if (retest[i] == '/') break;
                        text[i] = retest[i];
                        i += 1;
                }
                text[i] = 0;
        }
        //get the server name
        is_ipv6 = GF_FALSE;
        len = (u32)strlen(test);
        i = 0;
        while (i<len) {
                if (test[i] == '[') is_ipv6 = GF_TRUE;
                else if (test[i] == ']') is_ipv6 = GF_FALSE;
                if ((test[i] == '/') || (!is_ipv6 && (test[i] == ':'))) break;
                text[i] = test[i];
                i += 1;
        }
        text[i] = 0;
        strcpy(Server, text);
}

GF_Err RP_ConnectServiceEx(GF_InputService *plug, GF_ClientService *serv, const char *url, Bool skip_migration)
{
        char *session_cache;
        RTSPSession *sess;
        RTPClient *priv = (RTPClient *)plug->priv;

        /*store user address*/
        priv->service = serv;

        if (priv->dnload) gf_service_download_del(priv->dnload);
        priv->dnload = NULL;

        GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Opening service %s\n", url));

        /*load preferences*/
        RT_LoadPrefs(plug, priv);

        /*start thread*/
        gf_th_run(priv->th, RP_Thread, priv);

        if (!skip_migration) {
                session_cache = (char *) gf_modules_get_option((GF_BaseInterface *) plug, "Streaming", "SessionMigrationFile");
                if (session_cache && session_cache[0]) {
                        FILE *f = gf_fopen(session_cache, "rb");
                        if (f) {
                                gf_fclose(f);
                                GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Restarting RTSP session from %s\n", session_cache));
                                RP_FetchSDP(priv, (char *) session_cache, NULL, (char *) url);
                                return GF_OK;
                        }
                        if (!strncmp(session_cache, "http://", 7)) {
                                GF_LOG(GF_LOG_INFO, GF_LOG_RTP, ("[RTP] Restarting RTSP session from %s\n", session_cache));
                                RP_FetchSDP(priv, (char *) session_cache, NULL, (char *) url);
                                return GF_OK;
                        }
                }
        }


        /*local or remote SDP*/
        if (strstr(url, "data:application/sdp") || (strnicmp(url, "rtsp", 4) && strstr(url, ".sdp")) ) {
                RP_FetchSDP(priv, (char *) url, NULL, NULL);
                return GF_OK;
        }

        /*rtsp and rtsp over udp*/
        if (!strnicmp(url, "rtsp://", 7) || !strnicmp(url, "rtspu://", 8) || !strnicmp(url, "satip://", 8)) {
                char *the_url = gf_strdup(url);
                char *the_ext = strrchr(the_url, '#');
                if (the_ext) {
                        if (!stricmp(the_ext, "#audio")) priv->media_type = GF_MEDIA_OBJECT_AUDIO;
                        else if (!stricmp(the_ext, "#video")) priv->media_type = GF_MEDIA_OBJECT_VIDEO;
                        the_ext[0] = 0;
                }
                sess = RP_NewSession(priv, (char *) the_url);
                if (!strnicmp(url, "satip://", 8)) {
                        sess->satip = GF_TRUE;
                        sess->satip_server = gf_malloc(GF_MAX_PATH);
                        Satip_GetServerIP(url, sess->satip_server);
                }
                gf_free(the_url);
                if (!sess) {
                        gf_service_connect_ack(serv, NULL, GF_NOT_SUPPORTED);
                } else {
                        RP_Describe(sess, 0, NULL);
                }
                return GF_OK;
        }

        /*direct RTP (no control) or embedded data - this means the service is attached to a single channel (no IOD)
        reply right away*/
        gf_service_connect_ack(serv, NULL, GF_OK);
        RP_SetupObjects(priv);
        return GF_OK;
}

GF_Err RP_ConnectService(GF_InputService *plug, GF_ClientService *serv, const char *url)
{
        return RP_ConnectServiceEx(plug, serv, url, GF_FALSE);
}

static void RP_FlushCommands(RTPClient *rtp)
{
        u32 i, nb_com;
        RTSPSession *sess;
        /*process teardown on all sessions*/
        while (1) {
                nb_com = 0;
                i=0;
                while ((sess = (RTSPSession *)gf_list_enum(rtp->sessions, &i))) {
                        if (!sess->connect_error)
                                nb_com += gf_list_count(sess->rtsp_commands);
                }
                if (!nb_com) break;
                gf_sleep(10);
        }
}

static GF_Err RP_CloseService(GF_InputService *plug)
{
        u32 i;
        const char *opt;
        RTSPSession *sess;
        RTPClient *rtp = (RTPClient *)plug->priv;
        GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Closing service\n"));

        RP_FlushCommands(rtp);

        if (rtp->session_migration) {
                opt = gf_modules_get_option((GF_BaseInterface *) plug, "Streaming", "SessionMigrationPause");
                if (opt && !strcmp(opt, "yes")) {
                        GF_NetworkCommand com;
                        com.command_type = GF_NET_CHAN_PAUSE;
                        com.base.on_channel = NULL;
                        /*send pause on all sessions*/
                        i=0;
                        while ((sess = (RTSPSession *)gf_list_enum(rtp->sessions, &i))) {
                                RP_UserCommand(sess, NULL, &com);
                        }
                }
                RP_SaveSessionState(rtp);
        } else {
                /*remove session state file*/
                if (rtp->session_state_data) {
                        gf_free(rtp->session_state_data);
                        rtp->session_state_data = NULL;
                }

                /*send teardown on all sessions*/
                i=0;
                while ((sess = (RTSPSession *)gf_list_enum(rtp->sessions, &i))) {
                        RP_Teardown(sess, NULL);
                }
        }
        RP_FlushCommands(rtp);

        /*shutdown thread*/
        if (rtp->th_state==1) rtp->th_state = 0;

        /*confirm close*/
        gf_service_disconnect_ack(rtp->service, NULL, GF_OK);
        return GF_OK;
}

static GF_Descriptor *RP_GetServiceDesc(GF_InputService *plug, u32 expect_type, const char *sub_url)
{
        GF_Descriptor *desc;
        RTSPSession *sess = NULL;
        RTPClient *priv = (RTPClient *)plug->priv;

        GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Fetching service descriptor\n"));

        sess = gf_list_get(priv->sessions, 0);
        if (sess && sess->satip) {
                RTPStream *ch = gf_list_get(priv->channels, 0);
                if (!ch) return NULL;
                return ch->satip_m2ts_ifce->GetServiceDescriptor(ch->satip_m2ts_ifce, expect_type, sub_url);
        }

        if ((expect_type!=GF_MEDIA_OBJECT_UNDEF) && (expect_type!=GF_MEDIA_OBJECT_SCENE) && (expect_type!=GF_MEDIA_OBJECT_UPDATES)) {
                /*ignore the SDP IOD and regenerate one*/
                if (priv->session_desc) gf_odf_desc_del(priv->session_desc);
                priv->session_desc = NULL;
                priv->media_type = expect_type;
                return RP_EmulateIOD(priv, sub_url);
        }

        desc = priv->session_desc;
        priv->session_desc = NULL;
        return desc;
}

static GF_Err RP_ConnectChannel(GF_InputService *plug, LPNETCHANNEL channel, const char *url, Bool upstream)
{
        u32 ESID, i;
        RTPStream *ch, *next_ch;
        RTSPSession *sess = NULL;
        char *es_url;
        RTPClient *priv = (RTPClient *)plug->priv;
        if (upstream) return GF_NOT_SUPPORTED;

        GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Connecting channel @%08x - %s\n", channel, url));

        sess = gf_list_get(priv->sessions, 0);
        if (sess && sess->satip) {
                RTPStream *ch = gf_list_get(priv->channels, 0);
                if (!ch) return GF_SERVICE_ERROR;
                return ch->satip_m2ts_ifce->ConnectChannel(ch->satip_m2ts_ifce, channel, url, upstream);
        }

        ch = RP_FindChannel(priv, channel, 0, (char *) url, GF_FALSE);
        if (ch && (ch->status != RTP_Disconnected) ) return GF_SERVICE_ERROR;

        es_url = NULL;
        sess = NULL;
        if (strstr(url, "ES_ID=")) {
                sscanf(url, "ES_ID=%ud", &ESID);
                /*first case: simple URL (same namespace)*/
                ch = RP_FindChannel(priv, NULL, ESID, NULL, GF_FALSE);
                /*this should not happen, the sdp must describe all streams in the service*/
                if (!ch) return GF_STREAM_NOT_FOUND;

                /*search for next stream*/
                ch->next_stream = 0;
                for (i = 0; i < gf_list_count(priv->channels); i++) {
                        next_ch = (RTPStream *)gf_list_get(priv->channels, i);
                        if (next_ch->prev_stream == ch->mid)
                        {
                                ch->next_stream = next_ch->mid;
                                break;
                        }
                }

                /*assign app channel*/
                ch->channel = channel;
                sess = ch->rtsp;
        }
        /*rtsp url - create a session if needed*/
        else if (!strnicmp(url, "rtsp://", 7) || !strnicmp(url, "rtspu://", 8) || !strnicmp(url, "satip://", 8)) {
                sess = RP_CheckSession(priv, (char *) url);
                if (!sess) sess = RP_NewSession(priv, (char *) url);
                es_url = (char *) url;
        }
        /*data: url*/
        else if (strstr(url, "data:application/mpeg4-od-au;base64")
                 || strstr(url, "data:application/mpeg4-bifs-au;base64")
                 || strstr(url, "data:application/mpeg4-es-au;base64")
                ) {

                GF_SAFEALLOC(ch, RTPStream);
                if (!ch) return GF_OUT_OF_MEM;
                
                ch->control = gf_strdup(url);
                ch->owner = priv;
                ch->channel = channel;
                ch->status = RTP_Connected;
                /*register*/
                gf_list_add(priv->channels, ch);
                RP_ConfirmChannelConnect(ch, GF_OK);

                return GF_OK;
        }
        /*session migration resume - don't send data to the server*/
        if (ch && (ch->status==RTP_SessionResume)) {
                ch->flags |= RTP_CONNECTED;
                RP_InitStream(ch, GF_FALSE);
                RP_ConfirmChannelConnect(ch, GF_OK);
                return GF_OK;
        }
        /*send a DESCRIBE (not a setup) on the channel. If the channel is already created then the
        describe is skipped and a SETUP is sent directly, otherwise the channel is first described then setup*/
        if (sess) RP_Describe(sess, es_url, channel);
        /*otherwise confirm channel connection*/
        else RP_ConfirmChannelConnect(ch, GF_OK);

        return GF_OK;
}


static GF_Err RP_DisconnectChannel(GF_InputService *plug, LPNETCHANNEL channel)
{
        RTPStream *ch;
        RTPClient *priv = (RTPClient *)plug->priv;
        RTSPSession *sess = NULL;

        GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Disconnecting channel @%08x\n", channel));
        
        sess = gf_list_get(priv->sessions, 0);
        if (sess && sess->satip) {
                RTPStream *ch = gf_list_get(priv->channels, 0);
                if (!ch) return GF_SERVICE_ERROR;
                return ch->satip_m2ts_ifce->DisconnectChannel(ch->satip_m2ts_ifce, channel);
        }

        ch = RP_FindChannel(priv, channel, 0, NULL, GF_FALSE);
        if (!ch) return GF_STREAM_NOT_FOUND;
        gf_mx_p(priv->mx);
        /*disconnect stream BUT DO NOT DELETE IT since we don't store SDP*/
        ch->flags &= ~RTP_CONNECTED;
        ch->channel = NULL;
        gf_mx_v(priv->mx);
        gf_service_disconnect_ack(priv->service, channel, GF_OK);
        return GF_OK;
}

static void gf_rtp_switch_quality(RTPClient *rtp, Bool switch_up)
{
        u32 i,count;
        RTPStream *ch, *cur_ch;
        GF_NetworkCommand com;

        count = gf_list_count(rtp->channels);
        /*find the current stream*/
        ch = cur_ch = NULL;
        for (i = 0; i < count; i++) {
                cur_ch = (RTPStream *) gf_list_get(rtp->channels, i);
                if (cur_ch->mid != rtp->cur_mid) {
                        cur_ch=NULL;
                        continue;
                }
                break;
        }
        if (!cur_ch) return;

        if (switch_up)
        {
                /*this is the highest stream*/
                if (!cur_ch->next_stream)
                {
                        cur_ch->status = RTP_Running;
                        return;
                }
                else
                {
                        for (i = 0; i < count; i++) {
                                ch = (RTPStream *) gf_list_get(rtp->channels, i);
                                if (ch->mid == cur_ch->next_stream)
                                {
                                        /*resume streaming next channel*/
                                        gf_mx_p(rtp->mx);
                                        RP_InitStream(ch, GF_FALSE);
                                        gf_mx_v(rtp->mx);
                                        ch->status = RTP_Running;
                                        rtp->cur_mid = ch->mid;
                                        break;
                                }

                        }
                }
        }
        else
        {
                /*this is the lowest stream i.e base layer*/
                if (!cur_ch->prev_stream)
                {
                        cur_ch->status = RTP_Running;
                        return;
                }
                else
                {
                        for (i = 0; i < count; i++) {
                                ch = (RTPStream *) gf_list_get(rtp->channels, i);
                                if (ch->mid == cur_ch->prev_stream)
                                {
                                        /*stop streaming current channel*/
                                        gf_rtp_stop(cur_ch->rtp_ch);
                                        cur_ch->status = RTP_Connected;
                                        com.command_type = GF_NET_CHAN_RESET;
                                        com.base.on_channel = cur_ch;
                                        gf_service_command(rtp->service, &com, GF_OK);
                                        rtp->cur_mid = ch->mid;
                                        break;
                                }
                        }
                }
        }
        GF_LOG(GF_LOG_DEBUG, GF_LOG_CODEC, ("Switch from ES%d to ES %d\n", cur_ch->mid, ch->mid));
        return;
}

static GF_Err RP_ServiceCommand(GF_InputService *plug, GF_NetworkCommand *com)
{
        RTPStream *ch;
        RTPClient *priv = (RTPClient *)plug->priv;
        RTSPSession *sess = NULL;

        sess = gf_list_get(priv->sessions, 0);
        if (sess && sess->satip) {
                RTPStream *ch = gf_list_get(priv->channels, 0);
                if (!ch) return GF_SERVICE_ERROR;
                return ch->satip_m2ts_ifce->ServiceCommand(ch->satip_m2ts_ifce, com);
        }

        if (com->command_type==GF_NET_SERVICE_HAS_AUDIO) {
                u32 i;
                for (i=0; i<gf_list_count(priv->channels); i++) {
                        ch = (RTPStream*)gf_list_get(priv->channels, i);
                        if (ch->depacketizer->sl_map.StreamType==GF_STREAM_AUDIO)
                                return GF_OK;
                }
                return GF_NOT_SUPPORTED;
        }
        if (com->command_type==GF_NET_SERVICE_MIGRATION_INFO) {
                RP_SaveSessionState(priv);
                priv->session_migration = GF_TRUE;
                if (priv->session_state_data) {
                        com->migrate.data = priv->session_state_data;
                        com->migrate.data_len = (u32) strlen(priv->session_state_data);
                        return GF_OK;
                }
                return GF_NOT_SUPPORTED;
        }

        if (com->command_type == GF_NET_SERVICE_QUALITY_SWITCH)
        {
                gf_rtp_switch_quality(priv, com->switch_quality.up);
                return GF_OK;
        }

        /*ignore commands other than channels one*/
        if (!com->base.on_channel) {
                if (com->command_type==GF_NET_IS_CACHABLE) return GF_OK;
                return GF_NOT_SUPPORTED;
        }

        ch = RP_FindChannel(priv, com->base.on_channel, 0, NULL, GF_FALSE);
        if (!ch) return GF_STREAM_NOT_FOUND;

        switch (com->command_type) {
        case GF_NET_CHAN_SET_PULL:
                if (ch->rtp_ch || ch->rtsp || !ch->control) return GF_NOT_SUPPORTED;
                /*embedded channels work in pull mode*/
                if (strstr(ch->control, "data:application/")) return GF_OK;
                return GF_NOT_SUPPORTED;
        case GF_NET_CHAN_INTERACTIVE:
                /*looks like pure RTP / multicast etc, not interactive*/
                if (!ch->control) return GF_NOT_SUPPORTED;
                /*emulated broadcast mode*/
                else if (ch->flags & RTP_FORCE_BROADCAST) return GF_NOT_SUPPORTED;
                /*regular rtsp mode*/
                else if (ch->flags & RTP_HAS_RANGE) return GF_OK;
                /*embedded data*/
                else if (strstr(ch->control, "application")) return GF_OK;
                return GF_NOT_SUPPORTED;
        case GF_NET_CHAN_BUFFER:
                if (!(ch->rtp_ch || ch->rtsp || !ch->control)) {
                        com->buffer.max = com->buffer.min = 0;
                }
                //otherwise use default player config
                return GF_OK;
        case GF_NET_CHAN_DURATION:
                com->duration.duration = (ch->flags & RTP_HAS_RANGE) ? (ch->range_end - ch->range_start) : 0;
                return GF_OK;
        /*RTP channel config is done upon connection, once the complete SL mapping is known
        however we must store some info not carried in SDP*/
        case GF_NET_CHAN_CONFIG:
                if (com->cfg.frame_duration) ch->depacketizer->sl_hdr.au_duration = com->cfg.frame_duration;
                ch->ts_res = com->cfg.sl_config.timestampResolution;
                return GF_OK;

        case GF_NET_CHAN_PLAY:
                GF_LOG(GF_LOG_DEBUG, GF_LOG_RTP, ("[RTP] Processing play on channel @%08x - %s\n", ch, ch->rtsp ? "RTSP control" : "No control (RTP)" ));
                /*is this RTSP or direct RTP?*/
                ch->flags &= ~RTP_EOS;
                if (ch->rtsp) {
                        if (ch->status==RTP_SessionResume) {
                                const char *opt = gf_modules_get_option((GF_BaseInterface *) plug, "Streaming", "SessionMigrationPause");
                                if (opt && !strcmp(opt, "yes")) {
                                        ch->status = RTP_Connected;
                                        com->play.start_range = ch->current_start;
                                } else {
                                        ch->status = RTP_Running;
                                        return GF_OK;
                                }
                        }
                        RP_UserCommand(ch->rtsp, ch, com);
                } else {
                        ch->status = RTP_Running;
                        if (!ch->next_stream)
                                priv->cur_mid = ch->mid;

                        if (ch->rtp_ch) {
                                /*technically we shouldn't attempt to synchronize streams based on RTP, we should use RTCP/ However it
                                may happen that the RTCP traffic is absent ...*/
                                //ch->check_rtp_time = RTP_SET_TIME_RTP;
                                ch->rtcp_init = GF_FALSE;
                                gf_mx_p(priv->mx);
                                RP_InitStream(ch, (ch->flags & RTP_CONNECTED) ? GF_TRUE : GF_FALSE);
                                gf_mx_v(priv->mx);
                                gf_rtp_set_info_rtp(ch->rtp_ch, 0, 0, 0);
                        } else {
                                /*direct channel, store current start*/
                                ch->current_start = com->play.start_range;
                                ch->flags |= GF_RTP_NEW_AU;
                                gf_rtp_depacketizer_reset(ch->depacketizer, GF_FALSE);
                        }
                }
                return GF_OK;
        case GF_NET_CHAN_STOP:
                /*is this RTSP or direct RTP?*/
                if (ch->rtsp) {
                        if (! ch->owner->session_migration) {
                                RP_UserCommand(ch->rtsp, ch, com);
                        }
                } else {
                        ch->status = RTP_Connected;
                        ch->owner->last_ntp = 0;
                }
                ch->rtcp_init = GF_FALSE;
                return GF_OK;
        case GF_NET_CHAN_SET_SPEED:
        case GF_NET_CHAN_PAUSE:
        case GF_NET_CHAN_RESUME:
                assert(ch->rtsp);
                RP_UserCommand(ch->rtsp, ch, com);
                return GF_OK;

        case GF_NET_CHAN_GET_DSI:
                if (ch->depacketizer && ch->depacketizer->sl_map.configSize) {
                        com->get_dsi.dsi_len = ch->depacketizer->sl_map.configSize;
                        com->get_dsi.dsi = (char*)gf_malloc(sizeof(char)*com->get_dsi.dsi_len);
                        memcpy(com->get_dsi.dsi, ch->depacketizer->sl_map.config, sizeof(char)*com->get_dsi.dsi_len);
                } else {
                        com->get_dsi.dsi = NULL;
                        com->get_dsi.dsi_len = 0;
                }
                return GF_OK;


        case GF_NET_GET_STATS:
                memset(&com->net_stats, 0, sizeof(GF_NetComStats));
                if (ch->rtp_ch) {
                        u32 time;
                        Float bps;
                        com->net_stats.pck_loss_percentage = gf_rtp_get_loss(ch->rtp_ch);
                        if (ch->flags & RTP_INTERLEAVED) {
                                com->net_stats.multiplex_port = gf_rtsp_get_session_port(ch->rtsp->session);
                                com->net_stats.port = gf_rtp_get_low_interleave_id(ch->rtp_ch);
                                com->net_stats.ctrl_port = gf_rtp_get_hight_interleave_id(ch->rtp_ch);
                        } else {
                                com->net_stats.multiplex_port = 0;
                                gf_rtp_get_ports(ch->rtp_ch, &com->net_stats.port, &com->net_stats.ctrl_port);
                        }
                        if (ch->stat_stop_time) {
                                time = ch->stat_stop_time - ch->stat_start_time;
                        } else {
                                time = gf_sys_clock() - ch->stat_start_time;
                        }
                        bps = 8.0f * ch->rtp_bytes;
                        bps *= 1000;
                        bps /= time;
                        com->net_stats.bw_down = (u32) bps;
                        bps = 8.0f * ch->rtcp_bytes;
                        bps *= 1000;
                        bps /= time;
                        com->net_stats.ctrl_bw_down = (u32) bps;
                        bps = 8.0f * gf_rtp_get_tcp_bytes_sent(ch->rtp_ch);
                        bps *= 1000;
                        bps /= time;
                        com->net_stats.ctrl_bw_up = (u32) bps;
                }
                return GF_OK;
        default:
                break;
        }
        return GF_NOT_SUPPORTED;
}

static GF_Err RP_ChannelGetSLP(GF_InputService *plug, LPNETCHANNEL channel, char **out_data_ptr, u32 *out_data_size, GF_SLHeader *out_sl_hdr, Bool *sl_compressed, GF_Err *out_reception_status, Bool *is_new_data)
{
        char *data;
        RTPStream *ch;
        RTPClient *priv = (RTPClient *)plug->priv;

        ch = RP_FindChannel(priv, channel, 0, NULL, GF_FALSE);
        if (!ch) return GF_STREAM_NOT_FOUND;
        if (ch->rtp_ch || ch->rtsp || !ch->control) return GF_SERVICE_ERROR;
        if (ch->status != RTP_Running) return GF_SERVICE_ERROR;
        data = strstr(ch->control, ";base64");
        if (!data) return GF_SERVICE_ERROR;

        if (ch->current_start>=0) {
                *sl_compressed = GF_FALSE;
                memset(out_sl_hdr, 0, sizeof(GF_SLHeader));
                out_sl_hdr->accessUnitEndFlag = 1;
                out_sl_hdr->accessUnitStartFlag = 1;
                out_sl_hdr->compositionTimeStamp = (u64) (ch->current_start * ch->ts_res);
                out_sl_hdr->compositionTimeStampFlag = 1;
                out_sl_hdr->randomAccessPointFlag = 1;
                *out_reception_status = GF_OK;
                *is_new_data = (ch->flags & GF_RTP_NEW_AU) ? GF_TRUE : GF_FALSE;

                /*decode data*/
                data = strstr(data, ",");
                data += 1;
                *out_data_size = gf_base64_decode(data, (u32) strlen(data), ch->buffer, RTP_BUFFER_SIZE);
                /*FIXME - currently only support for empty SL header*/
                *out_data_ptr = ch->buffer;
                ch->flags &= ~GF_RTP_NEW_AU;
        } else {
                *out_data_ptr = NULL;
                *out_data_size = 0;
                *out_reception_status = GF_EOS;
                ch->flags |= RTP_EOS;
        }
        return GF_OK;
}

static GF_Err RP_ChannelReleaseSLP(GF_InputService *plug, LPNETCHANNEL channel)
{
        RTPStream *ch;
        RTPClient *priv = (RTPClient *)plug->priv;

        ch = RP_FindChannel(priv, channel, 0, NULL, GF_FALSE);
        if (!ch) return GF_STREAM_NOT_FOUND;
        if (ch->rtp_ch || ch->rtsp || !ch->control) return GF_SERVICE_ERROR;
        if (ch->status != RTP_Running) return GF_SERVICE_ERROR;

        /*this will trigger EOS at next fetch*/
        ch->current_start = -1.0;
        return GF_OK;
}

static Bool RP_CanHandleURLInService(GF_InputService *plug, const char *url)
{
        RTSPSession *sess;
        RTPClient *priv = (RTPClient *)plug->priv;

        if (strstr(url, "data:application/mpeg4-od-au;base64")
                || strstr(url, "data:application/mpeg4-bifs-au;base64")
                || strstr(url, "data:application/mpeg4-es-au;base64")
           ) return GF_TRUE;

        if (url[0]=='#') {
                Bool st_type = GF_FALSE;
                if (!stricmp(url, "#video")) st_type = GF_STREAM_VISUAL;
                else if (!stricmp(url, "#audio")) st_type = GF_STREAM_AUDIO;

                if (st_type) {
                        u32 i=0;
                        RTPStream *st;
                        while ((st = (RTPStream *)gf_list_enum(priv->channels, &i))) {
                                if (st->depacketizer && (st->depacketizer->sl_map.StreamType==st_type))
                                        return GF_TRUE;
                        }
                }
                return GF_FALSE;
        } else if (!RP_CanHandleURL(plug, url)) {
                return GF_FALSE;
        }
        /*if this URL is part of a running session then ok*/
        sess = RP_CheckSession(priv, (char *) url);
        if (sess) return GF_TRUE;
        return GF_FALSE;
}


GF_InputService *RTP_Load()
{
        RTPClient *priv;
        GF_InputService *plug;
        GF_SAFEALLOC(plug, GF_InputService);
        if (!plug) return NULL;
        GF_REGISTER_MODULE_INTERFACE(plug, GF_NET_CLIENT_INTERFACE, "GPAC RTP/RTSP Client", "gpac distribution")

        GF_SAFEALLOC(priv, RTPClient);
        if (!priv) {
                gf_free(plug);
                return NULL;
        }

        plug->CanHandleURL = RP_CanHandleURL;
        plug->CanHandleURLInService = RP_CanHandleURLInService;
        plug->ConnectService = RP_ConnectService;
        plug->CloseService = RP_CloseService;
        plug->GetServiceDescriptor = RP_GetServiceDesc;
        plug->ConnectChannel = RP_ConnectChannel;
        plug->DisconnectChannel = RP_DisconnectChannel;
        plug->ServiceCommand = RP_ServiceCommand;
        plug->RegisterMimeTypes = RP_RegisterMimeTypes;

        /*PULL mode for embedded streams*/
        plug->ChannelGetSLP = RP_ChannelGetSLP;
        plug->ChannelReleaseSLP = RP_ChannelReleaseSLP;

        priv->sessions = gf_list_new();
        priv->channels = gf_list_new();

        plug->priv = priv;

        priv->time_out = RTSP_DEFAULT_TIMEOUT;
        priv->mx = gf_mx_new("RTPDemux");
        priv->th = gf_th_new("RTPDemux");

        return plug;
}


void RTP_Delete(GF_BaseInterface *bi)
{
        RTPClient *rtp;
        u32 retry;
        GF_InputService *plug = (GF_InputService *) bi;
        rtp = (RTPClient *)plug->priv;

        /*shutdown thread*/
        if (rtp->th_state==1) rtp->th_state = 0;
        retry = 20;
        while ((rtp->th_state==1) && retry) {
                gf_sleep(10);
                retry--;
        }
        assert(retry);

        if (rtp->session_state_data) gf_free(rtp->session_state_data);

        RP_cleanup(rtp);
        gf_th_del(rtp->th);
        gf_mx_del(rtp->mx);
        gf_list_del(rtp->sessions);
        gf_list_del(rtp->channels);
        gf_free(rtp);
        gf_free(bi);
}

#endif


GPAC_MODULE_EXPORT
const u32 *QueryInterfaces()
{
        static u32 si [] = {
#ifndef GPAC_DISABLE_STREAMING
                GF_NET_CLIENT_INTERFACE,
#endif
                0
        };
        return si;
}

GPAC_MODULE_EXPORT
GF_BaseInterface *LoadInterface(u32 InterfaceType)
{
#ifndef GPAC_DISABLE_STREAMING
        if (InterfaceType == GF_NET_CLIENT_INTERFACE) return (GF_BaseInterface *)RTP_Load();
#endif
        return NULL;
}

GPAC_MODULE_EXPORT
void ShutdownInterface(GF_BaseInterface *ifce)
{
        switch (ifce->InterfaceType) {
#ifndef GPAC_DISABLE_STREAMING
        case GF_NET_CLIENT_INTERFACE:
                RTP_Delete(ifce);
                break;
#endif
        }
}

GPAC_MODULE_STATIC_DECLARATION( rtp_in )

/* [<][>][^][v][top][bottom][index][help] */