]> git.sesse.net Git - vlc/blob - modules/access_output/shout.c
access_out_shoutcast: constify.
[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 #ifdef HAVE_CONFIG_H
45 # include "config.h"
46 #endif
47
48 #include <vlc_common.h>
49 #include <vlc_plugin.h>
50 #include <vlc_sout.h>
51 #include <vlc_block.h>
52
53 #include <shout/shout.h>
54
55 /*****************************************************************************
56  * Module descriptor
57  *****************************************************************************/
58 static int  Open ( vlc_object_t * );
59 static void Close( vlc_object_t * );
60
61 #define SOUT_CFG_PREFIX "sout-shout-"
62
63 #define NAME_TEXT N_("Stream name")
64 #define NAME_LONGTEXT N_("Name to give to this stream/channel on the " \
65                          "shoutcast/icecast server." )
66
67 #define DESCRIPTION_TEXT N_("Stream description")
68 #define DESCRIPTION_LONGTEXT N_("Description of the stream content or " \
69                                 "information about your channel." )
70
71 #define MP3_TEXT N_("Stream MP3")
72 #define MP3_LONGTEXT N_("You normally have to feed the shoutcast module " \
73                         "with Ogg streams. It is also possible to stream " \
74                         "MP3 instead, so you can forward MP3 streams to " \
75                         "the shoutcast/icecast server." )
76
77 /* To be listed properly as a public stream on the Yellow Pages of shoutcast/icecast
78    the genres should match those used on the corresponding sites. Several examples
79    are Alternative, Classical, Comedy, Country etc. */
80
81 #define GENRE_TEXT N_("Genre description")
82 #define GENRE_LONGTEXT N_("Genre of the content. " )
83
84 #define URL_TEXT N_("URL description")
85 #define URL_LONGTEXT N_("URL with information about the stream or your channel. " )
86
87 /* The shout module only "transmits" data. It does not have direct access to
88    "codec level" information. Stream information such as bitrate, samplerate,
89    channel numbers and quality (in case of Ogg streaming) need to be set manually */
90
91 #define BITRATE_TEXT N_("Bitrate")
92 #define BITRATE_LONGTEXT N_("Bitrate information of the transcoded stream. " )
93
94 #define SAMPLERATE_TEXT N_("Samplerate")
95 #define SAMPLERATE_LONGTEXT N_("Samplerate information of the transcoded stream. " )
96
97 #define CHANNELS_TEXT N_("Number of channels")
98 #define CHANNELS_LONGTEXT N_("Number of channels information of the transcoded stream. " )
99
100 #define QUALITY_TEXT N_("Ogg Vorbis Quality")
101 #define QUALITY_LONGTEXT N_("Ogg Vorbis Quality information of the transcoded stream. " )
102
103 #define PUBLIC_TEXT N_("Stream public")
104 #define PUBLIC_LONGTEXT N_("Make the server publicly available on the 'Yellow Pages' " \
105                            "(directory listing of streams) on the icecast/shoutcast " \
106                            "website. Requires the bitrate information specified for " \
107                            "shoutcast. Requires Ogg streaming for icecast." )
108
109 vlc_module_begin ()
110     set_description( N_("IceCAST output") )
111     set_shortname( "Shoutcast" )
112     set_capability( "sout access", 0 )
113     set_category( CAT_SOUT )
114     set_subcategory( SUBCAT_SOUT_ACO )
115     add_shortcut( "shout" )
116     add_string( SOUT_CFG_PREFIX "name", "VLC media player - Live stream", NULL,
117                 NAME_TEXT, NAME_LONGTEXT, false )
118     add_string( SOUT_CFG_PREFIX "description",
119                  "Live stream from VLC media player", NULL,
120                 DESCRIPTION_TEXT, DESCRIPTION_LONGTEXT, false )
121     add_bool(   SOUT_CFG_PREFIX "mp3", false, NULL,
122                 MP3_TEXT, MP3_LONGTEXT, true )
123     add_string( SOUT_CFG_PREFIX "genre", "Alternative", NULL,
124                 GENRE_TEXT, GENRE_LONGTEXT, false )
125     add_string( SOUT_CFG_PREFIX "url", "http://www.videolan.org/vlc", NULL,
126                 URL_TEXT, URL_LONGTEXT, false )
127     add_string( SOUT_CFG_PREFIX "bitrate", "", NULL,
128                 BITRATE_TEXT, BITRATE_LONGTEXT, false )
129     add_string( SOUT_CFG_PREFIX "samplerate", "", NULL,
130                 SAMPLERATE_TEXT, SAMPLERATE_LONGTEXT, false )
131     add_string( SOUT_CFG_PREFIX "channels", "", NULL,
132                 CHANNELS_TEXT, CHANNELS_LONGTEXT, false )
133     add_string( SOUT_CFG_PREFIX "quality", "", NULL,
134                 QUALITY_TEXT, QUALITY_LONGTEXT, false )
135     add_bool(   SOUT_CFG_PREFIX "public", false, NULL,
136                 PUBLIC_TEXT, PUBLIC_LONGTEXT, true )
137     set_callbacks( Open, Close )
138 vlc_module_end ()
139
140 /*****************************************************************************
141  * Exported prototypes
142  *****************************************************************************/
143 static const char *const ppsz_sout_options[] = {
144     "name", "description", "mp3", "genre", "url", "bitrate", "samplerate",
145     "channels", "quality", "public", NULL
146 };
147
148
149 /*****************************************************************************
150  * Exported prototypes
151  *****************************************************************************/
152 static ssize_t Write( sout_access_out_t *, block_t * );
153 static int Seek ( sout_access_out_t *, off_t  );
154 static int Control( sout_access_out_t *, int, va_list );
155
156 struct sout_access_out_sys_t
157 {
158     shout_t *p_shout;
159 };
160
161 /*****************************************************************************
162  * Open: open the shout connection
163  *****************************************************************************/
164 static int Open( vlc_object_t *p_this )
165 {
166     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
167     sout_access_out_sys_t *p_sys;
168     shout_t *p_shout;
169     long i_ret;
170     unsigned int i_port;
171     char *psz_val;
172
173     char *psz_accessname;
174     char *psz_parser;
175     const char *psz_user;
176     const char *psz_pass;
177     const char *psz_host;
178     const char *psz_mount;
179     const char *psz_port;
180     char *psz_name;
181     char *psz_description;
182     char *psz_genre;
183     char *psz_url;
184
185     config_ChainParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg );
186
187     if( !p_access->psz_path )
188     {
189         msg_Err( p_access,
190                  "please specify url=user:password@host:port/mountpoint" );
191         return VLC_EGENERIC;
192     }
193
194     psz_accessname = psz_parser = strdup( p_access->psz_path );
195     if( !psz_parser )
196         return VLC_ENOMEM;
197
198     /* Parse connection data user:pwd@host:port/mountpoint */
199     psz_user = psz_parser;
200     while( psz_parser[0] && psz_parser[0] != ':' ) psz_parser++;
201     if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
202     psz_pass = psz_parser;
203     while( psz_parser[0] && psz_parser[0] != '@' ) psz_parser++;
204     if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
205     psz_host = psz_parser;
206     while( psz_parser[0] && psz_parser[0] != ':' ) psz_parser++;
207     if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
208     psz_port = psz_parser;
209     while( psz_parser[0] && psz_parser[0] != '/' ) psz_parser++;
210     if( psz_parser[0] ) { psz_parser[0] = 0; psz_parser++; }
211     psz_mount = psz_parser;
212
213     i_port = atoi( psz_port );
214
215     p_sys = p_access->p_sys = malloc( sizeof( sout_access_out_sys_t ) );
216     if( !p_sys )
217     {
218         free( psz_accessname );
219         return VLC_ENOMEM;
220     }
221
222     psz_name = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "name" );
223     psz_description = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "description" );
224     psz_genre = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "genre" );
225     psz_url = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "url" );
226
227     p_shout = p_sys->p_shout = shout_new();
228     if( !p_shout
229          || shout_set_host( p_shout, psz_host ) != SHOUTERR_SUCCESS
230          || shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY ) != SHOUTERR_SUCCESS
231          || shout_set_port( p_shout, i_port ) != SHOUTERR_SUCCESS
232          || shout_set_password( p_shout, psz_pass ) != SHOUTERR_SUCCESS
233          || shout_set_mount( p_shout, psz_mount ) != SHOUTERR_SUCCESS
234          || shout_set_user( p_shout, psz_user ) != SHOUTERR_SUCCESS
235          || shout_set_agent( p_shout, "VLC media player " VERSION ) != SHOUTERR_SUCCESS
236          || shout_set_name( p_shout, psz_name ) != SHOUTERR_SUCCESS
237          || shout_set_description( p_shout, psz_description ) != SHOUTERR_SUCCESS
238          || shout_set_genre( p_shout, psz_genre ) != SHOUTERR_SUCCESS
239          || shout_set_url( p_shout, psz_url ) != SHOUTERR_SUCCESS
240          /* || shout_set_nonblocking( p_shout, 1 ) != SHOUTERR_SUCCESS */
241       )
242     {
243         msg_Err( p_access, "failed to initialize shout streaming to %s:%i/%s",
244                  psz_host, i_port, psz_mount );
245         free( p_access->p_sys );
246         free( psz_accessname );
247         free( psz_name );
248         free( psz_description );
249         free( psz_genre );
250         free( psz_url );
251         return VLC_EGENERIC;
252     }
253
254     free( psz_name );
255     free( psz_description );
256     free( psz_genre );
257     free( psz_url );
258
259     if( var_GetBool( p_access, SOUT_CFG_PREFIX "mp3" ) )
260         i_ret = shout_set_format( p_shout, SHOUT_FORMAT_MP3 );
261     else
262         i_ret = shout_set_format( p_shout, SHOUT_FORMAT_OGG );
263
264     if( i_ret != SHOUTERR_SUCCESS )
265     {
266         msg_Err( p_access, "failed to set the shoutcast streaming format" );
267         free( p_access->p_sys );
268         free( psz_accessname );
269         return VLC_EGENERIC;
270     }
271
272     /* Don't force bitrate to 0 but only use when specified. This will otherwise
273        show an empty field on icecast directory listing instead of NA */
274     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "bitrate" );
275     if( psz_val )
276     {
277         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_BITRATE, psz_val );
278         if( i_ret != SHOUTERR_SUCCESS )
279         {
280             msg_Err( p_access, "failed to set the information about the bitrate" );
281             free( psz_val );
282             free( p_access->p_sys );
283             free( psz_accessname );
284             return VLC_EGENERIC;
285         }
286     }
287     else
288     {
289         /* Bitrate information is used for icecast/shoutcast servers directory
290            listings (sorting, stream info etc.) */
291         msg_Warn( p_access, "no bitrate information specified (required for listing " \
292                             "the server as public on the shoutcast website)" );
293     }
294
295     /* Information about samplerate, channels and quality will not be propagated
296        through the YP protocol for icecast to the public directory listing when
297        the icecast server is operating in shoutcast compatibility mode */
298
299     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "samplerate" );
300     if( psz_val )
301     {
302         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_SAMPLERATE, psz_val );
303         if( i_ret != SHOUTERR_SUCCESS )
304         {
305             msg_Err( p_access, "failed to set the information about the samplerate" );
306             free( psz_val );
307             free( p_access->p_sys );
308             free( psz_accessname );
309             return VLC_EGENERIC;
310         }
311     }
312
313     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "channels" );
314     if( psz_val )
315     {
316         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_CHANNELS, psz_val );
317         if( i_ret != SHOUTERR_SUCCESS )
318         {
319             msg_Err( p_access, "failed to set the information about the number of channels" );
320             free( psz_val );
321             free( p_access->p_sys );
322             free( psz_accessname );
323             return VLC_EGENERIC;
324         }
325     }
326
327     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "quality" );
328     if( psz_val )
329     {
330         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_QUALITY, psz_val );
331         if( i_ret != SHOUTERR_SUCCESS )
332         {
333             msg_Err( p_access, "failed to set the information about Ogg Vorbis quality" );
334             free( psz_val );
335             free( p_access->p_sys );
336             free( psz_accessname );
337             return VLC_EGENERIC;
338         }
339     }
340
341     if( var_GetBool( p_access, SOUT_CFG_PREFIX "public" ) )
342     {
343         i_ret = shout_set_public( p_shout, 1 );
344         if( i_ret != SHOUTERR_SUCCESS )
345         {
346             msg_Err( p_access, "failed to set the server status setting to public" );
347             free( p_access->p_sys );
348             free( psz_accessname );
349             return VLC_EGENERIC;
350         }
351     }
352
353     /* Connect at startup. Cycle through the possible protocols. */
354     i_ret = shout_get_connected( p_shout );
355     while ( i_ret != SHOUTERR_CONNECTED )
356     {
357         /* Shout parameters cannot be changed on an open connection */
358         i_ret = shout_close( p_shout );
359         if( i_ret == SHOUTERR_SUCCESS )
360         {
361             i_ret = SHOUTERR_UNCONNECTED;
362         }
363
364         /* Re-initialize for Shoutcast using ICY protocol. Not needed for initial connection
365            but it is when we are reconnecting after other protocol was tried. */
366         i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY );
367         if( i_ret != SHOUTERR_SUCCESS )
368         {
369             msg_Err( p_access, "failed to set the protocol to 'icy'" );
370             free( p_access->p_sys );
371             free( psz_accessname );
372             return VLC_EGENERIC;
373         }
374         i_ret = shout_open( p_shout );
375         if( i_ret == SHOUTERR_SUCCESS )
376         {
377             i_ret = SHOUTERR_CONNECTED;
378             msg_Dbg( p_access, "connected using 'icy' (shoutcast) protocol" );
379         }
380         else
381         {
382             msg_Warn( p_access, "failed to connect using 'icy' (shoutcast) protocol" );
383
384             /* Shout parameters cannot be changed on an open connection */
385             i_ret = shout_close( p_shout );
386             if( i_ret == SHOUTERR_SUCCESS )
387             {
388                 i_ret = SHOUTERR_UNCONNECTED;
389             }
390
391             /* IceCAST using HTTP protocol */
392             i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_HTTP );
393             if( i_ret != SHOUTERR_SUCCESS )
394             {
395                 msg_Err( p_access, "failed to set the protocol to 'http'" );
396                 free( p_access->p_sys );
397                 free( psz_accessname );
398                 return VLC_EGENERIC;
399             }
400             i_ret = shout_open( p_shout );
401             if( i_ret == SHOUTERR_SUCCESS )
402             {
403                 i_ret = SHOUTERR_CONNECTED;
404                 msg_Dbg( p_access, "connected using 'http' (icecast 2.x) protocol" );
405             }
406             else
407                 msg_Warn( p_access, "failed to connect using 'http' (icecast 2.x) protocol " );
408         }
409 /*
410         for non-blocking, use:
411         while( i_ret == SHOUTERR_BUSY )
412         {
413             sleep( 1 );
414             i_ret = shout_get_connected( p_shout );
415         }
416 */
417         if ( i_ret != SHOUTERR_CONNECTED )
418         {
419             msg_Warn( p_access, "unable to establish connection, retrying..." );
420             msleep( 30000000 );
421         }
422     }
423
424     if( i_ret != SHOUTERR_CONNECTED )
425     {
426         msg_Err( p_access, "failed to open shout stream to %s:%i/%s: %s",
427                  psz_host, i_port, psz_mount, shout_get_error(p_shout) );
428         free( p_access->p_sys );
429         free( psz_accessname );
430         return VLC_EGENERIC;
431     }
432
433     p_access->pf_write = Write;
434     p_access->pf_seek  = Seek;
435     p_access->pf_control = Control;
436
437     msg_Dbg( p_access, "shout access output opened (%s@%s:%i/%s)",
438              psz_user, psz_host, i_port, psz_mount );
439     free( psz_accessname );
440
441     return VLC_SUCCESS;
442 }
443
444 /*****************************************************************************
445  * Close: close the target
446  *****************************************************************************/
447 static void Close( vlc_object_t * p_this )
448 {
449     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
450
451     if( p_access->p_sys && p_access->p_sys->p_shout )
452     {
453         shout_close( p_access->p_sys->p_shout );
454         shout_shutdown();
455     }
456     free( p_access->p_sys );
457     msg_Dbg( p_access, "shout access output closed" );
458 }
459
460 static int Control( sout_access_out_t *p_access, int i_query, va_list args )
461 {
462     switch( i_query )
463     {
464         case ACCESS_OUT_CONTROLS_PACE:
465         {
466             bool *pb = va_arg( args, bool * );
467             *pb = strcmp( p_access->psz_access, "stream" );
468             break;
469         }
470
471         default:
472             return VLC_EGENERIC;
473     }
474     return VLC_SUCCESS;
475 }
476
477 /*****************************************************************************
478  * Write: standard write
479  *****************************************************************************/
480 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
481 {
482     size_t i_write = 0;
483
484     shout_sync( p_access->p_sys->p_shout );
485     while( p_buffer )
486     {
487         block_t *p_next = p_buffer->p_next;
488
489         if( shout_send( p_access->p_sys->p_shout,
490                         p_buffer->p_buffer, p_buffer->i_buffer )
491              == SHOUTERR_SUCCESS )
492         {
493             i_write += p_buffer->i_buffer;
494         }
495         else
496         {
497             msg_Err( p_access, "cannot write to stream: %s",
498                      shout_get_error(p_access->p_sys->p_shout) );
499
500             /* The most common cause seems to be a server disconnect, resulting in a
501                Socket Error which can only be fixed by closing and reconnecting.
502                Since we already began with a working connection, the most feasable
503                approach to get out of this error status is a (timed) reconnect approach. */
504             shout_close( p_access->p_sys->p_shout );
505             msg_Warn( p_access, "server unavailable? trying to reconnect..." );
506             /* Re-open the connection (protocol params have already been set) and re-sync */
507             if( shout_open( p_access->p_sys->p_shout ) == SHOUTERR_SUCCESS )
508             {
509                 shout_sync( p_access->p_sys->p_shout );
510                 msg_Warn( p_access, "reconnected to server" );
511             }
512             else
513             {
514                 msg_Err( p_access, "failed to reconnect to server" );
515                 block_ChainRelease (p_buffer);
516                 return VLC_EGENERIC;
517             }
518
519         }
520         block_Release( p_buffer );
521
522         /* XXX: Unsure if that's the cause for some audio trouble... */
523
524         p_buffer = p_next;
525     }
526
527     return i_write;
528 }
529
530 /*****************************************************************************
531  * Seek: seek to a specific location -- not supported
532  *****************************************************************************/
533 static int Seek( sout_access_out_t *p_access, off_t i_pos )
534 {
535     VLC_UNUSED(i_pos);
536     msg_Err( p_access, "cannot seek on shout" );
537     return VLC_EGENERIC;
538 }
539