aout_directx.c 30.7 KB
Newer Older
Sam Hocevar's avatar
 
Sam Hocevar committed
1 2 3
/*****************************************************************************
 * aout_directx.c: Windows DirectX audio output method
 *****************************************************************************
4
 * Copyright (C) 2001 VideoLAN
5
 * $Id: aout_directx.c,v 1.24 2002/06/02 09:03:54 sam Exp $
Sam Hocevar's avatar
 
Sam Hocevar committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
 *
 * Authors: Gildas Bazin <gbazin@netcourrier.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
 *****************************************************************************/

/*****************************************************************************
 * Preamble
 *****************************************************************************/
#include <errno.h>                                                 /* ENOMEM */
#include <fcntl.h>                                       /* open(), O_WRONLY */
#include <string.h>                                            /* strerror() */
Sam Hocevar's avatar
 
Sam Hocevar committed
30

Sam Hocevar's avatar
 
Sam Hocevar committed
31 32
#include <stdlib.h>                            /* calloc(), malloc(), free() */

33 34
#include <vlc/vlc.h>
#include <vlc/aout.h>
Sam Hocevar's avatar
 
Sam Hocevar committed
35

Gildas Bazin's avatar
 
Gildas Bazin committed
36 37
#include <mmsystem.h>
#include <dsound.h>
Sam Hocevar's avatar
 
Sam Hocevar committed
38

Gildas Bazin's avatar
 
Gildas Bazin committed
39 40 41 42 43 44 45 46
/*****************************************************************************
 * DirectSound GUIDs.
 * Defining them here allows us to get rid of the dxguid library during
 * the linking stage.
 *****************************************************************************/
#include <initguid.h>
DEFINE_GUID(IID_IDirectSoundNotify, 0xb0210783, 0x89cd, 0x11d0, 0xaf, 0x8, 0x0, 0xa0, 0xc9, 0x25, 0xcd, 0x16);

47 48 49 50 51 52 53 54 55 56 57 58
/*****************************************************************************
 * notification_thread_t: DirectX event thread
 *****************************************************************************/
typedef struct notification_thread_s
{
    VLC_COMMON_MEMBERS

    aout_thread_t * p_aout;
    DSBPOSITIONNOTIFY p_events[2];               /* play notification events */

} notification_thread_t;

Sam Hocevar's avatar
 
Sam Hocevar committed
59 60 61 62 63 64 65
/*****************************************************************************
 * aout_sys_t: directx audio output method descriptor
 *****************************************************************************
 * This structure is part of the audio output thread descriptor.
 * It describes the direct sound specific properties of an audio device.
 *****************************************************************************/

66
struct aout_sys_s
Sam Hocevar's avatar
 
Sam Hocevar committed
67 68 69 70 71 72 73 74 75 76
{
    LPDIRECTSOUND       p_dsobject;              /* main Direct Sound object */

    LPDIRECTSOUNDBUFFER p_dsbuffer_primary;     /* the actual sound card buffer
                                                   (not used directly) */

    LPDIRECTSOUNDBUFFER p_dsbuffer;   /* the sound buffer we use (direct sound
                                       * takes care of mixing all the
                                       * secondary buffers into the primary) */

Gildas Bazin's avatar
 
Gildas Bazin committed
77 78
    LPDIRECTSOUNDNOTIFY p_dsnotify;         /* the position notify interface */

Sam Hocevar's avatar
 
Sam Hocevar committed
79 80
    HINSTANCE           hdsound_dll;      /* handle of the opened dsound dll */

Gildas Bazin's avatar
 
Gildas Bazin committed
81 82
    long l_buffer_size;                       /* secondary sound buffer size */
    long l_write_position;             /* next write position for the buffer */
Sam Hocevar's avatar
 
Sam Hocevar committed
83

84
    volatile vlc_bool_t b_buffer_underflown;   /* buffer underflow detection */
Gildas Bazin's avatar
 
Gildas Bazin committed
85 86
    volatile long l_data_played_from_beginning;   /* for underflow detection */
    volatile long l_data_written_from_beginning;  /* for underflow detection */
Gildas Bazin's avatar
 
Gildas Bazin committed
87 88 89

    vlc_mutex_t buffer_lock;                            /* audio buffer lock */

90
    notification_thread_t * p_notif;                 /* DirectSoundThread id */
91
};
Sam Hocevar's avatar
 
Sam Hocevar committed
92 93 94 95

/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
96 97 98 99 100
static int     aout_Open        ( aout_thread_t * );
static int     aout_SetFormat   ( aout_thread_t * );
static int     aout_GetBufInfo  ( aout_thread_t *, int );
static void    aout_Play        ( aout_thread_t *, byte_t *, int );
static void    aout_Close       ( aout_thread_t * );
Sam Hocevar's avatar
 
Sam Hocevar committed
101

Gildas Bazin's avatar
 
Gildas Bazin committed
102
/* local functions */
103 104 105 106
static int  DirectxCreateSecondaryBuffer ( aout_thread_t * );
static void DirectxDestroySecondaryBuffer( aout_thread_t * );
static int  DirectxInitDSound            ( aout_thread_t * );
static void DirectSoundThread            ( notification_thread_t * );
Sam Hocevar's avatar
 
Sam Hocevar committed
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129

/*****************************************************************************
 * Functions exported as capabilities. They are declared as static so that
 * we don't pollute the namespace too much.
 *****************************************************************************/
void _M( aout_getfunctions )( function_list_t * p_function_list )
{
    p_function_list->functions.aout.pf_open = aout_Open;
    p_function_list->functions.aout.pf_setformat = aout_SetFormat;
    p_function_list->functions.aout.pf_getbufinfo = aout_GetBufInfo;
    p_function_list->functions.aout.pf_play = aout_Play;
    p_function_list->functions.aout.pf_close = aout_Close;
}

