root/modules/rtp_in/rtp_signaling.c

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

DEFINITIONS

This source file includes following definitions.
  1. channel_is_valid
  2. RP_StopChannel
  3. RP_SessionActive
  4. RP_QueueCommand
  5. RP_Setup
  6. RP_SetupChannel
  7. RP_ProcessSetup
  8. RP_PreprocessDescribe
  9. RP_ProcessDescribe
  10. RP_Describe
  11. SkipCommandOnSession
  12. RP_PreprocessUserCom
  13. RP_ProcessUserCommand
  14. RP_FlushAndTearDown
  15. RP_UserCommand
  16. RP_ProcessTeardown
  17. RP_Teardown

/*
 *                      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"
#include <gpac/internal/ietf_dev.h>

#ifndef GPAC_DISABLE_STREAMING

#define GPAC_SATIP_PORT 1400

Bool channel_is_valid(RTPClient *rtp, RTPStream *ch)
{
        u32 i=0;
        RTPStream *st;
        while ((st = (RTPStream *)gf_list_enum(rtp->channels, &i))) {
                if (st == ch) return GF_TRUE;
        }
        return GF_FALSE;
}

void RP_StopChannel(RTPStream *ch)
{
        if (!ch || !ch->rtsp) return;

        ch->flags &= ~RTP_SKIP_NEXT_COM;
        //ch->status = RTP_Disconnected;
        //remove interleaved
        if (gf_rtp_is_interleaved(ch->rtp_ch)) {
                gf_rtsp_unregister_interleave(ch->rtsp->session, gf_rtp_get_low_interleave_id(ch->rtp_ch));
        }
}

/*this prevent sending teardown on session with running channels*/
Bool RP_SessionActive(RTPStream *ch)
{
        RTPStream *ach;
        u32 i, count;
        i = count = 0;
        while ((ach = (RTPStream *)gf_list_enum(ch->owner->channels, &i))) {
                if (ach->rtsp != ch->rtsp) continue;
                /*count only active channels*/
                if (ach->status == RTP_Running) count++;
        }
        return count ? GF_TRUE : GF_FALSE;
}

static void RP_QueueCommand(RTSPSession *sess, RTPStream *ch, GF_RTSPCommand *com, Bool needs_sess_id)
{
        if (needs_sess_id) {
                com->Session = sess->session_id;
        }
        if (gf_mx_try_lock(sess->owner->mx)) {
                gf_list_add(sess->rtsp_commands, com);
                gf_mx_v(sess->owner->mx);
        } else {
                gf_list_add(sess->rtsp_commands, com);
        }
}



/*
                                                channel setup functions
                                                                                                                                */

void RP_Setup(RTPStream *ch)
{
        u16 def_first_port;
        const char *opt;
        GF_RTSPCommand *com;
        GF_RTSPTransport *trans;

        com = gf_rtsp_command_new();
        com->method = gf_strdup(GF_RTSP_SETUP);

        def_first_port = 0;
        opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceFirstPort");
        if (opt) def_first_port = atoi(opt);

        opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceMulticastIP");

        //setup ports if unicast non interleaved or multicast
        if (gf_rtp_is_unicast(ch->rtp_ch) && (ch->owner->transport_mode != 1) && !gf_rtp_is_interleaved(ch->rtp_ch) ) {
                gf_rtp_set_ports(ch->rtp_ch, def_first_port);
        } else if (opt) {
                gf_rtp_set_ports(ch->rtp_ch, def_first_port);
        }

        trans = gf_rtsp_transport_clone(gf_rtp_get_transport(ch->rtp_ch));

        /*some servers get confused when trying to resetup on the same remote ports, so reset info*/
        trans->port_first = trans->port_last = 0;
        trans->SSRC = 0;

        /*override transport: */
        /*1: multicast forced*/
        opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceMulticastIP");
        if (opt) {
                trans->IsUnicast = GF_FALSE;
                trans->destination = gf_strdup(opt);
                opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceMulticastTTL");
                trans->TTL = opt ? atoi(opt) : 127;
                if (trans->Profile) gf_free(trans->Profile);
                trans->Profile = gf_strdup(GF_RTSP_PROFILE_RTP_AVP);
                if (!(ch->rtsp->flags & RTSP_DSS_SERVER) ) {
                        trans->port_first = trans->client_port_first;
                        trans->port_last = trans->client_port_last;
                        /*this is correct but doesn't work with DSS: the server expects "client_port" to indicate
                        the multicast port, not "port" - this will send both*/
                        //trans->client_port_first = trans->client_port_last = 0;
                }
                gf_rtp_setup_transport(ch->rtp_ch, trans, NULL);
        }
        /*2: RTP over RTSP forced*/
        else if (ch->rtsp->flags & RTSP_FORCE_INTER) {
                if (trans->Profile) gf_free(trans->Profile);
                trans->Profile = gf_strdup(GF_RTSP_PROFILE_RTP_AVP_TCP);
                //some servers expect the interleaved to be set during the setup request
                trans->IsInterleaved = GF_TRUE;
                trans->rtpID = gf_list_find(ch->owner->channels, ch);
                trans->rtcpID = trans->rtpID+1;
                gf_rtp_setup_transport(ch->rtp_ch, trans, NULL);
        }

        if (trans->source) {
                gf_free(trans->source);
                trans->source = NULL;
        }

        /*turn off interleaving in case of re-setup, some servers don't like it (we still signal it
        through RTP/AVP/TCP profile so it's OK)*/
//      trans->IsInterleaved = 0;
        gf_list_add(com->Transports, trans);
        if (strlen(ch->control)) com->ControlString = gf_strdup(ch->control);

        com->user_data = ch;
        ch->status = RTP_WaitingForAck;

        RP_QueueCommand(ch->rtsp, ch, com, GF_TRUE);
}

