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