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