/*****************************************************************************
* Preamble
*****************************************************************************/
-#include <vlc/vlc.h>
-#include <vlc_sout.h>
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef WIN32
+# define _WIN32_WINNT 0x0501
+#endif
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_sout.h>
#include <vlc_network.h>
#include "vlc_url.h"
#define MUX_TEXT N_("Output muxer")
#define MUX_LONGTEXT N_( \
"Muxer to use for the stream." )
-#define DST_TEXT N_("Output destination")
-#define DST_LONGTEXT N_( \
- "Destination (URL) to use for the stream." )
+#define DEST_TEXT N_("Output destination")
+#define DEST_LONGTEXT N_( \
+ "Destination (URL) to use for the stream. Overrides path and bind parameters" )
+#define BIND_TEXT N_("address to bind to (helper setting for dst)")
+#define BIND_LONGTEXT N_( \
+ "address:port to bind vlc to listening incoming streams "\
+ "helper setting for dst,dst=bind+'/'+path. dst-parameter overrides this" )
+#define PATH_TEXT N_("filename for stream (helper setting for dst)")
+#define PATH_LONGTEXT N_( \
+ "Filename for stream "\
+ "helper setting for dst, dst=bind+'/'+path, dst-parameter overrides this" )
#define NAME_TEXT N_("Session name")
#define NAME_LONGTEXT N_( \
- "This allows you to specify a name for the session, that will be announced "\
- "if you choose to use SAP." )
+ "This is the name of the session that will be announced in the SDP " \
+ "(Session Descriptor)." )
#define GROUP_TEXT N_("Session groupname")
#define GROUP_LONGTEXT N_( \
"This allows you to specify a group for the session, that will be announced "\
"if you choose to use SAP." )
-#define DESC_TEXT N_("Session descriptipn")
+#define DESC_TEXT N_("Session description")
#define DESC_LONGTEXT N_( \
"This allows you to give a short description with details about the stream, " \
"that will be announced in the SDP (Session Descriptor)." )
#define SOUT_CFG_PREFIX "sout-standard-"
vlc_module_begin();
- set_shortname( _("Standard"));
- set_description( _("Standard stream output") );
+ set_shortname( N_("Standard"));
+ set_description( N_("Standard stream output") );
set_capability( "sout stream", 50 );
add_shortcut( "standard" );
add_shortcut( "std" );
set_subcategory( SUBCAT_SOUT_STREAM );
add_string( SOUT_CFG_PREFIX "access", "", NULL, ACCESS_TEXT,
- ACCESS_LONGTEXT, VLC_FALSE );
+ ACCESS_LONGTEXT, false );
add_string( SOUT_CFG_PREFIX "mux", "", NULL, MUX_TEXT,
- MUX_LONGTEXT, VLC_FALSE );
- add_string( SOUT_CFG_PREFIX "dst", "", NULL, DST_TEXT,
- DST_LONGTEXT, VLC_FALSE );
-
- add_bool( SOUT_CFG_PREFIX "sap", 0, NULL, SAP_TEXT, SAP_LONGTEXT, VLC_TRUE );
+ MUX_LONGTEXT, false );
+ add_string( SOUT_CFG_PREFIX "dst", "", NULL, DEST_TEXT,
+ DEST_LONGTEXT, false );
+ add_string( SOUT_CFG_PREFIX "bind", "", NULL, BIND_TEXT,
+ BIND_LONGTEXT, false );
+ add_string( SOUT_CFG_PREFIX "path", "", NULL, PATH_TEXT,
+ PATH_LONGTEXT, false );
+ change_unsafe();
+
+ add_bool( SOUT_CFG_PREFIX "sap", false, NULL, SAP_TEXT, SAP_LONGTEXT,
+ true );
add_string( SOUT_CFG_PREFIX "name", "", NULL, NAME_TEXT, NAME_LONGTEXT,
- VLC_TRUE );
+ true );
add_string( SOUT_CFG_PREFIX "group", "", NULL, GROUP_TEXT, GROUP_LONGTEXT,
- VLC_TRUE );
+ true );
add_string( SOUT_CFG_PREFIX "description", "", NULL, DESC_TEXT, DESC_LONGTEXT,
- VLC_TRUE );
+ true );
add_string( SOUT_CFG_PREFIX "url", "", NULL, URL_TEXT, URL_LONGTEXT,
- VLC_TRUE );
+ true );
add_string( SOUT_CFG_PREFIX "email", "", NULL, EMAIL_TEXT, EMAIL_LONGTEXT,
- VLC_TRUE );
+ true );
add_string( SOUT_CFG_PREFIX "phone", "", NULL, PHONE_TEXT, PHONE_LONGTEXT,
- VLC_TRUE );
+ true );
add_obsolete_bool( SOUT_CFG_PREFIX "sap-ipv6" );
set_callbacks( Open, Close );
/*****************************************************************************
* Exported prototypes
*****************************************************************************/
-static const char *ppsz_sout_options[] = {
+static const char *const ppsz_sout_options[] = {
"access", "mux", "url", "dst",
- "sap", "name", "group", "description", "url", "email", "phone", NULL
+ "sap", "name", "group", "description", "url", "email", "phone",
+ "bind", "path", NULL
};
#define DEFAULT_PORT 1234
{
sout_stream_t *p_stream = (sout_stream_t*)p_this;
sout_instance_t *p_sout = p_stream->p_sout;
+ sout_stream_sys_t *p_sys;
char *psz_mux;
char *psz_access;
- char *psz_url;
+ char *psz_url=NULL;
+ char *psz_bind;
+ char *psz_path;
vlc_value_t val;
psz_mux = *val.psz_string ? val.psz_string : NULL;
if( !*val.psz_string ) free( val.psz_string );
+ var_Get( p_stream, SOUT_CFG_PREFIX "bind", &val );
+ psz_bind = *val.psz_string ? val.psz_string : NULL;
+ if( !*val.psz_string ) free( val.psz_string);
+
+ var_Get( p_stream, SOUT_CFG_PREFIX "path", &val );
+ psz_path = *val.psz_string ? val.psz_string : NULL;
+ if( !*val.psz_string ) free( val.psz_string);
+
+ if( psz_bind ) psz_url = psz_bind;
+ if( psz_url && psz_path )
+ {
+ if( asprintf( &psz_url,"%s/%s",psz_url,psz_path ) == -1 )
+ psz_url = NULL;
+ free( psz_path );
+ }
var_Get( p_stream, SOUT_CFG_PREFIX "dst", &val );
- psz_url = *val.psz_string ? val.psz_string : NULL;
- if( !*val.psz_string ) free( val.psz_string );
+ if( *val.psz_string )
+ {
+ free( psz_url);
+ psz_url = val.psz_string;
+ }
+ else
+ free( val.psz_string );
- p_stream->p_sys = malloc( sizeof( sout_stream_sys_t) );
+ p_sys = p_stream->p_sys = malloc( sizeof( sout_stream_sys_t) );
+ if( !p_sys )
+ {
+ free( psz_url );
+ return VLC_ENOMEM;
+ }
p_stream->p_sys->p_session = NULL;
msg_Dbg( p_this, "creating `%s/%s://%s'", psz_access, psz_mux, psz_url );
if( psz_url && strrchr( psz_url, '.' ) )
{
/* by extension */
- static struct { const char *ext; const char *mux; } exttomux[] =
+ static struct { const char ext[6]; const char mux[32]; } exttomux[] =
{
{ "avi", "avi" },
{ "ogg", "ogg" },
{ "mpeg","ps" },
{ "ps", "ps" },
{ "mpeg1","mpeg1" },
- { "wav","wav" },
+ { "wav", "wav" },
{ "flv", "ffmpeg{mux=flv}" },
- { NULL, NULL }
+ { "mkv", "ffmpeg{mux=matroska}"},
+ { "", "" }
};
const char *psz_ext = strrchr( psz_url, '.' ) + 1;
int i;
msg_Dbg( p_this, "extension is %s", psz_ext );
- for( i = 0; exttomux[i].ext != NULL; i++ )
+ for( i = 0; exttomux[i].ext[0]; i++ )
{
if( !strcasecmp( psz_ext, exttomux[i].ext ) )
{
else
{
msg_Err( p_stream, "no access _and_ no muxer (fatal error)" );
+ free( psz_url );
+ free( p_sys );
return VLC_EGENERIC;
}
}
{
psz_mux = strdup("asfh");
}
- else if (!strcmp (psz_access, "udp")
- || !strcmp (psz_access, "rtp") || !strcmp (psz_access, "udplite")
- || !strcmp (psz_access, "tcp") || !strcmp (psz_access, "sctp")
- || !strcmp (psz_access, "dccp"))
+ else if (!strcmp (psz_access, "udp"))
{
psz_mux = strdup("ts");
}
else
{
msg_Err( p_stream, "no mux specified or found by extension" );
+ free( p_sys );
return VLC_EGENERIC;
}
}
}
/* fix or warn of incompatible couple */
- if( psz_mux && psz_access )
+ if( !strncmp( psz_access, "mmsh", 4 ) &&
+ strncmp( psz_mux, "asfh", 4 ) )
{
- if( !strncmp( psz_access, "mmsh", 4 ) &&
- strncmp( psz_mux, "asfh", 4 ) )
- {
- char *p = strchr( psz_mux,'{' );
+ char *p = strchr( psz_mux,'{' );
- msg_Warn( p_stream, "fixing to mmsh/asfh" );
- if( p )
- {
- /* -> a little memleak but ... */
- psz_mux = malloc( strlen( "asfh" ) + strlen( p ) + 1);
- sprintf( psz_mux, "asfh%s", p );
- }
- else
- {
- psz_mux = strdup("asfh");
- }
+ msg_Warn( p_stream, "fixing to mmsh/asfh" );
+ if( p )
+ {
+ if( asprintf( &p, "asfh%s", p ) == -1 )
+ p = NULL;
+ free( psz_mux );
+ psz_mux = p;
}
- else if( ( !strncmp( psz_access, "rtp", 3 ) ||
- !strncmp( psz_access, "udp", 3 ) ) &&
- strncmp( psz_mux, "ts", 2 ) )
+ else
{
- msg_Err( p_stream, "for now udp and rtp are only valid with TS" );
+ free( psz_mux );
+ psz_mux = strdup("asfh");
}
- else if( strncmp( psz_access, "file", 4 ) &&
- ( !strncmp( psz_mux, "mov", 3 ) ||
- !strncmp( psz_mux, "mp4", 3 ) ) )
+ }
+ else if( !strncmp( psz_access, "udp", 3 ) )
+ {
+ if( !strncmp( psz_mux, "ffmpeg", 6 ) )
+ { /* why would you use ffmpeg's ts muxer ? YOU DON'T LOVE VLC ??? */
+ char *psz_ffmpeg_mux = var_CreateGetString( p_this, "ffmpeg-mux" );
+ if( !psz_ffmpeg_mux || strncmp( psz_ffmpeg_mux, "mpegts", 6 ) )
+ msg_Err( p_stream, "UDP is only valid with TS" );
+ free( psz_ffmpeg_mux );
+ }
+ else if( strncmp( psz_mux, "ts", 2 ) )
{
- msg_Err( p_stream, "mov and mp4 work only with file output" );
+ msg_Err( p_stream, "UDP is only valid with TS" );
}
}
+ else if( strncmp( psz_access, "file", 4 ) &&
+ ( !strncmp( psz_mux, "mov", 3 ) ||
+ !strncmp( psz_mux, "mp4", 3 ) ) )
+ {
+ msg_Err( p_stream, "mov and mp4 work only with file output" );
+ }
msg_Dbg( p_this, "using `%s/%s://%s'", psz_access, psz_mux, psz_url );
{
msg_Err( p_stream, "no suitable sout access module for `%s/%s://%s'",
psz_access, psz_mux, psz_url );
- if( psz_access ) free( psz_access );
- if( psz_mux ) free( psz_mux );
+ free( psz_access );
+ free( psz_mux );
+ free( psz_url );
+ free( p_sys );
return VLC_EGENERIC;
}
msg_Dbg( p_stream, "access opened" );
psz_access, psz_mux, psz_url );
sout_AccessOutDelete( p_access );
- if( psz_access ) free( psz_access );
- if( psz_mux ) free( psz_mux );
+ free( psz_access );
+ free( psz_mux );
+ free( psz_url );
+ free( p_sys );
return VLC_EGENERIC;
}
msg_Dbg( p_stream, "mux opened" );
/* *** Create the SAP Session structure *** */
if( var_GetBool( p_stream, SOUT_CFG_PREFIX"sap" ) )
{
- session_descriptor_t *p_session;
- announce_method_t *p_method = sout_SAPMethod ();
- const int payload_type = 33;
-
- static const struct { const char *access; const char *fmt; } fmts[] =
- {
- /* TLS/DTLS variants (none implemented): */
- { "dtlslite", "UDPLite/TLS/RTP/AVP %d" },
- { "dtls", "UDP/TLS/RTP/AVP %d" },
- { "dccps", "DCCP/TLS/RTP/AVP %d" },
- { "tls", "TCP/TLS/RTP/AVP %d" },
- /* Plain text: */
- { "udplite", "UDPLite/RTP/AVP %d" },
- { "udp", "udp mpeg" },
- { "rtp", "RTP/AVP %d" },
- /* Currently unsupported access outputs: */
- { "dccp", "DCCP/RTP/AVP %d" },
- { "tcp", "TCP/RTP/AVP %d" },
- /* SRTP (none implemented): */
- { "srtp", "RTP/SAVP %d" },
- { "sudplite", "UDPLite/RTP/SAVP %d" },
- { "sdccp", "DCCP/RTP/SAVP %d" },
- { "stcp", "TCP/RTP/SAVP %d" },
- { NULL, NULL }
- };
- const char *psz_sdp_fmt = NULL;
- char *fmt, *src, *dst;
- int sport, dport;
-
- for (unsigned i = 0; fmts[i].access != NULL; i++)
- if (strncasecmp (fmts[i].access, psz_access, strlen (fmts[i].access)) == 0)
- {
- psz_sdp_fmt = fmts[i].fmt;
- break;
- }
+ /* Create the SDP */
+ static const struct addrinfo hints = {
+ .ai_family = AF_UNSPEC,
+ .ai_socktype = SOCK_DGRAM,
+ .ai_protocol = 0,
+ .ai_flags = AI_NUMERICHOST | AI_NUMERICSERV
+ };
+ char *shost = var_GetNonEmptyString (p_access, "src-addr");
+ char *dhost = var_GetNonEmptyString (p_access, "dst-addr");
+ int sport = var_GetInteger (p_access, "src-port");
+ int dport = var_GetInteger (p_access, "dst-port");
+ char port[6];
+ struct sockaddr_storage src, dst;
+ socklen_t srclen = 0, dstlen = 0;
+ struct addrinfo *res;
+
+ snprintf (port, sizeof (port), "%d", dport);
+ if (getaddrinfo (dhost, port, &hints, &res) == 0)
+ {
+ memcpy (&dst, res->ai_addr, dstlen = res->ai_addrlen);
+ freeaddrinfo (res);
+ }
- src = var_GetNonEmptyString (p_access, "src-addr");
- dst = var_GetNonEmptyString (p_access, "dst-addr");
- sport = var_GetInteger (p_access, "src-port");
- dport = var_GetInteger (p_access, "dst-port");
+ snprintf (port, sizeof (port), "%d", sport);
+ if (getaddrinfo (shost, port, &hints, &res) == 0)
+ {
+ memcpy (&src, res->ai_addr, srclen = res->ai_addrlen);
+ freeaddrinfo (res);
+ }
- if ((psz_sdp_fmt == NULL)
- || (asprintf (&fmt, psz_sdp_fmt, payload_type) == -1))
- fmt = NULL;
+ char *head = vlc_sdp_Start (VLC_OBJECT (p_stream), SOUT_CFG_PREFIX,
+ (struct sockaddr *)&src, srclen,
+ (struct sockaddr *)&dst, dstlen);
+ free (shost);
- msg_Dbg( p_stream, "SAP advertized format: %s", fmt);
- if ((fmt == NULL) || ((src == NULL) && (dst == NULL)))
+ char *psz_sdp = NULL;
+ if (head != NULL)
{
- msg_Err (p_access, "SAP announces not supported for access %s",
- psz_access);
- goto nosap;
+ if (asprintf (&psz_sdp, "%s"
+ "m=video %d udp mpeg\r\n", head, dport) == -1)
+ psz_sdp = NULL;
+ free (head);
}
- p_session = sout_AnnounceSessionCreate (VLC_OBJECT (p_stream),
- SOUT_CFG_PREFIX);
- sout_SessionSetMedia (VLC_OBJECT (p_stream), p_session, fmt,
- src, sport, dst, dport);
- sout_AnnounceRegister( p_sout, p_session, p_method );
- p_stream->p_sys->p_session = p_session;
- sout_MethodRelease (p_method);
-
-nosap:
- free (fmt);
- free (src);
- free (dst);
+ /* Register the SDP with the SAP thread */
+ if (psz_sdp != NULL)
+ {
+ announce_method_t *p_method = sout_SAPMethod ();
+ msg_Dbg (p_stream, "Generated SDP:\n%s", psz_sdp);
+
+ p_sys->p_session =
+ sout_AnnounceRegisterSDP (p_sout, psz_sdp, dhost, p_method);
+ sout_MethodRelease (p_method);
+ free( psz_sdp );
+ }
+ free (dhost);
}
p_stream->pf_add = Add;
p_stream->pf_del = Del;
p_stream->pf_send = Send;
- p_stream->p_sys->p_mux = p_mux;
-
- if( psz_access ) free( psz_access );
- if( psz_mux ) free( psz_mux );
- if( psz_url ) free( psz_url );
+ p_sys->p_mux = p_mux;
+ free( psz_access );
+ free( psz_mux );
+ free( psz_url );
return VLC_SUCCESS;
}
sout_access_out_t *p_access = p_sys->p_mux->p_access;
if( p_sys->p_session != NULL )
- {
sout_AnnounceUnRegister( p_stream->p_sout, p_sys->p_session );
- sout_AnnounceSessionDestroy( p_sys->p_session );
- }
-
sout_MuxDelete( p_sys->p_mux );
sout_AccessOutDelete( p_access );