]> git.sesse.net Git - vlc/blob - modules/audio_output/portaudio.c
* modules/audio_output/portaudio.c: implemented a PORTAUDIO_IS_SERIOUSLY_BROKEN mode...
[vlc] / modules / audio_output / portaudio.c
1 /*****************************************************************************
2  * portaudio.c : portaudio (v19) audio output plugin
3  *****************************************************************************
4  * Copyright (C) 2002 VideoLAN
5  * $Id$
6  *
7  * Authors: Frederic Ruget <frederic.ruget@free.fr>
8  *          Gildas Bazin <gbazin@videolan.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., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
23  *****************************************************************************/
24
25 /*****************************************************************************
26  * Preamble
27  *****************************************************************************/
28 #include <string.h>
29 #include <stdlib.h>
30
31 #include <vlc/vlc.h>
32 #include <vlc/aout.h>
33 #include <portaudio.h>
34
35 #include "aout_internal.h"
36
37 #define FRAME_SIZE 1024              /* The size is in samples, not in bytes */
38
39 #ifdef WIN32
40 #   define PORTAUDIO_IS_SERIOUSLY_BROKEN 1
41 #endif
42
43 /*****************************************************************************
44  * aout_sys_t: portaudio audio output method descriptor
45  *****************************************************************************/
46 typedef struct pa_thread_t
47 {
48     VLC_COMMON_MEMBERS
49     aout_instance_t *p_aout;
50
51     vlc_cond_t  wait;
52     vlc_mutex_t lock_wait;
53     vlc_bool_t  b_wait;
54     vlc_cond_t  signal;
55     vlc_mutex_t lock_signal;
56     vlc_bool_t  b_signal;
57
58 } pa_thread_t;
59
60 struct aout_sys_t
61 {
62     aout_instance_t *p_aout;
63     PaStream *p_stream;
64
65     PaDeviceIndex i_devices;
66     int i_sample_size;
67     PaDeviceIndex i_device_id;
68     const PaDeviceInfo *deviceInfo;
69 };
70
71 #ifdef PORTAUDIO_IS_SERIOUSLY_BROKEN
72 static vlc_bool_t b_init = 0;
73 static pa_thread_t *pa_thread;
74 static void PORTAUDIOThread( pa_thread_t * );
75 #endif
76
77 /*****************************************************************************
78  * Local prototypes.
79  *****************************************************************************/
80 static int  Open        ( vlc_object_t * );
81 static void Close       ( vlc_object_t * );
82 static void Play        ( aout_instance_t * );
83
84 static int PAOpenDevice( aout_instance_t * );
85 static int PAOpenStream( aout_instance_t * );
86
87 /*****************************************************************************
88  * Module descriptor
89  *****************************************************************************/
90 #define DEVICE_TEXT N_("Output device")
91 #define DEVICE_LONGTEXT N_("Portaudio identifier for the output device")
92
93 vlc_module_begin();
94     set_description( N_("PORTAUDIO audio output") );
95     add_integer( "portaudio-device", 0, NULL,
96                  DEVICE_TEXT, DEVICE_LONGTEXT, VLC_FALSE );
97     set_capability( "audio output", 0 );
98     set_callbacks( Open, Close );
99 vlc_module_end();
100
101 /* This routine will be called by the PortAudio engine when audio is needed.
102  * It may called at interrupt level on some machines so don't do anything
103  * that could mess up the system like calling malloc() or free().
104  */
105 static int paCallback( const void *inputBuffer, void *outputBuffer,
106                        unsigned long framesPerBuffer,
107                        const PaStreamCallbackTimeInfo *paDate,
108                        PaStreamCallbackFlags statusFlags, void *p_cookie )
109 {
110     struct aout_sys_t *p_sys = (struct aout_sys_t*) p_cookie;
111     aout_instance_t   *p_aout = p_sys->p_aout;
112     aout_buffer_t     *p_buffer;
113     mtime_t out_date;
114
115     out_date = mdate() + (mtime_t) ( 1000000 *
116         ( paDate->outputBufferDacTime - paDate->currentTime ) );
117     p_buffer = aout_OutputNextBuffer( p_aout, out_date, VLC_TRUE );
118
119     if ( p_buffer != NULL )
120     {
121         p_aout->p_vlc->pf_memcpy( outputBuffer, p_buffer->p_buffer,
122                                   framesPerBuffer * p_sys->i_sample_size );
123         /* aout_BufferFree may be dangereous here, but then so is
124          * aout_OutputNextBuffer (calls aout_BufferFree internally).
125          * one solution would be to link the no longer useful buffers
126          * in a second fifo (in aout_OutputNextBuffer too) and to
127          * wait until we are in Play to do the actual free.
128          */
129         aout_BufferFree( p_buffer );
130     }
131     else
132         /* Audio output buffer shortage -> stop the fill process and wait */
133     {
134         p_aout->p_vlc->pf_memset( outputBuffer, 0,
135                                   framesPerBuffer * p_sys->i_sample_size );
136     }
137     return 0;
138 }
139
140 /*****************************************************************************
141  * Open: open the audio device
142  *****************************************************************************/
143 static int Open( vlc_object_t * p_this )
144 {
145     aout_instance_t *p_aout = (aout_instance_t *)p_this;
146     struct aout_sys_t * p_sys;
147     vlc_value_t val;
148     int i_err;
149
150     msg_Dbg( p_aout, "Entering Open()");
151
152     /* Allocate p_sys structure */
153     p_sys = (aout_sys_t *)malloc( sizeof(aout_sys_t) );
154     if( p_sys == NULL )
155     {
156         msg_Err( p_aout, "out of memory" );
157         return VLC_ENOMEM;
158     }
159     p_sys->p_aout = p_aout;
160     p_sys->p_stream = 0;
161     p_aout->output.p_sys = p_sys;
162     p_aout->output.pf_play = Play;
163
164     /* Retrieve output device id from config */
165     var_Create( p_aout, "portaudio-device", VLC_VAR_INTEGER|VLC_VAR_DOINHERIT);
166     var_Get( p_aout, "portaudio-device", &val );
167     p_sys->i_device_id = val.i_int;
168
169 #ifdef PORTAUDIO_IS_SERIOUSLY_BROKEN
170     if( !b_init )
171     {
172         /* Test device */
173         if( PAOpenDevice( p_aout ) != VLC_SUCCESS )
174         {
175             msg_Err( p_aout, "cannot open portaudio device" );
176             free( p_sys );
177             return VLC_EGENERIC;
178         }
179
180         /* Close device for now. We'll re-open it later on */
181         if( ( i_err = Pa_Terminate() ) != paNoError )
182         {
183             msg_Err( p_aout, "Pa_Terminate returned %d", i_err );
184         }
185
186         b_init = VLC_TRUE;
187
188         /* Now we need to setup our DirectSound play notification structure */
189         pa_thread = vlc_object_create( p_aout, sizeof(pa_thread_t) );
190         pa_thread->p_aout = p_aout;
191         pa_thread->b_error = VLC_FALSE;
192         vlc_mutex_init( p_aout, &pa_thread->lock_wait );
193         vlc_cond_init( p_aout, &pa_thread->wait );
194         pa_thread->b_wait = VLC_FALSE;
195         vlc_mutex_init( p_aout, &pa_thread->lock_signal );
196         vlc_cond_init( p_aout, &pa_thread->signal );
197         pa_thread->b_signal = VLC_FALSE;
198
199         /* Create PORTAUDIOThread */
200         if( vlc_thread_create( pa_thread, "aout", PORTAUDIOThread,
201                                VLC_THREAD_PRIORITY_OUTPUT, VLC_FALSE ) )
202         {
203             msg_Err( p_aout, "cannot create PORTAUDIO thread" );
204             return VLC_EGENERIC;
205         }
206     }
207     else
208     {
209         pa_thread->p_aout = p_aout;
210         pa_thread->b_wait = VLC_FALSE;
211         pa_thread->b_signal = VLC_FALSE;
212         pa_thread->b_error = VLC_FALSE;
213     }
214
215     /* Signal start of stream */
216     vlc_mutex_lock( &pa_thread->lock_signal );
217     pa_thread->b_signal = VLC_TRUE;
218     vlc_cond_signal( &pa_thread->signal );
219     vlc_mutex_unlock( &pa_thread->lock_signal );
220
221     /* Wait until thread is ready */
222     vlc_mutex_lock( &pa_thread->lock_wait );
223     if( !pa_thread->b_wait )
224         vlc_cond_wait( &pa_thread->wait, &pa_thread->lock_wait );
225     vlc_mutex_unlock( &pa_thread->lock_wait );
226     pa_thread->b_wait = VLC_FALSE;
227
228     if( pa_thread->b_error )
229     {
230         msg_Err( p_aout, "PORTAUDIO thread failed" );
231         Close( p_this );
232         return VLC_EGENERIC;
233     }
234
235     return VLC_SUCCESS;
236
237 #else
238
239     if( PAOpenDevice( p_aout ) != VLC_SUCCESS )
240     {
241         msg_Err( p_aout, "cannot open portaudio device" );
242         free( p_sys );
243         return VLC_EGENERIC;
244     }
245
246     if( PAOpenStream( p_aout ) != VLC_SUCCESS )
247     {
248         msg_Err( p_aout, "cannot open portaudio device" );
249     }
250
251     return VLC_SUCCESS;
252
253 #endif
254 }
255
256 /*****************************************************************************
257  * Close: close the audio device
258  *****************************************************************************/
259 static void Close ( vlc_object_t *p_this )
260 {
261     aout_instance_t *p_aout = (aout_instance_t *)p_this;
262     aout_sys_t *p_sys = p_aout->output.p_sys;
263     int i_err;
264
265     msg_Dbg( p_aout, "closing portaudio");
266
267 #ifdef PORTAUDIO_IS_SERIOUSLY_BROKEN
268
269     /* Signal end of stream */
270     vlc_mutex_lock( &pa_thread->lock_signal );
271     pa_thread->b_signal = VLC_TRUE;
272     vlc_cond_signal( &pa_thread->signal );
273     vlc_mutex_unlock( &pa_thread->lock_signal );
274
275     /* Wait until thread is ready */
276     vlc_mutex_lock( &pa_thread->lock_wait );
277     if( !pa_thread->b_wait )
278         vlc_cond_wait( &pa_thread->wait, &pa_thread->lock_wait );
279     vlc_mutex_unlock( &pa_thread->lock_wait );
280     pa_thread->b_wait = VLC_FALSE;
281
282 #else
283
284     i_err = Pa_StopStream( p_sys->p_stream );
285     if( i_err != paNoError )
286     {
287         msg_Err( p_aout, "Pa_StopStream: %d (%s)", i_err,
288                  Pa_GetErrorText( i_err ) );
289     }
290     i_err = Pa_CloseStream( p_sys->p_stream );
291     if( i_err != paNoError )
292     {
293         msg_Err( p_aout, "Pa_CloseStream: %d (%s)", i_err,
294                  Pa_GetErrorText( i_err ) );
295     }
296
297     i_err = Pa_Terminate();
298     if( i_err != paNoError )
299     {
300         msg_Err( p_aout, "Pa_Terminate: %d (%s)", i_err,
301                  Pa_GetErrorText( i_err ) );
302     }
303
304 #endif
305
306     msg_Dbg( p_aout, "portaudio closed");
307     free( p_sys );
308 }
309
310 static int PAOpenDevice( aout_instance_t *p_aout )
311 {
312     aout_sys_t *p_sys = p_aout->output.p_sys;
313     const PaDeviceInfo *p_pdi;
314     PaError i_err;
315     vlc_value_t val, text;
316     int i;
317
318     /* Initialize portaudio */
319     i_err = Pa_Initialize();
320     if( i_err != paNoError )
321     {
322         msg_Err( p_aout, "Pa_Initialize returned %d : %s",
323                  i_err, Pa_GetErrorText( i_err ) );
324
325         return VLC_EGENERIC;
326     }
327
328     p_sys->i_devices = Pa_GetDeviceCount();
329     if( p_sys->i_devices < 0 )
330     {
331         i_err = p_sys->i_devices;
332         msg_Err( p_aout, "Pa_GetDeviceCount returned %d : %s", i_err,
333                  Pa_GetErrorText( i_err ) );
334
335         goto error;
336     }
337
338     /* Display all devices info */
339     msg_Dbg( p_aout, "number of devices = %d", p_sys->i_devices );
340     for( i = 0; i < p_sys->i_devices; i++ )
341     {
342         p_pdi = Pa_GetDeviceInfo( i );
343         msg_Dbg( p_aout, "------------------------------------- #%d", i );
344         msg_Dbg( p_aout, "Name         = %s", p_pdi->name );
345         msg_Dbg( p_aout, "Max Inputs   = %d, Max Outputs = %d",
346                   p_pdi->maxInputChannels, p_pdi->maxOutputChannels );
347     }
348     msg_Dbg( p_aout, "-------------------------------------" );
349
350     msg_Dbg( p_aout, "requested device is #%d", p_sys->i_device_id );
351     if( p_sys->i_device_id >= p_sys->i_devices )
352     {
353         msg_Err( p_aout, "device %d does not exist", p_sys->i_device_id );
354         goto error;
355     }
356     p_sys->deviceInfo = Pa_GetDeviceInfo( p_sys->i_device_id );
357
358     if( p_sys->deviceInfo->maxOutputChannels < 1 )
359     {
360         msg_Err( p_aout, "no channel available" );
361         goto error;
362     }
363
364     if( var_Type( p_aout, "audio-device" ) == 0 )
365     {
366         var_Create( p_aout, "audio-device", VLC_VAR_INTEGER|VLC_VAR_HASCHOICE);
367         text.psz_string = _("Audio Device");
368         var_Change( p_aout, "audio-device", VLC_VAR_SETTEXT, &text, NULL );
369
370         if( p_sys->deviceInfo->maxOutputChannels >= 1 )
371         {
372             val.i_int = AOUT_VAR_MONO;
373             text.psz_string = N_("Mono");
374             var_Change( p_aout, "audio-device", VLC_VAR_ADDCHOICE,
375                         &val, &text );
376             msg_Dbg( p_aout, "device supports 1 channel" );
377         }
378         if( p_sys->deviceInfo->maxOutputChannels >= 2 )
379         {
380             val.i_int = AOUT_VAR_STEREO;
381             text.psz_string = N_("Stereo");
382             var_Change( p_aout, "audio-device", VLC_VAR_ADDCHOICE,
383                         &val, &text );
384             var_Change( p_aout, "audio-device", VLC_VAR_SETDEFAULT,
385                         &val, NULL );
386             var_Set( p_aout, "audio-device", val );
387             msg_Dbg( p_aout, "device supports 2 channels" );
388         }
389         if( p_sys->deviceInfo->maxOutputChannels >= 4 )
390         {
391             val.i_int = AOUT_VAR_2F2R;
392             text.psz_string = N_("2 Front 2 Rear");
393             var_Change( p_aout, "audio-device", VLC_VAR_ADDCHOICE,
394                         &val, &text );
395             msg_Dbg( p_aout, "device supports 4 channels" );
396         }
397         if( p_sys->deviceInfo->maxOutputChannels >= 5 )
398         {
399             val.i_int = AOUT_VAR_3F2R;
400             text.psz_string = N_("3 Front 2 Rear");
401             var_Change( p_aout, "audio-device",
402                         VLC_VAR_ADDCHOICE, &val, &text );
403             msg_Dbg( p_aout, "device supports 5 channels" );
404         }
405         if( p_sys->deviceInfo->maxOutputChannels >= 6 )
406         {
407             val.i_int = AOUT_VAR_5_1;
408             text.psz_string = N_("5.1");
409             var_Change( p_aout, "audio-device", VLC_VAR_ADDCHOICE,
410                         &val, &text );
411             msg_Dbg( p_aout, "device supports 5.1 channels" );
412         }
413
414         var_AddCallback( p_aout, "audio-device", aout_ChannelsRestart, NULL );
415
416         val.b_bool = VLC_TRUE;
417         var_Set( p_aout, "intf-change", val );
418     }
419
420     /* Audio format is paFloat32 (always supported by portaudio v19) */
421     p_aout->output.output.i_format = VLC_FOURCC('f','l','3','2');
422
423     return VLC_SUCCESS;
424
425  error:
426     if( ( i_err = Pa_Terminate() ) != paNoError )
427     {
428         msg_Err( p_aout, "Pa_Terminate returned %d", i_err );
429     }
430     return VLC_EGENERIC;
431 }
432
433 static int PAOpenStream( aout_instance_t *p_aout )
434 {
435     aout_sys_t *p_sys = p_aout->output.p_sys;
436     const PaHostErrorInfo* paLastHostErrorInfo = Pa_GetLastHostErrorInfo();
437     PaStreamParameters paStreamParameters;
438     vlc_value_t val;
439     int i_channels, i_err;
440
441     if( var_Get( p_aout, "audio-device", &val ) < 0 )
442     {
443         return VLC_EGENERIC;
444     }
445
446     if( val.i_int == AOUT_VAR_5_1 )
447     {
448         p_aout->output.output.i_physical_channels
449             = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
450               | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT
451               | AOUT_CHAN_LFE;
452     }
453     else if( val.i_int == AOUT_VAR_3F2R )
454     {
455         p_aout->output.output.i_physical_channels
456             = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT | AOUT_CHAN_CENTER
457             | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT;
458     }
459     else if( val.i_int == AOUT_VAR_2F2R )
460     {
461         p_aout->output.output.i_physical_channels
462             = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT
463             | AOUT_CHAN_REARLEFT | AOUT_CHAN_REARRIGHT;
464     }
465     else if( val.i_int == AOUT_VAR_MONO )
466     {
467         p_aout->output.output.i_physical_channels = AOUT_CHAN_CENTER;
468     }
469     else
470     {
471         p_aout->output.output.i_physical_channels
472             = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT;
473     }
474
475     i_channels = aout_FormatNbChannels( &p_aout->output.output );
476     msg_Dbg( p_aout, "nb_channels requested = %d", i_channels );
477
478     /* Calculate the frame size in bytes */
479     p_sys->i_sample_size = 4 * i_channels;
480     p_aout->output.i_nb_samples = FRAME_SIZE;
481     aout_FormatPrepare( &p_aout->output.output );
482     aout_VolumeSoftInit( p_aout );
483
484     paStreamParameters.device = p_sys->i_device_id;
485     paStreamParameters.channelCount = i_channels;
486     paStreamParameters.sampleFormat = paFloat32;
487     paStreamParameters.suggestedLatency =
488         p_sys->deviceInfo->defaultLowOutputLatency;
489     paStreamParameters.hostApiSpecificStreamInfo = NULL;
490
491     i_err = Pa_OpenStream( &p_sys->p_stream, NULL /* no input */,
492                 &paStreamParameters, (double)p_aout->output.output.i_rate,
493                 FRAME_SIZE, paClipOff, paCallback, p_sys );
494     if( i_err != paNoError )
495     {
496         msg_Err( p_aout, "Pa_OpenStream returns %d : %s", i_err,
497                  Pa_GetErrorText( i_err ) );
498         if( i_err == paUnanticipatedHostError )
499         {
500             msg_Err( p_aout, "type %d code %ld : %s",
501                      paLastHostErrorInfo->hostApiType,
502                      paLastHostErrorInfo->errorCode,
503                      paLastHostErrorInfo->errorText );
504         }
505         p_sys->p_stream = 0;
506         return VLC_EGENERIC;
507     }
508
509     i_err = Pa_StartStream( p_sys->p_stream );
510     if( i_err != paNoError )
511     {
512         msg_Err( p_aout, "Pa_StartStream() failed" );
513         Pa_CloseStream( p_sys->p_stream );
514         return VLC_EGENERIC;
515     }
516
517     return VLC_SUCCESS;
518 }
519
520 /*****************************************************************************
521  * Play: play sound
522  *****************************************************************************/
523 static void Play( aout_instance_t * p_aout )
524 {
525 }
526
527 #ifdef PORTAUDIO_IS_SERIOUSLY_BROKEN
528 /*****************************************************************************
529  * PORTAUDIOThread: all interactions with libportaudio.a are handled
530  * in this single thread.  Otherwise libportaudio.a is _not_ happy :-(
531  *****************************************************************************/
532 static void PORTAUDIOThread( pa_thread_t *pa_thread )
533 {
534     aout_instance_t *p_aout;
535     aout_sys_t *p_sys;
536     int i_err;
537
538     while( !pa_thread->b_die )
539     {
540         /* Wait for start of stream */
541         vlc_mutex_lock( &pa_thread->lock_signal );
542         if( !pa_thread->b_signal )
543             vlc_cond_wait( &pa_thread->signal, &pa_thread->lock_signal );
544         vlc_mutex_unlock( &pa_thread->lock_signal );
545         pa_thread->b_signal = VLC_FALSE;
546
547         p_aout = pa_thread->p_aout;
548         p_sys = p_aout->output.p_sys;
549
550         if( PAOpenDevice( p_aout ) != VLC_SUCCESS )
551         {
552             msg_Err( p_aout, "cannot open portaudio device" );
553             pa_thread->b_error = VLC_TRUE;
554         }
555
556         if( !pa_thread->b_error && PAOpenStream( p_aout ) != VLC_SUCCESS )
557         {
558             msg_Err( p_aout, "cannot open portaudio device" );
559             pa_thread->b_error = VLC_TRUE;
560
561             i_err = Pa_Terminate();
562             if( i_err != paNoError )
563             {
564                 msg_Err( p_aout, "Pa_Terminate: %d (%s)", i_err,
565                          Pa_GetErrorText( i_err ) );
566             }
567         }
568
569         /* Tell the main thread that we are ready */
570         vlc_mutex_lock( &pa_thread->lock_wait );
571         pa_thread->b_wait = VLC_TRUE;
572         vlc_cond_signal( &pa_thread->wait );
573         vlc_mutex_unlock( &pa_thread->lock_wait );
574
575         /* Wait for end of stream */
576         vlc_mutex_lock( &pa_thread->lock_signal );
577         if( !pa_thread->b_signal )
578             vlc_cond_wait( &pa_thread->signal, &pa_thread->lock_signal );
579         vlc_mutex_unlock( &pa_thread->lock_signal );
580         pa_thread->b_signal = VLC_FALSE;
581
582         if( pa_thread->b_error ) continue;
583
584         i_err = Pa_StopStream( p_sys->p_stream );
585         if( i_err != paNoError )
586         {
587             msg_Err( p_aout, "Pa_StopStream: %d (%s)", i_err,
588                      Pa_GetErrorText( i_err ) );
589         }
590         i_err = Pa_CloseStream( p_sys->p_stream );
591         if( i_err != paNoError )
592         {
593             msg_Err( p_aout, "Pa_CloseStream: %d (%s)", i_err,
594                      Pa_GetErrorText( i_err ) );
595         }
596         i_err = Pa_Terminate();
597         if( i_err != paNoError )
598         {
599             msg_Err( p_aout, "Pa_Terminate: %d (%s)", i_err,
600                      Pa_GetErrorText( i_err ) );
601         }
602
603         /* Tell the main thread that we are ready */
604         vlc_mutex_lock( &pa_thread->lock_wait );
605         pa_thread->b_wait = VLC_TRUE;
606         vlc_cond_signal( &pa_thread->wait );
607         vlc_mutex_unlock( &pa_thread->lock_wait );
608     }
609 }
610 #endif