1 /*****************************************************************************
2 * shout.c: This module forwards vorbis streams to an icecast server
3 *****************************************************************************
4 * Copyright (C) 2005 the VideoLAN team
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 *****************************************************************************/
46 #include <vlc_block.h>
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 );
114 add_string( SOUT_CFG_PREFIX "description",
115 "Live stream from VLC media player", NULL,
116 DESCRIPTION_TEXT, DESCRIPTION_LONGTEXT, VLC_FALSE );
118 add_bool( SOUT_CFG_PREFIX "mp3", VLC_FALSE, NULL,
119 MP3_TEXT, MP3_LONGTEXT, VLC_TRUE );
121 add_string( SOUT_CFG_PREFIX "genre", "Alternative", NULL,
122 GENRE_TEXT, GENRE_LONGTEXT, VLC_FALSE );
124 add_string( SOUT_CFG_PREFIX "url", "http://www.videolan.org/vlc", NULL,
125 URL_TEXT, URL_LONGTEXT, VLC_FALSE );
127 add_string( SOUT_CFG_PREFIX "bitrate", "", NULL,
128 BITRATE_TEXT, BITRATE_LONGTEXT, VLC_FALSE );
130 add_string( SOUT_CFG_PREFIX "samplerate", "", NULL,
131 SAMPLERATE_TEXT, SAMPLERATE_LONGTEXT, VLC_FALSE );
133 add_string( SOUT_CFG_PREFIX "channels", "", NULL,
134 CHANNELS_TEXT, CHANNELS_LONGTEXT, VLC_FALSE );
136 add_string( SOUT_CFG_PREFIX "quality", "", NULL,
137 QUALITY_TEXT, QUALITY_LONGTEXT, VLC_FALSE );
139 add_bool( SOUT_CFG_PREFIX "public", VLC_FALSE, NULL,
140 PUBLIC_TEXT, PUBLIC_LONGTEXT, VLC_TRUE );
142 set_callbacks( Open, Close );
145 /*****************************************************************************
146 * Exported prototypes
147 *****************************************************************************/
148 static const char *ppsz_sout_options[] = {
149 "name", "description", "mp3", "genre", "url", "bitrate", "samplerate",
150 "channels", "quality", "public", NULL
154 /*****************************************************************************
155 * Exported prototypes
156 *****************************************************************************/
157 static ssize_t Write( sout_access_out_t *, block_t * );
158 static int Seek ( sout_access_out_t *, off_t );
160 struct sout_access_out_sys_t
165 /*****************************************************************************
166 * Open: open the shout connection
167 *****************************************************************************/
168 static int Open( vlc_object_t *p_this )
170 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
171 sout_access_out_sys_t *p_sys;
177 char *psz_accessname = NULL;
178 char *psz_parser = NULL;
179 char *psz_user = NULL;
180 char *psz_pass = NULL;
181 char *psz_host = NULL;
182 char *psz_mount = NULL;
183 char *psz_name = NULL;
184 char *psz_description = NULL;
185 char *tmp_port = NULL;
186 char *psz_genre = NULL;
187 char *psz_url = NULL;
189 config_ChainParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg );
191 psz_accessname = psz_parser = strdup( p_access->psz_path );
193 if( !p_access->psz_path )
196 "please specify url=user:password@host:port/mountpoint" );
200 /* Parse connection data user:pwd@host:port/mountpoint */
201 psz_user = 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_pass = psz_parser;
205 while( psz_parser[0] && psz_parser[0] != '@' ) psz_parser++;
206 if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
207 psz_host = psz_parser;
208 while( psz_parser[0] && psz_parser[0] != ':' ) psz_parser++;
209 if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
210 tmp_port = psz_parser;
211 while( psz_parser[0] && psz_parser[0] != '/' ) psz_parser++;
212 if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
213 psz_mount = psz_parser;
215 i_port = atoi( tmp_port );
217 p_sys = p_access->p_sys = malloc( sizeof( sout_access_out_sys_t ) );
220 msg_Err( p_access, "out of memory" );
221 free( psz_accessname );
225 var_Get( p_access, SOUT_CFG_PREFIX "name", &val );
226 if( *val.psz_string )
227 psz_name = val.psz_string;
229 free( val.psz_string );
231 var_Get( p_access, SOUT_CFG_PREFIX "description", &val );
232 if( *val.psz_string )
233 psz_description = val.psz_string;
235 free( val.psz_string );
237 var_Get( p_access, SOUT_CFG_PREFIX "genre", &val );
238 if( *val.psz_string )
239 psz_genre = val.psz_string;
241 free( val.psz_string );
243 var_Get( p_access, SOUT_CFG_PREFIX "url", &val );
244 if( *val.psz_string )
245 psz_url = val.psz_string;
247 free( val.psz_string );
249 p_shout = p_sys->p_shout = shout_new();
251 || shout_set_host( p_shout, psz_host ) != SHOUTERR_SUCCESS
252 || shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY ) != SHOUTERR_SUCCESS
253 || shout_set_port( p_shout, i_port ) != SHOUTERR_SUCCESS
254 || shout_set_password( p_shout, psz_pass ) != SHOUTERR_SUCCESS
255 || shout_set_mount( p_shout, psz_mount ) != SHOUTERR_SUCCESS
256 || shout_set_user( p_shout, psz_user ) != SHOUTERR_SUCCESS
257 || shout_set_agent( p_shout, "VLC media player " VERSION ) != SHOUTERR_SUCCESS
258 || shout_set_name( p_shout, psz_name ) != SHOUTERR_SUCCESS
259 || shout_set_description( p_shout, psz_description ) != SHOUTERR_SUCCESS
260 || shout_set_genre( p_shout, psz_genre ) != SHOUTERR_SUCCESS
261 || shout_set_url( p_shout, psz_url ) != SHOUTERR_SUCCESS
262 /* || shout_set_nonblocking( p_shout, 1 ) != SHOUTERR_SUCCESS */
265 msg_Err( p_access, "failed to initialize shout streaming to %s:%i/%s",
266 psz_host, i_port, psz_mount );
267 free( p_access->p_sys );
268 free( psz_accessname );
272 if( psz_name ) free( psz_name );
273 if( psz_description ) free( psz_description );
274 if( psz_genre ) free( psz_genre );
275 if( psz_url ) free( psz_url );
277 var_Get( p_access, SOUT_CFG_PREFIX "mp3", &val );
278 if( val.b_bool == VLC_TRUE )
279 i_ret = shout_set_format( p_shout, SHOUT_FORMAT_MP3 );
281 i_ret = shout_set_format( p_shout, SHOUT_FORMAT_OGG );
283 if( i_ret != SHOUTERR_SUCCESS )
285 msg_Err( p_access, "failed to set the shoutcast streaming format" );
286 free( p_access->p_sys );
287 free( psz_accessname );
291 /* Don't force bitrate to 0 but only use when specified. This will otherwise
292 show an empty field on icecast directory listing instead of NA */
293 var_Get( p_access, SOUT_CFG_PREFIX "bitrate", &val );
294 if( *val.psz_string )
296 i_ret = shout_set_audio_info( p_shout, SHOUT_AI_BITRATE, val.psz_string );
297 if( i_ret != SHOUTERR_SUCCESS )
299 msg_Err( p_access, "failed to set the information about the bitrate" );
300 free( p_access->p_sys );
301 free( psz_accessname );
307 /* Bitrate information is used for icecast/shoutcast servers directory
308 listings (sorting, stream info etc.) */
309 msg_Warn( p_access, "no bitrate information specified (required for listing " \
310 "the server as public on the shoutcast website)" );
311 free( val.psz_string );
314 /* Information about samplerate, channels and quality will not be propagated
315 through the YP protocol for icecast to the public directory listing when
316 the icecast server is operating in shoutcast compatibility mode */
318 var_Get( p_access, SOUT_CFG_PREFIX "samplerate", &val );
319 if( *val.psz_string )
321 i_ret = shout_set_audio_info( p_shout, SHOUT_AI_SAMPLERATE, val.psz_string );
322 if( i_ret != SHOUTERR_SUCCESS )
324 msg_Err( p_access, "failed to set the information about the samplerate" );
325 free( p_access->p_sys );
326 free( psz_accessname );
331 free( val.psz_string );
333 var_Get( p_access, SOUT_CFG_PREFIX "channels", &val );
334 if( *val.psz_string )
336 i_ret = shout_set_audio_info( p_shout, SHOUT_AI_CHANNELS, val.psz_string );
337 if( i_ret != SHOUTERR_SUCCESS )
339 msg_Err( p_access, "failed to set the information about the number of channels" );
340 free( p_access->p_sys );
341 free( psz_accessname );
346 free( val.psz_string );
348 var_Get( p_access, SOUT_CFG_PREFIX "quality", &val );
349 if( *val.psz_string )
351 i_ret = shout_set_audio_info( p_shout, SHOUT_AI_QUALITY, val.psz_string );
352 if( i_ret != SHOUTERR_SUCCESS )
354 msg_Err( p_access, "failed to set the information about Ogg Vorbis quality" );
355 free( p_access->p_sys );
356 free( psz_accessname );
361 free( val.psz_string );
363 var_Get( p_access, SOUT_CFG_PREFIX "public", &val );
364 if( val.b_bool == VLC_TRUE )
366 i_ret = shout_set_public( p_shout, 1 );
367 if( i_ret != SHOUTERR_SUCCESS )
369 msg_Err( p_access, "failed to set the server status setting to public" );
370 free( p_access->p_sys );
371 free( psz_accessname );
376 /* Connect at startup. Cycle through the possible protocols. */
377 i_ret = shout_get_connected( p_shout );
378 while ( i_ret != SHOUTERR_CONNECTED )
380 /* Shout parameters cannot be changed on an open connection */
381 i_ret = shout_close( p_shout );
382 if( i_ret == SHOUTERR_SUCCESS )
384 i_ret = SHOUTERR_UNCONNECTED;
387 /* Re-initialize for Shoutcast using ICY protocol. Not needed for initial connection
388 but it is when we are reconnecting after other protocol was tried. */
389 i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY );
390 if( i_ret != SHOUTERR_SUCCESS )
392 msg_Err( p_access, "failed to set the protocol to 'icy'" );
393 free( p_access->p_sys );
394 free( psz_accessname );
397 i_ret = shout_open( p_shout );
398 if( i_ret == SHOUTERR_SUCCESS )
400 i_ret = SHOUTERR_CONNECTED;
401 msg_Dbg( p_access, "connected using 'icy' (shoutcast) protocol" );
405 msg_Warn( p_access, "failed to connect using 'icy' (shoutcast) protocol" );
407 /* Shout parameters cannot be changed on an open connection */
408 i_ret = shout_close( p_shout );
409 if( i_ret == SHOUTERR_SUCCESS )
411 i_ret = SHOUTERR_UNCONNECTED;
414 /* IceCAST using HTTP protocol */
415 i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_HTTP );
416 if( i_ret != SHOUTERR_SUCCESS )
418 msg_Err( p_access, "failed to set the protocol to 'http'" );
419 free( p_access->p_sys );
420 free( psz_accessname );
423 i_ret = shout_open( p_shout );
424 if( i_ret == SHOUTERR_SUCCESS )
426 i_ret = SHOUTERR_CONNECTED;
427 msg_Dbg( p_access, "connected using 'http' (icecast 2.x) protocol" );
430 msg_Warn( p_access, "failed to connect using 'http' (icecast 2.x) protocol " );
433 for non-blocking, use:
434 while( i_ret == SHOUTERR_BUSY )
437 i_ret = shout_get_connected( p_shout );
440 if ( i_ret != SHOUTERR_CONNECTED )
442 msg_Warn( p_access, "unable to establish connection, retrying..." );
447 if( i_ret != SHOUTERR_CONNECTED )
449 msg_Err( p_access, "failed to open shout stream to %s:%i/%s: %s",
450 psz_host, i_port, psz_mount, shout_get_error(p_shout) );
451 free( p_access->p_sys );
452 free( psz_accessname );
456 p_access->pf_write = Write;
457 p_access->pf_seek = Seek;
459 msg_Dbg( p_access, "shout access output opened (%s@%s:%i/%s)",
460 psz_user, psz_host, i_port, psz_mount );
462 /* Update pace control flag */
463 if( p_access->psz_access && !strcmp( p_access->psz_access, "stream" ) )
465 p_access->p_sout->i_out_pace_nocontrol++;
468 free( psz_accessname );
473 /*****************************************************************************
474 * Close: close the target
475 *****************************************************************************/
476 static void Close( vlc_object_t * p_this )
478 sout_access_out_t *p_access = (sout_access_out_t*)p_this;
480 if( p_access->p_sys && p_access->p_sys->p_shout )
482 shout_close( p_access->p_sys->p_shout );
485 free( p_access->p_sys );
487 /* Update pace control flag */
488 if( p_access->psz_access && !strcmp( p_access->psz_access, "stream" ) )
490 p_access->p_sout->i_out_pace_nocontrol--;
493 msg_Dbg( p_access, "shout access output closed" );
496 /*****************************************************************************
497 * Write: standard write
498 *****************************************************************************/
499 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
503 shout_sync( p_access->p_sys->p_shout );
506 block_t *p_next = p_buffer->p_next;
508 if( shout_send( p_access->p_sys->p_shout,
509 p_buffer->p_buffer, p_buffer->i_buffer )
510 == SHOUTERR_SUCCESS )
512 i_write += p_buffer->i_buffer;
516 msg_Err( p_access, "cannot write to stream: %s",
517 shout_get_error(p_access->p_sys->p_shout) );
519 /* The most common cause seems to be a server disconnect, resulting in a
520 Socket Error which can only be fixed by closing and reconnecting.
521 Since we already began with a working connection, the most feasable
522 approach to get out of this error status is a (timed) reconnect approach. */
523 shout_close( p_access->p_sys->p_shout );
524 msg_Warn( p_access, "server unavailable? trying to reconnect..." );
525 /* Re-open the connection (protocol params have already been set) and re-sync */
526 if( shout_open( p_access->p_sys->p_shout ) == SHOUTERR_SUCCESS )
528 shout_sync( p_access->p_sys->p_shout );
529 msg_Warn( p_access, "reconnected to server" );
533 msg_Err( p_access, "failed to reconnect to server" );
537 block_Release( p_buffer );
539 /* XXX: Unsure if that's the cause for some audio trouble... */
547 /*****************************************************************************
548 * Seek: seek to a specific location -- not supported
549 *****************************************************************************/
550 static int Seek( sout_access_out_t *p_access, off_t i_pos )
552 msg_Err( p_access, "cannot seek on shout" );