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 $
7 * Authors: Colin Delacroix <colin@zoy.org>
8 * Jon Lech Johansen <jon-vl@nanocrew.net>
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.
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.
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 *****************************************************************************/
25 /*****************************************************************************
27 *****************************************************************************/
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>
35 #include <videolan/vlc.h>
37 #include "audio_output.h" /* aout_thread_t */
39 #include <Carbon/Carbon.h>
40 #include <CoreAudio/AudioHardware.h>
41 #include <CoreAudio/HostTime.h>
42 #include <AudioToolbox/AudioConverter.h>
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
52 AudioDeviceID device; // the audio device
53 AudioConverterRef s_converter; // the AudioConverter
54 int b_format; // format begun
56 AudioStreamBasicDescription s_src_stream_format;
57 AudioStreamBasicDescription s_dst_stream_format;
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
67 /*****************************************************************************
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 );
77 static int CABeginFormat ( aout_thread_t *p_aout );
78 static int CAEndFormat ( aout_thread_t *p_aout );
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 );
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 )
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;
101 /*****************************************************************************
102 * aout_Open: opens a CoreAudio HAL device
103 *****************************************************************************/
104 static int aout_Open( aout_thread_t *p_aout )
107 UInt32 ui_param_size;
109 struct thread_time_constraint_policy ttcpolicy;
115 /* allocate instance */
116 p_aout->p_sys = malloc( sizeof( aout_sys_t ) );
117 if( p_aout->p_sys == NULL )
119 intf_ErrMsg( "aout error: %s", strerror(ENOMEM) );
123 /* initialize members */
124 memset( p_aout->p_sys, 0, sizeof( aout_sys_t ) );
126 /* get the default output device */
127 ui_param_size = sizeof( p_aout->p_sys->device );
128 err = AudioHardwareGetProperty( kAudioHardwarePropertyDefaultOutputDevice,
130 (void *)&p_aout->p_sys->device );
134 intf_ErrMsg( "aout error: failed to get the device: %d", err );
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,
143 &p_aout->p_sys->ui_buffer_size );
147 intf_ErrMsg( "aout error: failed to get device buffer size: %d", err );
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,
156 &p_aout->p_sys->s_dst_stream_format );
160 intf_ErrMsg( "aout error: failed to get dst stream format: %d", err );
164 if( p_aout->p_sys->s_dst_stream_format.mFormatID != kAudioFormatLinearPCM )
166 intf_ErrMsg( "aout error: kAudioFormatLinearPCM required" );
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 );
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 ) );
179 if( CABeginFormat( p_aout ) )
181 intf_ErrMsg( "aout error: CABeginFormat failed" );
185 /* Go to time-constrained thread policy */
189 mib[1] = HW_BUS_FREQ;
192 if( sysctl(mib, miblen, &i_busspeed, &len, NULL, 0) == -1 )
194 intf_ErrMsg("vout error: couldn't go to time-constrained policy (bus speed)");
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;
211 if (thread_policy_set(mach_thread_self(),
212 THREAD_TIME_CONSTRAINT_POLICY, (int *)&ttcpolicy,
213 THREAD_TIME_CONSTRAINT_POLICY_COUNT) != KERN_SUCCESS)
215 intf_ErrMsg("vout error: couldn't go to time-constrained policy (thread_policy_set)");
222 /*****************************************************************************
223 * aout_SetFormat: pretends to set the dsp output format
224 *****************************************************************************/
225 static int aout_SetFormat( aout_thread_t *p_aout )
227 if( CAEndFormat( p_aout ) )
229 intf_ErrMsg( "aout error: CAEndFormat failed" );
233 switch( p_aout->i_format )
236 intf_ErrMsg( "Audio format (Signed 8) not supported now,"
237 "please report stream" );
241 intf_ErrMsg( "Audio format (Unsigned 8) not supported now,"
242 "please report stream" );
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;
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;
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;
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;
274 intf_ErrMsg( "Audio format (0x%08x) not supported now,"
275 "please report stream", p_aout->i_format );
279 /* source format is not float */
280 p_aout->p_sys->s_src_stream_format.mFormatFlags &=
281 ~kLinearPCMFormatFlagIsFloat;
283 /* if destination format is float, take size diff into account */
284 if( p_aout->p_sys->s_dst_stream_format.mFormatFlags &
285 kLinearPCMFormatFlagIsFloat )
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;
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;
299 if( CABeginFormat( p_aout ) )
301 intf_ErrMsg( "aout error: CABeginFormat failed" );
308 /*****************************************************************************
309 * aout_GetBufInfo: returns available bytes in buffer
310 *****************************************************************************/
311 static int aout_GetBufInfo( aout_thread_t *p_aout, int i_buffer_limit )
313 return( 0 ); /* send data as soon as possible */
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 )
327 aout_thread_t *p_aout = (aout_thread_t *)threadGlobals;
328 aout_sys_t *p_sys = p_aout->p_sys;
330 AudioTimeStamp host_time;
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;
337 /* move data into output data buffer */
338 if( p_sys->b_buffer_data )
340 BlockMoveData( p_sys->p_buffer,
341 outOutputData->mBuffers[ 0 ].mData,
342 p_sys->ui_buffer_size );
346 memset(outOutputData->mBuffers[ 0 ].mData, 0, p_sys->ui_buffer_size);
347 intf_WarnMsg(1, "aout warning: audio output is starving, expect glitches");
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 );
359 /*****************************************************************************
360 * aout_Play: plays a sound
361 *****************************************************************************/
362 static void aout_Play( aout_thread_t *p_aout, byte_t *buffer, int i_size )
365 UInt32 ui_buffer_size = p_aout->p_sys->ui_buffer_size;
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.
371 vlc_mutex_lock( &p_aout->p_sys->mutex_lock );
372 if ( p_aout->p_sys->b_buffer_data )
374 vlc_cond_wait( &p_aout->p_sys->cond_sync, &p_aout->p_sys->mutex_lock );
376 vlc_mutex_unlock( &p_aout->p_sys->mutex_lock );
378 err = AudioConverterConvertBuffer( p_aout->p_sys->s_converter,
381 p_aout->p_sys->p_buffer );
385 intf_ErrMsg( "aout error: ConvertBuffer failed: %d", err );
389 p_aout->p_sys->b_buffer_data = 1;
393 /*****************************************************************************
394 * aout_Close: closes the CoreAudio HAL device
395 *****************************************************************************/
396 static void aout_Close( aout_thread_t *p_aout )
398 if( CAEndFormat( p_aout ) )
400 intf_ErrMsg( "aout error: CAEndFormat failed" );
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 );
407 free( p_aout->p_sys );
410 /*****************************************************************************
411 * CABeginFormat: creates an AudioConverter
412 *****************************************************************************/
413 static int CABeginFormat( aout_thread_t *p_aout )
416 UInt32 ui_param_size;
418 if( p_aout->p_sys->b_format )
420 intf_ErrMsg( "aout error: CABeginFormat (b_format)" );
424 p_aout->p_sys->ui_buffer_size = 2 * 2 * sizeof(s16) *
425 ((s64)p_aout->i_rate * AOUT_BUFFER_DURATION) / 1000000;
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,
432 &p_aout->p_sys->ui_buffer_size );
433 //p_aout->i_latency = p_aout->p_sys->ui_buffer_size / 2;
437 intf_ErrMsg( "aout error: AudioDeviceSetProperty failed: %d", err );
441 /* allocate audio buffer */
442 p_aout->p_sys->p_buffer = NewPtrClear( p_aout->p_sys->ui_buffer_size );
444 if( p_aout->p_sys->p_buffer == nil )
446 intf_ErrMsg( "aout error: failed to allocate audio buffer" );
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 );
457 intf_ErrMsg( "aout error: AudioConverterNew failed: %d", err );
458 DisposePtr( p_aout->p_sys->p_buffer );
463 err = AudioDeviceAddIOProc( p_aout->p_sys->device,
464 (AudioDeviceIOProc)CAIOCallback,
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 );
475 /* open the output */
476 err = AudioDeviceStart( p_aout->p_sys->device,
477 (AudioDeviceIOProc)CAIOCallback );
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 );
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
493 p_aout->p_sys->b_format = 1;
498 /*****************************************************************************
499 * CAEndFormat: destroys the AudioConverter
500 *****************************************************************************/
501 static int CAEndFormat( aout_thread_t *p_aout )
505 if( !p_aout->p_sys->b_format )
507 intf_ErrMsg( "aout error: CAEndFormat (!b_format)" );
511 /* stop playing sound through the device */
512 err = AudioDeviceStop( p_aout->p_sys->device,
513 (AudioDeviceIOProc)CAIOCallback );
517 intf_ErrMsg( "aout error: AudioDeviceStop failed: %d", err );
521 /* remove the callback */
522 err = AudioDeviceRemoveIOProc( p_aout->p_sys->device,
523 (AudioDeviceIOProc)CAIOCallback );
527 intf_ErrMsg( "aout error: AudioDeviceRemoveIOProc failed: %d", err );
531 /* destroy the AudioConverter */
532 err = AudioConverterDispose( p_aout->p_sys->s_converter );
536 intf_ErrMsg( "aout error: AudioConverterDispose failed: %d", err );
540 /* release audio buffer */
541 DisposePtr( p_aout->p_sys->p_buffer );
543 p_aout->p_sys->b_format = 0;