/*filter setup if no session (rtp only)*/
GF_Err RP_SetupChannel(RTPStream *ch, ChannelDescribe *ch_desc)
{
        GF_Err resp;

        /*assign ES_ID of the channel*/
        if (ch_desc && !ch->ES_ID && ch_desc->ES_ID) ch->ES_ID = ch_desc->ES_ID;

        ch->status = RTP_Setup;

        /*assign channel handle if not done*/
        if (ch_desc && ch->channel) {
                assert(ch->channel == ch_desc->channel);
        } else if (!ch->channel && ch->rtsp && !ch->rtsp->satip) {
                assert(ch_desc);
                assert(ch_desc->channel);
                ch->channel = ch_desc->channel;
        }

        /*no session , setup for pure rtp*/
        if (!ch->rtsp) {
                ch->flags |= RTP_CONNECTED;
                /*init rtp*/
                resp = RP_InitStream(ch, GF_FALSE);
                /*send confirmation to user*/
                RP_ConfirmChannelConnect(ch, resp);
        } else {
                RP_Setup(ch);
        }
        return GF_OK;
}

void RP_ProcessSetup(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e)
{
        RTPStream *ch;
        u32 i;
        GF_RTSPTransport *trans;

        ch = (RTPStream *)com->user_data;
        if (e) goto exit;

        switch (sess->rtsp_rsp->ResponseCode) {
        case NC_RTSP_OK:
                break;
        case NC_RTSP_Not_Found:
                e = GF_STREAM_NOT_FOUND;
                goto exit;
        default:
                e = GF_SERVICE_ERROR;
                goto exit;
        }
        e = GF_SERVICE_ERROR;
        if (!ch) goto exit;

        /*assign session ID*/
        if (!sess->rtsp_rsp->Session) {
                e = GF_SERVICE_ERROR;
                goto exit;
        }
        if (!sess->session_id) sess->session_id = gf_strdup(sess->rtsp_rsp->Session);
        assert(!ch->session_id);

        /*transport setup: break at the first correct transport */
        i=0;
        while ((trans = (GF_RTSPTransport *)gf_list_enum(sess->rtsp_rsp->Transports, &i))) {
                /*copy over previous ports (hack for some servers overriding client ports)*/
                const char *opt = gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(ch->owner->service), "Streaming", "ForceClientPorts");
                if (opt && !stricmp(opt, "yes"))
                        gf_rtp_get_ports(ch->rtp_ch, &trans->client_port_first, &trans->client_port_last);

                if (gf_rtp_is_interleaved(ch->rtp_ch) && !trans->IsInterleaved) {
                        GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[RTSP] Requested interleaved RTP over RTSP but server did not setup interleave - cannot process command\n"));
                        e = GF_REMOTE_SERVICE_ERROR;
                        continue;
                }

                e = gf_rtp_setup_transport(ch->rtp_ch, trans, gf_rtsp_get_server_name(sess->session));
                if (!e) break;
        }
        if (e) goto exit;

        e = RP_InitStream(ch, GF_FALSE);
        if (e) goto exit;
        ch->status = RTP_Connected;

        //in case this is TCP channel, setup callbacks
        ch->flags &= ~RTP_INTERLEAVED;
        if (gf_rtp_is_interleaved(ch->rtp_ch)) {
                ch->flags |= RTP_INTERLEAVED;
                gf_rtsp_set_interleave_callback(sess->session, RP_DataOnTCP);
        }

        if (sess->satip) {
                ChannelControl *ch_ctrl = NULL;
                GF_RTSPCommand *com = gf_rtsp_command_new();
                com->method = gf_strdup(GF_RTSP_PLAY);
                GF_SAFEALLOC(ch_ctrl, ChannelControl);
                ch_ctrl->ch = ch;
                com->user_data = ch_ctrl;
                RP_QueueCommand(sess, ch, com, GF_TRUE);
        }

