1 /*****************************************************************************
2 * directx.c: Windows DirectX audio output method
3 *****************************************************************************
4 * Copyright (C) 2001 VideoLAN
5 * $Id: directx.c,v 1.10 2003/01/05 13:39:32 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 */
44 /* frame buffer status */
45 #define FRAME_QUEUED 0
48 /*****************************************************************************
50 * Defining them here allows us to get rid of the dxguid library during
52 *****************************************************************************/
54 DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16);
56 /*****************************************************************************
58 *****************************************************************************/
59 #ifndef WAVE_FORMAT_IEEE_FLOAT
60 # define WAVE_FORMAT_IEEE_FLOAT 0x0003
63 /*****************************************************************************
64 * notification_thread_t: DirectX event thread
65 *****************************************************************************/
66 typedef struct notification_thread_t
70 aout_instance_t * p_aout;
71 int i_frame_status[FRAMES_NUM]; /* status of each frame buffer */
72 DSBPOSITIONNOTIFY p_events[FRAMES_NUM]; /* play notification events */
73 int i_frame_size; /* Size in bytes of one frame */
77 } notification_thread_t;
79 /*****************************************************************************
80 * aout_sys_t: directx audio output method descriptor
81 *****************************************************************************
82 * This structure is part of the audio output thread descriptor.
83 * It describes the direct sound specific properties of an audio device.
84 *****************************************************************************/
87 LPDIRECTSOUND p_dsobject; /* main Direct Sound object */
89 LPDIRECTSOUNDBUFFER p_dsbuffer; /* the sound buffer we use (direct sound
90 * takes care of mixing all the
91 * secondary buffers into the primary) */
93 LPDIRECTSOUNDNOTIFY p_dsnotify; /* the position notify interface */
95 HINSTANCE hdsound_dll; /* handle of the opened dsound dll */
97 notification_thread_t * p_notif; /* DirectSoundThread id */
99 int b_playing; /* playing status */
102 /*****************************************************************************
104 *****************************************************************************/
105 static int OpenAudio ( vlc_object_t * );
106 static void CloseAudio ( vlc_object_t * );
108 static void Play ( aout_instance_t * );
110 /* local functions */
111 static int DirectxCreateSecondaryBuffer ( aout_instance_t * );
112 static void DirectxDestroySecondaryBuffer( aout_instance_t * );
113 static int DirectxInitDSound ( aout_instance_t * );
114 static void DirectSoundThread ( notification_thread_t * );
115 static int DirectxFillBuffer ( aout_instance_t *, int,
118 /*****************************************************************************
120 *****************************************************************************/
122 set_description( _("DirectX audio module") );
123 set_capability( "audio output", 100 );
124 add_shortcut( "directx" );
125 set_callbacks( OpenAudio, CloseAudio );
128 /*****************************************************************************
129 * OpenAudio: open the audio device
130 *****************************************************************************
131 * This function opens and setups Direct Sound.
132 *****************************************************************************/
133 static int OpenAudio( vlc_object_t *p_this )
135 aout_instance_t * p_aout = (aout_instance_t *)p_this;
138 msg_Dbg( p_aout, "Open" );
140 /* Allocate structure */
141 p_aout->output.p_sys = malloc( sizeof( aout_sys_t ) );
142 if( p_aout->output.p_sys == NULL )
144 msg_Err( p_aout, "out of memory" );
148 /* Initialize some variables */
149 p_aout->output.p_sys->p_dsobject = NULL;
150 p_aout->output.p_sys->p_dsbuffer = NULL;
151 p_aout->output.p_sys->p_dsnotify = NULL;
152 p_aout->output.p_sys->p_notif = NULL;
153 p_aout->output.p_sys->b_playing = 0;
155 p_aout->output.pf_play = Play;
156 aout_VolumeSoftInit( p_aout );
158 /* Initialise DirectSound */
159 if( DirectxInitDSound( p_aout ) )
161 msg_Err( p_aout, "cannot initialize DirectSound" );
165 /* Now we need to setup DirectSound play notification */
166 p_aout->output.p_sys->p_notif =
167 vlc_object_create( p_aout, sizeof(notification_thread_t) );
168 p_aout->output.p_sys->p_notif->p_aout = p_aout;
170 /* first we need to create the notification events */
171 for( i = 0; i < FRAMES_NUM; i++ )
172 p_aout->output.p_sys->p_notif->p_events[i].hEventNotify =
173 CreateEvent( NULL, FALSE, FALSE, NULL );
175 /* then create a new secondary buffer */
176 p_aout->output.output.i_format = VLC_FOURCC('f','l','3','2');
177 if( DirectxCreateSecondaryBuffer( p_aout ) )
179 msg_Dbg( p_aout, "cannot create WAVE_FORMAT_IEEE_FLOAT buffer" );
181 p_aout->output.output.i_format = VLC_FOURCC('s','1','6','l');
182 if( DirectxCreateSecondaryBuffer( p_aout ) )
184 msg_Err( p_aout, "cannot create WAVE_FORMAT_PCM buffer" );
189 /* then launch the notification thread */
190 msg_Dbg( p_aout, "creating DirectSoundThread" );
191 if( vlc_thread_create( p_aout->output.p_sys->p_notif,
192 "DirectSound Notification Thread",
194 VLC_THREAD_PRIORITY_HIGHEST, 1 ) )
196 msg_Err( p_aout, "cannot create DirectSoundThread" );
200 vlc_object_attach( p_aout->output.p_sys->p_notif, p_aout );
205 CloseAudio( VLC_OBJECT(p_aout) );
209 /*****************************************************************************
210 * Play: we'll start playing the directsound buffer here because at least here
211 * we know the first buffer has been put in the aout fifo and we also
213 *****************************************************************************/
214 static void Play( aout_instance_t *p_aout )
216 if( !p_aout->output.p_sys->b_playing )
218 aout_buffer_t *p_buffer;
220 p_aout->output.p_sys->b_playing = 1;
222 /* get the playing date of the first aout buffer */
223 p_aout->output.p_sys->p_notif->start_date =
224 aout_FifoFirstDate( p_aout, &p_aout->output.fifo );
226 /* fill in the first samples */
227 p_buffer = aout_FifoPop( p_aout, &p_aout->output.fifo );
228 DirectxFillBuffer( p_aout, 0, p_buffer );
230 /* wake up the audio output thread */
231 SetEvent( p_aout->output.p_sys->p_notif->p_events[0].hEventNotify );
235 /*****************************************************************************
236 * CloseAudio: close the audio device
237 *****************************************************************************/
238 static void CloseAudio( vlc_object_t *p_this )
240 aout_instance_t * p_aout = (aout_instance_t *)p_this;
242 msg_Dbg( p_aout, "Close" );
244 /* kill the position notification thread, if any */
245 if( p_aout->output.p_sys->p_notif )
247 vlc_object_detach( p_aout->output.p_sys->p_notif );
248 if( p_aout->output.p_sys->p_notif->b_thread )
250 p_aout->output.p_sys->p_notif->b_die = 1;
252 if( !p_aout->output.p_sys->b_playing )
253 /* wake up the audio thread */
255 p_aout->output.p_sys->p_notif->p_events[0].hEventNotify );
257 vlc_thread_join( p_aout->output.p_sys->p_notif );
259 vlc_object_destroy( p_aout->output.p_sys->p_notif );
262 /* release the secondary buffer */
263 DirectxDestroySecondaryBuffer( p_aout );
265 /* finally release the DirectSound object */
266 if( p_aout->output.p_sys->p_dsobject )
267 IDirectSound_Release( p_aout->output.p_sys->p_dsobject );
269 /* free DSOUND.DLL */
270 if( p_aout->output.p_sys->hdsound_dll )
271 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
273 free( p_aout->output.p_sys );
276 /*****************************************************************************
277 * DirectxInitDSound: handle all the gory details of DirectSound initialisation
278 *****************************************************************************/
279 static int DirectxInitDSound( aout_instance_t *p_aout )
281 HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);
283 p_aout->output.p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
284 if( p_aout->output.p_sys->hdsound_dll == NULL )
286 msg_Warn( p_aout, "cannot open DSOUND.DLL" );
290 OurDirectSoundCreate = (void *)GetProcAddress(
291 p_aout->output.p_sys->hdsound_dll,
292 "DirectSoundCreate" );
293 if( OurDirectSoundCreate == NULL )
295 msg_Warn( p_aout, "GetProcAddress FAILED" );
299 /* Create the direct sound object */
300 if( OurDirectSoundCreate( NULL, &p_aout->output.p_sys->p_dsobject, NULL )
303 msg_Warn( p_aout, "cannot create a direct sound device" );
307 /* Set DirectSound Cooperative level, ie what control we want over Windows
308 * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
309 * settings of the primary buffer, but also that only the sound of our
310 * application will be hearable when it will have the focus.
311 * !!! (this is not really working as intended yet because to set the
312 * cooperative level you need the window handle of your application, and
313 * I don't know of any easy way to get it. Especially since we might play
314 * sound without any video, and so what window handle should we use ???
315 * The hack for now is to use the Desktop window handle - it seems to be
317 if( IDirectSound_SetCooperativeLevel( p_aout->output.p_sys->p_dsobject,
321 msg_Warn( p_aout, "cannot set direct sound cooperative level" );
327 p_aout->output.p_sys->p_dsobject = NULL;
328 if( p_aout->output.p_sys->hdsound_dll )
330 FreeLibrary( p_aout->output.p_sys->hdsound_dll );
331 p_aout->output.p_sys->hdsound_dll = NULL;
337 /*****************************************************************************
338 * DirectxCreateSecondaryBuffer
339 *****************************************************************************
340 * This function creates the buffer we'll use to play audio.
341 * In DirectSound there are two kinds of buffers:
342 * - the primary buffer: which is the actual buffer that the soundcard plays
343 * - the secondary buffer(s): these buffers are the one actually used by
344 * applications and DirectSound takes care of mixing them into the primary.
346 * Once you create a secondary buffer, you cannot change its format anymore so
347 * you have to release the current and create another one.
348 *****************************************************************************/
349 static int DirectxCreateSecondaryBuffer( aout_instance_t *p_aout )
351 WAVEFORMATEX waveformat;
352 DSBUFFERDESC dsbdesc;
354 int i_nb_channels, i;
356 i_nb_channels = aout_FormatNbChannels( &p_aout->output.output );
357 if ( i_nb_channels >= 2 )
360 p_aout->output.output.i_physical_channels =
361 AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT;
366 p_aout->output.output.i_physical_channels =
370 /* First set the buffer format */
371 memset( &waveformat, 0, sizeof(WAVEFORMATEX) );
372 switch( p_aout->output.output.i_format )
374 case VLC_FOURCC('s','1','6','l'):
375 waveformat.wFormatTag = WAVE_FORMAT_PCM;
376 waveformat.wBitsPerSample = 16;
378 case VLC_FOURCC('f','l','3','2'):
379 waveformat.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
380 waveformat.wBitsPerSample = sizeof(float) * 8;
383 waveformat.nChannels = i_nb_channels;
384 waveformat.nSamplesPerSec = p_aout->output.output.i_rate;
385 waveformat.nBlockAlign = waveformat.wBitsPerSample / 8 *
386 waveformat.nChannels;
387 waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
388 waveformat.nBlockAlign;
390 aout_FormatPrepare( &p_aout->output.output );
392 /* Then fill in the descriptor */
393 memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
394 dsbdesc.dwSize = sizeof(DSBUFFERDESC);
395 dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
396 | DSBCAPS_CTRLPOSITIONNOTIFY /* We need notification */
397 | DSBCAPS_GLOBALFOCUS; /* Allows background playing */
398 dsbdesc.dwBufferBytes = FRAME_SIZE * FRAMES_NUM /* buffer size */
399 * p_aout->output.output.i_bytes_per_frame;
400 dsbdesc.lpwfxFormat = &waveformat;
402 if( IDirectSound_CreateSoundBuffer( p_aout->output.p_sys->p_dsobject,
404 &p_aout->output.p_sys->p_dsbuffer,
407 msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
411 /* backup the size of a frame */
412 p_aout->output.p_sys->p_notif->i_frame_size = FRAME_SIZE *
413 p_aout->output.output.i_bytes_per_frame;
415 memset(&dsbcaps, 0, sizeof(DSBCAPS));
416 dsbcaps.dwSize = sizeof(DSBCAPS);
417 IDirectSoundBuffer_GetCaps( p_aout->output.p_sys->p_dsbuffer, &dsbcaps );
418 msg_Dbg( p_aout, "requested %li bytes buffer and got %li bytes.",
419 FRAMES_NUM * p_aout->output.p_sys->p_notif->i_frame_size,
420 dsbcaps.dwBufferBytes );
422 /* Now the secondary buffer is created, we need to setup its position
424 for( i = 0; i < FRAMES_NUM; i++ )
426 p_aout->output.p_sys->p_notif->p_events[i].dwOffset = i *
427 p_aout->output.p_sys->p_notif->i_frame_size;
429 p_aout->output.p_sys->p_notif->i_frame_status[i] = FRAME_EMPTY;
432 /* Get the IDirectSoundNotify interface */
433 if FAILED( IDirectSoundBuffer_QueryInterface(
434 p_aout->output.p_sys->p_dsbuffer,
435 &IID_IDirectSoundNotify,
436 (LPVOID *)&p_aout->output.p_sys->p_dsnotify ) )
438 msg_Err( p_aout, "cannot get Notify interface" );
442 if FAILED( IDirectSoundNotify_SetNotificationPositions(
443 p_aout->output.p_sys->p_dsnotify,
445 p_aout->output.p_sys->p_notif->p_events ) )
447 msg_Err( p_aout, "cannot set position Notification" );
451 p_aout->output.i_nb_samples = FRAME_SIZE;
456 if( p_aout->output.p_sys->p_dsbuffer )
458 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
459 p_aout->output.p_sys->p_dsbuffer = NULL;
461 if( p_aout->output.p_sys->p_dsnotify )
463 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
464 p_aout->output.p_sys->p_dsnotify = NULL;
469 /*****************************************************************************
470 * DirectxCreateSecondaryBuffer
471 *****************************************************************************
472 * This function destroys the secondary buffer.
473 *****************************************************************************/
474 static void DirectxDestroySecondaryBuffer( aout_instance_t *p_aout )
476 /* make sure the buffer isn't playing */
477 if( p_aout->output.p_sys->p_dsbuffer )
478 IDirectSoundBuffer_Stop( p_aout->output.p_sys->p_dsbuffer );
480 if( p_aout->output.p_sys->p_dsnotify )
482 IDirectSoundNotify_Release( p_aout->output.p_sys->p_dsnotify );
483 p_aout->output.p_sys->p_dsnotify = NULL;
486 if( p_aout->output.p_sys->p_dsbuffer )
488 IDirectSoundBuffer_Release( p_aout->output.p_sys->p_dsbuffer );
489 p_aout->output.p_sys->p_dsbuffer = NULL;
493 /*****************************************************************************
494 * DirectxFillBuffer: Fill in one of the direct sound frame buffers.
495 *****************************************************************************
496 * Returns VLC_SUCCESS on success.
497 *****************************************************************************/
498 static int DirectxFillBuffer( aout_instance_t *p_aout, int i_frame,
499 aout_buffer_t *p_buffer )
501 notification_thread_t *p_notif = p_aout->output.p_sys->p_notif;
502 void *p_write_position, *p_wrap_around;
503 long l_bytes1, l_bytes2;
506 /* Before copying anything, we have to lock the buffer */
507 dsresult = IDirectSoundBuffer_Lock(
508 p_aout->output.p_sys->p_dsbuffer, /* DS buffer */
509 i_frame * p_notif->i_frame_size, /* Start offset */
510 p_notif->i_frame_size, /* Number of bytes */
511 &p_write_position, /* Address of lock start */
512 &l_bytes1, /* Count of bytes locked before wrap around */
513 &p_wrap_around, /* Buffer adress (if wrap around) */
514 &l_bytes2, /* Count of bytes after wrap around */
516 if( dsresult == DSERR_BUFFERLOST )
518 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
519 dsresult = IDirectSoundBuffer_Lock(
520 p_aout->output.p_sys->p_dsbuffer,
521 i_frame * p_notif->i_frame_size,
522 p_notif->i_frame_size,
529 if( dsresult != DS_OK )
531 msg_Warn( p_notif, "cannot lock buffer" );
532 if( p_buffer ) aout_BufferFree( p_buffer );
536 if ( p_buffer != NULL )
538 p_aout->p_vlc->pf_memcpy( p_write_position, p_buffer->p_buffer,
540 aout_BufferFree( p_buffer );
543 memset( p_write_position, 0, l_bytes1 );
545 /* Now the data has been copied, unlock the buffer */
546 IDirectSoundBuffer_Unlock( p_aout->output.p_sys->p_dsbuffer,
547 p_write_position, l_bytes1,
548 p_wrap_around, l_bytes2 );
553 /*****************************************************************************
554 * DirectSoundThread: this thread will capture play notification events.
555 *****************************************************************************
556 * We use this thread to emulate a callback mechanism. The thread probes for
557 * event notification and fills up the DS secondary buffer when needed.
558 *****************************************************************************/
559 static void DirectSoundThread( notification_thread_t *p_notif )
561 HANDLE notification_events[FRAMES_NUM];
563 aout_instance_t *p_aout = p_notif->p_aout;
564 int i, i_which_frame, i_last_frame, i_next_frame;
567 for( i = 0; i < FRAMES_NUM; i++ )
568 notification_events[i] = p_notif->p_events[i].hEventNotify;
570 /* Tell the main thread that we are ready */
571 vlc_thread_ready( p_notif );
573 msg_Dbg( p_notif, "DirectSoundThread ready" );
575 /* Wait here until Play() is called */
576 WaitForSingleObject( notification_events[0], INFINITE );
578 if( !p_notif->b_die )
580 mwait( p_notif->start_date - AOUT_PTS_TOLERANCE / 2 );
582 /* start playing the buffer */
583 dsresult = IDirectSoundBuffer_Play( p_aout->output.p_sys->p_dsbuffer,
586 DSBPLAY_LOOPING ); /* Flags */
587 if( dsresult == DSERR_BUFFERLOST )
589 IDirectSoundBuffer_Restore( p_aout->output.p_sys->p_dsbuffer );
590 dsresult = IDirectSoundBuffer_Play(
591 p_aout->output.p_sys->p_dsbuffer,
594 DSBPLAY_LOOPING ); /* Flags */
596 if( dsresult != DS_OK )
598 msg_Err( p_aout, "cannot start playing buffer" );
602 while( !p_notif->b_die )
604 aout_buffer_t *p_buffer;
607 /* wait for the position notification */
608 i_which_frame = WaitForMultipleObjects( FRAMES_NUM,
609 notification_events, 0,
610 INFINITE ) - WAIT_OBJECT_0;
617 /* We take into account the current latency */
618 if( IDirectSoundBuffer_GetCurrentPosition(
619 p_aout->output.p_sys->p_dsbuffer,
620 &l_latency, NULL ) == DS_OK )
622 if( l_latency > (i_which_frame * FRAME_SIZE)
623 && l_latency < ((i_which_frame+1) * FRAME_SIZE) )
625 l_latency = - ( l_latency /
626 p_aout->output.output.i_bytes_per_frame %
631 l_latency = FRAME_SIZE - ( l_latency /
632 p_aout->output.output.i_bytes_per_frame %
641 /* Mark last frame as empty */
642 i_last_frame = (i_which_frame + FRAMES_NUM -1) % FRAMES_NUM;
643 i_next_frame = (i_which_frame + 1) % FRAMES_NUM;
644 p_notif->i_frame_status[i_last_frame] = FRAME_EMPTY;
646 /* Try to fill in as many frame buffers as possible */
647 for( i = i_next_frame; (i % FRAMES_NUM) != i_which_frame; i++ )
650 /* Check if frame buf is already filled */
651 if( p_notif->i_frame_status[i % FRAMES_NUM] == FRAME_QUEUED )
654 p_buffer = aout_OutputNextBuffer( p_aout,
655 mtime + 1000000 / p_aout->output.output.i_rate *
656 ((i - i_next_frame + 1) * FRAME_SIZE + l_latency), VLC_FALSE );
658 /* If there is no audio data available and we have some buffered
659 * already, then just wait for the next time */
660 if( !p_buffer && (i != i_next_frame) )
662 //msg_Err( p_aout, "only %i frame buffers filled!",
663 // i - i_next_frame );
667 if( DirectxFillBuffer( p_aout, (i%FRAMES_NUM), p_buffer )
671 /* Mark the frame buffer as QUEUED */
672 p_notif->i_frame_status[i%FRAMES_NUM] = FRAME_QUEUED;
677 /* free the events */
678 for( i = 0; i < FRAMES_NUM; i++ )
679 CloseHandle( notification_events[i] );
681 msg_Dbg( p_notif, "DirectSoundThread exiting" );