srt: fix build issue with recent SRT library
[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  *
6  * Authors: Daniel Fischer <dan at subsignal dot org>
7  *          Derk-Jan Hartman <hartman at videolan dot org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public License
20  * along with this program; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Some Comments:
26  *
27  * - this only works for ogg and/or mp3, and we don't check this yet.
28  * - MP3 metadata is not passed along, since metadata is only available after
29  *   this module is opened.
30  *
31  * Typical usage:
32  *
33  * vlc v4l:/dev/video:input=2:norm=pal:size=192x144 \
34  * --sout '#transcode{vcodec=theora,vb=300,acodec=vorb,ab=96}\
35  * :std{access=shout,mux=ogg,dst=localhost:8005}'
36  *
37  *****************************************************************************/
38
39 /*****************************************************************************
40  * Preamble
41  *****************************************************************************/
42
43 #ifdef HAVE_CONFIG_H
44 # include "config.h"
45 #endif
46
47 #include <vlc_common.h>
48 #include <vlc_plugin.h>
49 #include <vlc_sout.h>
50 #include <vlc_block.h>
51 #include <vlc_url.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",
117                 NAME_TEXT, NAME_LONGTEXT, false )
118     add_string( SOUT_CFG_PREFIX "description", "Live stream from VLC media player",
119                 DESCRIPTION_TEXT, DESCRIPTION_LONGTEXT, false )
120     add_bool(   SOUT_CFG_PREFIX "mp3", false,
121                 MP3_TEXT, MP3_LONGTEXT, true )
122     add_string( SOUT_CFG_PREFIX "genre", "Alternative",
123                 GENRE_TEXT, GENRE_LONGTEXT, false )
124     add_string( SOUT_CFG_PREFIX "url", "http://www.videolan.org/vlc",
125                 URL_TEXT, URL_LONGTEXT, false )
126     add_string( SOUT_CFG_PREFIX "bitrate", "",
127                 BITRATE_TEXT, BITRATE_LONGTEXT, false )
128     add_string( SOUT_CFG_PREFIX "samplerate", "",
129                 SAMPLERATE_TEXT, SAMPLERATE_LONGTEXT, false )
130     add_string( SOUT_CFG_PREFIX "channels", "",
131                 CHANNELS_TEXT, CHANNELS_LONGTEXT, false )
132     add_string( SOUT_CFG_PREFIX "quality", "",
133                 QUALITY_TEXT, QUALITY_LONGTEXT, false )
134     add_bool(   SOUT_CFG_PREFIX "public", false,
135                 PUBLIC_TEXT, PUBLIC_LONGTEXT, true )
136     set_callbacks( Open, Close )
137 vlc_module_end ()
138
139 /*****************************************************************************
140  * Exported prototypes
141  *****************************************************************************/
142 static const char *const ppsz_sout_options[] = {
143     "name", "description", "mp3", "genre", "url", "bitrate", "samplerate",
144     "channels", "quality", "public", NULL
145 };
146
147
148 /*****************************************************************************
149  * Exported prototypes
150  *****************************************************************************/
151 static ssize_t Write( sout_access_out_t *, block_t * );
152 static int Control( sout_access_out_t *, int, va_list );
153
154 typedef struct
155 {
156     shout_t *p_shout;
157 } sout_access_out_sys_t;
158
159 /*****************************************************************************
160  * Open: open the shout connection
161  *****************************************************************************/
162 static int Open( vlc_object_t *p_this )
163 {
164     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
165     sout_access_out_sys_t *p_sys;
166     shout_t *p_shout;
167     long i_ret;
168     char *psz_val;
169
170     char *psz_name;
171     char *psz_description;
172     char *psz_genre;
173     char *psz_url;
174     vlc_url_t url;
175
176     config_ChainParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg );
177
178     vlc_UrlParse( &url , p_access->psz_path );
179     if( url.i_port <= 0 )
180         url.i_port = 8000;
181
182     if( url.psz_host == NULL )
183     {   /* Backward compatibility with bind@path syntax */
184         vlc_UrlClean( &url );
185         if( unlikely(asprintf( &psz_url, "//%s", p_access->psz_path ) == -1) )
186             return VLC_ENOMEM;
187         vlc_UrlParse( &url, psz_url );
188         free( psz_url );
189     }
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             vlc_tick_sleep( VLC_TICK_FROM_SEC(30) );
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_control = Control;
382
383     msg_Dbg( p_access, "shout access output opened (%s@%s:%i/%s)",
384              url.psz_username, url.psz_host, url.i_port, url.psz_path );
385
386     vlc_UrlClean( &url );
387     return VLC_SUCCESS;
388
389 error:
390     if( p_sys->p_shout )
391         shout_free( p_sys->p_shout );
392     vlc_UrlClean( &url );
393     free( p_sys );
394     return VLC_EGENERIC;
395 }
396
397 /*****************************************************************************
398  * Close: close the target
399  *****************************************************************************/
400 static void Close( vlc_object_t * p_this )
401 {
402     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
403     sout_access_out_sys_t *p_sys = p_access->p_sys;
404
405     if( p_sys->p_shout )
406     {
407         shout_close( p_sys->p_shout );
408         shout_free( p_sys->p_shout );
409         shout_shutdown();
410     }
411     free( p_sys );
412     msg_Dbg( p_access, "shout access output closed" );
413 }
414
415 static int Control( sout_access_out_t *p_access, int i_query, va_list args )
416 {
417     switch( i_query )
418     {
419         case ACCESS_OUT_CONTROLS_PACE:
420         {
421             bool *pb = va_arg( args, bool * );
422             *pb = strcmp( p_access->psz_access, "stream" );
423             break;
424         }
425
426         default:
427             return VLC_EGENERIC;
428     }
429     return VLC_SUCCESS;
430 }
431
432 /*****************************************************************************
433  * Write: standard write
434  *****************************************************************************/
435 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
436 {
437     sout_access_out_sys_t *p_sys = p_access->p_sys;
438     size_t i_write = 0;
439
440     shout_sync( p_sys->p_shout );
441     while( p_buffer )
442     {
443         block_t *p_next = p_buffer->p_next;
444
445         if( shout_send( p_sys->p_shout, p_buffer->p_buffer, p_buffer->i_buffer )
446              == SHOUTERR_SUCCESS )
447         {
448             i_write += p_buffer->i_buffer;
449         }
450         else
451         {
452             msg_Err( p_access, "cannot write to stream: %s",
453                      shout_get_error( p_sys->p_shout ) );
454
455             /* The most common cause seems to be a server disconnect, resulting in a
456                Socket Error which can only be fixed by closing and reconnecting.
457                Since we already began with a working connection, the most feasable
458                approach to get out of this error status is a (timed) reconnect approach. */
459             shout_close( p_sys->p_shout );
460             msg_Warn( p_access, "server unavailable? trying to reconnect..." );
461             /* Re-open the connection (protocol params have already been set) and re-sync */
462             if( shout_open( p_sys->p_shout ) == SHOUTERR_SUCCESS )
463             {
464                 shout_sync( p_sys->p_shout );
465                 msg_Warn( p_access, "reconnected to server" );
466             }
467             else
468             {
469                 msg_Err( p_access, "failed to reconnect to server" );
470                 block_ChainRelease( p_buffer );
471                 return VLC_EGENERIC;
472             }
473
474         }
475         block_Release( p_buffer );
476
477         /* XXX: Unsure if that's the cause for some audio trouble... */
478
479         p_buffer = p_next;
480     }
481
482     return i_write;
483 }