exit:
        /*confirm only on first connect, otherwise this is a re-SETUP of the rtsp session, not the channel*/
        if (ch && ! (ch->flags & RTP_CONNECTED) ) {
                if (!e)
                        ch->flags |= RTP_CONNECTED;
                RP_ConfirmChannelConnect(ch, e);
        }
        com->user_data = NULL;
}


/*
                                                session/channel describe functions
                                                                                                                                */
/*filter describe commands in case of ESD URLs*/
Bool RP_PreprocessDescribe(RTSPSession *sess, GF_RTSPCommand *com)
{
        RTPStream *ch;
        ChannelDescribe *ch_desc;
        /*not a channel describe*/
        if (!com->user_data) {
                RP_SendMessage(sess->owner->service, GF_OK, "Connecting...");
                return GF_TRUE;
        }

        ch_desc = (ChannelDescribe *)com->user_data;
        ch = RP_FindChannel(sess->owner, NULL, ch_desc->ES_ID, ch_desc->esd_url, GF_FALSE);
        if (!ch) return GF_TRUE;

        /*channel has been described already, skip describe and send setup directly*/
        RP_SetupChannel(ch, ch_desc);

        if (ch_desc->esd_url) gf_free(ch_desc->esd_url);
        gf_free(ch_desc);
        return GF_FALSE;
}

/*process describe reply*/
GF_Err RP_ProcessDescribe(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e)
{
        RTPStream *ch;
        ChannelDescribe *ch_desc;

        ch = NULL;
        ch_desc = (ChannelDescribe *)com->user_data;
        if (e) goto exit;

        switch (sess->rtsp_rsp->ResponseCode) {
        //TODO handle all 3xx codes  (redirections)
        case NC_RTSP_Multiple_Choice:
                e = ch_desc ? GF_STREAM_NOT_FOUND : GF_URL_ERROR;
                goto exit;
        case NC_RTSP_Not_Found:
                e = GF_URL_ERROR;
                goto exit;
        case NC_RTSP_OK:
                break;
        default:
                //we should have a basic error code mapping here
                e = GF_SERVICE_ERROR;
                goto exit;
        }

        ch = NULL;
        if (ch_desc) {
                ch = RP_FindChannel(sess->owner, ch_desc->channel, ch_desc->ES_ID, ch_desc->esd_url, GF_FALSE);
        } else {
                RP_SendMessage(sess->owner->service, GF_OK, "Connected");
        }

        /*error on loading SDP is done internally*/
        RP_LoadSDP(sess->owner, sess->rtsp_rsp->body, sess->rtsp_rsp->Content_Length, ch);

        if (!ch_desc) goto exit;
        if (!ch) {
                e = GF_STREAM_NOT_FOUND;
                goto exit;
        }
        e = RP_SetupChannel(ch, ch_desc);

exit:
        com->user_data = NULL;
        if (e) {
                if (!ch_desc) {
                        sess->connect_error = e;
                        return e;
                } else if (ch) {
                        RP_ConfirmChannelConnect(ch, e);
                } else {
                        gf_service_connect_ack(sess->owner->service, ch_desc->channel, e);
                }
        }
        if (ch_desc) gf_free(ch_desc);
        return GF_OK;
}

