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