/*****************************************************************************
 * aout_Open: open the audio device
 *****************************************************************************
 * This function opens and setups Direct Sound.
 *****************************************************************************/
static int aout_Open( aout_thread_t *p_aout )
{
    HRESULT dsresult;
    DSBUFFERDESC dsbuffer_desc;
Sam Hocevar's avatar
 
Sam Hocevar committed
130

131
    msg_Dbg( p_aout, "aout_Open" );
Sam Hocevar's avatar
 
Sam Hocevar committed
132 133 134 135 136 137

   /* Allocate structure */
    p_aout->p_sys = malloc( sizeof( aout_sys_t ) );

    if( p_aout->p_sys == NULL )
    {
138
        msg_Err( p_aout, "out of memory" );
Sam Hocevar's avatar
 
Sam Hocevar committed
139 140 141 142 143 144 145
        return( 1 );
    }

    /* Initialize some variables */
    p_aout->p_sys->p_dsobject = NULL;
    p_aout->p_sys->p_dsbuffer_primary = NULL;
    p_aout->p_sys->p_dsbuffer = NULL;
Gildas Bazin's avatar
 
Gildas Bazin committed
146
    p_aout->p_sys->p_dsnotify = NULL;
Gildas Bazin's avatar
 
Gildas Bazin committed
147 148
    p_aout->p_sys->l_data_written_from_beginning = 0;
    p_aout->p_sys->l_data_played_from_beginning = 0;
149
    vlc_mutex_init( p_aout, &p_aout->p_sys->buffer_lock );
Gildas Bazin's avatar
 
Gildas Bazin committed
150

Sam Hocevar's avatar
 
Sam Hocevar committed
151

Sam Hocevar's avatar
 
Sam Hocevar committed
152 153
    /* Initialise DirectSound */
    if( DirectxInitDSound( p_aout ) )
Sam Hocevar's avatar
 
Sam Hocevar committed
154
    {
155
        msg_Warn( p_aout, "cannot initialize DirectSound" );
Sam Hocevar's avatar
 
Sam Hocevar committed
156 157 158 159 160 161 162
        return( 1 );
    }

    /* Obtain (not create) Direct Sound primary buffer */
    memset( &dsbuffer_desc, 0, sizeof(DSBUFFERDESC) );
    dsbuffer_desc.dwSize = sizeof(DSBUFFERDESC);
    dsbuffer_desc.dwFlags = DSBCAPS_PRIMARYBUFFER;
163
    msg_Warn( p_aout, "create direct sound primary buffer" );
Sam Hocevar's avatar
 
Sam Hocevar committed
164 165 166 167 168 169
    dsresult = IDirectSound_CreateSoundBuffer(p_aout->p_sys->p_dsobject,
                                            &dsbuffer_desc,
                                            &p_aout->p_sys->p_dsbuffer_primary,
                                            NULL);
    if( dsresult != DS_OK )
    {
170
        msg_Warn( p_aout, "cannot create direct sound primary buffer" );
Sam Hocevar's avatar
 
Sam Hocevar committed
171 172 173 174 175 176 177
        IDirectSound_Release( p_aout->p_sys->p_dsobject );
        p_aout->p_sys->p_dsobject = NULL;
        p_aout->p_sys->p_dsbuffer_primary = NULL;
        return( 1 );
    }


Gildas Bazin's avatar
 
Gildas Bazin committed
178
    /* Now we need to setup DirectSound play notification */
Sam Hocevar's avatar
 
Sam Hocevar committed
179

Gildas Bazin's avatar
 
Gildas Bazin committed
180
    /* first we need to create the notification events */
181
    p_aout->p_sys->p_notif->p_events[0].hEventNotify =
Gildas Bazin's avatar
 
Gildas Bazin committed
182
        CreateEvent( NULL, FALSE, FALSE, NULL );
183
    p_aout->p_sys->p_notif->p_events[1].hEventNotify =
Gildas Bazin's avatar
 
Gildas Bazin committed
184 185 186
        CreateEvent( NULL, FALSE, FALSE, NULL );

    /* then launch the notification thread */
187
    msg_Dbg( p_aout, "creating DirectSoundThread" );
188 189 190 191 192
    p_aout->p_sys->p_notif =
                vlc_object_create( p_aout, sizeof(notification_thread_t) );
    p_aout->p_sys->p_notif->p_aout = p_aout;
    if( vlc_thread_create( p_aout->p_sys->p_notif,
                    "DirectSound Notification Thread", DirectSoundThread, 1 ) )
Sam Hocevar's avatar
 
Sam Hocevar committed
193
    {
194
        msg_Err( p_aout, "cannot create DirectSoundThread" );
Gildas Bazin's avatar
 
Gildas Bazin committed
195
        /* Let's go on anyway */
Sam Hocevar's avatar
 
Sam Hocevar committed
196 197
    }

198 199
    vlc_object_attach( p_aout->p_sys->p_notif, p_aout );

Sam Hocevar's avatar
 
Sam Hocevar committed
200 201 202 203 204 205 206 207 208 209 210 211
    return( 0 );
}

/*****************************************************************************
 * aout_SetFormat: reset the audio device and sets its format
 *****************************************************************************
 * This functions set a new audio format.
 * For this we need to close the current secondary buffer and create another
 * one with the desired format.
 *****************************************************************************/
