1 /*****************************************************************************
2 * shout.c: This module forwards vorbis streams to an icecast server
3 *****************************************************************************
4 * Copyright (C) 2005 VideoLAN
7 * Authors: Daniel Fischer <dan at subsignal dot org>
8 * Derk-Jan Hartman <hartman at videolan dot org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23 *****************************************************************************/
25 /*****************************************************************************
28 * - this only works for ogg and/or mp3, and we don't check this yet.
29 * - MP3 metadata is not passed along, since metadata is only available after
30 * this module is opened.
34 * vlc v4l:/dev/video:input=2:norm=pal:size=192x144 \
35 * --sout '#transcode{vcodec=theora,vb=300,acodec=vorb,ab=96}\
36 * :std{access=shout,mux=ogg,dst=localhost:8005}'
38 *****************************************************************************/
40 /*****************************************************************************
42 *****************************************************************************/
48 #include <shout/shout.h>
50 /*****************************************************************************
52 *****************************************************************************/
53 static int Open ( vlc_object_t * );
54 static void Close( vlc_object_t * );
56 #define SOUT_CFG_PREFIX "sout-shout-"
58 #define NAME_TEXT N_("Stream name")
59 #define NAME_LONGTEXT N_("Name to give to this stream/channel on the " \
60 "shoutcast/icecast server." )
62 #define DESCRIPTION_TEXT N_("Stream description")
63 #define DESCRIPTION_LONGTEXT N_("Description of the stream content or " \
64 "information about your channel." )
66 #define MP3_TEXT N_("Stream MP3")
67 #define MP3_LONGTEXT N_("You normally have to feed the shoutcast module " \
68 "with Ogg streams. It is also possible to stream " \
69 "MP3 instead, so you can forward MP3 streams to " \
70 "the shoutcast/icecast server." )
72 /* To be listed properly as a public stream on the Yellow Pages of shoutcast/icecast
73 the genres should match those used on the corresponding sites. Several examples
74 are Alternative, Classical, Comedy, Country etc. */
76 #define GENRE_TEXT N_("Genre description")
77 #define GENRE_LONGTEXT N_("Genre of the content. " )
79 #define URL_TEXT N_("URL description")
80 #define URL_LONGTEXT N_("URL with information about the stream or your channel. " )
82 /* The shout module only "transmits" data. It does not have direct access to
83 "codec level" information. Stream information such as bitrate, samplerate,
84 channel numbers and quality (in case of Ogg streaming) need to be set manually */
86 #define BITRATE_TEXT N_("Bitrate")
87 #define BITRATE_LONGTEXT N_("Bitrate information of the transcoded stream. " )
89 #define SAMPLERATE_TEXT N_("Samplerate")
90 #define SAMPLERATE_LONGTEXT N_("Samplerate information of the transcoded stream. " )
92 #define CHANNELS_TEXT N_("Number of channels")
93 #define CHANNELS_LONGTEXT N_("Number of channels information of the transcoded stream. " )
95 #define QUALITY_TEXT N_("Ogg Vorbis Quality")
96 #define QUALITY_LONGTEXT N_("Ogg Vorbis Quality information of the transcoded stream. " )
98 #define PUBLIC_TEXT N_("Stream public")
99 #define PUBLIC_LONGTEXT N_("Make the server publicly available on the 'Yellow Pages' " \
100 "(directory listing of streams) on the icecast/shoutcast " \
101 "website. Requires the bitrate information specified for " \
102 "shoutcast. Requires Ogg streaming for icecast." )
105 set_description( _("IceCAST output") );
106 set_shortname( "Shoutcast" );
107 set_capability( "sout access", 50 );
108 set_category( CAT_SOUT );
109 set_subcategory( SUBCAT_SOUT_ACO );
110 add_shortcut( "shout" );
111 add_string( SOUT_CFG_PREFIX "name", "VLC media player - Live stream", NULL,
112 NAME_TEXT, NAME_LONGTEXT, VLC_FALSE );
113 add_string( SOUT_CFG_PREFIX "description",
114 "Live stream from VLC media player", NULL,
115 DESCRIPTION_TEXT, DESCRIPTION_LONGTEXT, VLC_FALSE );
116 add_bool( SOUT_CFG_PREFIX "mp3", VLC_FALSE, NULL,
117 MP3_TEXT, MP3_LONGTEXT, VLC_TRUE );
118 add_string( SOUT_CFG_PREFIX "genre", "Alternative", NULL,
119 GENRE_TEXT, GENRE_LONGTEXT, VLC_FALSE );
120 add_string( SOUT_CFG_PREFIX "url", "http://www.videolan.org/vlc", NULL,
121 URL_TEXT, URL_LONGTEXT, VLC_FALSE );
122 add_string( SOUT_CFG_PREFIX "bitrate", "", NULL,
123 BITRATE_TEXT, BITRATE_LONGTEXT, VLC_FALSE );
124 add_string( SOUT_CFG_PREFIX "samplerate", "", NULL,
125 SAMPLERATE_TEXT, SAMPLERATE_LONGTEXT, VLC_FALSE );
126 add_string( SOUT_CFG_PREFIX "channels", "", NULL,
127 CHANNELS_TEXT, CHANNELS_LONGTEXT, VLC_FALSE );
128 add_string( SOUT_CFG_PREFIX "quality", "", NULL,
129 QUALITY_TEXT, QUALITY_LONGTEXT, VLC_FALSE );
130 add_bool( SOUT_CFG_PREFIX "public", VLC_FALSE, NULL,
131 PUBLIC_TEXT, PUBLIC_LONGTEXT, VLC_TRUE );
132 set_callbacks( Open, Close );
135 /*****************************************************************************
136 * Exported prototypes
137 *****************************************************************************/
138 static const char *ppsz_sout_options[] = {
139 "name", "description", "mp3", "genre", "url", "bitrate", "samplerate",
140 "channels", "quality", "public", NULL
144 /*****************************************************************************
145 * Exported prototypes
146 *****************************************************************************/
147 static int Write( sout_access_out_t *, block_t * );
148 static int Seek ( sout_access_out_t *, off_t );
149 static int Read ( sout_access_out_t *, block_t * );
151 struct sout_access_out_sys_t
156 /*****************************************************************************
157 * Open: open the shout connection
158 *****************************************************************************/
159 static int Open( vlc_object_t *p_this )
161 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
162 sout_access_out_sys_t *p_sys;
168 char *psz_accessname = NULL;
169 char *psz_parser = NULL;
170 char *psz_user = NULL;
171 char *psz_pass = NULL;
172 char *psz_host = NULL;
173 char *psz_mount = NULL;
174 char *psz_name = NULL;
175 char *psz_description = NULL;
176 char *tmp_port = NULL;
177 char *psz_genre = NULL;
178 char *psz_url = NULL;
180 sout_CfgParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg );
182 psz_accessname = psz_parser = strdup( p_access->psz_name );
184 if( !p_access->psz_name )
187 "please specify url=user:password@host:port/mountpoint" );
191 /* Parse connection data user:pwd@host:port/mountpoint */
192 psz_user = psz_parser;
193 while( psz_parser[0] && psz_parser[0] != ':' ) psz_parser++;
194 if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
195 psz_pass = psz_parser;
196 while( psz_parser[0] && psz_parser[0] != '@' ) psz_parser++;
197 if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
198 psz_host = psz_parser;
199 while( psz_parser[0] && psz_parser[0] != ':' ) psz_parser++;
200 if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
201 tmp_port = psz_parser;
202 while( psz_parser[0] && psz_parser[0] != '/' ) psz_parser++;
203 if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
204 psz_mount = psz_parser;
206 i_port = atoi( tmp_port );
208 p_sys = p_access->p_sys = malloc( sizeof( sout_access_out_sys_t ) );
211 msg_Err( p_access, "out of memory" );
212 free( psz_accessname );
216 var_Get( p_access, SOUT_CFG_PREFIX "name", &val );
217 if( *val.psz_string )
218 psz_name = val.psz_string;
220 free( val.psz_string );
222 var_Get( p_access, SOUT_CFG_PREFIX "description", &val );
223 if( *val.psz_string )
224 psz_description = val.psz_string;
226 free( val.psz_string );
228 var_Get( p_access, SOUT_CFG_PREFIX "genre", &val );
229 if( *val.psz_string )
230 psz_genre = val.psz_string;
232 free( val.psz_string );
234 var_Get( p_access, SOUT_CFG_PREFIX "url", &val );
235 if( *val.psz_string )
236 psz_url = val.psz_string;
238 free( val.psz_string );
240 p_shout = p_sys->p_shout = shout_new();
242 || shout_set_host( p_shout, psz_host ) != SHOUTERR_SUCCESS
243 || shout_set_protocol( p_shout, SHOUT_PROTOCOL_HTTP ) != SHOUTERR_SUCCESS
244 || shout_set_port( p_shout, i_port ) != SHOUTERR_SUCCESS
245 || shout_set_password( p_shout, psz_pass ) != SHOUTERR_SUCCESS
246 || shout_set_mount( p_shout, psz_mount ) != SHOUTERR_SUCCESS
247 || shout_set_user( p_shout, psz_user ) != SHOUTERR_SUCCESS
248 || shout_set_agent( p_shout, "VLC media player " VERSION ) != SHOUTERR_SUCCESS
249 || shout_set_name( p_shout, psz_name ) != SHOUTERR_SUCCESS
250 || shout_set_description( p_shout, psz_description ) != SHOUTERR_SUCCESS
251 || shout_set_genre( p_shout, psz_genre ) != SHOUTERR_SUCCESS
252 || shout_set_url( p_shout, psz_url ) != SHOUTERR_SUCCESS
253 // || shout_set_nonblocking( p_shout, 1 ) != SHOUTERR_SUCCESS
256 msg_Err( p_access, "failed to initialize shout streaming to %s:%i/%s",
257 psz_host, i_port, psz_mount );
258 free( p_access->p_sys );
259 free( psz_accessname );
263 if( psz_name ) free( psz_name );
264 if( psz_description ) free( psz_description );
265 if( psz_genre ) free( psz_genre );
266 if( psz_url ) free( psz_url );
268 var_Get( p_access, SOUT_CFG_PREFIX "mp3", &val );
269 if( val.b_bool == VLC_TRUE )
270 i_ret = shout_set_format( p_shout, SHOUT_FORMAT_MP3 );
272 i_ret = shout_set_format( p_shout, SHOUT_FORMAT_OGG );
274 if( i_ret != SHOUTERR_SUCCESS )
276 msg_Err( p_access, "failed to set the shoutcast streaming format" );
277 free( p_access->p_sys );
278 free( psz_accessname );
283 /* Don't force bitrate to 0 but only use when specified. This will otherwise
284 show an empty field on icecast directory listing instead of NA */
285 var_Get( p_access, SOUT_CFG_PREFIX "bitrate", &val );
286 if( *val.psz_string )
288 i_ret = shout_set_audio_info( p_shout, SHOUT_AI_BITRATE, val.psz_string );
292 /* Bitrate information is used for icecast/shoutcast servers directory
293 listings (sorting, stream info etc.) */
294 msg_Warn( p_access, "no bitrate information specified (required for listing " \
295 "the server as public on the shoutcast website)" );
296 free( val.psz_string );
299 if( i_ret != SHOUTERR_SUCCESS )
301 msg_Err( p_access, "failed to set the information about the bitrate" );
302 free( p_access->p_sys );
303 free( psz_accessname );
307 /* Information about samplerate, channels and quality will not be propagated
308 through the YP protocol for icecast to the public directory listing when
309 the icecast server is operating in shoutcast compatibility mode */
311 var_Get( p_access, SOUT_CFG_PREFIX "samplerate", &val );
312 if( *val.psz_string )
313 i_ret = shout_set_audio_info( p_shout, SHOUT_AI_SAMPLERATE, val.psz_string );
315 free( val.psz_string );
317 if( i_ret != SHOUTERR_SUCCESS )
319 msg_Err( p_access, "failed to set the information about the samplerate" );
320 free( p_access->p_sys );
321 free( psz_accessname );
325 var_Get( p_access, SOUT_CFG_PREFIX "channels", &val );
326 if( *val.psz_string )
327 i_ret = shout_set_audio_info( p_shout, SHOUT_AI_CHANNELS, val.psz_string );
329 free( val.psz_string );
331 if( i_ret != SHOUTERR_SUCCESS )
333 msg_Err( p_access, "failed to set the information about the number of channels" );
334 free( p_access->p_sys );
335 free( psz_accessname );
339 var_Get( p_access, SOUT_CFG_PREFIX "quality", &val );
340 if( *val.psz_string )
341 i_ret = shout_set_audio_info( p_shout, SHOUT_AI_QUALITY, val.psz_string );
343 free( val.psz_string );
345 if( i_ret != SHOUTERR_SUCCESS )
347 msg_Err( p_access, "failed to set the information about Ogg Vorbis quality" );
348 free( p_access->p_sys );
349 free( psz_accessname );
353 var_Get( p_access, SOUT_CFG_PREFIX "public", &val );
354 if( val.b_bool == VLC_TRUE )
355 i_ret = shout_set_public( p_shout, 1 );
357 if( i_ret != SHOUTERR_SUCCESS )
359 msg_Err( p_access, "failed to set the server status setting to public" );
360 free( p_access->p_sys );
361 free( psz_accessname );
365 i_ret = shout_open( p_shout );
366 if( i_ret == SHOUTERR_SUCCESS )
368 i_ret = SHOUTERR_CONNECTED;
372 /* If default 'http' protocol for icecast 2.x fails, fall back to 'icy'
373 for shoutcast server */
374 msg_Warn( p_access, "failed to connect using 'http' (icecast 2.x) protocol, "
375 "switching to 'icy' (shoutcast)" );
377 i_ret = shout_get_format( p_shout );
378 if( i_ret != SHOUT_FORMAT_MP3 )
380 msg_Err( p_access, "failed to use 'icy' protocol: only MP3 " \
381 "streaming to shoutcast is supported" );
382 free( p_access->p_sys );
383 free( psz_accessname );
387 /* Shout parameters cannot be changed on an open connection */
388 i_ret = shout_close( p_shout );
389 if( i_ret == SHOUTERR_SUCCESS )
391 i_ret = SHOUTERR_UNCONNECTED;
394 i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY );
395 if( i_ret != SHOUTERR_SUCCESS )
397 msg_Err( p_access, "failed to set the protocol to 'icy'" );
398 free( p_access->p_sys );
399 free( psz_accessname );
403 i_ret = shout_open( p_shout );
404 if( i_ret == SHOUTERR_SUCCESS )
406 i_ret = SHOUTERR_CONNECTED;
411 for non-blocking, use:
412 while( i_ret == SHOUTERR_BUSY )
415 i_ret = shout_get_connected( p_shout );
418 if( i_ret != SHOUTERR_CONNECTED )
420 msg_Err( p_access, "failed to open shout stream to %s:%i/%s: %s",
421 psz_host, i_port, psz_mount, shout_get_error(p_shout) );
422 free( p_access->p_sys );
423 free( psz_accessname );
427 p_access->pf_write = Write;
428 p_access->pf_read = Read;
429 p_access->pf_seek = Seek;
431 msg_Dbg( p_access, "shout access output opened (%s@%s:%i/%s)",
432 psz_user, psz_host, i_port, psz_mount );
434 /* Update pace control flag */
435 if( p_access->psz_access && !strcmp( p_access->psz_access, "stream" ) )
437 p_access->p_sout->i_out_pace_nocontrol++;
440 free( psz_accessname );
445 /*****************************************************************************
446 * Close: close the target
447 *****************************************************************************/
448 static void Close( vlc_object_t * p_this )
450 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
452 if( p_access->p_sys && p_access->p_sys->p_shout )
454 shout_close( p_access->p_sys->p_shout );
457 free( p_access->p_sys );
459 /* Update pace control flag */
460 if( p_access->psz_access && !strcmp( p_access->psz_access, "stream" ) )
462 p_access->p_sout->i_out_pace_nocontrol--;
465 msg_Dbg( p_access, "shout access output closed" );
468 /*****************************************************************************
469 * Read: standard read -- not supported
470 *****************************************************************************/
471 static int Read( sout_access_out_t *p_access, block_t *p_buffer )
473 msg_Err( p_access, "cannot read from shout" );
477 /*****************************************************************************
478 * Write: standard write
479 *****************************************************************************/
480 static int Write( sout_access_out_t *p_access, block_t *p_buffer )
484 shout_sync( p_access->p_sys->p_shout );
487 block_t *p_next = p_buffer->p_next;
489 if( shout_send( p_access->p_sys->p_shout,
490 p_buffer->p_buffer, p_buffer->i_buffer )
491 == SHOUTERR_SUCCESS )
493 i_write += p_buffer->i_buffer;
497 msg_Err( p_access, "cannot write to stream: %s",
498 shout_get_error(p_access->p_sys->p_shout) );
500 block_Release( p_buffer );
502 /* XXX: Unsure if that's the cause for some audio trouble... */
510 /*****************************************************************************
511 * Seek: seek to a specific location -- not supported
512 *****************************************************************************/
513 static int Seek( sout_access_out_t *p_access, off_t i_pos )
515 msg_Err( p_access, "cannot seek on shout" );