1 /*****************************************************************************
2 * directx.c: Windows DirectX audio output method
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: directx.c,v 1.5 2002/10/28 22:31:49 gbazin Exp $
7 * Authors: Gildas Bazin <gbazin@netcourrier.com>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
27 #include <errno.h> /* ENOMEM */
28 #include <fcntl.h> /* open(), O_WRONLY */
29 #include <string.h> /* strerror() */
31 #include <stdlib.h> /* calloc(), malloc(), free() */
35 #include "aout_internal.h"
41 #define FRAME_SIZE 2048 /* The size is in samples, not in bytes */
43 /*****************************************************************************
45 * Defining them here allows us to get rid of the dxguid library during
47 *****************************************************************************/
49 DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16);
51 /*****************************************************************************
53 *****************************************************************************/
54 #ifndef WAVE_FORMAT_IEEE_FLOAT
55 # define WAVE_FORMAT_IEEE_FLOAT 0x0003
58 /*****************************************************************************
59 * notification_thread_t: DirectX event thread
60 *****************************************************************************/
61 typedef struct notification_thread_t
65 aout_instance_t * p_aout;
66 DSBPOSITIONNOTIFY p_events[2]; /* play notification events */
67 int i_buffer_size; /* Size in bytes of one frame */
69 } notification_thread_t;
71 /*****************************************************************************
72 * aout_sys_t: directx audio output method descriptor
73 *****************************************************************************
74 * This structure is part of the audio output thread descriptor.
75 * It describes the direct sound specific properties of an audio device.
76 *****************************************************************************/
79 LPDIRECTSOUND p_dsobject; /* main Direct Sound object */
81 LPDIRECTSOUNDBUFFER p_dsbuffer_primary; /* the actual sound card buffer
82 (not used directly) */
84 LPDIRECTSOUNDBUFFER p_dsbuffer; /* the sound buffer we use (direct sound
85 * takes care of mixing all the
86 * secondary buffers into the primary) */
88 LPDIRECTSOUNDNOTIFY p_dsnotify; /* the position notify interface */
90 HINSTANCE hdsound_dll; /* handle of the opened dsound dll */
92 notification_thread_t * p_notif; /* DirectSoundThread id */
96 /*****************************************************************************
98 *****************************************************************************/
99 static int OpenAudio ( vlc_object_t * );
100 static void CloseAudio ( vlc_object_t * );
102 static void Play ( aout_instance_t * );
104 /* local functions */
105 static int DirectxCreateSecondaryBuffer ( aout_instance_t * );
106 static void DirectxDestroySecondaryBuffer( aout_instance_t * );
107 static int DirectxInitDSound ( aout_instance_t * );
108 static void DirectSoundThread ( notification_thread_t * );
110 /*****************************************************************************
112 *****************************************************************************/
114 set_description( _("DirectX audio module") );
115 set_capability( "audio output", 100 );
116 add_shortcut( "directx" );
117 set_callbacks( OpenAudio, CloseAudio );
120 /*****************************************************************************
121 * OpenAudio: open the audio device
122 *****************************************************************************
123 * This function opens and setups Direct Sound.
124 *****************************************************************************/
125 static int OpenAudio( vlc_object_t *p_this )
127 aout_instance_t * p_aout = (aout_instance_t *)p_this;
129 DSBUFFERDESC dsbuffer_desc;
131 msg_Dbg( p_aout, "Open" );
133 /* Allocate structure */
134 p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
135 if( p_aout->output.p_sys == NULL )
137 msg_Err( p_aout, "out of memory" );
141 /* Initialize some variables */
142 p_aout->output.p_sys->p_dsobject = NULL;
143 p_aout->output.p_sys->p_dsbuffer_primary = NULL;
144 p_aout->output.p_sys->p_dsbuffer = NULL;
145 p_aout->output.p_sys->p_dsnotify = NULL;
146 p_aout->output.p_sys->p_notif = NULL;
148 p_aout->output.pf_play = Play;
149 aout_VolumeSoftInit( p_aout );
151 /* Initialise DirectSound */
152 if( DirectxInitDSound( p_aout ) )
154 msg_Err( p_aout, "cannot initialize DirectSound" );
158 /* Obtain (not create) Direct Sound primary buffer */
159 memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
160 dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
161 dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
162 msg_Warn( p_aout, "create direct sound primary buffer" );
163 dsresult = IDirectSound_CreateSoundBuffer(p_aout->output.p_sys->p_dsobject,
165 &p_aout->output.p_sys->p_dsbuffer_primary,
167 if( dsresult != DS_OK )
169 msg_Err( p_aout, "cannot create direct sound primary buffer" );
173 /* Now we need to setup DirectSound play notification */
174 p_aout->output.p_sys->p_notif =
175 vlc_object_create( p_aout, sizeof(notification_thread_t) );
176 p_aout->output.p_sys->p_notif->p_aout = p_aout;
178 /* first we need to create the notification events */
179 p_aout->output.p_sys->p_notif->p_events[0].hEventNotify =
180 CreateEvent( NULL, FALSE, FALSE, NULL );
181 p_aout->output.p_sys->p_notif->p_events[1].hEventNotify =
182 CreateEvent( NULL, FALSE, FALSE, NULL );
184 /* then create a new secondary buffer */
185 p_aout->output.output.i_format = VLC_FOURCC('f','l','3','2');
186 if( DirectxCreateSecondaryBuffer( p_aout ) )
188 msg_Err( p_aout, "cannot create WAVE_FORMAT_IEEE_FLOAT buffer" );
190 p_aout->output.output.i_format = VLC_FOURCC('s','1','6','l');
191 if( DirectxCreateSecondaryBuffer( p_aout ) )
193 msg_Err( p_aout, "cannot create WAVE_FORMAT_PCM buffer" );
198 /* start playing the buffer */
199 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
202 DSBPLAY_LOOPING ); /* Flags */
203 if( dsresult == DSERR_BUFFERLOST )
205 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
206 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
209 DSBPLAY_LOOPING ); /* Flags */
211 if( dsresult != DS_OK )
213 msg_Warn( p_aout, "cannot play buffer" );
216 /* then launch the notification thread */
217 msg_Dbg( p_aout, "creating DirectSoundThread" );
218 if( vlc_thread_create( p_aout->output.p_sys->p_notif,
219 "DirectSound Notification Thread",
221 THREAD_PRIORITY_TIME_CRITICAL, 1 ) )
223 msg_Err( p_aout, "cannot create DirectSoundThread" );
227 vlc_object_attach( p_aout->output.p_sys->p_notif, p_aout );
232 CloseAudio( VLC_OBJECT(p_aout) );
236 /*****************************************************************************
237 * Play: nothing to do
238 *****************************************************************************/
239 static void Play( aout_instance_t *p_aout )
243 /*****************************************************************************
244 * CloseAudio: close the audio device
245 *****************************************************************************/
246 static void CloseAudio( vlc_object_t *p_this )
248 aout_instance_t * p_aout = (aout_instance_t *)p_this;
250 msg_Dbg( p_aout, "Close" );
252 /* kill the position notification thread, if any */
253 if( p_aout->output.p_sys->p_notif )
255 vlc_object_detach( p_aout->output.p_sys->p_notif );
256 if( p_aout->output.p_sys->p_notif->b_thread )
258 p_aout->output.p_sys->p_notif->b_die = 1;
259 vlc_thread_join( p_aout->output.p_sys->p_notif );
261 vlc_object_destroy( p_aout->output.p_sys->p_notif );
264 /* release the secondary buffer */
265 DirectxDestroySecondaryBuffer( p_aout );
267 /* then release the primary buffer */
268 if( p_aout->output.p_sys->p_dsbuffer_primary )
269 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer_primary );
271 /* finally release the DirectSound object */
272 if( p_aout->output.p_sys->p_dsobject )
273 IDirectSound_Release( p_aout->output.p_sys->p_dsobject );
275 /* free DSOUND.DLL */
276 if( p_aout->output.p_sys->hdsound_dll )
277 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
279 free( p_aout->output.p_sys );
282 /*****************************************************************************
283 * DirectxInitDSound: handle all the gory details of DirectSound initialisation
284 *****************************************************************************/
285 static int DirectxInitDSound( aout_instance_t *p_aout )
287 HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
289 p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
290 if( p_aout->output.p_sys->hdsound_dll == NULL )
292 msg_Warn( p_aout, "cannot open DSOUND.DLL" );
296 OurDirectSoundCreate = (void *)GetProcAddress(
297 p_aout->output.p_sys->hdsound_dll,
298 "DirectSoundCreate" );
299 if( OurDirectSoundCreate == NULL )
301 msg_Warn( p_aout, "GetProcAddress FAILED" );
305 /* Create the direct sound object */
306 if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
309 msg_Warn( p_aout, "cannot create a direct sound device" );
313 /* Set DirectSound Cooperative level, ie what control we want over Windows
314 * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
315 * settings of the primary buffer, but also that only the sound of our
316 * application will be hearable when it will have the focus.
317 * !!! (this is not really working as intended yet because to set the
318 * cooperative level you need the window handle of your application, and
319 * I don't know of any easy way to get it. Especially since we might play
320 * sound without any video, and so what window handle should we use ???
321 * The hack for now is to use the Desktop window handle - it seems to be
323 if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
327 msg_Warn( p_aout, "cannot set direct sound cooperative level" );
333 p_aout->output.p_sys->p_dsobject = NULL;
334 if( p_aout->output.p_sys->hdsound_dll )
336 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
337 p_aout->output.p_sys->hdsound_dll = NULL;
343 /*****************************************************************************
344 * DirectxCreateSecondaryBuffer
345 *****************************************************************************
346 * This function creates the buffer we'll use to play audio.
347 * In DirectSound there are two kinds of buffers:
348 * - the primary buffer: which is the actual buffer that the soundcard plays
349 * - the secondary buffer(s): these buffers are the one actually used by
350 * applications and DirectSound takes care of mixing them into the primary.
352 * Once you create a secondary buffer, you cannot change its format anymore so
353 * you have to release the current and create another one.
354 *****************************************************************************/
355 static int DirectxCreateSecondaryBuffer( aout_instance_t *p_aout )
357 WAVEFORMATEX waveformat;
358 DSBUFFERDESC dsbdesc;
362 i_nb_channels = aout_FormatNbChannels( &p_aout->output.output );
363 if ( i_nb_channels > 2 )
366 p_aout->output.output.i_channels = AOUT_CHAN_STEREO;
369 /* First set the buffer format */
370 memset(&waveformat, 0, sizeof(WAVEFORMATEX));
371 switch( p_aout->output.output.i_format )
373 case VLC_FOURCC('s','1','6','l'):
374 waveformat.wFormatTag = WAVE_FORMAT_PCM;
375 waveformat.wBitsPerSample = 16;
377 case VLC_FOURCC('f','l','3','2'):
378 waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
379 waveformat.wBitsPerSample = sizeof(float) * 8;
382 waveformat.nChannels = i_nb_channels;
383 waveformat.nSamplesPerSec = p_aout->output.output.i_rate;
384 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
385 waveformat.nChannels;
386 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
387 waveformat.nBlockAlign;
389 aout_FormatPrepare( &p_aout->output.output );
391 /* Then fill in the descriptor */
392 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
393 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
394 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
395 | DSBCAPS_CTRLPOSITIONNOTIFY /* We need notification */
396 | DSBCAPS_GLOBALFOCUS; /* Allows background playing */
397 dsbdesc.dwBufferBytes = FRAME_SIZE * 2 /* frames*/ /* buffer size */
398 * p_aout->output.output.i_bytes_per_frame;
399 dsbdesc.lpwfxFormat = &waveformat;
401 if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
403 &p_aout->output.p_sys->p_dsbuffer,
406 msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
410 /* backup the size of a frame */
411 p_aout->output.p_sys->p_notif->i_buffer_size = FRAME_SIZE *
412 p_aout->output.output.i_bytes_per_frame;
414 memset(&dsbcaps, 0, sizeof(DSBCAPS));
415 dsbcaps.dwSize = sizeof(DSBCAPS);
416 IDirectSoundBuffer_GetCaps( p_aout->output.p_sys->p_dsbuffer, &dsbcaps );
417 msg_Dbg( p_aout, "requested %li bytes buffer and got %li bytes.",
418 2 * p_aout->output.p_sys->p_notif->i_buffer_size,
419 dsbcaps.dwBufferBytes );
421 /* Now the secondary buffer is created, we need to setup its position
423 p_aout->output.p_sys->p_notif->p_events[0].dwOffset = 0;
424 p_aout->output.p_sys->p_notif->p_events[1].dwOffset =
425 p_aout->output.p_sys->p_notif->i_buffer_size;
427 /* Get the IDirectSoundNotify interface */
428 if FAILED( IDirectSoundBuffer_QueryInterface(
429 p_aout->output.p_sys->p_dsbuffer,
430 &IID_IDirectSoundNotify,
431 (LPVOID *)&p_aout->output.p_sys->p_dsnotify ) )
433 msg_Err( p_aout, "cannot get Notify interface" );
437 if FAILED( IDirectSoundNotify_SetNotificationPositions(
438 p_aout->output.p_sys->p_dsnotify, 2,
439 p_aout->output.p_sys->p_notif->p_events ) )
441 msg_Err( p_aout, "cannot set position Notification" );
445 p_aout->output.i_nb_samples = FRAME_SIZE;
450 if( p_aout->output.p_sys->p_dsbuffer )
452 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
453 p_aout->output.p_sys->p_dsbuffer = NULL;
455 if( p_aout->output.p_sys->p_dsnotify )
457 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
458 p_aout->output.p_sys->p_dsnotify = NULL;
463 /*****************************************************************************
464 * DirectxCreateSecondaryBuffer
465 *****************************************************************************
466 * This function destroys the secondary buffer.
467 *****************************************************************************/
468 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
470 /* make sure the buffer isn't playing */
471 if( p_aout->output.p_sys->p_dsbuffer )
472 IDirectSoundBuffer_Stop( p_aout->output.p_sys->p_dsbuffer );
474 if( p_aout->output.p_sys->p_dsnotify )
476 IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
477 p_aout->output.p_sys->p_dsnotify = NULL;
480 if( p_aout->output.p_sys->p_dsbuffer )
482 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
483 p_aout->output.p_sys->p_dsbuffer = NULL;
487 /*****************************************************************************
488 * DirectSoundThread: this thread will capture play notification events.
489 *****************************************************************************
490 * We use this thread to emulate a callback mechanism. The thread probes for
491 * event notification and fills up the DS secondary buffer when needed.
492 *****************************************************************************/
493 static void DirectSoundThread( notification_thread_t *p_notif )
495 HANDLE notification_events[2];
497 aout_instance_t *p_aout = p_notif->p_aout;
499 notification_events[0] = p_notif->p_events[0].hEventNotify;
500 notification_events[1] = p_notif->p_events[1].hEventNotify;
502 /* Tell the main thread that we are ready */
503 vlc_thread_ready( p_notif );
505 msg_Dbg( p_notif, "DirectSoundThread ready" );
507 while( !p_notif->b_die )
510 void *p_write_position, *p_wrap_around;
511 long l_bytes1, l_bytes2;
512 aout_buffer_t * p_buffer;
513 long l_play_position;
515 /* wait for the position notification */
516 i_which_event = WaitForMultipleObjects( 2, notification_events, 0,
517 INFINITE ) - WAIT_OBJECT_0;
522 /* Before copying anything, we have to lock the buffer */
523 dsresult = IDirectSoundBuffer_Lock(
525 p_aout->output.p_sys->p_dsbuffer,
526 /* Offset of lock start */
527 i_which_event ? 0 : p_notif->i_buffer_size,
528 p_notif->i_buffer_size, /* Number of bytes */
529 &p_write_position, /* Address of lock start */
530 &l_bytes1, /* Count of bytes locked before wrap around */
531 &p_wrap_around, /* Buffer adress (if wrap around) */
532 &l_bytes2, /* Count of bytes after wrap around */
534 if( dsresult == DSERR_BUFFERLOST )
536 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
537 dsresult = IDirectSoundBuffer_Lock(
538 p_aout->output.p_sys->p_dsbuffer,
539 i_which_event ? 0 : p_notif->i_buffer_size,
540 p_notif->i_buffer_size,
547 if( dsresult != DS_OK )
549 msg_Warn( p_notif, "cannot lock buffer" );
553 /* We take into account the current latency */
554 if( IDirectSoundBuffer_GetCurrentPosition(
555 p_aout->output.p_sys->p_dsbuffer,
556 &l_play_position, NULL ) == DS_OK )
558 if( l_play_position > (i_which_event * FRAME_SIZE)
559 && l_play_position < ((i_which_event+1) * FRAME_SIZE) )
561 l_play_position = FRAME_SIZE - ( l_play_position /
562 p_aout->output.output.i_bytes_per_frame %
567 l_play_position = 2 * FRAME_SIZE - ( l_play_position /
568 p_aout->output.output.i_bytes_per_frame %
574 l_play_position = FRAME_SIZE;
577 p_buffer = aout_OutputNextBuffer( p_aout,
578 mdate() + 1000000 / p_aout->output.output.i_rate * l_play_position,
581 /* Now do the actual memcpy into the circular buffer */
582 if ( l_bytes1 != p_notif->i_buffer_size )
583 msg_Err( p_aout, "Wrong buffer size: %d, %d", l_bytes1,
584 p_notif->i_buffer_size );
586 if ( p_buffer != NULL )
588 p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
590 aout_BufferFree( p_buffer );
594 memset( p_write_position, 0, l_bytes1 );
597 /* Now the data has been copied, unlock the buffer */
598 IDirectSoundBuffer_Unlock( p_aout->output.p_sys->p_dsbuffer,
599 p_write_position, l_bytes1, p_wrap_around, l_bytes2 );
603 /* free the events */
604 CloseHandle( notification_events[0] );
605 CloseHandle( notification_events[1] );
607 msg_Dbg( p_notif, "DirectSoundThread exiting" );