static int aout_SetFormat( aout_thread_t *p_aout )
{
Gildas Bazin's avatar
 
Gildas Bazin committed
212 213
    HRESULT       dsresult;
    WAVEFORMATEX  *p_waveformat;
Gildas Bazin's avatar
 
Gildas Bazin committed
214
    unsigned long i_size_struct;
Sam Hocevar's avatar
 
Sam Hocevar committed
215

216
    msg_Dbg( p_aout, "aout_SetFormat" );
Sam Hocevar's avatar
 
Sam Hocevar committed
217

Gildas Bazin's avatar
 
Gildas Bazin committed
218 219 220 221 222 223
    /* Set the format of Direct Sound primary buffer */

    /* first we need to know the current format */
    dsresult = IDirectSoundBuffer_GetFormat( p_aout->p_sys->p_dsbuffer_primary,
                                             NULL, 0, &i_size_struct );
    if( dsresult == DS_OK )
Sam Hocevar's avatar
 
Sam Hocevar committed
224
    {
Gildas Bazin's avatar
 
Gildas Bazin committed
225 226 227 228 229
        p_waveformat = malloc( i_size_struct );
        dsresult = IDirectSoundBuffer_GetFormat(
                                             p_aout->p_sys->p_dsbuffer_primary,
                                             p_waveformat, i_size_struct,
                                             NULL );
Sam Hocevar's avatar
 
Sam Hocevar committed
230 231
    }

Gildas Bazin's avatar
 
Gildas Bazin committed
232 233 234 235
    if( dsresult == DS_OK )
    {
        /* Here we'll change the format */
        p_waveformat->nChannels        = 2; 
Sam Hocevar's avatar
 
Sam Hocevar committed
236 237
        p_waveformat->nSamplesPerSec   = (p_aout->i_rate < 44100) ? 44100
                                             : p_aout->i_rate; 
Gildas Bazin's avatar
 
Gildas Bazin committed
238 239 240 241 242 243 244 245 246 247
        p_waveformat->wBitsPerSample   = 16; 
        p_waveformat->nBlockAlign      = p_waveformat->wBitsPerSample / 8 *
                                             p_waveformat->nChannels;
        p_waveformat->nAvgBytesPerSec  = p_waveformat->nSamplesPerSec *
                                             p_waveformat->nBlockAlign;

        dsresult = IDirectSoundBuffer_SetFormat(
                                             p_aout->p_sys->p_dsbuffer_primary,
                                             p_waveformat );
    }
248
    else msg_Warn( p_aout, "cannot get primary buffer format" );
Gildas Bazin's avatar
 
Gildas Bazin committed
249

Sam Hocevar's avatar
 
Sam Hocevar committed
250
    if( dsresult != DS_OK )
251
        msg_Warn( p_aout, "cannot set primary buffer format" );
Gildas Bazin's avatar
 
Gildas Bazin committed
252 253 254 255 256 257 258 259 260 261 262


    /* Now we need to take care of Direct Sound secondary buffer */

    vlc_mutex_lock( &p_aout->p_sys->buffer_lock );

    /* first release the current secondary buffer */
    DirectxDestroySecondaryBuffer( p_aout );

    /* then create a new secondary buffer */
    if( DirectxCreateSecondaryBuffer( p_aout ) )
Sam Hocevar's avatar
 
Sam Hocevar committed
263
    {
264
        msg_Warn( p_aout, "cannot create buffer" );
Gildas Bazin's avatar
 
Gildas Bazin committed
265
        vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
266 267
        return( 1 );
    }
268

Gildas Bazin's avatar
 
Gildas Bazin committed
269 270
    vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );

Sam Hocevar's avatar
 
Sam Hocevar committed
271 272 273 274 275 276
    return( 0 );
}

/*****************************************************************************
 * aout_GetBufInfo: buffer status query
 *****************************************************************************
Gildas Bazin's avatar
 
Gildas Bazin committed
277 278
 * returns the number of bytes in the audio buffer that have not yet been
 * sent to the sound device.
Sam Hocevar's avatar
 
Sam Hocevar committed
279
 *****************************************************************************/
Sam Hocevar's avatar
 
Sam Hocevar committed
280
static int aout_GetBufInfo( aout_thread_t *p_aout, int i_buffer_limit )
Sam Hocevar's avatar
 
Sam Hocevar committed
281
{
Gildas Bazin's avatar
 
Gildas Bazin committed
282
    long l_play_position, l_notused, l_result;
Sam Hocevar's avatar
 
Sam Hocevar committed
283 284
    HRESULT dsresult;

Gildas Bazin's avatar
 
Gildas Bazin committed
285
    if( p_aout->p_sys->b_buffer_underflown )
Sam Hocevar's avatar
 
Sam Hocevar committed
286
    {
287
        msg_Warn( p_aout, "aout_GetBufInfo underflow" );
Sam Hocevar's avatar
 
Sam Hocevar committed
288
        return( i_buffer_limit );
Sam Hocevar's avatar
 
Sam Hocevar committed
289
    }
Gildas Bazin's avatar
 
Gildas Bazin committed
290 291

    dsresult = IDirectSoundBuffer_GetCurrentPosition(p_aout->p_sys->p_dsbuffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
292
                                                 &l_play_position, &l_notused);
Sam Hocevar's avatar
 
Sam Hocevar committed
293 294
    if( dsresult != DS_OK )
    {
295
        msg_Warn( p_aout, "aout_GetBufInfo cannot get current pos" );
Sam Hocevar's avatar
 
Sam Hocevar committed
296
        return( i_buffer_limit );
Sam Hocevar's avatar
 
Sam Hocevar committed
297 298
    }

Gildas Bazin's avatar
 
Gildas Bazin committed
299 300 301 302
    l_result = (p_aout->p_sys->l_write_position >= l_play_position) ?
      (p_aout->p_sys->l_write_position - l_play_position)
               : (p_aout->p_sys->l_buffer_size - l_play_position
                  + p_aout->p_sys->l_write_position);
Sam Hocevar's avatar
 
Sam Hocevar committed
303

Gildas Bazin's avatar
 
Gildas Bazin committed
304
#if 0
305
    msg_Dbg( p_aout, "aout_GetBufInfo: %i", i_result);
Gildas Bazin's avatar
 
Gildas Bazin committed
306
#endif
Gildas Bazin's avatar
 
Gildas Bazin committed
307
    return l_result;
Sam Hocevar's avatar
 
Sam Hocevar committed
308 309 310 311 312 313
}