/*send describe*/
void RP_Describe(RTSPSession *sess, char *esd_url, LPNETCHANNEL channel)
{
        const char *opt;
        RTPStream *ch;
        ChannelDescribe *ch_desc;
        GF_RTSPCommand *com;

        /*locate the channel by URL - if we have one, this means the channel is already described
        this happens when 2 ESD with URL use the same RTSP service - skip describe and send setup*/
        if (esd_url || channel) {
                ch = RP_FindChannel(sess->owner, channel, 0, esd_url, GF_FALSE);
                if (ch) {
                        if (!ch->channel) ch->channel = channel;
                        switch (ch->status) {
                        case RTP_Connected:
                        case RTP_Running:
                                RP_ConfirmChannelConnect(ch, GF_OK);
                                return;
                        default:
                                break;
                        }
                        ch_desc = (ChannelDescribe *)gf_malloc(sizeof(ChannelDescribe));
                        ch_desc->esd_url = esd_url ? gf_strdup(esd_url) : NULL;
                        ch_desc->channel = channel;
                        RP_SetupChannel(ch, ch_desc);

                        if (esd_url) gf_free(ch_desc->esd_url);
                        gf_free(ch_desc);
                        return;
                }
                /*channel not found, send describe on service*/
        }

        /*send describe*/
        com = gf_rtsp_command_new();
        if (!sess->satip) {
                com->method = gf_strdup(GF_RTSP_DESCRIBE);
        } else {
                GF_Err e;
                GF_RTSPTransport *trans;
                RTPStream *ch = NULL;

                com->method = gf_strdup(GF_RTSP_SETUP);

                /*setup transport ports*/
                GF_SAFEALLOC(trans, GF_RTSPTransport);
                trans->IsUnicast = GF_TRUE;
                trans->client_port_first = GPAC_SATIP_PORT;
                trans->client_port_last = GPAC_SATIP_PORT+1;
                trans->Profile = gf_strdup(GF_RTSP_PROFILE_RTP_AVP);
                gf_list_add(com->Transports, trans);

                /*hardcoded channel*/
                ch = RP_NewSatipStream(sess->owner, sess->satip_server);
                if (!ch) {
                        GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("SAT>IP: couldn't create the RTP stream.\n"));
                        return;
                }
                e = RP_AddStream(sess->owner, ch, "*");
                if (e) {
                        GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("SAT>IP: couldn't add the RTP stream.\n"));
                        return;
                }
                com->user_data = ch;
        }

        if (channel || esd_url) {
                com->Accept = gf_strdup("application/sdp");
                com->ControlString = esd_url ? gf_strdup(esd_url) : NULL;

                ch_desc = (ChannelDescribe *)gf_malloc(sizeof(ChannelDescribe));
                ch_desc->esd_url = esd_url ? gf_strdup(esd_url) : NULL;
                ch_desc->channel = channel;

                com->user_data = ch_desc;
        } else {
                //always accept both SDP and IOD
                com->Accept = gf_strdup("application/sdp, application/mpeg4-iod");
//              com->Accept = gf_strdup("application/sdp");
        }

        /*need better tuning ...*/
        opt = (char *) gf_modules_get_option((GF_BaseInterface *) gf_service_get_interface(sess->owner->service), "Network", "Bandwidth");
        if (opt && !stricmp(opt, "yes")) com->Bandwidth = atoi(opt);

        RP_QueueCommand(sess, NULL, com, GF_FALSE);
}


static void SkipCommandOnSession(RTPStream *ch)
{
        u32 i;
        RTPStream *a_ch;
        if (!ch || (ch->flags & RTP_SKIP_NEXT_COM) || !(ch->rtsp->flags & RTSP_AGG_CONTROL) ) return;
        i=0;
        while ((a_ch = (RTPStream *)gf_list_enum(ch->owner->channels, &i))) {
                if ((ch == a_ch) || (a_ch->rtsp != ch->rtsp) ) continue;
                if (a_ch->status>=RTP_Connected)
                        a_ch->flags |= RTP_SKIP_NEXT_COM;
        }
}


/*
                                                channel control functions
                                                                                                                                */
