]> git.sesse.net Git - vlc/blob - modules/access_output/shout.c
input options whitelisting, step 2 (refs #1371)
[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         change_safe();
114     add_string( SOUT_CFG_PREFIX "description",
115                  "Live stream from VLC media player", NULL,
116                 DESCRIPTION_TEXT, DESCRIPTION_LONGTEXT, VLC_FALSE );
117         change_safe();
118     add_bool(   SOUT_CFG_PREFIX "mp3", VLC_FALSE, NULL,
119                 MP3_TEXT, MP3_LONGTEXT, VLC_TRUE );
120         change_safe();
121     add_string( SOUT_CFG_PREFIX "genre", "Alternative", NULL,
122                 GENRE_TEXT, GENRE_LONGTEXT, VLC_FALSE );
123         change_safe();
124     add_string( SOUT_CFG_PREFIX "url", "http://www.videolan.org/vlc", NULL,
125                 URL_TEXT, URL_LONGTEXT, VLC_FALSE );
126         change_safe();
127     add_string( SOUT_CFG_PREFIX "bitrate", "", NULL,
128                 BITRATE_TEXT, BITRATE_LONGTEXT, VLC_FALSE );
129         change_safe();
130     add_string( SOUT_CFG_PREFIX "samplerate", "", NULL,
131                 SAMPLERATE_TEXT, SAMPLERATE_LONGTEXT, VLC_FALSE );
132         change_safe();
133     add_string( SOUT_CFG_PREFIX "channels", "", NULL,
134                 CHANNELS_TEXT, CHANNELS_LONGTEXT, VLC_FALSE );
135         change_safe();
136     add_string( SOUT_CFG_PREFIX "quality", "", NULL,
137                 QUALITY_TEXT, QUALITY_LONGTEXT, VLC_FALSE );
138         change_safe();
139     add_bool(   SOUT_CFG_PREFIX "public", VLC_FALSE, NULL,
140                 PUBLIC_TEXT, PUBLIC_LONGTEXT, VLC_TRUE );
141         change_safe();
142     set_callbacks( Open, Close );
143 vlc_module_end();
144
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
151 };
152
153
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  );
159
160 struct sout_access_out_sys_t
161 {
162     shout_t *p_shout;
163 };
164
165 /*****************************************************************************
166  * Open: open the shout connection
167  *****************************************************************************/
168 static int Open( vlc_object_t *p_this )
169 {
170     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
171     sout_access_out_sys_t *p_sys;
172     shout_t *p_shout;
173     long i_ret;
174     unsigned int i_port;
175     vlc_value_t val;
176
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;
188
189     config_ChainParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg );
190
191     psz_accessname = psz_parser = strdup( p_access->psz_path );
192
193     if( !p_access->psz_path )
194     {
195         msg_Err( p_access,
196                  "please specify url=user:password@host:port/mountpoint" );
197         return VLC_EGENERIC;
198     }
199
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;
214
215     i_port = atoi( tmp_port );
216
217     p_sys = p_access->p_sys = malloc( sizeof( sout_access_out_sys_t ) );
218     if( !p_sys )
219     {
220         msg_Err( p_access, "out of memory" );
221         free( psz_accessname );
222         return VLC_ENOMEM;
223     }
224
225     var_Get( p_access, SOUT_CFG_PREFIX "name", &val );
226     if( *val.psz_string )
227         psz_name = val.psz_string;
228     else
229         free( val.psz_string );
230
231     var_Get( p_access, SOUT_CFG_PREFIX "description", &val );
232     if( *val.psz_string )
233         psz_description = val.psz_string;
234     else
235         free( val.psz_string );
236
237     var_Get( p_access, SOUT_CFG_PREFIX "genre", &val );
238     if( *val.psz_string )
239         psz_genre = val.psz_string;
240     else
241         free( val.psz_string );
242
243     var_Get( p_access, SOUT_CFG_PREFIX "url", &val );
244     if( *val.psz_string )
245         psz_url = val.psz_string;
246     else
247         free( val.psz_string );
248
249     p_shout = p_sys->p_shout = shout_new();
250     if( !p_shout
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 */
263       )
264     {
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 );
269         return VLC_EGENERIC;
270     }
271
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 );
276
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 );
280     else
281         i_ret = shout_set_format( p_shout, SHOUT_FORMAT_OGG );
282
283     if( i_ret != SHOUTERR_SUCCESS )
284     {
285         msg_Err( p_access, "failed to set the shoutcast streaming format" );
286         free( p_access->p_sys );
287         free( psz_accessname );
288         return VLC_EGENERIC;
289     }
290
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 )
295     {
296         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_BITRATE, val.psz_string );
297         if( i_ret != SHOUTERR_SUCCESS )
298         {
299             msg_Err( p_access, "failed to set the information about the bitrate" );
300             free( p_access->p_sys );
301             free( psz_accessname );
302             return VLC_EGENERIC;
303         }
304     }
305     else
306     {
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 );
312     }
313
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 */
317
318     var_Get( p_access, SOUT_CFG_PREFIX "samplerate", &val );
319     if( *val.psz_string )
320     {
321         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_SAMPLERATE, val.psz_string );
322         if( i_ret != SHOUTERR_SUCCESS )
323         {
324             msg_Err( p_access, "failed to set the information about the samplerate" );
325             free( p_access->p_sys );
326             free( psz_accessname );
327             return VLC_EGENERIC;
328         }
329     }
330     else
331         free( val.psz_string );
332
333     var_Get( p_access, SOUT_CFG_PREFIX "channels", &val );
334     if( *val.psz_string )
335     {
336         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_CHANNELS, val.psz_string );
337         if( i_ret != SHOUTERR_SUCCESS )
338         {
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 );
342             return VLC_EGENERIC;
343         }
344     }
345     else
346         free( val.psz_string );
347
348     var_Get( p_access, SOUT_CFG_PREFIX "quality", &val );
349     if( *val.psz_string )
350     {
351         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_QUALITY, val.psz_string );
352         if( i_ret != SHOUTERR_SUCCESS )
353         {
354             msg_Err( p_access, "failed to set the information about Ogg Vorbis quality" );
355             free( p_access->p_sys );
356             free( psz_accessname );
357             return VLC_EGENERIC;
358         }
359     }
360     else
361         free( val.psz_string );
362
363     var_Get( p_access, SOUT_CFG_PREFIX "public", &val );
364     if( val.b_bool == VLC_TRUE )
365     {
366         i_ret = shout_set_public( p_shout, 1 );
367         if( i_ret != SHOUTERR_SUCCESS )
368         {
369             msg_Err( p_access, "failed to set the server status setting to public" );
370             free( p_access->p_sys );
371             free( psz_accessname );
372             return VLC_EGENERIC;
373         }
374     }
375
376     /* Connect at startup. Cycle through the possible protocols. */
377     i_ret = shout_get_connected( p_shout );
378     while ( i_ret != SHOUTERR_CONNECTED )
379     {
380         /* Shout parameters cannot be changed on an open connection */
381         i_ret = shout_close( p_shout );
382         if( i_ret == SHOUTERR_SUCCESS )
383         {
384             i_ret = SHOUTERR_UNCONNECTED;
385         }
386
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 )
391         {
392             msg_Err( p_access, "failed to set the protocol to 'icy'" );
393             free( p_access->p_sys );
394             free( psz_accessname );
395             return VLC_EGENERIC;
396         }
397         i_ret = shout_open( p_shout );
398         if( i_ret == SHOUTERR_SUCCESS )
399         {
400             i_ret = SHOUTERR_CONNECTED;
401             msg_Dbg( p_access, "connected using 'icy' (shoutcast) protocol" );
402         }
403         else
404         {
405             msg_Warn( p_access, "failed to connect using 'icy' (shoutcast) protocol" );
406
407             /* Shout parameters cannot be changed on an open connection */
408             i_ret = shout_close( p_shout );
409             if( i_ret == SHOUTERR_SUCCESS )
410             {
411                 i_ret = SHOUTERR_UNCONNECTED;
412             }
413
414             /* IceCAST using HTTP protocol */
415             i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_HTTP );
416             if( i_ret != SHOUTERR_SUCCESS )
417             {
418                 msg_Err( p_access, "failed to set the protocol to 'http'" );
419                 free( p_access->p_sys );
420                 free( psz_accessname );
421                 return VLC_EGENERIC;
422             }
423             i_ret = shout_open( p_shout );
424             if( i_ret == SHOUTERR_SUCCESS )
425             {
426                 i_ret = SHOUTERR_CONNECTED;
427                 msg_Dbg( p_access, "connected using 'http' (icecast 2.x) protocol" );
428             }
429             else
430                 msg_Warn( p_access, "failed to connect using 'http' (icecast 2.x) protocol " );
431         }
432 /*
433         for non-blocking, use:
434         while( i_ret == SHOUTERR_BUSY )
435         {
436             sleep( 1 );
437             i_ret = shout_get_connected( p_shout );
438         }
439 */
440         if ( i_ret != SHOUTERR_CONNECTED )
441         {
442             msg_Warn( p_access, "unable to establish connection, retrying..." );
443             msleep( 30000000 );
444         }
445     }
446
447     if( i_ret != SHOUTERR_CONNECTED )
448     {
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 );
453         return VLC_EGENERIC;
454     }
455
456     p_access->pf_write = Write;
457     p_access->pf_seek  = Seek;
458
459     msg_Dbg( p_access, "shout access output opened (%s@%s:%i/%s)",
460              psz_user, psz_host, i_port, psz_mount );
461
462     /* Update pace control flag */
463     if( p_access->psz_access && !strcmp( p_access->psz_access, "stream" ) )
464     {
465         p_access->p_sout->i_out_pace_nocontrol++;
466     }
467
468     free( psz_accessname );
469
470     return VLC_SUCCESS;
471 }
472
473 /*****************************************************************************
474  * Close: close the target
475  *****************************************************************************/
476 static void Close( vlc_object_t * p_this )
477 {
478     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
479
480     if( p_access->p_sys && p_access->p_sys->p_shout )
481     {
482         shout_close( p_access->p_sys->p_shout );
483         shout_shutdown();
484     }
485     free( p_access->p_sys );
486
487     /* Update pace control flag */
488     if( p_access->psz_access && !strcmp( p_access->psz_access, "stream" ) )
489     {
490         p_access->p_sout->i_out_pace_nocontrol--;
491     }
492
493     msg_Dbg( p_access, "shout access output closed" );
494 }
495
496 /*****************************************************************************
497  * Write: standard write
498  *****************************************************************************/
499 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
500 {
501     size_t i_write = 0;
502
503     shout_sync( p_access->p_sys->p_shout );
504     while( p_buffer )
505     {
506         block_t *p_next = p_buffer->p_next;
507
508         if( shout_send( p_access->p_sys->p_shout,
509                         p_buffer->p_buffer, p_buffer->i_buffer )
510              == SHOUTERR_SUCCESS )
511         {
512             i_write += p_buffer->i_buffer;
513         }
514         else
515         {
516             msg_Err( p_access, "cannot write to stream: %s",
517                      shout_get_error(p_access->p_sys->p_shout) );
518
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 )
527             {
528                 shout_sync( p_access->p_sys->p_shout );
529                 msg_Warn( p_access, "reconnected to server" );
530             }
531             else
532             {
533                 msg_Err( p_access, "failed to reconnect to server" );
534             }
535
536         }
537         block_Release( p_buffer );
538
539         /* XXX: Unsure if that's the cause for some audio trouble... */
540
541         p_buffer = p_next;
542     }
543
544     return i_write;
545 }
546
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 )
551 {
552     msg_Err( p_access, "cannot seek on shout" );
553     return VLC_EGENERIC;
554 }
555