]> git.sesse.net Git - vlc/blob - modules/access_output/shout.c
* modules/access_output/shout.c: automatic reconnect to server at startup.
[vlc] / modules / access_output / shout.c
1 /*****************************************************************************
2  * shout.c: This module forwards vorbis streams to an icecast server
3  *****************************************************************************
4  * Copyright (C) 2005 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Daniel Fischer <dan at subsignal dot org>
8  *          Derk-Jan Hartman <hartman at videolan dot org>
9  *
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.
14  *
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.
19  *
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  *****************************************************************************/
24
25 /*****************************************************************************
26  * Some Comments:
27  *
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.
31  *
32  * Typical usage:
33  *
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}'
37  *
38  *****************************************************************************/
39
40 /*****************************************************************************
41  * Preamble
42  *****************************************************************************/
43
44 #include <vlc/vlc.h>
45 #include <vlc_sout.h>
46 #include <vlc_block.h>
47
48 #include <shout/shout.h>
49
50 /*****************************************************************************
51  * Module descriptor
52  *****************************************************************************/
53 static int  Open ( vlc_object_t * );
54 static void Close( vlc_object_t * );
55
56 #define SOUT_CFG_PREFIX "sout-shout-"
57
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." )
61
62 #define DESCRIPTION_TEXT N_("Stream description")
63 #define DESCRIPTION_LONGTEXT N_("Description of the stream content or " \
64                                 "information about your channel." )
65
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." )
71
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. */
75
76 #define GENRE_TEXT N_("Genre description")
77 #define GENRE_LONGTEXT N_("Genre of the content. " )
78
79 #define URL_TEXT N_("URL description")
80 #define URL_LONGTEXT N_("URL with information about the stream or your channel. " )
81
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 */
85
86 #define BITRATE_TEXT N_("Bitrate")
87 #define BITRATE_LONGTEXT N_("Bitrate information of the transcoded stream. " )
88
89 #define SAMPLERATE_TEXT N_("Samplerate")
90 #define SAMPLERATE_LONGTEXT N_("Samplerate information of the transcoded stream. " )
91
92 #define CHANNELS_TEXT N_("Number of channels")
93 #define CHANNELS_LONGTEXT N_("Number of channels information of the transcoded stream. " )
94
95 #define QUALITY_TEXT N_("Ogg Vorbis Quality")
96 #define QUALITY_LONGTEXT N_("Ogg Vorbis Quality information of the transcoded stream. " )
97
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." )
103
104 vlc_module_begin();
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 );
133 vlc_module_end();
134
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
141 };
142
143
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 * );
150
151 struct sout_access_out_sys_t
152 {
153     shout_t *p_shout;
154 };
155
156 /*****************************************************************************
157  * Open: open the shout connection
158  *****************************************************************************/
159 static int Open( vlc_object_t *p_this )
160 {
161     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
162     sout_access_out_sys_t *p_sys;
163     shout_t *p_shout;
164     long i_ret;
165     unsigned int i_port;
166     vlc_value_t val;
167
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;
179
180     config_ChainParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg );
181
182     psz_accessname = psz_parser = strdup( p_access->psz_path );
183
184     if( !p_access->psz_path )
185     {
186         msg_Err( p_access,
187                  "please specify url=user:password@host:port/mountpoint" );
188         return VLC_EGENERIC;
189     }
190
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;
205
206     i_port = atoi( tmp_port );
207
208     p_sys = p_access->p_sys = malloc( sizeof( sout_access_out_sys_t ) );
209     if( !p_sys )
210     {
211         msg_Err( p_access, "out of memory" );
212         free( psz_accessname );
213         return VLC_ENOMEM;
214     }
215
216     var_Get( p_access, SOUT_CFG_PREFIX "name", &val );
217     if( *val.psz_string )
218         psz_name = val.psz_string;
219     else
220         free( val.psz_string );
221
222     var_Get( p_access, SOUT_CFG_PREFIX "description", &val );
223     if( *val.psz_string )
224         psz_description = val.psz_string;
225     else
226         free( val.psz_string );
227
228     var_Get( p_access, SOUT_CFG_PREFIX "genre", &val );
229     if( *val.psz_string )
230         psz_genre = val.psz_string;
231     else
232         free( val.psz_string );
233
234     var_Get( p_access, SOUT_CFG_PREFIX "url", &val );
235     if( *val.psz_string )
236         psz_url = val.psz_string;
237     else
238         free( val.psz_string );
239
240     p_shout = p_sys->p_shout = shout_new();
241     if( !p_shout
242          || shout_set_host( p_shout, psz_host ) != SHOUTERR_SUCCESS
243          || shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY ) != 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 */
254       )
255     {
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 );
260         return VLC_EGENERIC;
261     }
262
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 );
267
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 );
271     else
272         i_ret = shout_set_format( p_shout, SHOUT_FORMAT_OGG );
273
274     if( i_ret != SHOUTERR_SUCCESS )
275     {
276         msg_Err( p_access, "failed to set the shoutcast streaming format" );
277         free( p_access->p_sys );
278         free( psz_accessname );
279         return VLC_EGENERIC;
280     }
281
282     /* Don't force bitrate to 0 but only use when specified. This will otherwise
283        show an empty field on icecast directory listing instead of NA */
284     var_Get( p_access, SOUT_CFG_PREFIX "bitrate", &val );
285     if( *val.psz_string )
286     {
287         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_BITRATE, val.psz_string );
288         if( i_ret != SHOUTERR_SUCCESS )
289         {
290             msg_Err( p_access, "failed to set the information about the bitrate" );
291             free( p_access->p_sys );
292             free( psz_accessname );
293             return VLC_EGENERIC;
294         }
295     }
296     else
297     {
298         /* Bitrate information is used for icecast/shoutcast servers directory
299            listings (sorting, stream info etc.) */
300         msg_Warn( p_access, "no bitrate information specified (required for listing " \
301                             "the server as public on the shoutcast website)" );
302         free( val.psz_string );
303     }
304
305     /* Information about samplerate, channels and quality will not be propagated
306        through the YP protocol for icecast to the public directory listing when
307        the icecast server is operating in shoutcast compatibility mode */
308
309     var_Get( p_access, SOUT_CFG_PREFIX "samplerate", &val );
310     if( *val.psz_string )
311     {
312         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_SAMPLERATE, val.psz_string );
313         if( i_ret != SHOUTERR_SUCCESS )
314         {
315             msg_Err( p_access, "failed to set the information about the samplerate" );
316             free( p_access->p_sys );
317             free( psz_accessname );
318             return VLC_EGENERIC;
319         }
320     }
321     else
322         free( val.psz_string );
323
324     var_Get( p_access, SOUT_CFG_PREFIX "channels", &val );
325     if( *val.psz_string )
326     {
327         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_CHANNELS, val.psz_string );
328         if( i_ret != SHOUTERR_SUCCESS )
329         {
330             msg_Err( p_access, "failed to set the information about the number of channels" );
331             free( p_access->p_sys );
332             free( psz_accessname );
333             return VLC_EGENERIC;
334         }
335     }
336     else
337         free( val.psz_string );
338
339     var_Get( p_access, SOUT_CFG_PREFIX "quality", &val );
340     if( *val.psz_string )
341     {
342         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_QUALITY, val.psz_string );
343         if( i_ret != SHOUTERR_SUCCESS )
344         {
345             msg_Err( p_access, "failed to set the information about Ogg Vorbis quality" );
346             free( p_access->p_sys );
347             free( psz_accessname );
348             return VLC_EGENERIC;
349         }
350     }
351     else
352         free( val.psz_string );
353
354     var_Get( p_access, SOUT_CFG_PREFIX "public", &val );
355     if( val.b_bool == VLC_TRUE )
356     {
357         i_ret = shout_set_public( p_shout, 1 );
358         if( i_ret != SHOUTERR_SUCCESS )
359         {
360             msg_Err( p_access, "failed to set the server status setting to public" );
361             free( p_access->p_sys );
362             free( psz_accessname );
363             return VLC_EGENERIC;
364         }
365     }
366
367     /* Connect at startup. Cycle through the possible protocols. */
368     i_ret = shout_get_connected( p_shout );
369     while ( i_ret != SHOUTERR_CONNECTED )
370     {
371         /* Shout parameters cannot be changed on an open connection */
372         i_ret = shout_close( p_shout );
373         if( i_ret == SHOUTERR_SUCCESS )
374         {
375             i_ret = SHOUTERR_UNCONNECTED;
376         }
377
378         /* Re-initialize for Shoutcast using ICY protocol. Not needed for initial connection
379            but it is when we are reconnecting after other protocol was tried. */
380         i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY );
381         if( i_ret != SHOUTERR_SUCCESS )
382         {
383             msg_Err( p_access, "failed to set the protocol to 'icy'" );
384             free( p_access->p_sys );
385             free( psz_accessname );
386             return VLC_EGENERIC;
387         }
388         i_ret = shout_open( p_shout );
389         if( i_ret == SHOUTERR_SUCCESS )
390         {
391             i_ret = SHOUTERR_CONNECTED;
392             msg_Dbg( p_access, "connected using 'icy' (shoutcast) protocol" );
393         }
394         else
395         {
396             msg_Warn( p_access, "failed to connect using 'icy' (shoutcast) protocol" );
397
398             /* Shout parameters cannot be changed on an open connection */
399             i_ret = shout_close( p_shout );
400             if( i_ret == SHOUTERR_SUCCESS )
401             {
402                 i_ret = SHOUTERR_UNCONNECTED;
403             }
404
405             /* IceCAST using HTTP protocol */
406             i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_HTTP );
407             if( i_ret != SHOUTERR_SUCCESS )
408             {
409                 msg_Err( p_access, "failed to set the protocol to 'http'" );
410                 free( p_access->p_sys );
411                 free( psz_accessname );
412                 return VLC_EGENERIC;
413             }
414             i_ret = shout_open( p_shout );
415             if( i_ret == SHOUTERR_SUCCESS )
416             {
417                 i_ret = SHOUTERR_CONNECTED;
418                 msg_Dbg( p_access, "connected using 'http' (icecast 2.x) protocol" );
419             }
420             else
421                 msg_Warn( p_access, "failed to connect using 'http' (icecast 2.x) protocol " );
422         }
423 /*
424         for non-blocking, use:
425         while( i_ret == SHOUTERR_BUSY )
426         {
427             sleep( 1 );
428             i_ret = shout_get_connected( p_shout );
429         }
430 */
431         /* Only wait when we have no connection */
432         if ( i_ret != SHOUTERR_CONNECTED )
433         {
434             msg_Warn( p_access, "unable to establish connection. waiting 30 seconds to retry..." );
435             msleep( 30000000 );
436         }
437     }
438
439     if( i_ret != SHOUTERR_CONNECTED )
440     {
441         msg_Err( p_access, "failed to open shout stream to %s:%i/%s: %s",
442                  psz_host, i_port, psz_mount, shout_get_error(p_shout) );
443         free( p_access->p_sys );
444         free( psz_accessname );
445         return VLC_EGENERIC;
446     }
447
448     p_access->pf_write = Write;
449     p_access->pf_read  = Read;
450     p_access->pf_seek  = Seek;
451
452     msg_Dbg( p_access, "shout access output opened (%s@%s:%i/%s)",
453              psz_user, psz_host, i_port, psz_mount );
454
455     /* Update pace control flag */
456     if( p_access->psz_access && !strcmp( p_access->psz_access, "stream" ) )
457     {
458         p_access->p_sout->i_out_pace_nocontrol++;
459     }
460
461     free( psz_accessname );
462
463     return VLC_SUCCESS;
464 }
465
466 /*****************************************************************************
467  * Close: close the target
468  *****************************************************************************/
469 static void Close( vlc_object_t * p_this )
470 {
471     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
472
473     if( p_access->p_sys && p_access->p_sys->p_shout )
474     {
475         shout_close( p_access->p_sys->p_shout );
476         shout_shutdown();
477     }
478     free( p_access->p_sys );
479
480     /* Update pace control flag */
481     if( p_access->psz_access && !strcmp( p_access->psz_access, "stream" ) )
482     {
483         p_access->p_sout->i_out_pace_nocontrol--;
484     }
485
486     msg_Dbg( p_access, "shout access output closed" );
487 }
488
489 /*****************************************************************************
490  * Read: standard read -- not supported
491  *****************************************************************************/
492 static int Read( sout_access_out_t *p_access, block_t *p_buffer )
493 {
494     msg_Err( p_access, "cannot read from shout" );
495     return VLC_EGENERIC;
496 }
497
498 /*****************************************************************************
499  * Write: standard write
500  *****************************************************************************/
501 static int Write( sout_access_out_t *p_access, block_t *p_buffer )
502 {
503     size_t i_write = 0;
504
505     shout_sync( p_access->p_sys->p_shout );
506     while( p_buffer )
507     {
508         block_t *p_next = p_buffer->p_next;
509
510         if( shout_send( p_access->p_sys->p_shout,
511                         p_buffer->p_buffer, p_buffer->i_buffer )
512              == SHOUTERR_SUCCESS )
513         {
514             i_write += p_buffer->i_buffer;
515         }
516         else
517         {
518             msg_Err( p_access, "cannot write to stream: %s",
519                      shout_get_error(p_access->p_sys->p_shout) );
520
521             /* The most common cause seems to be a server disconnect, resulting in a
522                Socket Error which can only be fixed by closing and reconnecting.
523                Since we already began with a working connection, the most feasable
524                approach to get out of this error status is a (timed) reconnect approach. */
525             shout_close( p_access->p_sys->p_shout );
526             msg_Warn( p_access, "server unavailable? waiting 30 seconds to reconnect..." );
527             msleep( 30000000 );
528             /* Re-open the connection (protocol params have already been set) and re-sync */
529             if( shout_open( p_access->p_sys->p_shout ) == SHOUTERR_SUCCESS )
530             {
531                 shout_sync( p_access->p_sys->p_shout );
532                 msg_Warn( p_access, "reconnected to server" );
533             }
534             else
535             {
536                 msg_Err( p_access, "failed to reconnect to server" );
537             }
538
539         }
540         block_Release( p_buffer );
541
542         /* XXX: Unsure if that's the cause for some audio trouble... */
543
544         p_buffer = p_next;
545     }
546
547     return i_write;
548 }
549
550 /*****************************************************************************
551  * Seek: seek to a specific location -- not supported
552  *****************************************************************************/
553 static int Seek( sout_access_out_t *p_access, off_t i_pos )
554 {
555     msg_Err( p_access, "cannot seek on shout" );
556     return VLC_EGENERIC;
557 }
558