]> git.sesse.net Git - vlc/blob - plugins/macosx/aout_macosx.m
* Desperate attempt to improve audio output ;
[vlc] / plugins / macosx / aout_macosx.m
1 /*****************************************************************************
2  * aout_macosx.c : CoreAudio output plugin
3  *****************************************************************************
4  * Copyright (C) 2001 VideoLAN
5  * $Id: aout_macosx.m,v 1.2 2002/05/19 00:34:54 massiot 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 #include <mach/mach_init.h>
30 #include <mach/task_policy.h>
31 #include <mach/thread_act.h>
32 #include <mach/thread_policy.h>
33 #include <sys/sysctl.h>
34
35 #include <videolan/vlc.h>
36
37 #include "audio_output.h"                                   /* aout_thread_t */
38
39 #include <Carbon/Carbon.h>
40 #include <CoreAudio/AudioHardware.h>
41 #include <CoreAudio/HostTime.h>
42 #include <AudioToolbox/AudioConverter.h>
43
44 /*****************************************************************************
45  * aout_sys_t: private audio output method descriptor
46  *****************************************************************************
47  * This structure is part of the audio output thread descriptor.
48  * It describes the CoreAudio specific properties of an output thread.
49  *****************************************************************************/
50 typedef struct aout_sys_s
51 {
52     AudioDeviceID       device;         // the audio device
53     AudioConverterRef   s_converter;    // the AudioConverter
54     int                 b_format;       // format begun 
55
56     AudioStreamBasicDescription s_src_stream_format;
57     AudioStreamBasicDescription s_dst_stream_format;
58
59     Ptr                 p_buffer;       // ptr to the 32 bit float data
60     UInt32              ui_buffer_size; // audio device buffer size
61     boolean_t           b_buffer_data;  // available buffer data?
62     vlc_mutex_t         mutex_lock;     // pthread locks for sync of
63     vlc_cond_t          cond_sync;      // aout_Play and callback
64     mtime_t             clock_diff;     // diff between system clock & audio
65 } aout_sys_t;
66
67 /*****************************************************************************
68  * Local prototypes.
69  *****************************************************************************/
70 static int      aout_Open       ( aout_thread_t *p_aout );
71 static int      aout_SetFormat  ( aout_thread_t *p_aout );
72 static int      aout_GetBufInfo ( aout_thread_t *p_aout, int i_buffer_info );
73 static void     aout_Play       ( aout_thread_t *p_aout,
74                                   byte_t *buffer, int i_size );
75 static void     aout_Close      ( aout_thread_t *p_aout );
76
77 static int      CABeginFormat   ( aout_thread_t *p_aout );
78 static int      CAEndFormat     ( aout_thread_t *p_aout );
79
80 static OSStatus CAIOCallback    ( AudioDeviceID inDevice,
81                                   const AudioTimeStamp *inNow, 
82                                   const void *inInputData, 
83                                   const AudioTimeStamp *inInputTime,
84                                   AudioBufferList *outOutputData, 
85                                   const AudioTimeStamp *inOutputTime, 
86                                   void *threadGlobals );
87
88 /*****************************************************************************
89  * Functions exported as capabilities. They are declared as static so that
90  * we don't pollute the namespace too much.
91  *****************************************************************************/
92 void _M( aout_getfunctions )( function_list_t * p_function_list )
93 {
94     p_function_list->functions.aout.pf_open = aout_Open;
95     p_function_list->functions.aout.pf_setformat = aout_SetFormat;
96     p_function_list->functions.aout.pf_getbufinfo = aout_GetBufInfo;
97     p_function_list->functions.aout.pf_play = aout_Play;
98     p_function_list->functions.aout.pf_close = aout_Close;
99 }
100
101 /*****************************************************************************
102  * aout_Open: opens a CoreAudio HAL device
103  *****************************************************************************/
104 static int aout_Open( aout_thread_t *p_aout )
105 {
106     OSStatus err;
107     UInt32 ui_param_size;
108
109     struct thread_time_constraint_policy ttcpolicy;
110     int mib[2];
111     unsigned int miblen;
112     int i_busspeed;
113     size_t len;
114
115     /* allocate instance */
116     p_aout->p_sys = malloc( sizeof( aout_sys_t ) );
117     if( p_aout->p_sys == NULL )
118     {
119         intf_ErrMsg( "aout error: %s", strerror(ENOMEM) );
120         return( 1 );
121     }
122
123     /* initialize members */
124     memset( p_aout->p_sys, 0, sizeof( aout_sys_t ) );
125
126     /* get the default output device */
127     ui_param_size = sizeof( p_aout->p_sys->device );
128     err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice,
129                                     &ui_param_size, 
130                                     (void *)&p_aout->p_sys->device );
131
132     if( err != noErr ) 
133     {
134         intf_ErrMsg( "aout error: failed to get the device: %d", err );
135         return( -1 );
136     }
137
138     /* get the buffer size that the device uses for IO */
139     ui_param_size = sizeof( p_aout->p_sys->ui_buffer_size );
140     err = AudioDeviceGetProperty( p_aout->p_sys->device, 0, false, 
141                                   kAudioDevicePropertyBufferSize, 
142                                   &ui_param_size,
143                                   &p_aout->p_sys->ui_buffer_size );
144
145     if( err != noErr )
146     {
147         intf_ErrMsg( "aout error: failed to get device buffer size: %d", err );
148         return( -1 );
149     }
150
151     /* get a description of the data format used by the device */
152     ui_param_size = sizeof( p_aout->p_sys->s_dst_stream_format ); 
153     err = AudioDeviceGetProperty( p_aout->p_sys->device, 0, false, 
154                                   kAudioDevicePropertyStreamFormat, 
155                                   &ui_param_size,
156                                   &p_aout->p_sys->s_dst_stream_format );
157
158     if( err != noErr )
159     {
160         intf_ErrMsg( "aout error: failed to get dst stream format: %d", err );
161         return( -1 );
162     }
163
164     if( p_aout->p_sys->s_dst_stream_format.mFormatID != kAudioFormatLinearPCM )
165     {
166         intf_ErrMsg( "aout error: kAudioFormatLinearPCM required" );
167         return( -1 );
168     }
169
170     /* initialize mutex and cond */
171     vlc_mutex_init( &p_aout->p_sys->mutex_lock );
172     vlc_cond_init( &p_aout->p_sys->cond_sync );
173
174     /* initialize source stream format */
175     memcpy( &p_aout->p_sys->s_src_stream_format,
176             &p_aout->p_sys->s_dst_stream_format,
177             sizeof( p_aout->p_sys->s_src_stream_format ) );
178
179     if( CABeginFormat( p_aout ) )
180     {
181         intf_ErrMsg( "aout error: CABeginFormat failed" );
182         return( -1 );
183     }
184
185     /* Go to time-constrained thread policy */
186
187     /* Get bus speed */
188     mib[0] = CTL_HW;
189     mib[1] = HW_BUS_FREQ;
190     miblen = 2;
191     len = 4;
192     if( sysctl(mib, miblen, &i_busspeed, &len, NULL, 0) == -1 )
193     {
194         intf_ErrMsg("vout error: couldn't go to time-constrained policy (bus speed)");
195     }
196     else
197     {
198         /* This is in AbsoluteTime units, which are equal to
199          * 1/4 the bus speed on most machines. */
200         /* FIXME : these are random numbers ! */
201         /* hard-coded numbers are approximations for 100 MHz bus speed.
202          * assume that app deals in frame-sized chunks, e.g. 30 per second.
203          * ttcpolicy.period = 833333; */
204         ttcpolicy.period = i_busspeed / 120;
205         /* ttcpolicy.computation = 60000; */
206         ttcpolicy.computation = i_busspeed / 1440;
207         /* ttcpolicy.constraint = 120000; */
208         ttcpolicy.constraint = i_busspeed / 720;
209         ttcpolicy.preemptible = 1;
210
211         if (thread_policy_set(mach_thread_self(),
212                   THREAD_TIME_CONSTRAINT_POLICY, (int *)&ttcpolicy,
213                   THREAD_TIME_CONSTRAINT_POLICY_COUNT) != KERN_SUCCESS)
214         {
215             intf_ErrMsg("vout error: couldn't go to time-constrained policy (thread_policy_set)");
216         }
217     }
218
219     return( 0 );
220 }
221
222 /*****************************************************************************
223  * aout_SetFormat: pretends to set the dsp output format
224  *****************************************************************************/
225 static int aout_SetFormat( aout_thread_t *p_aout )
226 {
227     if( CAEndFormat( p_aout ) )
228     {
229         intf_ErrMsg( "aout error: CAEndFormat failed" );
230         return( -1 );
231     }
232
233     switch( p_aout->i_format )
234     {
235         case AOUT_FMT_S8:
236             intf_ErrMsg( "Audio format (Signed 8) not supported now,"
237                          "please report stream" );
238             return( -1 );
239                     
240         case AOUT_FMT_U8:
241             intf_ErrMsg( "Audio format (Unsigned 8) not supported now,"
242                          "please report stream" );
243             return( -1 );
244
245         case AOUT_FMT_S16_LE:
246             p_aout->p_sys->s_src_stream_format.mFormatFlags &=
247                 ~kLinearPCMFormatFlagIsBigEndian;
248             p_aout->p_sys->s_src_stream_format.mFormatFlags |=
249                 kLinearPCMFormatFlagIsSignedInteger;
250             break;
251
252         case AOUT_FMT_S16_BE:
253             p_aout->p_sys->s_src_stream_format.mFormatFlags |=
254                 kLinearPCMFormatFlagIsBigEndian;
255             p_aout->p_sys->s_src_stream_format.mFormatFlags |=
256                 kLinearPCMFormatFlagIsSignedInteger;
257             break;
258
259         case AOUT_FMT_U16_LE:
260             p_aout->p_sys->s_src_stream_format.mFormatFlags &=
261                 ~kLinearPCMFormatFlagIsBigEndian;
262             p_aout->p_sys->s_src_stream_format.mFormatFlags &=
263                 ~kLinearPCMFormatFlagIsSignedInteger;
264             break;
265                     
266         case AOUT_FMT_U16_BE:
267             p_aout->p_sys->s_src_stream_format.mFormatFlags |=
268                 kLinearPCMFormatFlagIsBigEndian;
269             p_aout->p_sys->s_src_stream_format.mFormatFlags &=
270                 ~kLinearPCMFormatFlagIsSignedInteger;
271             break;
272                     
273         default:
274             intf_ErrMsg( "Audio format (0x%08x) not supported now,"
275                          "please report stream", p_aout->i_format );
276             return( -1 );
277     }
278
279     /* source format is not float */
280     p_aout->p_sys->s_src_stream_format.mFormatFlags &=
281         ~kLinearPCMFormatFlagIsFloat;
282
283     /* if destination format is float, take size diff into account */
284     if( p_aout->p_sys->s_dst_stream_format.mFormatFlags & 
285         kLinearPCMFormatFlagIsFloat )
286     {
287         p_aout->p_sys->s_src_stream_format.mBytesPerPacket =
288             p_aout->p_sys->s_dst_stream_format.mBytesPerPacket / 2;
289         p_aout->p_sys->s_src_stream_format.mBytesPerFrame =
290             p_aout->p_sys->s_src_stream_format.mBytesPerFrame / 2;
291         p_aout->p_sys->s_src_stream_format.mBitsPerChannel =
292             p_aout->p_sys->s_src_stream_format.mBitsPerChannel / 2;
293     }
294
295     /* set sample rate and channels per frame */
296     p_aout->p_sys->s_src_stream_format.mSampleRate = p_aout->i_rate; 
297     p_aout->p_sys->s_src_stream_format.mChannelsPerFrame = p_aout->i_channels;
298
299     if( CABeginFormat( p_aout ) )
300     {
301         intf_ErrMsg( "aout error: CABeginFormat failed" );
302         return( -1 );
303     }
304
305     return( 0 );
306 }
307
308 /*****************************************************************************
309  * aout_GetBufInfo: returns available bytes in buffer
310  *****************************************************************************/
311 static int aout_GetBufInfo( aout_thread_t *p_aout, int i_buffer_limit )
312 {
313     return( 0 ); /* send data as soon as possible */
314 }
315
316 /*****************************************************************************
317  * CAIOCallback : callback for audio output
318  *****************************************************************************/
319 static OSStatus CAIOCallback( AudioDeviceID inDevice,
320                               const AudioTimeStamp *inNow, 
321                               const void *inInputData,
322                               const AudioTimeStamp *inInputTime, 
323                               AudioBufferList *outOutputData,
324                               const AudioTimeStamp *inOutputTime, 
325                               void *threadGlobals )
326 {
327     aout_thread_t *p_aout = (aout_thread_t *)threadGlobals;
328     aout_sys_t *p_sys = p_aout->p_sys;
329
330     AudioTimeStamp host_time;
331
332     host_time.mFlags = kAudioTimeStampHostTimeValid;
333     AudioDeviceTranslateTime( inDevice, inOutputTime, &host_time );
334     //intf_Msg( "%lld", AudioConvertHostTimeToNanos(host_time.mHostTime) / 1000 + p_aout->p_sys->clock_diff - p_aout->date );
335     p_aout->date = p_aout->p_sys->clock_diff + AudioConvertHostTimeToNanos(host_time.mHostTime) / 1000;
336
337     /* move data into output data buffer */
338     if( p_sys->b_buffer_data )
339     {
340         BlockMoveData( p_sys->p_buffer,
341                        outOutputData->mBuffers[ 0 ].mData, 
342                        p_sys->ui_buffer_size );
343     }
344     else
345     {
346         memset(outOutputData->mBuffers[ 0 ].mData, 0, p_sys->ui_buffer_size);
347         intf_WarnMsg(1, "aout warning: audio output is starving, expect glitches");
348     }
349
350     /* see aout_Play below */
351     vlc_mutex_lock( &p_sys->mutex_lock );
352     p_sys->b_buffer_data = 0;
353     vlc_cond_signal( &p_sys->cond_sync );
354     vlc_mutex_unlock( &p_sys->mutex_lock );
355
356     return( noErr );     
357 }
358
359 /*****************************************************************************
360  * aout_Play: plays a sound
361  *****************************************************************************/
362 static void aout_Play( aout_thread_t *p_aout, byte_t *buffer, int i_size )
363 {
364     OSStatus err;
365     UInt32 ui_buffer_size = p_aout->p_sys->ui_buffer_size;
366
367     /* 
368      * wait for a callback to occur (to flush the buffer), so aout_Play
369      * can't be called twice, losing the data we just wrote. 
370      */
371     vlc_mutex_lock( &p_aout->p_sys->mutex_lock );
372     if ( p_aout->p_sys->b_buffer_data )
373     {
374         vlc_cond_wait( &p_aout->p_sys->cond_sync, &p_aout->p_sys->mutex_lock );
375     }
376     vlc_mutex_unlock( &p_aout->p_sys->mutex_lock );
377
378     err = AudioConverterConvertBuffer( p_aout->p_sys->s_converter,
379                                        i_size, buffer,
380                                        &ui_buffer_size,
381                                        p_aout->p_sys->p_buffer );
382
383     if( err != noErr )
384     {
385         intf_ErrMsg( "aout error: ConvertBuffer failed: %d", err );
386     }
387     else
388     {
389         p_aout->p_sys->b_buffer_data = 1;
390     }
391 }
392
393 /*****************************************************************************
394  * aout_Close: closes the CoreAudio HAL device
395  *****************************************************************************/
396 static void aout_Close( aout_thread_t *p_aout )
397 {
398     if( CAEndFormat( p_aout ) )
399     {
400         intf_ErrMsg( "aout error: CAEndFormat failed" );
401     }
402
403     /* destroy lock and cond */
404     vlc_mutex_destroy( &p_aout->p_sys->mutex_lock );
405     vlc_cond_destroy( &p_aout->p_sys->cond_sync );
406
407     free( p_aout->p_sys );
408 }
409
410 /*****************************************************************************
411  * CABeginFormat: creates an AudioConverter 
412  *****************************************************************************/
413 static int CABeginFormat( aout_thread_t *p_aout )
414 {
415     OSStatus err;
416     UInt32 ui_param_size;
417
418     if( p_aout->p_sys->b_format )
419     {
420         intf_ErrMsg( "aout error: CABeginFormat (b_format)" );
421         return( 1 );
422     }
423
424     p_aout->p_sys->ui_buffer_size = 2 * 2 * sizeof(s16) * 
425         ((s64)p_aout->i_rate * AOUT_BUFFER_DURATION) / 1000000; 
426
427     /* set the buffer size that the device uses for IO */
428     ui_param_size = sizeof( p_aout->p_sys->ui_buffer_size );
429     err = AudioDeviceSetProperty( p_aout->p_sys->device, 0, 0, false, 
430                                   kAudioDevicePropertyBufferSize, 
431                                   ui_param_size,
432                                   &p_aout->p_sys->ui_buffer_size );
433     //p_aout->i_latency = p_aout->p_sys->ui_buffer_size / 2;
434
435     if( err != noErr )
436     {
437         intf_ErrMsg( "aout error: AudioDeviceSetProperty failed: %d", err );
438         return( 1 );
439     }
440
441     /* allocate audio buffer */ 
442     p_aout->p_sys->p_buffer = NewPtrClear( p_aout->p_sys->ui_buffer_size );
443
444     if( p_aout->p_sys->p_buffer == nil )
445     {
446         intf_ErrMsg( "aout error: failed to allocate audio buffer" );
447         return( 1 );
448     }
449
450     /* create a new AudioConverter */
451     err = AudioConverterNew( &p_aout->p_sys->s_src_stream_format,
452                              &p_aout->p_sys->s_dst_stream_format,
453                              &p_aout->p_sys->s_converter );
454
455     if( err != noErr )
456     {
457         intf_ErrMsg( "aout error: AudioConverterNew failed: %d", err );
458         DisposePtr( p_aout->p_sys->p_buffer );
459         return( 1 );
460     }
461
462     /* add callback */
463     err = AudioDeviceAddIOProc( p_aout->p_sys->device, 
464                                 (AudioDeviceIOProc)CAIOCallback, 
465                                 (void *)p_aout );
466
467     if( err != noErr )
468     {
469         intf_ErrMsg( "aout error: AudioDeviceAddIOProc failed: %d", err );
470         AudioConverterDispose( p_aout->p_sys->s_converter );
471         DisposePtr( p_aout->p_sys->p_buffer );
472         return( 1 );
473     } 
474
475     /* open the output */
476     err = AudioDeviceStart( p_aout->p_sys->device,
477                             (AudioDeviceIOProc)CAIOCallback );
478
479     if( err != noErr )
480     {
481         intf_ErrMsg( "aout error: AudioDeviceStart failed: %d", err );
482         AudioConverterDispose( p_aout->p_sys->s_converter );
483         DisposePtr( p_aout->p_sys->p_buffer );
484         return( 1 );
485     }
486
487     /* Let's pray for the following operation to be atomic... */
488     p_aout->p_sys->clock_diff = mdate()
489          - AudioConvertHostTimeToNanos(AudioGetCurrentHostTime()) / 1000
490          + (mtime_t)p_aout->p_sys->ui_buffer_size / 4 * 1000000 / (mtime_t)p_aout->i_rate
491          + p_main->i_desync;
492
493     p_aout->p_sys->b_format = 1;
494
495     return( 0 );
496 }
497
498 /*****************************************************************************
499  * CAEndFormat: destroys the AudioConverter 
500  *****************************************************************************/
501 static int CAEndFormat( aout_thread_t *p_aout )
502 {
503     OSStatus err; 
504
505     if( !p_aout->p_sys->b_format )
506     {
507         intf_ErrMsg( "aout error: CAEndFormat (!b_format)" );
508         return( 1 );
509     }
510
511     /* stop playing sound through the device */
512     err = AudioDeviceStop( p_aout->p_sys->device,
513                            (AudioDeviceIOProc)CAIOCallback ); 
514
515     if( err != noErr )
516     {
517         intf_ErrMsg( "aout error: AudioDeviceStop failed: %d", err );
518         return( 1 );
519     }
520
521     /* remove the callback */
522     err = AudioDeviceRemoveIOProc( p_aout->p_sys->device,
523                                    (AudioDeviceIOProc)CAIOCallback ); 
524
525     if( err != noErr )
526     {
527         intf_ErrMsg( "aout error: AudioDeviceRemoveIOProc failed: %d", err );
528         return( 1 );
529     }
530
531     /* destroy the AudioConverter */
532     err = AudioConverterDispose( p_aout->p_sys->s_converter );
533
534     if( err != noErr )
535     {
536         intf_ErrMsg( "aout error: AudioConverterDispose failed: %d", err );
537         return( 1 );
538     }
539
540     /* release audio buffer */
541     DisposePtr( p_aout->p_sys->p_buffer );
542
543     p_aout->p_sys->b_format = 0;
544
545     return( 0 );
546 }