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