1 /*****************************************************************************
2  * shout.c: This module forwards vorbis streams to an icecast server
3  *****************************************************************************
4  * Copyright (C) 2005 VLC authors and VideoLAN
5  * $Id: fbc81a0ddd91281584373aaa062001a12b4d35b1 $
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 Control( sout_access_out_t *, int, va_list );
154 
155 struct sout_access_out_sys_t
156 {
157     shout_t *p_shout;
158 };
159 
160 /*****************************************************************************
161  * Open: open the shout connection
162  *****************************************************************************/
Open(vlc_object_t * p_this)163 static int Open( vlc_object_t *p_this )
164 {
165     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
166     sout_access_out_sys_t *p_sys;
167     shout_t *p_shout;
168     long i_ret;
169     char *psz_val;
170 
171     char *psz_name;
172     char *psz_description;
173     char *psz_genre;
174     char *psz_url;
175     vlc_url_t url;
176 
177     config_ChainParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg );
178 
179     if( !p_access->psz_path )
180     {
181         msg_Err( p_access,
182                  "please specify url=user:password@host:port/mountpoint" );
183         return VLC_EGENERIC;
184     }
185 
186     vlc_UrlParse( &url , p_access->psz_path );
187     if( url.i_port <= 0 )
188         url.i_port = 8000;
189 
190     if( url.psz_host == NULL )
191     {   /* Backward compatibility with bind@path syntax */
192         vlc_UrlClean( &url );
193         if( unlikely(asprintf( &psz_url, "//%s", p_access->psz_path ) == -1) )
194             return VLC_ENOMEM;
195         vlc_UrlParse( &url, psz_url );
196         free( psz_url );
197     }
198 
199     p_sys = p_access->p_sys = malloc( sizeof( sout_access_out_sys_t ) );
200     if( !p_sys )
201     {
202         vlc_UrlClean( &url );
203         return VLC_ENOMEM;
204     }
205 
206     psz_name = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "name" );
207     psz_description = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "description" );
208     psz_genre = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "genre" );
209     psz_url = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "url" );
210 
211     p_shout = p_sys->p_shout = shout_new();
212     if( !p_shout
213          || shout_set_host( p_shout, url.psz_host ) != SHOUTERR_SUCCESS
214          || shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY ) != SHOUTERR_SUCCESS
215          || shout_set_port( p_shout, url.i_port ) != SHOUTERR_SUCCESS
216          || shout_set_password( p_shout, url.psz_password ) != SHOUTERR_SUCCESS
217          || shout_set_mount( p_shout, url.psz_path ) != SHOUTERR_SUCCESS
218          || shout_set_user( p_shout, url.psz_username ) != SHOUTERR_SUCCESS
219          || shout_set_agent( p_shout, "VLC media player " VERSION ) != SHOUTERR_SUCCESS
220          || shout_set_name( p_shout, psz_name ) != SHOUTERR_SUCCESS
221          || shout_set_description( p_shout, psz_description ) != SHOUTERR_SUCCESS
222          || shout_set_genre( p_shout, psz_genre ) != SHOUTERR_SUCCESS
223          || shout_set_url( p_shout, psz_url ) != SHOUTERR_SUCCESS
224          /* || shout_set_nonblocking( p_shout, 1 ) != SHOUTERR_SUCCESS */
225       )
226     {
227         msg_Err( p_access, "failed to initialize shout streaming to %s:%i/%s",
228                  url.psz_host, url.i_port, url.psz_path );
229 
230         free( psz_name );
231         free( psz_description );
232         free( psz_genre );
233         free( psz_url );
234         goto error;
235     }
236 
237     free( psz_name );
238     free( psz_description );
239     free( psz_genre );
240     free( psz_url );
241 
242     i_ret = shout_set_format( p_shout, var_GetBool( p_access, SOUT_CFG_PREFIX "mp3" ) ?
243                                        SHOUT_FORMAT_MP3 : SHOUT_FORMAT_OGG );
244 
245     if( i_ret != SHOUTERR_SUCCESS )
246     {
247         msg_Err( p_access, "failed to set the shoutcast streaming format" );
248         goto error;
249     }
250 
251     /* Don't force bitrate to 0 but only use when specified. This will otherwise
252        show an empty field on icecast directory listing instead of NA */
253     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "bitrate" );
254     if( psz_val )
255     {
256         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_BITRATE, psz_val );
257         free( psz_val );
258         if( i_ret != SHOUTERR_SUCCESS )
259         {
260             msg_Err( p_access, "failed to set the information about the bitrate" );
261             goto error;
262         }
263     }
264     else
265     {
266         /* Bitrate information is used for icecast/shoutcast servers directory
267            listings (sorting, stream info etc.) */
268         msg_Warn( p_access, "no bitrate information specified (required for listing " \
269                             "the server as public on the shoutcast website)" );
270     }
271 
272     /* Information about samplerate, channels and quality will not be propagated
273        through the YP protocol for icecast to the public directory listing when
274        the icecast server is operating in shoutcast compatibility mode */
275 
276     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "samplerate" );
277     if( psz_val )
278     {
279         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_SAMPLERATE, psz_val );
280         free( psz_val );
281         if( i_ret != SHOUTERR_SUCCESS )
282         {
283             msg_Err( p_access, "failed to set the information about the samplerate" );
284             goto error;
285         }
286     }
287 
288     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "channels" );
289     if( psz_val )
290     {
291         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_CHANNELS, psz_val );
292         free( psz_val );
293         if( i_ret != SHOUTERR_SUCCESS )
294         {
295             msg_Err( p_access, "failed to set the information about the number of channels" );
296             goto error;
297         }
298     }
299 
300     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "quality" );
301     if( psz_val )
302     {
303         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_QUALITY, psz_val );
304         free( psz_val );
305         if( i_ret != SHOUTERR_SUCCESS )
306         {
307             msg_Err( p_access, "failed to set the information about Ogg Vorbis quality" );
308             goto error;
309         }
310     }
311 
312     if( var_GetBool( p_access, SOUT_CFG_PREFIX "public" ) )
313     {
314         i_ret = shout_set_public( p_shout, 1 );
315         if( i_ret != SHOUTERR_SUCCESS )
316         {
317             msg_Err( p_access, "failed to set the server status setting to public" );
318             goto error;
319         }
320     }
321 
322     /* Connect at startup. Cycle through the possible protocols. */
323     i_ret = shout_get_connected( p_shout );
324     while ( i_ret != SHOUTERR_CONNECTED )
325     {
326         /* Shout parameters cannot be changed on an open connection */
327         shout_close( p_shout );
328 
329         /* Re-initialize for Shoutcast using ICY protocol. Not needed for initial connection
330            but it is when we are reconnecting after other protocol was tried. */
331         i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY );
332         if( i_ret != SHOUTERR_SUCCESS )
333         {
334             msg_Err( p_access, "failed to set the protocol to 'icy'" );
335             goto error;
336         }
337         i_ret = shout_open( p_shout );
338         if( i_ret == SHOUTERR_SUCCESS )
339         {
340             i_ret = SHOUTERR_CONNECTED;
341             msg_Dbg( p_access, "connected using 'icy' (shoutcast) protocol" );
342         }
343         else
344         {
345             msg_Warn( p_access, "failed to connect using 'icy' (shoutcast) protocol" );
346 
347             /* Shout parameters cannot be changed on an open connection */
348             shout_close( p_shout );
349 
350             /* IceCAST using HTTP protocol */
351             i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_HTTP );
352             if( i_ret != SHOUTERR_SUCCESS )
353             {
354                 msg_Err( p_access, "failed to set the protocol to 'http'" );
355                 goto error;
356             }
357             i_ret = shout_open( p_shout );
358             if( i_ret == SHOUTERR_SUCCESS )
359             {
360                 i_ret = SHOUTERR_CONNECTED;
361                 msg_Dbg( p_access, "connected using 'http' (icecast 2.x) protocol" );
362             }
363             else
364                 msg_Warn( p_access, "failed to connect using 'http' (icecast 2.x) protocol " );
365         }
366 /*
367         for non-blocking, use:
368         while( i_ret == SHOUTERR_BUSY )
369         {
370             sleep( 1 );
371             i_ret = shout_get_connected( p_shout );
372         }
373 */
374         if ( i_ret != SHOUTERR_CONNECTED )
375         {
376             msg_Warn( p_access, "unable to establish connection, retrying..." );
377             msleep( 30000000 );
378         }
379     }
380 
381     if( i_ret != SHOUTERR_CONNECTED )
382     {
383         msg_Err( p_access, "failed to open shout stream to %s:%i/%s: %s",
384                  url.psz_host, url.i_port, url.psz_path, shout_get_error(p_shout) );
385         goto error;
386     }
387 
388     p_access->pf_write = Write;
389     p_access->pf_control = Control;
390 
391     msg_Dbg( p_access, "shout access output opened (%s@%s:%i/%s)",
392              url.psz_username, url.psz_host, url.i_port, url.psz_path );
393 
394     vlc_UrlClean( &url );
395     return VLC_SUCCESS;
396 
397 error:
398     if( p_sys->p_shout )
399         shout_free( p_sys->p_shout );
400     vlc_UrlClean( &url );
401     free( p_sys );
402     return VLC_EGENERIC;
403 }
404 
405 /*****************************************************************************
406  * Close: close the target
407  *****************************************************************************/
Close(vlc_object_t * p_this)408 static void Close( vlc_object_t * p_this )
409 {
410     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
411     sout_access_out_sys_t *p_sys = p_access->p_sys;
412 
413     if( p_sys->p_shout )
414     {
415         shout_close( p_sys->p_shout );
416         shout_free( p_sys->p_shout );
417         shout_shutdown();
418     }
419     free( p_sys );
420     msg_Dbg( p_access, "shout access output closed" );
421 }
422 
Control(sout_access_out_t * p_access,int i_query,va_list args)423 static int Control( sout_access_out_t *p_access, int i_query, va_list args )
424 {
425     switch( i_query )
426     {
427         case ACCESS_OUT_CONTROLS_PACE:
428         {
429             bool *pb = va_arg( args, bool * );
430             *pb = strcmp( p_access->psz_access, "stream" );
431             break;
432         }
433 
434         default:
435             return VLC_EGENERIC;
436     }
437     return VLC_SUCCESS;
438 }
439 
440 /*****************************************************************************
441  * Write: standard write
442  *****************************************************************************/
Write(sout_access_out_t * p_access,block_t * p_buffer)443 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
444 {
445     sout_access_out_sys_t *p_sys = p_access->p_sys;
446     size_t i_write = 0;
447 
448     shout_sync( p_sys->p_shout );
449     while( p_buffer )
450     {
451         block_t *p_next = p_buffer->p_next;
452 
453         if( shout_send( p_sys->p_shout, p_buffer->p_buffer, p_buffer->i_buffer )
454              == SHOUTERR_SUCCESS )
455         {
456             i_write += p_buffer->i_buffer;
457         }
458         else
459         {
460             msg_Err( p_access, "cannot write to stream: %s",
461                      shout_get_error( p_sys->p_shout ) );
462 
463             /* The most common cause seems to be a server disconnect, resulting in a
464                Socket Error which can only be fixed by closing and reconnecting.
465                Since we already began with a working connection, the most feasable
466                approach to get out of this error status is a (timed) reconnect approach. */
467             shout_close( p_sys->p_shout );
468             msg_Warn( p_access, "server unavailable? trying to reconnect..." );
469             /* Re-open the connection (protocol params have already been set) and re-sync */
470             if( shout_open( p_sys->p_shout ) == SHOUTERR_SUCCESS )
471             {
472                 shout_sync( p_sys->p_shout );
473                 msg_Warn( p_access, "reconnected to server" );
474             }
475             else
476             {
477                 msg_Err( p_access, "failed to reconnect to server" );
478                 block_ChainRelease( p_buffer );
479                 return VLC_EGENERIC;
480             }
481 
482         }
483         block_Release( p_buffer );
484 
485         /* XXX: Unsure if that's the cause for some audio trouble... */
486 
487         p_buffer = p_next;
488     }
489 
490     return i_write;
491 }
492