]> git.sesse.net Git - vlc/blob - plugins/alsa/aout_alsa.c
* Fixed a bug in the gtk interface which caused vlc to go amok after
[vlc] / plugins / alsa / aout_alsa.c
1 /*****************************************************************************
2  * aout_alsa.c : Alsa functions library
3  *****************************************************************************
4  * Copyright (C) 2000 VideoLAN
5  * $Id: aout_alsa.c,v 1.18 2001/07/12 20:44:52 reno Exp $
6  *
7  * Authors: Henri Fallon <henri@videolan.org> - Original Author
8  *          Jeffrey Baker <jwbaker@acm.org> - Port to ALSA 1.0 API
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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 #define MODULE_NAME alsa
26 #include "modules_inner.h"
27
28 /*****************************************************************************
29  * Preamble
30  *****************************************************************************/
31
32 #include "defs.h"
33
34 #include <errno.h>                                                 /* ENOMEM */
35 #include <string.h>                                            /* strerror() */
36 #include <stdio.h>                                           /* "intf_msg.h" */
37 #include <stdlib.h>                            /* calloc(), malloc(), free() */
38
39 #include <sys/asoundlib.h>
40
41 #include "config.h"
42 #include "common.h"                                     /* boolean_t, byte_t */
43 #include "threads.h"
44 #include "mtime.h"
45 #include "tests.h"
46
47 #include "audio_output.h"                                   /* aout_thread_t */
48
49 #include "intf_msg.h"                        /* intf_DbgMsg(), intf_ErrMsg() */
50 #include "main.h"
51
52 #include "modules.h"
53 #include "modules_export.h"
54
55 typedef struct alsa_device_s
56 {
57     int i_num;
58 } alsa_device_t;
59
60 typedef struct alsa_card_s
61 {
62     int i_num;
63 } alsa_card_t;
64
65 /* here we store plugin dependant informations */
66
67 typedef struct aout_sys_s
68 {
69     snd_pcm_t   * p_alsa_handle;
70     unsigned long buffer_time;
71     unsigned long period_time;
72     unsigned long chunk_size;
73     unsigned long buffer_size;
74     unsigned long rate;
75     unsigned int  bytes_per_sample;
76     unsigned int  samples_per_frame;
77     unsigned int  bytes_per_frame;
78 } aout_sys_t;
79
80
81 /*****************************************************************************
82  * aout_Probe: probes the audio device and return a score
83  *****************************************************************************
84  * This function tries to open the dps and returns a score to the plugin
85  * manager so that it can make its choice.
86  *****************************************************************************/
87 static int aout_Probe( probedata_t *p_data )
88 {
89     int i_open_return, i_close_return;
90     aout_sys_t local_sys;
91
92     /* Open device */
93     i_open_return = snd_pcm_open( &(local_sys.p_alsa_handle), "plug:0,0",
94                                   SND_PCM_STREAM_PLAYBACK, 0 );
95     if( i_open_return )
96     {
97         intf_WarnMsg( 2, "aout info: could not probe ALSA device (%s)",
98                       snd_strerror( i_open_return ) );
99         return ( 0 );
100     }
101
102     /* Close it */
103     i_close_return = snd_pcm_close( local_sys.p_alsa_handle );
104
105     if( i_close_return )
106     {
107         intf_ErrMsg( "aout error: could not close ALSA device (%s)",
108                      snd_strerror( i_close_return ) );
109         return( 0 );
110     }
111
112     if( TestMethod( AOUT_METHOD_VAR, "alsa" ) )
113     {
114         return( 999 );
115     }
116
117     /* And return score */
118     return( 100 );
119 }
120
121 /*****************************************************************************
122  * aout_Open : creates a handle and opens an alsa device
123  *****************************************************************************
124  * This function opens an alsa device, through the alsa API
125  *****************************************************************************/
126 static int aout_Open( aout_thread_t *p_aout )
127 {
128
129     int i_open_returns;
130
131     /* Allocate structures */
132     p_aout->p_sys = malloc( sizeof( aout_sys_t ) );
133     if( p_aout->p_sys == NULL )
134     {
135         intf_ErrMsg( "aout error: failed allocating memory for ALSA (%s)",
136                      strerror(ENOMEM) );
137         return( 1 );
138     }
139
140     p_aout->i_format   = AOUT_FORMAT_DEFAULT;
141     p_aout->i_channels = 1 + main_GetIntVariable( AOUT_STEREO_VAR,
142                                                   AOUT_STEREO_DEFAULT );
143     p_aout->l_rate     = main_GetIntVariable( AOUT_RATE_VAR,
144                                               AOUT_RATE_DEFAULT );
145
146     /* Open device */
147     if( ( i_open_returns = snd_pcm_open(&(p_aout->p_sys->p_alsa_handle),
148                                         "plug:0,0",
149                                         SND_PCM_STREAM_PLAYBACK, 0) ) )
150     {
151         intf_ErrMsg( "aout error: could not open ALSA device (%s)",
152                      snd_strerror(i_open_returns) );
153         return( -1 );
154     }
155
156     intf_DbgMsg( "aout info: ALSA device successfully opened" );
157     return( 0 );
158 }
159
160
161 /*****************************************************************************
162  * aout_SetFormat : sets the alsa output format
163  *****************************************************************************
164  * This function prepares the device, sets the rate, format, the mode
165  * ( "play as soon as you have data" ), and buffer information.
166  *****************************************************************************/
167 static int aout_SetFormat( aout_thread_t *p_aout )
168 {
169
170     int i_rv;
171     int i_format;
172
173     snd_pcm_hw_params_t *p_hw;
174     snd_pcm_sw_params_t *p_sw;
175
176     snd_pcm_hw_params_alloca(&p_hw);
177     snd_pcm_sw_params_alloca(&p_sw);
178
179     switch (p_aout->i_format)
180     {
181         case AOUT_FMT_S16_LE:
182             i_format = SND_PCM_FORMAT_S16_LE;
183             p_aout->p_sys->bytes_per_sample = 2;
184             break;
185
186         default:
187             i_format = SND_PCM_FORMAT_S16_BE;
188             p_aout->p_sys->bytes_per_sample = 2;
189             break;
190     }
191
192     p_aout->p_sys->samples_per_frame = p_aout->i_channels;
193     p_aout->p_sys->bytes_per_frame = p_aout->p_sys->samples_per_frame *
194                                      p_aout->p_sys->bytes_per_sample;
195
196     i_rv = snd_pcm_hw_params_any( p_aout->p_sys->p_alsa_handle, p_hw );
197     if( i_rv < 0 )
198     {
199         intf_ErrMsg( "aout error: unable to retrieve initial parameters" );
200         return( -1 );
201     }
202
203     i_rv = snd_pcm_hw_params_set_access( p_aout->p_sys->p_alsa_handle, p_hw,
204                                          SND_PCM_ACCESS_RW_INTERLEAVED );
205     if( i_rv < 0 )
206     {
207         intf_ErrMsg( "aout error: unable to set interleaved stream format" );
208         return( -1 );
209     }
210
211     i_rv = snd_pcm_hw_params_set_format( p_aout->p_sys->p_alsa_handle,
212                                          p_hw, i_format );
213     if( i_rv < 0 )
214     {
215         intf_ErrMsg( "aout error: unable to set stream sample size and word"
216                      " order" );
217         return( -1 );
218     }
219
220     i_rv = snd_pcm_hw_params_set_channels( p_aout->p_sys->p_alsa_handle, p_hw,
221                                            p_aout->i_channels );
222     if( i_rv < 0 )
223     {
224         intf_ErrMsg( "aout error: unable to set number of output channels" );
225         return( -1 );
226     }
227
228     i_rv = snd_pcm_hw_params_set_rate_near( p_aout->p_sys->p_alsa_handle, p_hw,
229                                             p_aout->l_rate, 0 );
230     if( i_rv < 0 )
231     {
232         intf_ErrMsg( "aout error: unable to set sample rate" );
233         return( -1 );
234     }
235     p_aout->p_sys->rate = i_rv;
236
237     i_rv = snd_pcm_hw_params_set_buffer_time_near( p_aout->p_sys->p_alsa_handle,
238                                                    p_hw, AOUT_BUFFER_DURATION,
239                                                    0 );
240     if( i_rv < 0 )
241     {
242         intf_ErrMsg( "aout error: unable to set buffer time" );
243         return( -1 );
244     }
245     p_aout->p_sys->buffer_time = i_rv;
246
247     i_rv = snd_pcm_hw_params_set_period_time_near( p_aout->p_sys->p_alsa_handle,
248          p_hw, p_aout->p_sys->buffer_time / p_aout->p_sys->bytes_per_frame, 0 );
249     if( i_rv < 0 )
250     {
251         intf_ErrMsg( "aout error: unable to set period time" );
252         return( -1 );
253     }
254     p_aout->p_sys->period_time = i_rv;
255
256     i_rv = snd_pcm_hw_params(p_aout->p_sys->p_alsa_handle, p_hw);
257     if (i_rv < 0)
258     {
259         intf_ErrMsg( "aout error: unable to set hardware configuration" );
260         return( -1 );
261     }
262
263     p_aout->p_sys->chunk_size = snd_pcm_hw_params_get_period_size( p_hw, 0 );
264     p_aout->p_sys->buffer_size = snd_pcm_hw_params_get_buffer_size( p_hw );
265
266     snd_pcm_sw_params_current( p_aout->p_sys->p_alsa_handle, p_sw );
267     i_rv = snd_pcm_sw_params_set_sleep_min( p_aout->p_sys->p_alsa_handle, p_sw,
268                                             0 );
269
270     i_rv = snd_pcm_sw_params_set_avail_min( p_aout->p_sys->p_alsa_handle, p_sw,
271                                             p_aout->p_sys->chunk_size );
272
273     /* Worked with the CVS version but not with 0.9beta3
274     i_rv = snd_pcm_sw_params_set_start_threshold( p_aout->p_sys->p_alsa_handle,
275                                             p_sw, p_aout->p_sys->buffer_size );
276
277     i_rv = snd_pcm_sw_params_set_stop_threshold( p_aout->p_sys->p_alsa_handle,
278                                              p_sw, p_aout->p_sys->buffer_size);
279     */
280     i_rv = snd_pcm_sw_params( p_aout->p_sys->p_alsa_handle, p_sw );
281     if( i_rv < 0 )
282     {
283         intf_ErrMsg( "aout error: unable to set software configuration" );
284         return( -1 );
285     }
286
287     p_aout->i_latency = 0;
288     
289     return( 0 );
290 }
291
292 /*****************************************************************************
293  * aout_HandleXrun : reprepare the output
294  *****************************************************************************
295  * When buffer gets empty, the driver goes in "Xrun" state, where it needs
296  * to be reprepared before playing again
297  *****************************************************************************/
298 static void aout_HandleXrun(aout_thread_t *p_aout)
299 {
300     int i_rv;
301
302     intf_ErrMsg( "aout error: resetting output after buffer underrun" );
303
304     i_rv = snd_pcm_reset( p_aout->p_sys->p_alsa_handle );
305     i_rv = snd_pcm_prepare( p_aout->p_sys->p_alsa_handle );
306     if( i_rv < 0 )
307     {
308         intf_ErrMsg( "aout error: unable to recover from buffer underrun (%s)",
309                      snd_strerror( i_rv ) );
310     }
311 }
312
313
314 /*****************************************************************************
315  * aout_BufInfo: buffer status query
316  *****************************************************************************
317  * This function returns the number of used byte in the queue.
318  * It also deals with errors : indeed if the device comes to run out
319  * of data to play, it switches to the "underrun" status. It has to
320  * be flushed and re-prepared
321  *****************************************************************************/
322 static long aout_GetBufInfo( aout_thread_t *p_aout, long l_buffer_limit )
323 {
324     snd_pcm_status_t *p_status;
325     int i_alsa_get_status_returns;
326
327     snd_pcm_status_alloca( &p_status );
328
329     i_alsa_get_status_returns = snd_pcm_status( p_aout->p_sys->p_alsa_handle,
330                                                 p_status );
331
332     if( i_alsa_get_status_returns )
333     {
334         intf_ErrMsg ( "aout error: failed getting alsa buffer info (%s)",
335                       snd_strerror ( i_alsa_get_status_returns ) );
336         return ( -1 );
337     }
338
339     switch( snd_pcm_status_get_state( p_status ) )
340     {
341         case SND_PCM_STATE_XRUN :
342             aout_HandleXrun( p_aout );
343             break;
344
345         case SND_PCM_STATE_OPEN:
346         case SND_PCM_STATE_PREPARED:
347         case SND_PCM_STATE_RUNNING:
348             break;
349
350         default:
351             intf_ErrMsg( "aout error: unhandled condition %i",
352                          snd_pcm_status_get_state( p_status ) );
353             break;
354     }
355
356     return( snd_pcm_status_get_avail(p_status) *
357             p_aout->p_sys->bytes_per_frame );
358 }
359
360 /*****************************************************************************
361  * aout_Play : plays a sample
362  *****************************************************************************
363  * Plays a sample using the snd_pcm_writei function from the alsa API
364  *****************************************************************************/
365 static void aout_Play( aout_thread_t *p_aout, byte_t *buffer, int i_size )
366 {
367     snd_pcm_uframes_t tot_frames;
368     snd_pcm_uframes_t frames_left;
369     snd_pcm_uframes_t rv;
370
371     tot_frames = i_size / p_aout->p_sys->bytes_per_frame;
372     frames_left = tot_frames;
373
374     while( frames_left > 0 )
375     {
376         rv = snd_pcm_writei( p_aout->p_sys->p_alsa_handle, buffer +
377                              (tot_frames - frames_left) *
378                              p_aout->p_sys->bytes_per_frame, frames_left );
379
380         if( (signed int) rv < 0 )
381         {
382             intf_ErrMsg( "aout error: failed writing to output (%s)",
383                          snd_strerror( rv ) );
384             return;
385         }
386
387         frames_left -= rv;
388     }
389 }
390
391 /*****************************************************************************
392  * aout_Close : close the Alsa device
393  *****************************************************************************/
394 static void aout_Close( aout_thread_t *p_aout )
395 {
396     int i_close_returns;
397
398     i_close_returns = snd_pcm_close( p_aout->p_sys->p_alsa_handle );
399
400     if( i_close_returns )
401     {
402         intf_ErrMsg( "aout error: failed closing ALSA device (%s)",
403                      i_close_returns, snd_strerror( i_close_returns ) );
404     }
405
406     free( p_aout->p_sys );
407
408     intf_DbgMsg( "aout: ALSA device closed" );
409 }
410
411 /*****************************************************************************
412  * Functions exported as capabilities. They are declared as static so that
413  * we don't pollute the namespace too much.
414  *****************************************************************************/
415 void _M( aout_getfunctions )( function_list_t * p_function_list )
416 {
417     p_function_list->pf_probe = aout_Probe;
418     p_function_list->functions.aout.pf_open = aout_Open;
419     p_function_list->functions.aout.pf_setformat = aout_SetFormat;
420     p_function_list->functions.aout.pf_getbufinfo = aout_GetBufInfo;
421     p_function_list->functions.aout.pf_play = aout_Play;
422     p_function_list->functions.aout.pf_close = aout_Close;
423 }
424