]> git.sesse.net Git - vlc/blob - plugins/macosx/aout_macosx.m
* ALL: new module API. Makes a few things a lot simpler, and we gain
[vlc] / plugins / macosx / aout_macosx.m
1 /*****************************************************************************
2  * aout_macosx.m: CoreAudio output plugin
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: aout_macosx.m,v 1.9 2002/07/31 20:56:52 sam Exp $
6  *
7  * Authors: Colin Delacroix <colin@zoy.org>
8  *          Jon Lech Johansen <jon-vl@nanocrew.net>
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
30 #include <vlc/vlc.h>
31 #include <vlc/aout.h>
32
33 #include <Carbon/Carbon.h>
34 #include <CoreAudio/AudioHardware.h>
35 #include <CoreAudio/HostTime.h>
36 #include <AudioToolbox/AudioConverter.h>
37
38 /*****************************************************************************
39  * aout_sys_t: private audio output method descriptor
40  *****************************************************************************
41  * This structure is part of the audio output thread descriptor.
42  * It describes the CoreAudio specific properties of an output thread.
43  *****************************************************************************/
44 struct aout_sys_t
45 {
46     AudioDeviceID       device;         // the audio device
47     AudioConverterRef   s_converter;    // the AudioConverter
48     int                 b_format;       // format begun 
49
50     AudioStreamBasicDescription s_src_stream_format;
51     AudioStreamBasicDescription s_dst_stream_format;
52
53     Ptr                 p_buffer;       // ptr to the 32 bit float data
54     UInt32              ui_buffer_size; // audio device buffer size
55     vlc_bool_t          b_buffer_data;  // available buffer data?
56     vlc_mutex_t         mutex_lock;     // pthread locks for sync of
57     vlc_cond_t          cond_sync;      // Play and callback
58     mtime_t             clock_diff;     // diff between system clock & audio
59 };
60
61 /*****************************************************************************
62  * Local prototypes.
63  *****************************************************************************/
64 static int      SetFormat       ( aout_thread_t * );
65 static int      GetBufInfo      ( aout_thread_t *, int );
66 static void     Play            ( aout_thread_t *, byte_t *, int );
67
68 static int      CABeginFormat   ( aout_thread_t * );
69 static int      CAEndFormat     ( aout_thread_t * );
70
71 static OSStatus CAIOCallback    ( AudioDeviceID inDevice,
72                                   const AudioTimeStamp *inNow, 
73                                   const void *inInputData, 
74                                   const AudioTimeStamp *inInputTime,
75                                   AudioBufferList *outOutputData, 
76                                   const AudioTimeStamp *inOutputTime, 
77                                   void *threadGlobals );
78
79 /*****************************************************************************
80  * OpenAudio: opens a CoreAudio HAL device
81  *****************************************************************************/
82 int E_(OpenAudio) ( vlc_object_t *p_this )
83 {
84     aout_thread_t * p_aout = (aout_thread_t *)p_this;
85     OSStatus err;
86     UInt32 ui_param_size;
87
88     /* allocate instance */
89     p_aout->p_sys = malloc( sizeof( aout_sys_t ) );
90     if( p_aout->p_sys == NULL )
91     {
92         msg_Err( p_aout, "out of memory" );
93         return( 1 );
94     }
95
96     /* initialize members */
97     memset( p_aout->p_sys, 0, sizeof( aout_sys_t ) );
98
99     /* get the default output device */
100     ui_param_size = sizeof( p_aout->p_sys->device );
101     err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice,
102                                     &ui_param_size, 
103                                     (void *)&p_aout->p_sys->device );
104
105     if( err != noErr ) 
106     {
107         msg_Err( p_aout, "failed to get the device: %d", err );
108         return( -1 );
109     }
110
111     /* get the buffer size that the device uses for IO */
112     ui_param_size = sizeof( p_aout->p_sys->ui_buffer_size );
113     err = AudioDeviceGetProperty( p_aout->p_sys->device, 0, false, 
114                                   kAudioDevicePropertyBufferSize, 
115                                   &ui_param_size,
116                                   &p_aout->p_sys->ui_buffer_size );
117
118     if( err != noErr )
119     {
120         msg_Err( p_aout, "failed to get device buffer size: %d", err );
121         return( -1 );
122     }
123
124     /* get a description of the data format used by the device */
125     ui_param_size = sizeof( p_aout->p_sys->s_dst_stream_format ); 
126     err = AudioDeviceGetProperty( p_aout->p_sys->device, 0, false, 
127                                   kAudioDevicePropertyStreamFormat, 
128                                   &ui_param_size,
129                                   &p_aout->p_sys->s_dst_stream_format );
130
131     if( err != noErr )
132     {
133         msg_Err( p_aout, "failed to get dst stream format: %d", err );
134         return( -1 );
135     }
136
137     if( p_aout->p_sys->s_dst_stream_format.mFormatID != kAudioFormatLinearPCM )
138     {
139         msg_Err( p_aout, "kAudioFormatLinearPCM required" );
140         return( -1 );
141     }
142
143     /* initialize mutex and cond */
144     vlc_mutex_init( p_aout, &p_aout->p_sys->mutex_lock );
145     vlc_cond_init( p_aout, &p_aout->p_sys->cond_sync );
146
147     /* initialize source stream format */
148     memcpy( &p_aout->p_sys->s_src_stream_format,
149             &p_aout->p_sys->s_dst_stream_format,
150             sizeof( p_aout->p_sys->s_src_stream_format ) );
151
152     if( CABeginFormat( p_aout ) )
153     {
154         msg_Err( p_aout, "CABeginFormat failed" );
155         return( -1 );
156     }
157
158     p_aout->pf_setformat = SetFormat;
159     p_aout->pf_getbufinfo = GetBufInfo;
160     p_aout->pf_play = Play;
161
162     return( 0 );
163 }
164
165 /*****************************************************************************
166  * SetFormat: pretends to set the dsp output format
167  *****************************************************************************/
168 static int SetFormat( aout_thread_t *p_aout )
169 {
170     if( CAEndFormat( p_aout ) )
171     {
172         msg_Err( p_aout, "CAEndFormat failed" );
173         return( -1 );
174     }
175
176     switch( p_aout->i_format )
177     {
178         case AOUT_FMT_S8:
179             msg_Err( p_aout,
180                      "Signed 8 not supported yet, please report stream" );
181             return( -1 );
182                     
183         case AOUT_FMT_U8:
184             msg_Err( p_aout,
185                      "Unsigned 8 not supported yet, please report stream" );
186             return( -1 );
187
188         case AOUT_FMT_S16_LE:
189             p_aout->p_sys->s_src_stream_format.mFormatFlags &=
190                 ~kLinearPCMFormatFlagIsBigEndian;
191             p_aout->p_sys->s_src_stream_format.mFormatFlags |=
192                 kLinearPCMFormatFlagIsSignedInteger;
193             break;
194
195         case AOUT_FMT_S16_BE:
196             p_aout->p_sys->s_src_stream_format.mFormatFlags |=
197                 kLinearPCMFormatFlagIsBigEndian;
198             p_aout->p_sys->s_src_stream_format.mFormatFlags |=
199                 kLinearPCMFormatFlagIsSignedInteger;
200             break;
201
202         case AOUT_FMT_U16_LE:
203             p_aout->p_sys->s_src_stream_format.mFormatFlags &=
204                 ~kLinearPCMFormatFlagIsBigEndian;
205             p_aout->p_sys->s_src_stream_format.mFormatFlags &=
206                 ~kLinearPCMFormatFlagIsSignedInteger;
207             break;
208                     
209         case AOUT_FMT_U16_BE:
210             p_aout->p_sys->s_src_stream_format.mFormatFlags |=
211                 kLinearPCMFormatFlagIsBigEndian;
212             p_aout->p_sys->s_src_stream_format.mFormatFlags &=
213                 ~kLinearPCMFormatFlagIsSignedInteger;
214             break;
215                     
216         default:
217             msg_Err( p_aout, "audio format (0x%08x) not supported now,"
218                              "please report stream", p_aout->i_format );
219             return( -1 );
220     }
221
222     /* source format is not float */
223     p_aout->p_sys->s_src_stream_format.mFormatFlags &=
224         ~kLinearPCMFormatFlagIsFloat;
225
226     /* if destination format is float, take size diff into account */
227     if( p_aout->p_sys->s_dst_stream_format.mFormatFlags & 
228         kLinearPCMFormatFlagIsFloat )
229     {
230         p_aout->p_sys->s_src_stream_format.mBytesPerPacket =
231             p_aout->p_sys->s_dst_stream_format.mBytesPerPacket / 2;
232         p_aout->p_sys->s_src_stream_format.mBytesPerFrame =
233             p_aout->p_sys->s_src_stream_format.mBytesPerFrame / 2;
234         p_aout->p_sys->s_src_stream_format.mBitsPerChannel =
235             p_aout->p_sys->s_src_stream_format.mBitsPerChannel / 2;
236     }
237
238     /* set sample rate and channels per frame */
239     p_aout->p_sys->s_src_stream_format.mSampleRate = p_aout->i_rate; 
240     p_aout->p_sys->s_src_stream_format.mChannelsPerFrame = p_aout->i_channels;
241
242     if( CABeginFormat( p_aout ) )
243     {
244         msg_Err( p_aout, "CABeginFormat failed" );
245         return( -1 );
246     }
247
248     return( 0 );
249 }
250
251 /*****************************************************************************
252  * GetBufInfo: returns available bytes in buffer
253  *****************************************************************************/
254 static int GetBufInfo( aout_thread_t *p_aout, int i_buffer_limit )
255 {
256     return( 0 ); /* send data as soon as possible */
257 }
258
259 /*****************************************************************************
260  * CAIOCallback : callback for audio output
261  *****************************************************************************/
262 static OSStatus CAIOCallback( AudioDeviceID inDevice,
263                               const AudioTimeStamp *inNow, 
264                               const void *inInputData,
265                               const AudioTimeStamp *inInputTime, 
266                               AudioBufferList *outOutputData,
267                               const AudioTimeStamp *inOutputTime, 
268                               void *threadGlobals )
269 {
270     aout_thread_t *p_aout = (aout_thread_t *)threadGlobals;
271     aout_sys_t *p_sys = p_aout->p_sys;
272
273     AudioTimeStamp host_time;
274
275     host_time.mFlags = kAudioTimeStampHostTimeValid;
276     AudioDeviceTranslateTime( inDevice, inOutputTime, &host_time );
277     //intf_Msg( "%lld", AudioConvertHostTimeToNanos(host_time.mHostTime) / 1000 + p_aout->p_sys->clock_diff - p_aout->date );
278     p_aout->date = p_aout->p_sys->clock_diff + AudioConvertHostTimeToNanos(host_time.mHostTime) / 1000;
279
280     /* move data into output data buffer */
281     if( p_sys->b_buffer_data )
282     {
283         BlockMoveData( p_sys->p_buffer,
284                        outOutputData->mBuffers[ 0 ].mData, 
285                        p_sys->ui_buffer_size );
286     }
287     else
288     {
289         memset(outOutputData->mBuffers[ 0 ].mData, 0, p_sys->ui_buffer_size);
290 //X        msg_Warn( p_aout, "audio output is starving, expect glitches" );
291     }
292
293     /* see Play below */
294     vlc_mutex_lock( &p_sys->mutex_lock );
295     p_sys->b_buffer_data = 0;
296     vlc_cond_signal( &p_sys->cond_sync );
297     vlc_mutex_unlock( &p_sys->mutex_lock );
298
299     return( noErr );     
300 }
301
302 /*****************************************************************************
303  * Play: play a sound
304  *****************************************************************************/
305 static void Play( aout_thread_t *p_aout, byte_t *buffer, int i_size )
306 {
307     OSStatus err;
308     UInt32 ui_buffer_size = p_aout->p_sys->ui_buffer_size;
309
310     /* 
311      * wait for a callback to occur (to flush the buffer), so Play
312      * can't be called twice, losing the data we just wrote. 
313      */
314     vlc_mutex_lock( &p_aout->p_sys->mutex_lock );
315     if ( p_aout->p_sys->b_buffer_data )
316     {
317         vlc_cond_wait( &p_aout->p_sys->cond_sync, &p_aout->p_sys->mutex_lock );
318     }
319     vlc_mutex_unlock( &p_aout->p_sys->mutex_lock );
320
321     err = AudioConverterConvertBuffer( p_aout->p_sys->s_converter,
322                                        i_size, buffer,
323                                        &ui_buffer_size,
324                                        p_aout->p_sys->p_buffer );
325
326     if( err != noErr )
327     {
328         msg_Err( p_aout, "ConvertBuffer failed: %d", err );
329     }
330     else
331     {
332         p_aout->p_sys->b_buffer_data = 1;
333     }
334 }
335
336 /*****************************************************************************
337  * CloseAudio: closes the CoreAudio HAL device
338  *****************************************************************************/
339 void E_(CloseAudio) ( vlc_object_t *p_this )
340 {
341     aout_thread_t * p_aout = (aout_thread_t *)p_this;
342
343     if( CAEndFormat( p_aout ) )
344     {
345         msg_Err( p_aout, "CAEndFormat failed" );
346     }
347
348     /* destroy lock and cond */
349     vlc_mutex_destroy( &p_aout->p_sys->mutex_lock );
350     vlc_cond_destroy( &p_aout->p_sys->cond_sync );
351
352     free( p_aout->p_sys );
353 }
354
355 /*****************************************************************************
356  * CABeginFormat: creates an AudioConverter 
357  *****************************************************************************/
358 static int CABeginFormat( aout_thread_t *p_aout )
359 {
360     OSStatus err;
361     UInt32 ui_param_size;
362
363     if( p_aout->p_sys->b_format )
364     {
365         msg_Err( p_aout, "CABeginFormat (b_format)" );
366         return( 1 );
367     }
368
369     p_aout->p_sys->ui_buffer_size = 2 * 2 * sizeof(s16) * 
370         ((s64)p_aout->i_rate * AOUT_BUFFER_DURATION) / 1000000; 
371
372     /* set the buffer size that the device uses for IO */
373     ui_param_size = sizeof( p_aout->p_sys->ui_buffer_size );
374     err = AudioDeviceSetProperty( p_aout->p_sys->device, 0, 0, false, 
375                                   kAudioDevicePropertyBufferSize, 
376                                   ui_param_size,
377                                   &p_aout->p_sys->ui_buffer_size );
378     //p_aout->i_latency = p_aout->p_sys->ui_buffer_size / 2;
379
380     if( err != noErr )
381     {
382         msg_Err( p_aout, "AudioDeviceSetProperty failed: %d", err );
383         return( 1 );
384     }
385
386     /* allocate audio buffer */ 
387     p_aout->p_sys->p_buffer = NewPtrClear( p_aout->p_sys->ui_buffer_size );
388
389     if( p_aout->p_sys->p_buffer == nil )
390     {
391         msg_Err( p_aout, "failed to allocate audio buffer" );
392         return( 1 );
393     }
394
395     /* create a new AudioConverter */
396     err = AudioConverterNew( &p_aout->p_sys->s_src_stream_format,
397                              &p_aout->p_sys->s_dst_stream_format,
398                              &p_aout->p_sys->s_converter );
399
400     if( err != noErr )
401     {
402         msg_Err( p_aout, "AudioConverterNew failed: %d", err );
403         DisposePtr( p_aout->p_sys->p_buffer );
404         return( 1 );
405     }
406
407     /* add callback */
408     err = AudioDeviceAddIOProc( p_aout->p_sys->device, 
409                                 (AudioDeviceIOProc)CAIOCallback, 
410                                 (void *)p_aout );
411
412     if( err != noErr )
413     {
414         msg_Err( p_aout, "AudioDeviceAddIOProc failed: %d", err );
415         AudioConverterDispose( p_aout->p_sys->s_converter );
416         DisposePtr( p_aout->p_sys->p_buffer );
417         return( 1 );
418     } 
419
420     /* open the output */
421     err = AudioDeviceStart( p_aout->p_sys->device,
422                             (AudioDeviceIOProc)CAIOCallback );
423
424     if( err != noErr )
425     {
426         msg_Err( p_aout, "AudioDeviceStart failed: %d", err );
427         AudioConverterDispose( p_aout->p_sys->s_converter );
428         DisposePtr( p_aout->p_sys->p_buffer );
429         return( 1 );
430     }
431
432     /* Let's pray for the following operation to be atomic... */
433     p_aout->p_sys->clock_diff = mdate()
434          - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) / 1000
435          + (mtime_t)p_aout->p_sys->ui_buffer_size / 4 * 1000000 / (mtime_t)p_aout->i_rate
436          + p_aout->p_vlc->i_desync;
437
438     p_aout->p_sys->b_format = 1;
439
440     return( 0 );
441 }
442
443 /*****************************************************************************
444  * CAEndFormat: destroys the AudioConverter 
445  *****************************************************************************/
446 static int CAEndFormat( aout_thread_t *p_aout )
447 {
448     OSStatus err; 
449
450     if( !p_aout->p_sys->b_format )
451     {
452         msg_Err( p_aout, "CAEndFormat (!b_format)" );
453         return( 1 );
454     }
455
456     /* stop playing sound through the device */
457     err = AudioDeviceStop( p_aout->p_sys->device,
458                            (AudioDeviceIOProc)CAIOCallback ); 
459
460     if( err != noErr )
461     {
462         msg_Err( p_aout, "AudioDeviceStop failed: %d", err );
463         return( 1 );
464     }
465
466     /* remove the callback */
467     err = AudioDeviceRemoveIOProc( p_aout->p_sys->device,
468                                    (AudioDeviceIOProc)CAIOCallback ); 
469
470     if( err != noErr )
471     {
472         msg_Err( p_aout, "AudioDeviceRemoveIOProc failed: %d", err );
473         return( 1 );
474     }
475
476     /* destroy the AudioConverter */
477     err = AudioConverterDispose( p_aout->p_sys->s_converter );
478
479     if( err != noErr )
480     {
481         msg_Err( p_aout, "AudioConverterDispose failed: %d", err );
482         return( 1 );
483     }
484
485     /* release audio buffer */
486     DisposePtr( p_aout->p_sys->p_buffer );
487
488     p_aout->p_sys->b_format = 0;
489
490     return( 0 );
491 }