/***************************************************************************** * vod.c: rtsp VoD server module ***************************************************************************** * Copyright (C) 2003-2006, 2010 VLC authors and VideoLAN * $Id$ * * Authors: Laurent Aimar * Gildas Bazin * Pierre Ynard * * This program 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.1 of the License, or * (at your option) any later version. * * This program 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 program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ /***************************************************************************** * Preamble *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include #include "rtp.h" /***************************************************************************** * Exported prototypes *****************************************************************************/ typedef struct media_es_t media_es_t; struct media_es_t { int es_id; rtp_format_t rtp_fmt; rtsp_stream_id_t *rtsp_id; }; struct vod_media_t { /* VoD server */ vod_t *p_vod; /* RTSP server */ rtsp_stream_t *rtsp; /* ES list */ int i_es; media_es_t **es; const char *psz_mux; /* Infos */ vlc_tick_t i_length; }; struct vod_sys_t { char *psz_rtsp_path; /* */ vlc_thread_t thread; block_fifo_t *p_fifo_cmd; }; /* rtsp delayed command (to avoid deadlock between vlm/httpd) */ typedef enum { RTSP_CMD_TYPE_STOP, RTSP_CMD_TYPE_ADD, RTSP_CMD_TYPE_DEL, } rtsp_cmd_type_t; /* */ typedef struct { int i_type; vod_media_t *p_media; char *psz_arg; } rtsp_cmd_t; static vod_media_t *MediaNew( vod_t *, const char *, input_item_t * ); static void MediaDel( vod_t *, vod_media_t * ); static void MediaAskDel ( vod_t *, vod_media_t * ); static void* CommandThread( void *obj ); static void CommandPush( vod_t *, rtsp_cmd_type_t, vod_media_t *, const char *psz_arg ); /***************************************************************************** * Open: Starts the RTSP server module *****************************************************************************/ int OpenVoD( vlc_object_t *p_this ) { vod_t *p_vod = (vod_t *)p_this; vod_sys_t *p_sys = NULL; char *psz_url; p_vod->p_sys = p_sys = malloc( sizeof( vod_sys_t ) ); if( !p_sys ) goto error; psz_url = var_InheritString( p_vod, "rtsp-host" ); if( psz_url == NULL ) p_sys->psz_rtsp_path = strdup( "/" ); else { vlc_url_t url; vlc_UrlParse( &url, psz_url ); free( psz_url ); if( url.psz_path == NULL ) p_sys->psz_rtsp_path = strdup( "/" ); else if( !( strlen( url.psz_path ) > 0 && url.psz_path[strlen( url.psz_path ) - 1] == '/' ) ) { if( asprintf( &p_sys->psz_rtsp_path, "%s/", url.psz_path ) == -1 ) { p_sys->psz_rtsp_path = NULL; vlc_UrlClean( &url ); goto error; } } else p_sys->psz_rtsp_path = strdup( url.psz_path ); vlc_UrlClean( &url ); } p_vod->pf_media_new = MediaNew; p_vod->pf_media_del = MediaAskDel; p_sys->p_fifo_cmd = block_FifoNew(); if( vlc_clone( &p_sys->thread, CommandThread, p_vod, VLC_THREAD_PRIORITY_LOW ) ) { msg_Err( p_vod, "cannot spawn rtsp vod thread" ); block_FifoRelease( p_sys->p_fifo_cmd ); goto error; } return VLC_SUCCESS; error: if( p_sys ) { free( p_sys->psz_rtsp_path ); free( p_sys ); } return VLC_EGENERIC; } /***************************************************************************** * Close: *****************************************************************************/ void CloseVoD( vlc_object_t * p_this ) { vod_t *p_vod = (vod_t *)p_this; vod_sys_t *p_sys = p_vod->p_sys; /* Stop command thread */ vlc_cancel( p_sys->thread ); vlc_join( p_sys->thread, NULL ); while( block_FifoCount( p_sys->p_fifo_cmd ) > 0 ) { rtsp_cmd_t cmd; block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd ); memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) ); block_Release( p_block_cmd ); if ( cmd.i_type == RTSP_CMD_TYPE_DEL ) MediaDel(p_vod, cmd.p_media); free( cmd.psz_arg ); } block_FifoRelease( p_sys->p_fifo_cmd ); free( p_sys->psz_rtsp_path ); free( p_sys ); } /***************************************************************************** * Media handling *****************************************************************************/ static vod_media_t *MediaNew( vod_t *p_vod, const char *psz_name, input_item_t *p_item ) { vod_media_t *p_media = calloc( 1, sizeof(vod_media_t) ); if( !p_media ) return NULL; p_media->p_vod = p_vod; p_media->rtsp = NULL; TAB_INIT( p_media->i_es, p_media->es ); p_media->psz_mux = NULL; p_media->i_length = input_item_GetDuration( p_item ); vlc_mutex_lock( &p_item->lock ); msg_Dbg( p_vod, "media '%s' has %i declared ES", psz_name, p_item->i_es ); for( int i = 0; i < p_item->i_es; i++ ) { es_format_t *p_fmt = p_item->es[i]; switch( p_fmt->i_codec ) { case VLC_FOURCC( 'm', 'p', '2', 't' ): p_media->psz_mux = "ts"; break; case VLC_FOURCC( 'm', 'p', '2', 'p' ): p_media->psz_mux = "ps"; break; } assert(p_media->psz_mux == NULL || p_item->i_es == 1); media_es_t *p_es = calloc( 1, sizeof(media_es_t) ); if( !p_es ) continue; p_es->es_id = p_fmt->i_id; p_es->rtsp_id = NULL; if (rtp_get_fmt(VLC_OBJECT(p_vod), p_fmt, p_media->psz_mux, &p_es->rtp_fmt) != VLC_SUCCESS) { free(p_es); continue; } TAB_APPEND( p_media->i_es, p_media->es, p_es ); msg_Dbg(p_vod, " - added ES %u %s (%4.4s)", p_es->rtp_fmt.payload_type, p_es->rtp_fmt.ptname, (char *)&p_fmt->i_codec); } vlc_mutex_unlock( &p_item->lock ); if (p_media->i_es == 0) { msg_Err(p_vod, "no ES was added to the media, aborting"); goto error; } msg_Dbg(p_vod, "adding media '%s'", psz_name); CommandPush( p_vod, RTSP_CMD_TYPE_ADD, p_media, psz_name ); return p_media; error: MediaDel(p_vod, p_media); return NULL; } static void MediaSetup( vod_t *p_vod, vod_media_t *p_media, const char *psz_name ) { vod_sys_t *p_sys = p_vod->p_sys; char *psz_path; if( asprintf( &psz_path, "%s%s", p_sys->psz_rtsp_path, psz_name ) < 0 ) return; p_media->rtsp = RtspSetup(VLC_OBJECT(p_vod), p_media, psz_path); free( psz_path ); if (p_media->rtsp == NULL) return; for (int i = 0; i < p_media->i_es; i++) { media_es_t *p_es = p_media->es[i]; p_es->rtsp_id = RtspAddId(p_media->rtsp, NULL, 0, p_es->rtp_fmt.clock_rate, -1); } } static void MediaAskDel ( vod_t *p_vod, vod_media_t *p_media ) { msg_Dbg( p_vod, "deleting media" ); CommandPush( p_vod, RTSP_CMD_TYPE_DEL, p_media, NULL ); } static void MediaDel( vod_t *p_vod, vod_media_t *p_media ) { (void) p_vod; if (p_media->rtsp != NULL) { for (int i = 0; i < p_media->i_es; i++) { media_es_t *p_es = p_media->es[i]; if (p_es->rtsp_id != NULL) RtspDelId(p_media->rtsp, p_es->rtsp_id); } RtspUnsetup(p_media->rtsp); } for( int i = 0; i < p_media->i_es; i++ ) { free( p_media->es[i]->rtp_fmt.fmtp ); free( p_media->es[i] ); } free( p_media->es ); free( p_media ); } static void CommandPush( vod_t *p_vod, rtsp_cmd_type_t i_type, vod_media_t *p_media, const char *psz_arg ) { rtsp_cmd_t cmd; block_t *p_cmd; cmd.i_type = i_type; cmd.p_media = p_media; if( psz_arg ) cmd.psz_arg = strdup(psz_arg); else cmd.psz_arg = NULL; p_cmd = block_Alloc( sizeof(rtsp_cmd_t) ); memcpy( p_cmd->p_buffer, &cmd, sizeof(cmd) ); block_FifoPut( p_vod->p_sys->p_fifo_cmd, p_cmd ); } static void* CommandThread( void *obj ) { vod_t *p_vod = (vod_t*)obj; vod_sys_t *p_sys = p_vod->p_sys; for( ;; ) { block_t *p_block_cmd = block_FifoGet( p_sys->p_fifo_cmd ); rtsp_cmd_t cmd; if( !p_block_cmd ) break; int canc = vlc_savecancel (); memcpy( &cmd, p_block_cmd->p_buffer, sizeof(cmd) ); block_Release( p_block_cmd ); /* */ switch( cmd.i_type ) { case RTSP_CMD_TYPE_ADD: MediaSetup(p_vod, cmd.p_media, cmd.psz_arg); break; case RTSP_CMD_TYPE_DEL: MediaDel(p_vod, cmd.p_media); break; case RTSP_CMD_TYPE_STOP: vod_MediaControl( p_vod, cmd.p_media, cmd.psz_arg, VOD_MEDIA_STOP ); break; default: break; } free( cmd.psz_arg ); vlc_restorecancel (canc); } return NULL; } /***************************************************************************** * SDPGenerateVoD * FIXME: needs to be merged more? *****************************************************************************/ char *SDPGenerateVoD( const vod_media_t *p_media, const char *rtsp_url ) { assert(rtsp_url != NULL); /* Check against URL format rtsp://[]:/ */ bool ipv6 = strlen( rtsp_url ) > 7 && rtsp_url[7] == '['; /* Dummy destination address for RTSP */ struct sockaddr_storage dst; socklen_t dstlen = ipv6 ? sizeof( struct sockaddr_in6 ) : sizeof( struct sockaddr_in ); memset (&dst, 0, dstlen); dst.ss_family = ipv6 ? AF_INET6 : AF_INET; #ifdef HAVE_SA_LEN dst.ss_len = dstlen; #endif struct vlc_memstream sdp; if( vlc_sdp_Start( &sdp, VLC_OBJECT( p_media->p_vod ), "sout-rtp-", NULL, 0, (struct sockaddr *)&dst, dstlen ) ) return NULL; if( p_media->i_length > 0 ) { lldiv_t d = lldiv( p_media->i_length / 1000, 1000 ); sdp_AddAttribute( &sdp, "range"," npt=0-%lld.%03u", d.quot, (unsigned)d.rem ); } sdp_AddAttribute( &sdp, "control", "%s", rtsp_url ); /* No locking needed, the ES table can't be modified now */ for( int i = 0; i < p_media->i_es; i++ ) { media_es_t *p_es = p_media->es[i]; rtp_format_t *rtp_fmt = &p_es->rtp_fmt; const char *mime_major; /* major MIME type */ switch( rtp_fmt->cat ) { case VIDEO_ES: mime_major = "video"; break; case AUDIO_ES: mime_major = "audio"; break; case SPU_ES: mime_major = "text"; break; default: continue; } sdp_AddMedia( &sdp, mime_major, "RTP/AVP", 0, rtp_fmt->payload_type, false, 0, rtp_fmt->ptname, rtp_fmt->clock_rate, rtp_fmt->channels, rtp_fmt->fmtp ); char *track_url = RtspAppendTrackPath( p_es->rtsp_id, rtsp_url ); if( track_url != NULL ) { sdp_AddAttribute( &sdp, "control", "%s", track_url ); free( track_url ); } } return vlc_memstream_close( &sdp ) ? NULL : sdp.ptr; } int vod_check_range(vod_media_t *p_media, const char *psz_session, int64_t start, int64_t end) { (void) psz_session; if (p_media->i_length > 0 && (start > p_media->i_length || end > p_media->i_length)) return VLC_EGENERIC; return VLC_SUCCESS; } /* TODO: add support in the VLM for queueing proper PLAY requests with * start and end times, fetch whether the input is seekable... and then * clean this up */ void vod_play(vod_media_t *p_media, const char *psz_session, int64_t *start, int64_t end) { if (vod_check_range(p_media, psz_session, *start, end) != VLC_SUCCESS) return; /* We're passing the #vod{} sout chain here */ vod_MediaControl(p_media->p_vod, p_media, psz_session, VOD_MEDIA_PLAY, "vod", start); } void vod_pause(vod_media_t *p_media, const char *psz_session, int64_t *npt) { vod_MediaControl(p_media->p_vod, p_media, psz_session, VOD_MEDIA_PAUSE, npt); } void vod_stop(vod_media_t *p_media, const char *psz_session) { CommandPush(p_media->p_vod, RTSP_CMD_TYPE_STOP, p_media, psz_session); } const char *vod_get_mux(const vod_media_t *p_media) { return p_media->psz_mux; } /* Match an RTP id to a VoD media ES and RTSP track to initialize it * with the data that was already set up */ int vod_init_id(vod_media_t *p_media, const char *psz_session, int es_id, sout_stream_id_sys_t *sout_id, rtp_format_t *rtp_fmt, uint32_t *ssrc, uint16_t *seq_init) { media_es_t *p_es; if (p_media->psz_mux != NULL) { assert(p_media->i_es == 1); p_es = p_media->es[0]; } else { p_es = NULL; /* No locking needed, the ES table can't be modified now */ for (int i = 0; i < p_media->i_es; i++) { if (p_media->es[i]->es_id == es_id) { p_es = p_media->es[i]; break; } } if (p_es == NULL) return VLC_EGENERIC; } memcpy(rtp_fmt, &p_es->rtp_fmt, sizeof(*rtp_fmt)); if (p_es->rtp_fmt.fmtp != NULL) rtp_fmt->fmtp = strdup(p_es->rtp_fmt.fmtp); return RtspTrackAttach(p_media->rtsp, psz_session, p_es->rtsp_id, sout_id, ssrc, seq_init); } /* Remove references to the RTP id from its RTSP track */ void vod_detach_id(vod_media_t *p_media, const char *psz_session, sout_stream_id_sys_t *sout_id) { RtspTrackDetach(p_media->rtsp, psz_session, sout_id); }