/*****************************************************************************
 * aout_Play: play a sound buffer
 *****************************************************************************
 * This function writes a buffer of i_length bytes
Gildas Bazin's avatar
 
Gildas Bazin committed
314
 * Don't forget that DirectSound buffers are circular buffers.
Sam Hocevar's avatar
 
Sam Hocevar committed
315 316 317 318
 *****************************************************************************/
static void aout_Play( aout_thread_t *p_aout, byte_t *buffer, int i_size )
{
    VOID            *p_write_position, *p_start_buffer;
Gildas Bazin's avatar
 
Gildas Bazin committed
319
    long            l_bytes1, l_bytes2, l_play_position;
Sam Hocevar's avatar
 
Sam Hocevar committed
320 321
    HRESULT         dsresult;

Gildas Bazin's avatar
 
Gildas Bazin committed
322 323
    /* protect buffer access (because of DirectSoundThread) */
    vlc_mutex_lock( &p_aout->p_sys->buffer_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
324

Gildas Bazin's avatar
 
Gildas Bazin committed
325
    if( p_aout->p_sys->b_buffer_underflown )
Sam Hocevar's avatar
 
Sam Hocevar committed
326
    {
Gildas Bazin's avatar
 
Gildas Bazin committed
327 328 329 330
        /*  there has been an underflow so we need to play the new sample
         *  as soon as possible. This is why we query the play position */
        dsresult = IDirectSoundBuffer_GetCurrentPosition(
                                            p_aout->p_sys->p_dsbuffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
331 332
                                            &l_play_position,
                                            &p_aout->p_sys->l_write_position );
Gildas Bazin's avatar
 
Gildas Bazin committed
333 334
        if( dsresult != DS_OK )
        {
335
            msg_Warn( p_aout, "cannot get buffer position" );
Gildas Bazin's avatar
 
Gildas Bazin committed
336
            p_aout->p_sys->l_write_position = 0; 
Gildas Bazin's avatar
 
Gildas Bazin committed
337 338
        }

339
        msg_Warn( p_aout, "aout_Play underflow" );
Gildas Bazin's avatar
 
Gildas Bazin committed
340 341
        /* reinitialise the underflow detection counters */
        p_aout->p_sys->b_buffer_underflown = 0;
Gildas Bazin's avatar
 
Gildas Bazin committed
342
        p_aout->p_sys->l_data_written_from_beginning = 0;
Gildas Bazin's avatar
 
Gildas Bazin committed
343

Gildas Bazin's avatar
 
Gildas Bazin committed
344 345 346 347
#define WRITE_P  p_aout->p_sys->l_write_position
#define PLAY_P   l_play_position
#define BUF_SIZE p_aout->p_sys->l_buffer_size
        p_aout->p_sys->l_data_played_from_beginning = -(WRITE_P %(BUF_SIZE/2));
Gildas Bazin's avatar
 
Gildas Bazin committed
348 349
        if( PLAY_P < BUF_SIZE/2 && WRITE_P > BUF_SIZE/2 )
        {
Gildas Bazin's avatar
 
Gildas Bazin committed
350
            p_aout->p_sys->l_data_played_from_beginning -= (BUF_SIZE/2);
Gildas Bazin's avatar
 
Gildas Bazin committed
351 352 353
        }
        if( PLAY_P > BUF_SIZE/2 && WRITE_P < BUF_SIZE/2 )
        {
Gildas Bazin's avatar
 
Gildas Bazin committed
354
            p_aout->p_sys->l_data_played_from_beginning -= (BUF_SIZE/2);
Gildas Bazin's avatar
 
Gildas Bazin committed
355 356 357 358
        }        
#undef WRITE_P
#undef PLAY_P
#undef BUF_SIZE
Sam Hocevar's avatar
 
Sam Hocevar committed
359 360 361 362
    }

    /* Before copying anything, we have to lock the buffer */
    dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
363
                   p_aout->p_sys->l_write_position,  /* Offset of lock start */
Sam Hocevar's avatar
 
Sam Hocevar committed
364 365
                   i_size,                        /* Number of bytes to lock */
                   &p_write_position,               /* Address of lock start */
Gildas Bazin's avatar
 
Gildas Bazin committed
366
                   &l_bytes1,    /* Count of bytes locked before wrap around */
Sam Hocevar's avatar
 
Sam Hocevar committed
367
                   &p_start_buffer,        /* Buffer adress (if wrap around) */
Gildas Bazin's avatar
 
Gildas Bazin committed
368
                   &l_bytes2,            /* Count of bytes after wrap around */
Sam Hocevar's avatar
 
Sam Hocevar committed
369 370 371 372 373
                   0);                                              /* Flags */
    if( dsresult == DSERR_BUFFERLOST )
    {
        IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
        dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
374
                                            p_aout->p_sys->l_write_position,
Sam Hocevar's avatar
 
Sam Hocevar committed
375 376
                                            i_size,
                                            &p_write_position,
Gildas Bazin's avatar
 
Gildas Bazin committed
377
                                            &l_bytes1,
Sam Hocevar's avatar
 
Sam Hocevar committed
378
                                            &p_start_buffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
379
                                            &l_bytes2,
Sam Hocevar's avatar
 
Sam Hocevar committed
380 381 382 383 384
                                            0);

    }
    if( dsresult != DS_OK )
    {
385
        msg_Warn( p_aout, "aout_Play cannot lock buffer" );
Gildas Bazin's avatar
 
Gildas Bazin committed
386
        vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
387 388 389
        return;
    }