/*remove command if session is using aggregated control*/
Bool RP_PreprocessUserCom(RTSPSession *sess, GF_RTSPCommand *com)
{
        ChannelControl *ch_ctrl;
        RTPStream *ch;
        GF_Err e;
        Bool skip_it;

        ch_ctrl = NULL;
        if (strcmp(com->method, GF_RTSP_TEARDOWN)) ch_ctrl = (ChannelControl *)com->user_data;
        if (!ch_ctrl || !ch_ctrl->ch) return GF_TRUE;
        ch = ch_ctrl->ch;

        if (!sess->satip) {
                if (!ch->channel || !channel_is_valid(sess->owner, ch)) {
                        gf_free(ch_ctrl);
                        com->user_data = NULL;
                        return GF_FALSE;
                }

                assert(ch->rtsp == sess);
                assert(ch->channel == ch_ctrl->com.base.on_channel);
        }

        skip_it = GF_FALSE;
        if (!com->Session) {
                /*re-SETUP failed*/
                if (!strcmp(com->method, GF_RTSP_PLAY) || !strcmp(com->method, GF_RTSP_PAUSE)) {
                        e = GF_SERVICE_ERROR;
                        goto err_exit;
                }
                /*this is a stop, no need for SessionID just skip*/
                skip_it = GF_TRUE;
        } else {
                SkipCommandOnSession(ch);
        }

        /*check if aggregation discards this command*/
        if (skip_it || ( (sess->flags & RTSP_AGG_CONTROL) && (ch->flags & RTP_SKIP_NEXT_COM) )) {
                ch->flags &= ~RTP_SKIP_NEXT_COM;
                gf_service_command(sess->owner->service, &ch_ctrl->com, GF_OK);
                gf_free(ch_ctrl);
                com->user_data = NULL;
                return GF_FALSE;
        }
        return GF_TRUE;

err_exit:
        gf_rtsp_reset_aggregation(ch->rtsp->session);
        ch->status = RTP_Disconnected;
        ch->check_rtp_time = RTP_SET_TIME_NONE;
        gf_service_command(sess->owner->service, &ch_ctrl->com, e);
        gf_free(ch_ctrl);
        com->user_data = NULL;
        return GF_FALSE;
}

