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