Gildas Bazin's avatar
 
Gildas Bazin committed
390
    /* Now do the actual memcpy (two memcpy because the buffer is circular) */
Gildas Bazin's avatar
 
Gildas Bazin committed
391
    memcpy( p_write_position, buffer, l_bytes1 );
Sam Hocevar's avatar
 
Sam Hocevar committed
392
    if( p_start_buffer != NULL )
Gildas Bazin's avatar
 
Gildas Bazin committed
393
    {
Gildas Bazin's avatar
 
Gildas Bazin committed
394
        memcpy( p_start_buffer, buffer + l_bytes1, l_bytes2 );
Gildas Bazin's avatar
 
Gildas Bazin committed
395
    }
Sam Hocevar's avatar
 
Sam Hocevar committed
396 397 398

    /* Now the data has been copied, unlock the buffer */
    IDirectSoundBuffer_Unlock( p_aout->p_sys->p_dsbuffer, 
Gildas Bazin's avatar
 
Gildas Bazin committed
399
            p_write_position, l_bytes1, p_start_buffer, l_bytes2 );
Sam Hocevar's avatar
 
Sam Hocevar committed
400 401

    /* Update the write position index of the buffer*/
Gildas Bazin's avatar
 
Gildas Bazin committed
402 403 404
    p_aout->p_sys->l_write_position += i_size;
    p_aout->p_sys->l_write_position %= p_aout->p_sys->l_buffer_size;
    p_aout->p_sys->l_data_written_from_beginning += i_size;
Gildas Bazin's avatar
 
Gildas Bazin committed
405 406

    vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422

    /* The play function has no effect if the buffer is already playing */
    dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer,
                                        0,                         /* Unused */
                                        0,                         /* Unused */
                                        DSBPLAY_LOOPING );          /* Flags */
    if( dsresult == DSERR_BUFFERLOST )
    {
        IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
        dsresult = IDirectSoundBuffer_Play( p_aout->p_sys->p_dsbuffer,
                                            0,                     /* Unused */
                                            0,                     /* Unused */
                                            DSBPLAY_LOOPING );      /* Flags */
    }
    if( dsresult != DS_OK )
    {
423
        msg_Warn( p_aout, "aout_Play cannot play buffer" );
Sam Hocevar's avatar
 
Sam Hocevar committed
424 425 426 427 428 429 430 431 432 433
        return;
    }

}

/*****************************************************************************
 * aout_Close: close the audio device
 *****************************************************************************/
static void aout_Close( aout_thread_t *p_aout )
{
Sam Hocevar's avatar
 
Sam Hocevar committed
434

435
    msg_Dbg( p_aout, "aout_Close" );
Sam Hocevar's avatar
 
Sam Hocevar committed
436

437
    /* kill the position notification thread, if any */
438
    vlc_object_detach_all( p_aout->p_sys->p_notif );
439 440 441 442 443 444
    if( p_aout->p_sys->p_notif->b_thread )
    {
        p_aout->p_sys->p_notif->b_die = 1;
        vlc_thread_join( p_aout->p_sys->p_notif );
    }
    vlc_object_destroy( p_aout->p_sys->p_notif );
Sam Hocevar's avatar
 
Sam Hocevar committed
445

Gildas Bazin's avatar
 
Gildas Bazin committed
446 447
    /* release the secondary buffer */
    DirectxDestroySecondaryBuffer( p_aout );
Sam Hocevar's avatar
 
Sam Hocevar committed
448 449 450 451 452 453 454 455 456 457 458 459 460 461 462

    /* then release the primary buffer */
    if( p_aout->p_sys->p_dsbuffer_primary != NULL )
    {
        IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer_primary );
        p_aout->p_sys->p_dsbuffer_primary = NULL;
    }  

    /* finally release the DirectSound object */
    if( p_aout->p_sys->p_dsobject != NULL )
    {
        IDirectSound_Release( p_aout->p_sys->p_dsobject );
        p_aout->p_sys->p_dsobject = NULL;
    }  
    
Sam Hocevar's avatar
 
Sam Hocevar committed
463 464 465 466 467 468 469
    /* free DSOUND.DLL */
    if( p_aout->p_sys->hdsound_dll != NULL )
    {
       FreeLibrary( p_aout->p_sys->hdsound_dll );
       p_aout->p_sys->hdsound_dll = NULL;
    }

Sam Hocevar's avatar
 
Sam Hocevar committed
470 471 472 473 474 475 476 477 478
    /* Close the Output. */
    if ( p_aout->p_sys != NULL )
    { 
        free( p_aout->p_sys );
        p_aout->p_sys = NULL;
    }
}

/*****************************************************************************
Sam Hocevar's avatar
 
Sam Hocevar committed
479 480 481 482 483 484 485 486 487 488
 * DirectxInitDSound
 *****************************************************************************
 *****************************************************************************/