void RP_ProcessUserCommand(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e)
{
        ChannelControl *ch_ctrl;
        RTPStream *ch, *agg_ch;
        u32 i, count;
        GF_RTPInfo *info;

        ch_ctrl = (ChannelControl *)com->user_data;
        ch = ch_ctrl->ch;

        if (sess->satip) {
                if (!strcmp(com->method, GF_RTSP_PLAY)) {
                        char url[64];
                        snprintf(url, 64, "mpegts-sk://%p", ch->rtp_ch->rtp);
                        e = ch->satip_m2ts_ifce->ConnectService(ch->satip_m2ts_ifce, sess->owner->service, url);
                        if (e) {
                                assert(0);
                                GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[SATIP] Couldn't connect the M2TS service.\n"));
                                return;
                        } else {
                                ch->satip_m2ts_service_connected = GF_TRUE;
                        }
                } else if (!strcmp(com->method, GF_RTSP_PLAY)) {
                        if (ch->satip_m2ts_service_connected) {
                                ch->satip_m2ts_ifce->CloseService(ch->satip_m2ts_ifce);
                                ch->satip_m2ts_service_connected = GF_FALSE;
                        }
                } else {
                        GF_LOG(GF_LOG_ERROR, GF_LOG_RTP, ("[SATIP] Unhandled RTSP command: %s\n", com->method));
                        return;
                }
                return;
        }

        if (ch) {
                if (!ch->channel || !channel_is_valid(sess->owner, ch)) {
                        gf_free(ch_ctrl);
                        com->user_data = NULL;
                        return;
                }

                assert(ch->channel==ch_ctrl->com.base.on_channel);
        }

        /*some consistency checking: on interleaved sessions, some servers do NOT reply to the
        teardown. If our command is STOP just skip the error notif*/
        if (e) {
                if (!strcmp(com->method, GF_RTSP_TEARDOWN)) {
                        goto process_reply;
                } else {
                        /*spec is not really clear about what happens if the server doesn't support
                        non aggregated operations. Since this happen only on pause/play, we consider
                        that no error occured and wait for next play*/
                        if (sess->rtsp_rsp->ResponseCode == NC_RTSP_Only_Aggregate_Operation_Allowed) {
                                sess->flags |= RTSP_AGG_ONLY;
                                sess->rtsp_rsp->ResponseCode = NC_RTSP_OK;
                        } else {
                                goto err_exit;
                        }
                }
        }

        switch (sess->rtsp_rsp->ResponseCode) {
        //handle all 3xx codes  (redirections)
        case NC_RTSP_Method_Not_Allowed:
                e = GF_NOT_SUPPORTED;
                goto err_exit;
        case NC_RTSP_OK:
                break;
        default:
                //we should have a basic error code mapping here
                e = GF_SERVICE_ERROR;
                goto err_exit;
        }

process_reply:

        gf_service_command(sess->owner->service, &ch_ctrl->com, GF_OK);

        if ( (ch_ctrl->com.command_type==GF_NET_CHAN_PLAY)
                || (ch_ctrl->com.command_type==GF_NET_CHAN_SET_SPEED)
                || (ch_ctrl->com.command_type==GF_NET_CHAN_RESUME) ) {

                //auto-detect any aggregated control if not done yet
                if (gf_list_count(sess->rtsp_rsp->RTP_Infos) > 1) {
                        sess->flags |= RTSP_AGG_CONTROL;
                }

                //process all RTP infos
                count = gf_list_count(sess->rtsp_rsp->RTP_Infos);
                for (i=0; i<count; i++) {
                        info = (GF_RTPInfo*)gf_list_get(sess->rtsp_rsp->RTP_Infos, i);
                        agg_ch = RP_FindChannel(sess->owner, NULL, 0, info->url, GF_FALSE);

                        if (!agg_ch || (agg_ch->rtsp != sess) ) continue;
                        /*channel is already playing*/
                        if (agg_ch->status == RTP_Running) {
                                gf_rtp_set_info_rtp(agg_ch->rtp_ch, info->seq, info->rtp_time, info->ssrc);
                                agg_ch->check_rtp_time = RTP_SET_TIME_RTP;
                                continue;
                        }

                        /*if play/seeking we must send update RTP/NPT link*/
                        if (ch_ctrl->com.command_type != GF_NET_CHAN_RESUME) {
                                agg_ch->check_rtp_time = RTP_SET_TIME_RTP;
                        }
                        /*this is used to discard RTP packets re-sent on resume*/
                        else {
                                agg_ch->check_rtp_time = RTP_SET_TIME_RTP_SEEK;
                        }
                        /* reset the buffers */
                        RP_InitStream(agg_ch, GF_TRUE);

                        gf_rtp_set_info_rtp(agg_ch->rtp_ch, info->seq, info->rtp_time, info->ssrc);
                        agg_ch->status = RTP_Running;

                        /*skip next play command on this channel if aggregated control*/
                        if ((ch != agg_ch) && ch && (ch->rtsp->flags & RTSP_AGG_CONTROL) ) agg_ch->flags |= RTP_SKIP_NEXT_COM;


                        if (gf_rtp_is_interleaved(agg_ch->rtp_ch)) {
                                gf_rtsp_register_interleave(sess->session,
                                                            agg_ch,
                                                            gf_rtp_get_low_interleave_id(agg_ch->rtp_ch),
                                                            gf_rtp_get_hight_interleave_id(agg_ch->rtp_ch));
                        }
                }
                /*no rtp info (just in case), no time mapped - set to 0 and specify we're not interactive*/
                if (ch && !i) {
                        ch->current_start = 0.0;
                        ch->check_rtp_time = RTP_SET_TIME_RTP;
                        RP_InitStream(ch, GF_TRUE);
                        ch->status = RTP_Running;
                        if (gf_rtp_is_interleaved(ch->rtp_ch)) {
                                gf_rtsp_register_interleave(sess->session,
                                                            ch, gf_rtp_get_low_interleave_id(ch->rtp_ch), gf_rtp_get_hight_interleave_id(ch->rtp_ch));
                        }
                }
                if (ch) ch->flags &= ~RTP_SKIP_NEXT_COM;
        } else if (ch_ctrl->com.command_type == GF_NET_CHAN_PAUSE) {
                if (ch) {
                        SkipCommandOnSession(ch);
                        ch->flags &= ~RTP_SKIP_NEXT_COM;

                }
        } else if (ch_ctrl->com.command_type == GF_NET_CHAN_STOP) {
        }
        gf_free(ch_ctrl);
        com->user_data = NULL;
        return;


err_exit:
        gf_service_command(sess->owner->service, &ch_ctrl->com, e);
        if (ch) {
                ch->status = RTP_Disconnected;
                gf_rtsp_reset_aggregation(ch->rtsp->session);
                ch->check_rtp_time = RTP_SET_TIME_NONE;
        }
        gf_free(ch_ctrl);
        com->user_data = NULL;
}

