]> git.sesse.net Git - vlc/blob - modules/access_output/shout.c
Qt is GPLv2+
[vlc] / 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, 0 );
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         i_ret = shout_close( p_shout );
320         if( i_ret == SHOUTERR_SUCCESS )
321         {
322             i_ret = SHOUTERR_UNCONNECTED;
323         }
324
325         /* Re-initialize for Shoutcast using ICY protocol. Not needed for initial connection
326            but it is when we are reconnecting after other protocol was tried. */
327         i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY );
328         if( i_ret != SHOUTERR_SUCCESS )
329         {
330             msg_Err( p_access, "failed to set the protocol to 'icy'" );
331             goto error;
332         }
333         i_ret = shout_open( p_shout );
334         if( i_ret == SHOUTERR_SUCCESS )
335         {
336             i_ret = SHOUTERR_CONNECTED;
337             msg_Dbg( p_access, "connected using 'icy' (shoutcast) protocol" );
338         }
339         else
340         {
341             msg_Warn( p_access, "failed to connect using 'icy' (shoutcast) protocol" );
342
343             /* Shout parameters cannot be changed on an open connection */
344             i_ret = shout_close( p_shout );
345             if( i_ret == SHOUTERR_SUCCESS )
346             {
347                 i_ret = SHOUTERR_UNCONNECTED;
348             }
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_seek  = Seek;
390     p_access->pf_control = Control;
391
392     msg_Dbg( p_access, "shout access output opened (%s@%s:%i/%s)",
393              url.psz_username, url.psz_host, url.i_port, url.psz_path );
394
395     vlc_UrlClean( &url );
396     return VLC_SUCCESS;
397
398 error:
399     if( p_sys->p_shout )
400         shout_free( p_sys->p_shout );
401     vlc_UrlClean( &url );
402     free( p_sys );
403     return VLC_EGENERIC;
404 }
405
406 /*****************************************************************************
407  * Close: close the target
408  *****************************************************************************/
409 static void Close( vlc_object_t * p_this )
410 {
411     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
412     sout_access_out_sys_t *p_sys = p_access->p_sys;
413
414     if( p_sys->p_shout )
415     {
416         shout_close( p_sys->p_shout );
417         shout_free( p_sys->p_shout );
418         shout_shutdown();
419     }
420     free( p_sys );
421     msg_Dbg( p_access, "shout access output closed" );
422 }
423
424 static int Control( sout_access_out_t *p_access, int i_query, va_list args )
425 {
426     switch( i_query )
427     {
428         case ACCESS_OUT_CONTROLS_PACE:
429         {
430             bool *pb = va_arg( args, bool * );
431             *pb = strcmp( p_access->psz_access, "stream" );
432             break;
433         }
434
435         default:
436             return VLC_EGENERIC;
437     }
438     return VLC_SUCCESS;
439 }
440
441 /*****************************************************************************
442  * Write: standard write
443  *****************************************************************************/
444 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
445 {
446     sout_access_out_sys_t *p_sys = p_access->p_sys;
447     size_t i_write = 0;
448
449     shout_sync( p_sys->p_shout );
450     while( p_buffer )
451     {
452         block_t *p_next = p_buffer->p_next;
453
454         if( shout_send( p_sys->p_shout, p_buffer->p_buffer, p_buffer->i_buffer )
455              == SHOUTERR_SUCCESS )
456         {
457             i_write += p_buffer->i_buffer;
458         }
459         else
460         {
461             msg_Err( p_access, "cannot write to stream: %s",
462                      shout_get_error( p_sys->p_shout ) );
463
464             /* The most common cause seems to be a server disconnect, resulting in a
465                Socket Error which can only be fixed by closing and reconnecting.
466                Since we already began with a working connection, the most feasable
467                approach to get out of this error status is a (timed) reconnect approach. */
468             shout_close( p_sys->p_shout );
469             msg_Warn( p_access, "server unavailable? trying to reconnect..." );
470             /* Re-open the connection (protocol params have already been set) and re-sync */
471             if( shout_open( p_sys->p_shout ) == SHOUTERR_SUCCESS )
472             {
473                 shout_sync( p_sys->p_shout );
474                 msg_Warn( p_access, "reconnected to server" );
475             }
476             else
477             {
478                 msg_Err( p_access, "failed to reconnect to server" );
479                 block_ChainRelease( p_buffer );
480                 return VLC_EGENERIC;
481             }
482
483         }
484         block_Release( p_buffer );
485
486         /* XXX: Unsure if that's the cause for some audio trouble... */
487
488         p_buffer = p_next;
489     }
490
491     return i_write;
492 }
493
494 /*****************************************************************************
495  * Seek: seek to a specific location -- not supported
496  *****************************************************************************/
497 static int Seek( sout_access_out_t *p_access, off_t i_pos )
498 {
499     VLC_UNUSED(i_pos);
500     msg_Err( p_access, "cannot seek on shout" );
501     return VLC_EGENERIC;
502 }
503