static int DirectxInitDSound( aout_thread_t *p_aout )
{
    HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN);

    p_aout->p_sys->hdsound_dll = LoadLibrary("DSOUND.DLL");
    if( p_aout->p_sys->hdsound_dll == NULL )
    {
489
      msg_Warn( p_aout, "cannot open DSOUND.DLL" );
Sam Hocevar's avatar
 
Sam Hocevar committed
490 491 492 493 494 495 496 497
      return( 1 );
    }

    OurDirectSoundCreate = (void *)GetProcAddress( p_aout->p_sys->hdsound_dll,
                                                   "DirectSoundCreate" );

    if( OurDirectSoundCreate == NULL )
    {
498
      msg_Warn( p_aout, "GetProcAddress FAILED" );
Sam Hocevar's avatar
 
Sam Hocevar committed
499 500 501 502 503 504 505 506
      FreeLibrary( p_aout->p_sys->hdsound_dll );
      p_aout->p_sys->hdsound_dll = NULL;
      return( 1 );
    }

    /* Create the direct sound object */
    if( OurDirectSoundCreate(NULL, &p_aout->p_sys->p_dsobject, NULL) != DS_OK )
    {
507
        msg_Warn( p_aout, "cannot create a direct sound device" );
Sam Hocevar's avatar
 
Sam Hocevar committed
508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527
        p_aout->p_sys->p_dsobject = NULL;
        FreeLibrary( p_aout->p_sys->hdsound_dll );
        p_aout->p_sys->hdsound_dll = NULL;
        return( 1 );
    }

    /* Set DirectSound Cooperative level, ie what control we want over Windows
     * sound device. In our case, DSSCL_EXCLUSIVE means that we can modify the
     * settings of the primary buffer, but also that only the sound of our
     * application will be hearable when it will have the focus.
     * !!! (this is not really working as intended yet because to set the
     * cooperative level you need the window handle of your application, and
     * I don't know of any easy way to get it. Especially since we might play
     * sound without any video, and so what window handle should we use ???
     * The hack for now is to use the Desktop window handle - it seems to be
     * working */
    if( IDirectSound_SetCooperativeLevel(p_aout->p_sys->p_dsobject,
                                         GetDesktopWindow(),
                                         DSSCL_EXCLUSIVE) )
    {
528
        msg_Warn( p_aout, "cannot set direct sound cooperative level" );
Sam Hocevar's avatar
 
Sam Hocevar committed
529 530 531 532 533 534 535
    }

    return( 0 );
}

/*****************************************************************************
 * DirectxCreateSecondaryBuffer
Sam Hocevar's avatar
 
Sam Hocevar committed
536 537 538 539 540 541 542 543 544 545
 *****************************************************************************
 * This function creates the buffer we'll use to play audio.
 * In DirectSound there are two kinds of buffers:
 * - the primary buffer: which is the actual buffer that the soundcard plays
 * - the secondary buffer(s): these buffers are the one actually used by
 *    applications and DirectSound takes care of mixing them into the primary.
 *
 * Once you create a secondary buffer, you cannot change its format anymore so
 * you have to release the current and create another one.
 *****************************************************************************/
Sam Hocevar's avatar
 
Sam Hocevar committed
546
static int DirectxCreateSecondaryBuffer( aout_thread_t *p_aout )
Sam Hocevar's avatar
 
Sam Hocevar committed
547
{
Gildas Bazin's avatar
 
Gildas Bazin committed
548 549 550
    WAVEFORMATEX         waveformat;
    DSBUFFERDESC         dsbdesc;
    DSBCAPS              dsbcaps;
Sam Hocevar's avatar
 
Sam Hocevar committed
551 552 553

    /* First set the buffer format */
    memset(&waveformat, 0, sizeof(WAVEFORMATEX)); 
Gildas Bazin's avatar
 
Gildas Bazin committed
554 555
    waveformat.wFormatTag      = WAVE_FORMAT_PCM; 
    waveformat.nChannels       = p_aout->i_channels; 
Sam Hocevar's avatar
 
Sam Hocevar committed
556
    waveformat.nSamplesPerSec  = p_aout->i_rate; 
Gildas Bazin's avatar
 
Gildas Bazin committed
557 558
    waveformat.wBitsPerSample  = 16; 
    waveformat.nBlockAlign     = waveformat.wBitsPerSample / 8 *
Sam Hocevar's avatar
 
Sam Hocevar committed
559 560 561 562 563 564 565 566
                                 waveformat.nChannels;
    waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec *
                                     waveformat.nBlockAlign;

    /* Then fill in the descriptor */
    memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); 
    dsbdesc.dwSize = sizeof(DSBUFFERDESC); 
    dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2/* Better position accuracy */
Gildas Bazin's avatar
 
Gildas Bazin committed
567
                    | DSBCAPS_CTRLPOSITIONNOTIFY     /* We need notification */
Sam Hocevar's avatar
 
Sam Hocevar committed
568
                    | DSBCAPS_GLOBALFOCUS;      /* Allows background playing */
Gildas Bazin's avatar
 
Gildas Bazin committed
569
    dsbdesc.dwBufferBytes = waveformat.nAvgBytesPerSec * 2;  /* 2 sec buffer */
Sam Hocevar's avatar
 
Sam Hocevar committed
570 571 572 573 574 575 576
    dsbdesc.lpwfxFormat = &waveformat; 
 
    if( IDirectSound_CreateSoundBuffer( p_aout->p_sys->p_dsobject,
                                        &dsbdesc,
                                        &p_aout->p_sys->p_dsbuffer,
                                        NULL) != DS_OK )
    {
577
        msg_Warn( p_aout, "cannot create direct sound secondary buffer" );
Sam Hocevar's avatar
 
Sam Hocevar committed
578 579 580 581 582 583
        p_aout->p_sys->p_dsbuffer = NULL;
        return( 1 );
    }

    /* backup the size of the secondary sound buffer */
    memset(&dsbcaps, 0, sizeof(DSBCAPS)); 
