/*****************************************************************************
- * live.cpp : live.com support.
+ * livedotcom.cpp : LIVE555 Streaming Media support.
*****************************************************************************
- * Copyright (C) 2003-2004 the VideoLAN team
+ * Copyright (C) 2003-2005 the VideoLAN team
* $Id$
*
* Authors: Laurent Aimar <fenrir@via.ecp.fr>
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
*****************************************************************************/
/*****************************************************************************
"value should be set in millisecond units." )
#define KASENNA_TEXT N_( "Kasenna RTSP dialect")
-#define KASENNA_LONGTEXT N_( "Kasenna server speak an old and unstandard " \
+#define KASENNA_LONGTEXT N_( "Kasenna servers use an old and unstandard " \
"dialect of RTSP. When you set this parameter, VLC will try this dialect "\
- "for communication. In this mode you cannot talk to normal RTSP servers." )
+ "for communication. In this mode you cannot connect to normal RTSP servers." )
vlc_module_begin();
- set_description( _("live.com (RTSP/RTP/SDP) demuxer" ) );
+ set_description( _("RTP/RTSP/SDP demuxer (using Live555.com)" ) );
set_capability( "demux2", 50 );
- set_shortname( "Live.com RTP/RTSP");
+ set_shortname( "RTP/RTSP");
set_callbacks( Open, Close );
add_shortcut( "live" );
set_category( CAT_INPUT );
add_bool( "rtsp-tcp", 0, NULL,
N_("Use RTP over RTSP (TCP)"),
N_("Use RTP over RTSP (TCP)"), VLC_TRUE );
+ add_integer( "rtp-client-port", -1, NULL,
+ N_("Client port"),
+ N_("Port to use for the RTP source of the session"), VLC_TRUE );
+#if LIVEMEDIA_LIBRARY_VERSION_INT > 1130457500
+ add_bool( "rtsp-http", 0, NULL,
+ N_("Tunnel RTSP and RTP over HTTP"),
+ N_("Tunnel RTSP and RTP over HTTP"), VLC_TRUE );
+ add_integer( "rtsp-http-port", 80, NULL,
+ N_("HTTP tunnel port"),
+ N_("Port to use for tunneling the RTSP/RTP over HTTP"), VLC_TRUE );
+#endif
add_integer( "rtsp-caching", 4 * DEFAULT_PTS_DELAY / 1000, NULL,
- CACHING_TEXT, CACHING_LONGTEXT, VLC_TRUE );
+ CACHING_TEXT, CACHING_LONGTEXT, VLC_TRUE );
add_bool( "rtsp-kasenna", VLC_FALSE, NULL, KASENNA_TEXT,
KASENNA_LONGTEXT, VLC_TRUE );
vlc_module_end();
/*****************************************************************************
* Local prototypes
*****************************************************************************/
+
typedef struct
{
demux_t *p_demux;
} live_track_t;
+struct timeout_thread_t
+{
+ VLC_COMMON_MEMBERS
+
+ int64_t i_remain;
+ demux_sys_t *p_sys;
+};
+
struct demux_sys_t
{
char *p_sdp; /* XXX mallocated */
live_track_t **track; /* XXX mallocated */
mtime_t i_pcr;
mtime_t i_pcr_start;
+ mtime_t i_pcr_previous;
+ mtime_t i_pcr_repeatdate;
+ int i_pcr_repeats;
/* Asf */
asf_header_t asfh;
mtime_t i_length;
mtime_t i_start;
+ /* timeout thread information */
+ int i_timeout; /* session timeout value in seconds */
+ vlc_bool_t b_timeout_call;/* mark to send an RTSP call to prevent server timeout */
+ timeout_thread_t *p_timeout; /* the actual thread that makes sure we don't timeout */
+
/* */
vlc_bool_t b_multicast; /* true if one of the tracks is multicasted */
vlc_bool_t b_no_data; /* true if we never receive any data */
static int Control( demux_t *, int, va_list );
static int ParseASF( demux_t * );
-
static int RollOverTcp( demux_t * );
static void StreamRead( void *, unsigned int, unsigned int,
static void StreamClose( void * );
static void TaskInterrupt( void * );
+static void TimeoutPrevention( timeout_thread_t * );
+
+#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1117756800
static unsigned char* parseH264ConfigStr( char const* configStr,
- unsigned& configSize );
+ unsigned int& configSize );
+#endif
/*****************************************************************************
* DemuxOpen:
static int Open ( vlc_object_t *p_this )
{
demux_t *p_demux = (demux_t*)p_this;
- demux_sys_t *p_sys;
+ demux_sys_t *p_sys = NULL;
MediaSubsessionIterator *iter;
MediaSubsession *sub;
v, o, s fields are mandatory and in this order */
if( stream_Peek( p_demux->s, &p_peek, 7 ) < 7 ) return VLC_EGENERIC;
- if( strncmp( (char*)p_peek, "v=0\r\n", 5 ) &&
- strncmp( (char*)p_peek, "v=0\n", 4 ) &&
+ if( memcmp( (char*)p_peek, "v=0\r\n", 5 ) &&
+ memcmp( (char*)p_peek, "v=0\n", 4 ) &&
( p_peek[0] < 'a' || p_peek[0] > 'z' || p_peek[1] != '=' ) )
{
return VLC_EGENERIC;
p_demux->pf_demux = Demux;
p_demux->pf_control= Control;
p_demux->p_sys = p_sys = (demux_sys_t*)malloc( sizeof( demux_sys_t ) );
+ if( !p_sys ) return VLC_ENOMEM;
+
p_sys->p_sdp = NULL;
p_sys->scheduler = NULL;
p_sys->env = NULL;
p_sys->track = NULL;
p_sys->i_pcr = 0;
p_sys->i_pcr_start = 0;
+ p_sys->i_pcr_previous = 0;
+ p_sys->i_pcr_repeatdate = 0;
+ p_sys->i_pcr_repeats = 0;
p_sys->i_length = 0;
p_sys->i_start = 0;
p_sys->p_out_asf = NULL;
p_sys->b_no_data = VLC_TRUE;
p_sys->i_no_data_ti = 0;
+ p_sys->p_timeout = NULL;
+ p_sys->i_timeout = 0;
+ p_sys->b_timeout_call = VLC_FALSE;
p_sys->b_multicast = VLC_FALSE;
- p_sys->psz_path = p_demux->psz_path;
+ p_sys->psz_path = strdup(p_demux->psz_path);
if( ( p_sys->scheduler = BasicTaskScheduler::createNew() ) == NULL )
goto error;
}
- if( strcasecmp( p_demux->psz_access, "sdp" ) &&
- vlc_UrlIsNotEncoded( p_sys->psz_path ) )
+ if( strcasecmp( p_demux->psz_access, "sdp" ) )
{
- p_sys->psz_path = vlc_UrlEncode( p_sys->psz_path );
- if( p_sys->psz_path == NULL )
- goto error;
+ char *p = p_sys->psz_path;
+ while( (p = strchr( p, ' ' )) != NULL ) *p = '+';
}
if( p_demux->s == NULL && !strcasecmp( p_demux->psz_access, "rtsp" ) )
{
char *psz_url;
char *psz_options;
+#if LIVEMEDIA_LIBRARY_VERSION_INT > 1130457500
+ int i_http_port = 0;
+ if( var_CreateGetBool( p_demux, "rtsp-http" ) )
+ i_http_port = var_CreateGetInteger( p_demux, "rtsp-http-port" );
+
+ if( ( p_sys->rtsp = RTSPClient::createNew(*p_sys->env, 1/*verbose*/,
+ "VLC Media Player", i_http_port ) ) == NULL )
+#else
if( ( p_sys->rtsp = RTSPClient::createNew(*p_sys->env, 1/*verbose*/,
"VLC Media Player" ) ) == NULL )
+#endif
{
msg_Err( p_demux, "RTSPClient::createNew failed (%s)",
p_sys->env->getResultMsg() );
sprintf( psz_url, "rtsp://%s", p_sys->psz_path );
psz_options = p_sys->rtsp->sendOptionsCmd( psz_url );
- if( psz_options )
- delete [] psz_options;
+ if( psz_options ) delete [] psz_options;
p_sdp = (uint8_t*)p_sys->rtsp->describeURL( psz_url,
- NULL, var_CreateGetBool( p_demux, "rtsp-kasenna" ) );
+ NULL, var_CreateGetBool( p_demux, "rtsp-kasenna" ) ) ;
if( p_sdp == NULL )
{
msg_Err( p_demux, "describeURL failed (%s)",
{
unsigned int i_buffer = 0;
Boolean bInit;
+ int i_client_port;
+
+ i_client_port = var_CreateGetInteger( p_demux, "rtp-client-port" );
+ if( i_client_port != -1 )
+ sub->setClientPortNum( i_client_port );
/* Value taken from mplayer */
if( !strcmp( sub->mediumName(), "audio" ) )
if( !p_sys->rtsp->playMediaSession( *p_sys->ms ) )
{
msg_Err( p_demux, "PLAY failed %s", p_sys->env->getResultMsg() );
+ delete iter;
goto error;
}
+
+ /* Retrieve the timeout value and set up a timeout prevention thread */
+#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1138089600
+ p_sys->i_timeout = p_sys->rtsp->sessionTimeoutParameter();
+#endif
+ if( p_sys->i_timeout > 0 )
+ {
+ msg_Dbg( p_demux, "We have a timeout of %d seconds", p_sys->i_timeout );
+ p_sys->p_timeout = (timeout_thread_t *)vlc_object_create( p_demux, sizeof(timeout_thread_t) );
+ p_sys->p_timeout->p_sys = p_demux->p_sys; /* lol, object recursion :D */
+ if( vlc_thread_create( p_sys->p_timeout, "liveMedia-timeout", TimeoutPrevention,
+ VLC_THREAD_PRIORITY_LOW, VLC_TRUE ) )
+ {
+ msg_Err( p_demux, "cannot spawn liveMedia timeout thread" );
+ delete iter;
+ vlc_object_destroy( p_sys->p_timeout );
+ goto error;
+ }
+ msg_Dbg( p_demux, "spawned timeout thread" );
+ vlc_object_attach( p_sys->p_timeout, p_demux );
+ }
}
/* Create all es struct */
{
live_track_t *tk;
- if( sub->readSource() == NULL )
- {
- continue;
- }
+ if( sub->readSource() == NULL ) continue;
tk = (live_track_t*)malloc( sizeof( live_track_t ) );
tk->p_demux = p_demux;
{
es_format_Init( &tk->fmt, AUDIO_ES, VLC_FOURCC('u','n','d','f') );
tk->fmt.audio.i_channels = sub->numChannels();
- tk->fmt.audio.i_rate = sub->rtpSource()->timestampFrequency();
+ tk->fmt.audio.i_rate = sub->rtpTimestampFrequency();
if( !strcmp( sub->codecName(), "MPA" ) ||
!strcmp( sub->codecName(), "MPA-ROBUST" ) ||
{
tk->fmt.i_codec = VLC_FOURCC( 'a', 'l', 'a', 'w' );
}
+ else if( !strncmp( sub->codecName(), "G726", 4 ) )
+ {
+ tk->fmt.i_codec = VLC_FOURCC( 'g', '7', '2', '6' );
+ tk->fmt.audio.i_rate = 8000;
+ tk->fmt.audio.i_channels = 1;
+ if( !strcmp( sub->codecName()+5, "40" ) )
+ tk->fmt.i_bitrate = 40000;
+ else if( !strcmp( sub->codecName()+5, "32" ) )
+ tk->fmt.i_bitrate = 32000;
+ else if( !strcmp( sub->codecName()+5, "24" ) )
+ tk->fmt.i_bitrate = 24000;
+ else if( !strcmp( sub->codecName()+5, "16" ) )
+ tk->fmt.i_bitrate = 16000;
+ }
+ else if( !strcmp( sub->codecName(), "AMR" ) )
+ {
+ tk->fmt.i_codec = VLC_FOURCC( 's', 'a', 'm', 'r' );
+ }
+ else if( !strcmp( sub->codecName(), "AMR-WB" ) )
+ {
+ tk->fmt.i_codec = VLC_FOURCC( 's', 'a', 'w', 'b' );
+ }
else if( !strcmp( sub->codecName(), "MP4A-LATM" ) )
{
unsigned int i_extra;
memcpy( tk->fmt.p_extra, p_extra, i_extra );
delete[] p_extra;
}
+
+#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1141257600
+ /* Because the "faad" decoder does not handle the LATM data length field
+ at the start of each returned LATM frame, tell the RTP source to omit it. */
+ ((MPEG4LATMAudioRTPSource*)sub->rtpSource())->omitLATMDataLengthField();
+#endif
}
else if( !strcmp( sub->codecName(), "MPEG4-GENERIC" ) )
{
}
else if( !strcmp( sub->codecName(), "H264" ) )
{
- unsigned int i_extra;
- uint8_t *p_extra;
-
+#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1117756800
+ unsigned int i_extra = 0;
+ uint8_t *p_extra = NULL;
+#endif
tk->fmt.i_codec = VLC_FOURCC( 'H', '2', '6', '4' );
tk->fmt.b_packetized = VLC_FALSE;
return VLC_SUCCESS;
error:
- if( p_sys->p_out_asf )
- {
- stream_DemuxDelete( p_sys->p_out_asf );
- }
- if( p_sys->ms )
- {
- Medium::close( p_sys->ms );
- }
- if( p_sys->rtsp )
- {
- Medium::close( p_sys->rtsp );
- }
- if( p_sys->env )
- {
- RECLAIM_ENV(p_sys->env);
- }
- if( p_sys->scheduler )
- {
- delete p_sys->scheduler;
- }
- if( p_sys->p_sdp )
+ if( p_sys->p_out_asf ) stream_DemuxDelete( p_sys->p_out_asf );
+ if( p_sys->ms ) Medium::close( p_sys->ms );
+ if( p_sys->rtsp ) Medium::close( p_sys->rtsp );
+ if( p_sys->env ) RECLAIM_ENV(p_sys->env);
+ if( p_sys->p_timeout )
{
- free( p_sys->p_sdp );
+ p_sys->p_timeout->b_die = VLC_TRUE;
+ vlc_thread_join( p_sys->p_timeout );
+ vlc_object_detach( p_sys->p_timeout );
+ vlc_object_destroy( p_sys->p_timeout );
}
- if( ( p_sys->psz_path != NULL )
- && ( p_sys->psz_path != p_demux->psz_path ) )
- free( p_sys->psz_path );
+ if( p_sys->scheduler ) delete p_sys->scheduler;
+ if( p_sys->p_sdp ) free( p_sys->p_sdp );
+ if( p_sys->psz_path ) free( p_sys->psz_path );
free( p_sys );
return VLC_EGENERIC;
Medium::close( p_sys->ms );
+ if( p_sys->p_timeout )
+ {
+ p_sys->p_timeout->b_die = VLC_TRUE;
+ vlc_thread_join( p_sys->p_timeout );
+ vlc_object_detach( p_sys->p_timeout );
+ vlc_object_destroy( p_sys->p_timeout );
+ }
+
if( p_sys->rtsp ) Medium::close( p_sys->rtsp );
if( p_sys->env ) RECLAIM_ENV(p_sys->env);
if( p_sys->scheduler ) delete p_sys->scheduler;
if( p_sys->p_sdp ) free( p_sys->p_sdp );
- if( p_sys->psz_path != p_demux->psz_path )
- free( p_sys->psz_path );
+ if( p_sys->psz_path ) free( p_sys->psz_path );
free( p_sys );
}
mtime_t i_pcr = 0;
int i;
+ /* Check if we need to send the server a Keep-A-Live signal */
+ if( p_sys->b_timeout_call && p_sys->rtsp && p_sys->ms )
+ {
+ char *psz_bye = NULL;
+#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1138089600
+ p_sys->rtsp->getMediaSessionParameter( *p_sys->ms, NULL, psz_bye );
+#endif
+ p_sys->b_timeout_call = VLC_FALSE;
+ }
+
for( i = 0; i < p_sys->i_track; i++ )
{
live_track_t *tk = p_sys->track[i];
}
}
+ /* When a On Demand QT stream ends, the last frame keeps going with the same PCR/PTS value */
+ /* This tests for that, so we can later decide to end this session */
+ if( i_pcr > 0 && p_sys->i_pcr == p_sys->i_pcr_previous )
+ {
+ if( p_sys->i_pcr_repeats == 0 )
+ p_sys->i_pcr_repeatdate = mdate();
+ p_sys->i_pcr_repeats++;
+ }
+ else
+ {
+ p_sys->i_pcr_previous = p_sys->i_pcr;
+ p_sys->i_pcr_repeatdate = 0;
+ p_sys->i_pcr_repeats = 0;
+ }
+
+ if( p_sys->i_pcr_repeats > 5 && mdate() > p_sys->i_pcr_repeatdate + 1000000 )
+ {
+ /* We need at least 5 repeats over at least a second of time before we EOF */
+ msg_Dbg( p_demux, "suspect EOF due to end of VoD session" );
+ return 0;
+ }
+
/* First warm we want to read data */
p_sys->event = 0;
for( i = 0; i < p_sys->i_track; i++ )
live_track_t *tk = p_sys->track[i];
if( !tk->b_muxed && !tk->b_rtcp_sync &&
- tk->rtpSource->hasBeenSynchronizedUsingRTCP() )
+ tk->rtpSource && tk->rtpSource->hasBeenSynchronizedUsingRTCP() )
{
msg_Dbg( p_demux, "tk->rtpSource->hasBeenSynchronizedUsingRTCP()" );
if( p_sys->rtsp && p_sys->i_length > 0 )
{
- MediaSubsessionIterator *iter =
- new MediaSubsessionIterator( *p_sys->ms );
- MediaSubsession *sub;
- int i;
-
- while( ( sub = iter->next() ) != NULL )
+ if( !p_sys->rtsp->playMediaSession( *p_sys->ms, time ) )
{
- p_sys->rtsp->playMediaSubsession( *sub, time );
+ msg_Err( p_demux, "PLAY failed %s", p_sys->env->getResultMsg() );
+ return VLC_EGENERIC;
}
- delete iter;
p_sys->i_start = (mtime_t)(f * (double)p_sys->i_length);
p_sys->i_pcr_start = 0;
p_sys->i_pcr = 0;
-
- for( i = 0; i < p_sys->i_track; i++ )
- {
- p_sys->track[i]->i_pts = 0;
- }
return VLC_SUCCESS;
}
#if 0 /* Disable for now until we have a clock synchro algo
* which works with something else than MPEG over UDP */
*pb = VLC_FALSE;
-#endif
+#else
*pb = VLC_TRUE;
+#endif
return VLC_SUCCESS;
case DEMUX_SET_PAUSE_STATE:
double d_npt;
- MediaSubsessionIterator *iter;
- MediaSubsession *sub;
d_npt = ( (double)( p_sys->i_pcr - p_sys->i_pcr_start +
p_sys->i_start ) ) / 1000000.00;
if( p_sys->rtsp == NULL )
return VLC_EGENERIC;
- iter = new MediaSubsessionIterator( *p_sys->ms );
- while( ( sub = iter->next() ) != NULL )
- {
- if( ( b_bool && !p_sys->rtsp->pauseMediaSubsession( *sub ) ) ||
- ( !b_bool && !p_sys->rtsp->playMediaSubsession( *sub,
+ if( ( b_bool && !p_sys->rtsp->pauseMediaSession( *p_sys->ms ) ) ||
+ ( !b_bool && !p_sys->rtsp->playMediaSession( *p_sys->ms,
d_npt > 0 ? d_npt : -1 ) ) )
- {
- delete iter;
+ {
+ msg_Err( p_demux, "PLAY or PAUSE failed %s", p_sys->env->getResultMsg() );
return VLC_EGENERIC;
- }
}
- delete iter;
#if 0
/* reset PCR and PCR start, mmh won't work well for multi-stream I fear */
for( i = 0; i < p_sys->i_track; i++ )
p_block->i_dts = ( tk->fmt.i_cat == VIDEO_ES ) ? 0 : i_pts;
p_block->i_pts = i_pts;
}
- //fprintf( stderr, "tk -> dpts=%lld\n", i_pts - tk->i_pts );
if( tk->b_muxed )
{
demux_t *p_demux = tk->p_demux;
demux_sys_t *p_sys = p_demux->p_sys;
- fprintf( stderr, "StreamClose\n" );
+ msg_Dbg( p_demux, "StreamClose" );
p_sys->event = 0xff;
p_demux->b_error = VLC_TRUE;
{
demux_t *p_demux = (demux_t*)p_private;
- fprintf( stderr, "TaskInterrupt\n" );
-
p_demux->p_sys->i_no_data_ti++;
/* Avoid lock */
p_demux->p_sys->event = 0xff;
}
+/*****************************************************************************
+ *
+ *****************************************************************************/
+static void TimeoutPrevention( timeout_thread_t *p_timeout )
+{
+ p_timeout->b_die = VLC_FALSE;
+ p_timeout->i_remain = (int64_t)p_timeout->p_sys->i_timeout -2;
+ p_timeout->i_remain *= 1000000;
+
+ vlc_thread_ready( p_timeout );
+
+ /* Avoid lock */
+ while( !p_timeout->b_die )
+ {
+ if( p_timeout->i_remain <= 0 )
+ {
+ p_timeout->i_remain = (int64_t)p_timeout->p_sys->i_timeout -2;
+ p_timeout->i_remain *= 1000000;
+ p_timeout->p_sys->b_timeout_call = VLC_TRUE;
+ msg_Dbg( p_timeout, "reset the timeout timer" );
+ }
+ p_timeout->i_remain -= 200000;
+ msleep( 200000 ); /* 200 ms */
+ }
+}
+
/*****************************************************************************
*
*****************************************************************************/
/* Always smaller */
p_header = block_New( p_demux, psz_end - psz_asf );
p_header->i_buffer = b64_decode( (char*)p_header->p_buffer, psz_asf );
- fprintf( stderr, "Size=%d Hdrb64=%s\n", p_header->i_buffer, psz_asf );
+ //msg_Dbg( p_demux, "Size=%d Hdrb64=%s", p_header->i_buffer, psz_asf );
if( p_header->i_buffer <= 0 )
{
free( psz_asf );
return VLC_SUCCESS;
}
+#if LIVEMEDIA_LIBRARY_VERSION_INT >= 1117756800
static unsigned char* parseH264ConfigStr( char const* configStr,
- unsigned& configSize )
+ unsigned int& configSize )
{
char *dup, *psz;
+ if( configSize )
configSize = 0;
if( configStr == NULL || *configStr == '\0' )
psz = p;
}
- free( dup );
-
+ if( dup ) free( dup );
return cfg;
}
+#endif
/*char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";*/
static int b64_decode( char *dest, char *src )