#if 0
static void RP_FlushAndTearDown(RTSPSession *sess)
{
        GF_RTSPCommand *com;
        gf_mx_p(sess->owner->mx);

        while (gf_list_count(sess->rtsp_commands)) {
                com = (GF_RTSPCommand *)gf_list_get(sess->rtsp_commands, 0);
                gf_list_rem(sess->rtsp_commands, 0);
                gf_rtsp_command_del(com);
        }
        if (sess->flags & RTSP_WAIT_REPLY) {
                GF_Err e;
                while (1) {
                        e = gf_rtsp_get_response(sess->session, sess->rtsp_rsp);
                        if (e!= GF_IP_NETWORK_EMPTY) break;
                }
                sess->flags &= ~RTSP_WAIT_REPLY;
        }
        gf_mx_v(sess->owner->mx);


        /*no private stack on teardown - shutdown now*/
        com     = gf_rtsp_command_new();
        com->method = gf_strdup(GF_RTSP_TEARDOWN);
        RP_QueueCommand(sess, NULL, com, 1);
}
#endif


void RP_UserCommand(RTSPSession *sess, RTPStream *ch, GF_NetworkCommand *command)
{
        RTPStream *a_ch;
        ChannelControl *ch_ctrl;
        u32 i;
        Bool needs_setup = GF_FALSE;
        GF_RTSPCommand *com;
        GF_RTSPRange *range;

        switch (command->command_type) {
        case GF_NET_CHAN_PLAY:
        case GF_NET_CHAN_RESUME:
                needs_setup = GF_TRUE;
                break;
        case GF_NET_CHAN_PAUSE:
        case GF_NET_CHAN_STOP:
                break;
        default:
                gf_service_command(sess->owner->service, command, GF_NOT_SUPPORTED);
                return;
        }


        /*we may need to re-setup stream/session*/
        if (needs_setup) {
                if (ch->status == RTP_Disconnected) {
                        if (sess->flags & RTSP_AGG_CONTROL) {
                                i=0;
                                while ((a_ch = (RTPStream *)gf_list_enum(sess->owner->channels, &i))) {
                                        if (a_ch->rtsp != sess) continue;
                                        if (a_ch->status == RTP_Disconnected)
                                                RP_Setup(a_ch);
                                }
                        } else {
                                RP_Setup(ch);
                        }
                }
        }

        com     = gf_rtsp_command_new();
        range = NULL;

        if ( (command->command_type==GF_NET_CHAN_PLAY) || (command->command_type==GF_NET_CHAN_RESUME) ) {

                range = gf_rtsp_range_new();
                range->start = ch->range_start;
                range->end = ch->range_end;

                com->method = gf_strdup(GF_RTSP_PLAY);

                ch->paused = GF_FALSE;

                /*specify pause range on resume - this is not mandatory but most servers need it*/
                if (command->command_type==GF_NET_CHAN_RESUME) {
                        range->start = ch->current_start;

                        ch->stat_start_time -= ch->stat_stop_time;
                        ch->stat_start_time += gf_sys_clock();
                        ch->stat_stop_time = 0;
                } else {
                        range->start = ch->range_start;
                        if (command->play.start_range>=0) range->start += command->play.start_range;
                        range->end = ch->range_start;
                        if (command->play.end_range >=0) {
                                range->end += command->play.end_range;
                                if (range->end > ch->range_end) range->end = ch->range_end;
                        }

                        ch->stat_start_time = gf_sys_clock();
                        ch->stat_stop_time = 0;
                }
                /*if aggregated the command is sent once, so store info at session level*/
                if (ch->flags & RTP_SKIP_NEXT_COM) {
                        ch->current_start = ch->rtsp->last_range;
                } else {
                        ch->rtsp->last_range = range->start;
                        ch->current_start = range->start;
                }
                /*some RTSP servers don't accept Range=npt:0.0- (for ex, broadcast only...), so skip it if:
                - a range was given in initial describe
                - the command is not a RESUME
                */
                if (!(ch->flags & RTP_HAS_RANGE) && (command->command_type != GF_NET_CHAN_RESUME) ) {
                        gf_rtsp_range_del(range);
                        com->Range = NULL;
                } else {
                        com->Range = range;
                }

                if (sess->flags & RTSP_AGG_CONTROL)
                        SkipCommandOnSession(ch);
                else if (strlen(ch->control))
                        com->ControlString = gf_strdup(ch->control);

                if (RP_SessionActive(ch)) {
                        if (!com->ControlString && ch->control) com->ControlString = gf_strdup(ch->control);
                } else {
                        if (com->ControlString) {
                                gf_free(com->ControlString);
                                com->ControlString=NULL;
                        }
                }

        } else if (command->command_type==GF_NET_CHAN_PAUSE) {
                com->method = gf_strdup(GF_RTSP_PAUSE);
                if (ch) {
                        range = gf_rtsp_range_new();
                        /*update current time*/
                        ch->current_start += gf_rtp_get_current_time(ch->rtp_ch);
                        ch->stat_stop_time = gf_sys_clock();
                        range->start = ch->current_start;
                        range->end = -1.0;
                        com->Range = range;

                        if (sess->flags & RTSP_AGG_CONTROL)
                                SkipCommandOnSession(ch);
                        else if (strlen(ch->control))
                                com->ControlString = gf_strdup(ch->control);

                        ch->paused = GF_TRUE;
                }
        }
        else if (command->command_type==GF_NET_CHAN_STOP) {
                ch->current_start = 0;
                ch->stat_stop_time = gf_sys_clock();

                ch->status = RTP_Connected;
                RP_InitStream(ch, GF_TRUE);

                /*if server only support aggregation on pause, skip the command or issue
                a teardown if last active stream*/
                if (ch->rtsp->flags & RTSP_AGG_ONLY) {
                        RP_StopChannel(ch);
                        if (com) gf_rtsp_command_del(com);
                        if (!RP_SessionActive(ch))
                                RP_Teardown(sess, ch);
                        return;
                }
                /* otherwise send a PAUSE on the stream */
                else {
                        if (ch->paused) {
                                if (com) gf_rtsp_command_del(com);
                                return;
                        }
                        range = gf_rtsp_range_new();
                        range->start = 0;
                        range->end = -1;
                        com->method = gf_strdup(GF_RTSP_PAUSE);
                        com->Range = range;
                        /*only pause the specified stream*/
                        if (ch->control) com->ControlString = gf_strdup(ch->control);
                }
        } else {
                gf_service_command(sess->owner->service, command, GF_NOT_SUPPORTED);
                gf_rtsp_command_del(com);
                return;
        }

        ch_ctrl = (ChannelControl *)gf_malloc(sizeof(ChannelControl));
        ch_ctrl->ch = ch;
        memcpy(&ch_ctrl->com, command, sizeof(GF_NetworkCommand));
        com->user_data = ch_ctrl;

        RP_QueueCommand(sess, ch, com, GF_TRUE);
        return;
}


/*
                                                session/channel teardown functions
                                                                                                                                */
void RP_ProcessTeardown(RTSPSession *sess, GF_RTSPCommand *com, GF_Err e)
{
        RTPStream *ch = (RTPStream *)com->user_data;
        if (ch) {
                if (ch->session_id) gf_free(ch->session_id);
                ch->session_id = NULL;
        } else {
                if (sess->session_id) gf_free(sess->session_id);
                sess->session_id = NULL;
        }
}

void RP_Teardown(RTSPSession *sess, RTPStream *ch)
{
        GF_RTSPCommand *com;
        if (sess->owner->session_migration) return;
        /*we need a session id*/
        if (!sess->session_id) return;
        /*ignore teardown on channels*/
        if ((sess->flags & RTSP_AGG_CONTROL) && ch) return;

        com = gf_rtsp_command_new();
        com->method = gf_strdup(GF_RTSP_TEARDOWN);
        /*this only works in RTSP2*/
        if (ch && ch->control) {
                com->ControlString = gf_strdup(ch->control);
                com->user_data = ch;
        }

        RP_QueueCommand(sess, ch, com, GF_TRUE);
}

#endif /*GPAC_DISABLE_STREAMING*/

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