Sam Hocevar's avatar
 
Sam Hocevar committed
584
    dsbcaps.dwSize = sizeof(DSBCAPS);
Sam Hocevar's avatar
 
Sam Hocevar committed
585
    IDirectSoundBuffer_GetCaps( p_aout->p_sys->p_dsbuffer, &dsbcaps  );
Gildas Bazin's avatar
 
Gildas Bazin committed
586 587
    p_aout->p_sys->l_buffer_size = dsbcaps.dwBufferBytes;
    p_aout->p_sys->l_write_position = 0;
Gildas Bazin's avatar
 
Gildas Bazin committed
588

589 590
    msg_Dbg( p_aout, "DirectxCreateSecondaryBuffer: %li",
                     p_aout->p_sys->l_buffer_size );
Sam Hocevar's avatar
 
Sam Hocevar committed
591

Gildas Bazin's avatar
 
Gildas Bazin committed
592 593
    /* Now the secondary buffer is created, we need to setup its position
     * notification */
594 595
    p_aout->p_sys->p_notif->p_events[0].dwOffset = 0;    /* notif position */
    p_aout->p_sys->p_notif->p_events[1].dwOffset = dsbcaps.dwBufferBytes / 2;
Gildas Bazin's avatar
 
Gildas Bazin committed
596 597 598 599 600 601

    /* Get the IDirectSoundNotify interface */
    if FAILED( IDirectSoundBuffer_QueryInterface( p_aout->p_sys->p_dsbuffer,
                                                  &IID_IDirectSoundNotify,
                                       (LPVOID *)&p_aout->p_sys->p_dsnotify ) )
    {
602
        msg_Warn( p_aout, "cannot get Notify interface" );
Gildas Bazin's avatar
 
Gildas Bazin committed
603 604 605 606 607 608 609 610
        /* Go on anyway */
        p_aout->p_sys->p_dsnotify = NULL;
        return( 0 );
    }
        
    if FAILED( IDirectSoundNotify_SetNotificationPositions(
                                        p_aout->p_sys->p_dsnotify,
                                        2,
611
                                        p_aout->p_sys->p_notif->p_events ) )
Gildas Bazin's avatar
 
Gildas Bazin committed
612
    {
613
        msg_Warn( p_aout, "cannot set position Notification" );
Gildas Bazin's avatar
 
Gildas Bazin committed
614 615 616
        /* Go on anyway */
    }

Sam Hocevar's avatar
 
Sam Hocevar committed
617 618
    return( 0 );
}
Gildas Bazin's avatar
 
Gildas Bazin committed
619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653

/*****************************************************************************
 * DirectxCreateSecondaryBuffer
 *****************************************************************************
 * This function destroy the secondary buffer.
 *****************************************************************************/
static void DirectxDestroySecondaryBuffer( aout_thread_t *p_aout )
{
    /* make sure the buffer isn't playing */
    if( p_aout->p_sys->p_dsbuffer != NULL )
    {
        IDirectSoundBuffer_Stop( p_aout->p_sys->p_dsbuffer );
    }

    if( p_aout->p_sys->p_dsnotify != NULL )
    {
        IDirectSoundNotify_Release( p_aout->p_sys->p_dsnotify );
        p_aout->p_sys->p_dsnotify = NULL;
    }

    if( p_aout->p_sys->p_dsbuffer != NULL )
    {
        IDirectSoundBuffer_Release( p_aout->p_sys->p_dsbuffer );
        p_aout->p_sys->p_dsbuffer = NULL;
    }
}

/*****************************************************************************
 * DirectSoundThread: this thread will capture play notification events. 
 *****************************************************************************
 * As Direct Sound uses circular buffers, we need to use event notification to
 * manage them.
 * Using event notification implies blocking the thread until the event is
 * signaled so we really need to run this in a separate thread.
 *****************************************************************************/
654
static void DirectSoundThread( notification_thread_t *p_notif )
Gildas Bazin's avatar
 
