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