access_output: allow pf_seek to be NULL, simplify
[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  * $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 Control( sout_access_out_t *, int, va_list );
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     char *psz_val;
170
171     char *psz_name;
172     char *psz_description;
173     char *psz_genre;
174     char *psz_url;
175     vlc_url_t url;
176
177     config_ChainParse( p_access, SOUT_CFG_PREFIX, ppsz_sout_options, p_access->p_cfg );
178
179     if( !p_access->psz_path )
180     {
181         msg_Err( p_access,
182                  "please specify url=user:password@host:port/mountpoint" );
183         return VLC_EGENERIC;
184     }
185
186     vlc_UrlParse( &url , p_access->psz_path );
187     if( url.i_port <= 0 )
188         url.i_port = 8000;
189
190     p_sys = p_access->p_sys = malloc( sizeof( sout_access_out_sys_t ) );
191     if( !p_sys )
192     {
193         vlc_UrlClean( &url );
194         return VLC_ENOMEM;
195     }
196
197     psz_name = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "name" );
198     psz_description = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "description" );
199     psz_genre = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "genre" );
200     psz_url = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "url" );
201
202     p_shout = p_sys->p_shout = shout_new();
203     if( !p_shout
204          || shout_set_host( p_shout, url.psz_host ) != SHOUTERR_SUCCESS
205          || shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY ) != SHOUTERR_SUCCESS
206          || shout_set_port( p_shout, url.i_port ) != SHOUTERR_SUCCESS
207          || shout_set_password( p_shout, url.psz_password ) != SHOUTERR_SUCCESS
208          || shout_set_mount( p_shout, url.psz_path ) != SHOUTERR_SUCCESS
209          || shout_set_user( p_shout, url.psz_username ) != SHOUTERR_SUCCESS
210          || shout_set_agent( p_shout, "VLC media player " VERSION ) != SHOUTERR_SUCCESS
211          || shout_set_name( p_shout, psz_name ) != SHOUTERR_SUCCESS
212          || shout_set_description( p_shout, psz_description ) != SHOUTERR_SUCCESS
213          || shout_set_genre( p_shout, psz_genre ) != SHOUTERR_SUCCESS
214          || shout_set_url( p_shout, psz_url ) != SHOUTERR_SUCCESS
215          /* || shout_set_nonblocking( p_shout, 1 ) != SHOUTERR_SUCCESS */
216       )
217     {
218         msg_Err( p_access, "failed to initialize shout streaming to %s:%i/%s",
219                  url.psz_host, url.i_port, url.psz_path );
220
221         free( psz_name );
222         free( psz_description );
223         free( psz_genre );
224         free( psz_url );
225         goto error;
226     }
227
228     free( psz_name );
229     free( psz_description );
230     free( psz_genre );
231     free( psz_url );
232
233     i_ret = shout_set_format( p_shout, var_GetBool( p_access, SOUT_CFG_PREFIX "mp3" ) ?
234                                        SHOUT_FORMAT_MP3 : SHOUT_FORMAT_OGG );
235
236     if( i_ret != SHOUTERR_SUCCESS )
237     {
238         msg_Err( p_access, "failed to set the shoutcast streaming format" );
239         goto error;
240     }
241
242     /* Don't force bitrate to 0 but only use when specified. This will otherwise
243        show an empty field on icecast directory listing instead of NA */
244     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "bitrate" );
245     if( psz_val )
246     {
247         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_BITRATE, psz_val );
248         free( psz_val );
249         if( i_ret != SHOUTERR_SUCCESS )
250         {
251             msg_Err( p_access, "failed to set the information about the bitrate" );
252             goto error;
253         }
254     }
255     else
256     {
257         /* Bitrate information is used for icecast/shoutcast servers directory
258            listings (sorting, stream info etc.) */
259         msg_Warn( p_access, "no bitrate information specified (required for listing " \
260                             "the server as public on the shoutcast website)" );
261     }
262
263     /* Information about samplerate, channels and quality will not be propagated
264        through the YP protocol for icecast to the public directory listing when
265        the icecast server is operating in shoutcast compatibility mode */
266
267     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "samplerate" );
268     if( psz_val )
269     {
270         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_SAMPLERATE, psz_val );
271         free( psz_val );
272         if( i_ret != SHOUTERR_SUCCESS )
273         {
274             msg_Err( p_access, "failed to set the information about the samplerate" );
275             goto error;
276         }
277     }
278
279     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "channels" );
280     if( psz_val )
281     {
282         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_CHANNELS, psz_val );
283         free( psz_val );
284         if( i_ret != SHOUTERR_SUCCESS )
285         {
286             msg_Err( p_access, "failed to set the information about the number of channels" );
287             goto error;
288         }
289     }
290
291     psz_val = var_GetNonEmptyString( p_access, SOUT_CFG_PREFIX "quality" );
292     if( psz_val )
293     {
294         i_ret = shout_set_audio_info( p_shout, SHOUT_AI_QUALITY, psz_val );
295         free( psz_val );
296         if( i_ret != SHOUTERR_SUCCESS )
297         {
298             msg_Err( p_access, "failed to set the information about Ogg Vorbis quality" );
299             goto error;
300         }
301     }
302
303     if( var_GetBool( p_access, SOUT_CFG_PREFIX "public" ) )
304     {
305         i_ret = shout_set_public( p_shout, 1 );
306         if( i_ret != SHOUTERR_SUCCESS )
307         {
308             msg_Err( p_access, "failed to set the server status setting to public" );
309             goto error;
310         }
311     }
312
313     /* Connect at startup. Cycle through the possible protocols. */
314     i_ret = shout_get_connected( p_shout );
315     while ( i_ret != SHOUTERR_CONNECTED )
316     {
317         /* Shout parameters cannot be changed on an open connection */
318         shout_close( p_shout );
319
320         /* Re-initialize for Shoutcast using ICY protocol. Not needed for initial connection
321            but it is when we are reconnecting after other protocol was tried. */
322         i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_ICY );
323         if( i_ret != SHOUTERR_SUCCESS )
324         {
325             msg_Err( p_access, "failed to set the protocol to 'icy'" );
326             goto error;
327         }
328         i_ret = shout_open( p_shout );
329         if( i_ret == SHOUTERR_SUCCESS )
330         {
331             i_ret = SHOUTERR_CONNECTED;
332             msg_Dbg( p_access, "connected using 'icy' (shoutcast) protocol" );
333         }
334         else
335         {
336             msg_Warn( p_access, "failed to connect using 'icy' (shoutcast) protocol" );
337
338             /* Shout parameters cannot be changed on an open connection */
339             shout_close( p_shout );
340
341             /* IceCAST using HTTP protocol */
342             i_ret = shout_set_protocol( p_shout, SHOUT_PROTOCOL_HTTP );
343             if( i_ret != SHOUTERR_SUCCESS )
344             {
345                 msg_Err( p_access, "failed to set the protocol to 'http'" );
346                 goto error;
347             }
348             i_ret = shout_open( p_shout );
349             if( i_ret == SHOUTERR_SUCCESS )
350             {
351                 i_ret = SHOUTERR_CONNECTED;
352                 msg_Dbg( p_access, "connected using 'http' (icecast 2.x) protocol" );
353             }
354             else
355                 msg_Warn( p_access, "failed to connect using 'http' (icecast 2.x) protocol " );
356         }
357 /*
358         for non-blocking, use:
359         while( i_ret == SHOUTERR_BUSY )
360         {
361             sleep( 1 );
362             i_ret = shout_get_connected( p_shout );
363         }
364 */
365         if ( i_ret != SHOUTERR_CONNECTED )
366         {
367             msg_Warn( p_access, "unable to establish connection, retrying..." );
368             msleep( 30000000 );
369         }
370     }
371
372     if( i_ret != SHOUTERR_CONNECTED )
373     {
374         msg_Err( p_access, "failed to open shout stream to %s:%i/%s: %s",
375                  url.psz_host, url.i_port, url.psz_path, shout_get_error(p_shout) );
376         goto error;
377     }
378
379     p_access->pf_write = Write;
380     p_access->pf_control = Control;
381
382     msg_Dbg( p_access, "shout access output opened (%s@%s:%i/%s)",
383              url.psz_username, url.psz_host, url.i_port, url.psz_path );
384
385     vlc_UrlClean( &url );
386     return VLC_SUCCESS;
387
388 error:
389     if( p_sys->p_shout )
390         shout_free( p_sys->p_shout );
391     vlc_UrlClean( &url );
392     free( p_sys );
393     return VLC_EGENERIC;
394 }
395
396 /*****************************************************************************
397  * Close: close the target
398  *****************************************************************************/
399 static void Close( vlc_object_t * p_this )
400 {
401     sout_access_out_t *p_access = (sout_access_out_t*)p_this;
402     sout_access_out_sys_t *p_sys = p_access->p_sys;
403
404     if( p_sys->p_shout )
405     {
406         shout_close( p_sys->p_shout );
407         shout_free( p_sys->p_shout );
408         shout_shutdown();
409     }
410     free( p_sys );
411     msg_Dbg( p_access, "shout access output closed" );
412 }
413
414 static int Control( sout_access_out_t *p_access, int i_query, va_list args )
415 {
416     switch( i_query )
417     {
418         case ACCESS_OUT_CONTROLS_PACE:
419         {
420             bool *pb = va_arg( args, bool * );
421             *pb = strcmp( p_access->psz_access, "stream" );
422             break;
423         }
424
425         default:
426             return VLC_EGENERIC;
427     }
428     return VLC_SUCCESS;
429 }
430
431 /*****************************************************************************
432  * Write: standard write
433  *****************************************************************************/
434 static ssize_t Write( sout_access_out_t *p_access, block_t *p_buffer )
435 {
436     sout_access_out_sys_t *p_sys = p_access->p_sys;
437     size_t i_write = 0;
438
439     shout_sync( p_sys->p_shout );
440     while( p_buffer )
441     {
442         block_t *p_next = p_buffer->p_next;
443
444         if( shout_send( p_sys->p_shout, p_buffer->p_buffer, p_buffer->i_buffer )
445              == SHOUTERR_SUCCESS )
446         {
447             i_write += p_buffer->i_buffer;
448         }
449         else
450         {
451             msg_Err( p_access, "cannot write to stream: %s",
452                      shout_get_error( p_sys->p_shout ) );
453
454             /* The most common cause seems to be a server disconnect, resulting in a
455                Socket Error which can only be fixed by closing and reconnecting.
456                Since we already began with a working connection, the most feasable
457                approach to get out of this error status is a (timed) reconnect approach. */
458             shout_close( p_sys->p_shout );
459             msg_Warn( p_access, "server unavailable? trying to reconnect..." );
460             /* Re-open the connection (protocol params have already been set) and re-sync */
461             if( shout_open( p_sys->p_shout ) == SHOUTERR_SUCCESS )
462             {
463                 shout_sync( p_sys->p_shout );
464                 msg_Warn( p_access, "reconnected to server" );
465             }
466             else
467             {
468                 msg_Err( p_access, "failed to reconnect to server" );
469                 block_ChainRelease( p_buffer );
470                 return VLC_EGENERIC;
471             }
472
473         }
474         block_Release( p_buffer );
475
476         /* XXX: Unsure if that's the cause for some audio trouble... */
477
478         p_buffer = p_next;
479     }
480
481     return i_write;
482 }