Gildas Bazin committed
655 656 657
{
    HANDLE  notification_events[2];
    VOID    *p_write_position, *p_start_buffer;
Gildas Bazin's avatar
 
Gildas Bazin committed
658
    long    l_bytes1, l_bytes2;
Gildas Bazin's avatar
 
Gildas Bazin committed
659
    HRESULT dsresult;
Gildas Bazin's avatar
 
Gildas Bazin committed
660
    long    l_buffer_size, l_play_position, l_data_in_buffer;
Gildas Bazin's avatar
 
Gildas Bazin committed
661

662 663 664 665 666 667 668 669
    aout_thread_t *p_aout = p_notif->p_aout;

#define P_EVENTS p_aout->p_sys->p_notif->p_events
    notification_events[0] = P_EVENTS[0].hEventNotify;
    notification_events[1] = P_EVENTS[1].hEventNotify;

    /* Tell the main thread that we are ready */
    vlc_thread_ready( p_notif );
Gildas Bazin's avatar
 
Gildas Bazin committed
670 671 672 673 674

    /* this thread must be high-priority */
    if( !SetThreadPriority( GetCurrentThread(),
                            THREAD_PRIORITY_ABOVE_NORMAL ) )
    {
675
        msg_Warn( p_notif, "DirectSoundThread could not renice itself" );
Gildas Bazin's avatar
 
Gildas Bazin committed
676 677
    }

678
    msg_Dbg( p_notif, "DirectSoundThread ready" );
Gildas Bazin's avatar
 
Gildas Bazin committed
679

680
    while( !p_notif->b_die )
Gildas Bazin's avatar
 
Gildas Bazin committed
681 682
    {
        /* wait for the position notification */
Gildas Bazin's avatar
 
Gildas Bazin committed
683
        l_play_position = WaitForMultipleObjects( 2, notification_events,
Gildas Bazin's avatar
 
Gildas Bazin committed
684 685 686
                                                  0, INFINITE ); 
        vlc_mutex_lock( &p_aout->p_sys->buffer_lock );

687
        if( p_notif->b_die )
Gildas Bazin's avatar
 
Gildas Bazin committed
688 689 690 691 692
        {
            break;
        }

        /* check for buffer underflow (bodge for wrap around) */
Gildas Bazin's avatar
 
Gildas Bazin committed
693 694 695 696 697
        l_buffer_size = p_aout->p_sys->l_buffer_size;
        l_play_position = (l_play_position - WAIT_OBJECT_0) * l_buffer_size/2;
        p_aout->p_sys->l_data_played_from_beginning += (l_buffer_size/2);
        l_data_in_buffer = p_aout->p_sys->l_data_written_from_beginning -
                               p_aout->p_sys->l_data_played_from_beginning; 
Gildas Bazin's avatar
 
Gildas Bazin committed
698 699

        /* detect wrap-around */
Gildas Bazin's avatar
 
Gildas Bazin committed
700
        if( l_data_in_buffer < (-l_buffer_size/2) )
Gildas Bazin's avatar
 
Gildas Bazin committed
701
        {
702 703
            msg_Dbg( p_notif, "DirectSoundThread wrap around: %li",
                              l_data_in_buffer );
Gildas Bazin's avatar
 
Gildas Bazin committed
704
            l_data_in_buffer += l_buffer_size;
Gildas Bazin's avatar
 
Gildas Bazin committed
705 706 707
        }

        /* detect underflow */
Gildas Bazin's avatar
 
Gildas Bazin committed
708
        if( l_data_in_buffer <= 0 )
Gildas Bazin's avatar
 
Gildas Bazin committed
709
        {
710
            msg_Warn( p_notif,
711
                      "DirectSoundThread underflow: %li", l_data_in_buffer );
Gildas Bazin's avatar
 
Gildas Bazin committed
712
            p_aout->p_sys->b_buffer_underflown = 1;
Gildas Bazin's avatar
 
Gildas Bazin committed
713 714 715 716
            p_aout->p_sys->l_write_position =
                  (l_play_position + l_buffer_size/2) % l_buffer_size;
            l_data_in_buffer = l_buffer_size / 2;
            p_aout->p_sys->l_data_played_from_beginning -= (l_buffer_size/2);
Gildas Bazin's avatar
 
Gildas Bazin committed
717 718 719 720 721 722 723
        }


        /* Clear the data which has already been played */

        /* Before copying anything, we have to lock the buffer */
        dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
724 725
                   p_aout->p_sys->l_write_position,  /* Offset of lock start */
                   l_buffer_size - l_data_in_buffer,      /* Number of bytes */
Gildas Bazin's avatar
 
Gildas Bazin committed
726
                   &p_write_position,               /* Address of lock start */
Gildas Bazin's avatar
 
Gildas Bazin committed
727
                   &l_bytes1,    /* Count of bytes locked before wrap around */
Gildas Bazin's avatar
 
Gildas Bazin committed
728
                   &p_start_buffer,        /* Buffer adress (if wrap around) */
Gildas Bazin's avatar
 
Gildas Bazin committed
729
                   &l_bytes2,            /* Count of bytes after wrap around */
Gildas Bazin's avatar
 
Gildas Bazin committed
730 731 732 733 734
                   0);                                              /* Flags */
        if( dsresult == DSERR_BUFFERLOST )
        {
            IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
            dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
735 736
                                          p_aout->p_sys->l_write_position,
                                          l_buffer_size - l_data_in_buffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
737
                                          &p_write_position,
Gildas Bazin's avatar
 
Gildas Bazin committed
738
                                          &l_bytes1,
Gildas Bazin's avatar
 
Gildas Bazin committed
739
                                          &p_start_buffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
740
                                          &l_bytes2,
Gildas Bazin's avatar
 
Gildas Bazin committed
741 742 743 744
                                          0);
        }
        if( dsresult != DS_OK )
        {
745
            msg_Warn( p_notif, "aout_Play cannot lock buffer" );
Gildas Bazin's avatar
 
Gildas Bazin committed
746 747 748 749 750
            vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );
            return;
        }

        /* Now do the actual memcpy (two because the buffer is circular) */
Gildas Bazin's avatar
 
Gildas Bazin committed
751
        memset( p_write_position, 0, l_bytes1 );
Gildas Bazin's avatar
 
Gildas Bazin committed
752 753
        if( p_start_buffer != NULL )
        {
Gildas Bazin's avatar
 
Gildas Bazin committed
754
            memset( p_start_buffer, 0, l_bytes2 );
Gildas Bazin's avatar
 
Gildas Bazin committed
755 756 757 758
        }

        /* Now the data has been copied, unlock the buffer */
        IDirectSoundBuffer_Unlock( p_aout->p_sys->p_dsbuffer, 
Gildas Bazin's avatar
 
Gildas Bazin committed
759
                        p_write_position, l_bytes1, p_start_buffer, l_bytes2 );
Gildas Bazin's avatar
 
Gildas Bazin committed
760 761 762 763 764 765 766 767 768

        vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );

    }

    /* free the events */
    CloseHandle( notification_events[0] );
    CloseHandle( notification_events[1] );

769
    msg_Dbg( p_notif, "DirectSoundThread exiting" );
Gildas Bazin's avatar
 
Gildas Bazin committed
770 771

}