aout_directx.c 30.8 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
Sam Hocevar's avatar
 
Sam Hocevar committed
5
 * $Id: aout_directx.c,v 1.20 2002/02/24 22:06:50 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 33
#include <stdio.h>                                           /* "intf_msg.h" */
#include <stdlib.h>                            /* calloc(), malloc(), free() */

Sam Hocevar's avatar
 
Sam Hocevar committed
34
#include <videolan/vlc.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 39 40

#include "audio_output.h"                                   /* aout_thread_t */

Gildas Bazin's avatar
 
Gildas Bazin committed
41 42 43 44 45 46 47 48
/*****************************************************************************
 * 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);

Sam Hocevar's avatar
 
Sam Hocevar committed
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
/*****************************************************************************
 * 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.
 *****************************************************************************/

typedef struct aout_sys_s
{
    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
67 68
    LPDIRECTSOUNDNOTIFY p_dsnotify;         /* the position notify interface */

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

Sam Hocevar's avatar
 
Sam Hocevar committed
71 72
    int i_buffer_size;                        /* secondary sound buffer size */
    int i_write_position;              /* next write position for the buffer */
Sam Hocevar's avatar
 
Sam Hocevar committed
73

Gildas Bazin's avatar
 
Gildas Bazin committed
74
    volatile boolean_t b_buffer_underflown;    /* buffer underflow detection */
Sam Hocevar's avatar
 
Sam Hocevar committed
75 76
    volatile int i_data_played_from_beginning;    /* for underflow detection */
    volatile int i_data_written_from_beginning;   /* for underflow detection */
Gildas Bazin's avatar
 
Gildas Bazin committed
77 78 79 80 81 82 83 84

    vlc_mutex_t buffer_lock;                            /* audio buffer lock */

    vlc_thread_t notification_thread_id;             /* DirectSoundThread id */

    DSBPOSITIONNOTIFY notification_events[2];    /* play notification events */

    boolean_t b_notification_thread_die;          /* flag to kill the thread */
Sam Hocevar's avatar
 
Sam Hocevar committed
85 86 87 88 89 90 91 92

} aout_sys_t;

/*****************************************************************************
 * Local prototypes.
 *****************************************************************************/
static int     aout_Open        ( aout_thread_t *p_aout );
static int     aout_SetFormat   ( aout_thread_t *p_aout );
Sam Hocevar's avatar
 
Sam Hocevar committed
93
static int     aout_GetBufInfo  ( aout_thread_t *p_aout, int i_buffer_info );
Sam Hocevar's avatar
 
Sam Hocevar committed
94 95 96 97
static void    aout_Play        ( aout_thread_t *p_aout,
                                  byte_t *buffer, int i_size );
static void    aout_Close       ( aout_thread_t *p_aout );

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

/*****************************************************************************
 * 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
126 127

    intf_WarnMsg( 3, "aout: DirectX aout_Open ");
Sam Hocevar's avatar
 
Sam Hocevar committed
128 129 130 131 132 133 134 135 136 137 138 139 140 141

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

    if( p_aout->p_sys == NULL )
    {
        intf_ErrMsg( "aout error: %s", strerror(ENOMEM) );
        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
142 143
    p_aout->p_sys->p_dsnotify = NULL;
    p_aout->p_sys->b_notification_thread_die = 0;
Sam Hocevar's avatar
 
Sam Hocevar committed
144 145
    p_aout->p_sys->i_data_written_from_beginning = 0;
    p_aout->p_sys->i_data_played_from_beginning = 0;
Gildas Bazin's avatar
 
Gildas Bazin committed
146 147
    vlc_mutex_init( &p_aout->p_sys->buffer_lock );

Sam Hocevar's avatar
 
Sam Hocevar committed
148

Sam Hocevar's avatar
 
Sam Hocevar committed
149 150
    /* Initialise DirectSound */
    if( DirectxInitDSound( p_aout ) )
Sam Hocevar's avatar
 
Sam Hocevar committed
151
    {
Sam Hocevar's avatar
 
Sam Hocevar committed
152
        intf_WarnMsg( 3, "aout: can't initialise DirectSound ");
Sam Hocevar's avatar
 
Sam Hocevar committed
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
        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;
    intf_WarnMsg( 3, "aout: Create direct sound primary buffer ");
    dsresult = IDirectSound_CreateSoundBuffer(p_aout->p_sys->p_dsobject,
                                            &dsbuffer_desc,
                                            &p_aout->p_sys->p_dsbuffer_primary,
                                            NULL);
    if( dsresult != DS_OK )
    {
        intf_WarnMsg( 3, "aout: can't create direct sound primary buffer ");
        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
175
    /* Now we need to setup DirectSound play notification */
Sam Hocevar's avatar
 
Sam Hocevar committed
176

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

    /* then launch the notification thread */
    intf_WarnMsg( 3, "aout: aout_Open creating DirectSoundThread" );
    if( vlc_thread_create( &p_aout->p_sys->notification_thread_id,
                           "DirectSound Notification Thread",
                           (void *) DirectSoundThread, (void *) p_aout) )
Sam Hocevar's avatar
 
Sam Hocevar committed
188
    {
Gildas Bazin's avatar
 
Gildas Bazin committed
189 190 191
        intf_ErrMsg( "aout error: can't create DirectSoundThread" );
        intf_ErrMsg("aout error: %s", strerror(ENOMEM));
        /* Let's go on anyway */
Sam Hocevar's avatar
 
Sam Hocevar committed
192 193 194 195 196 197 198 199 200 201 202 203 204 205
    }

    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
206 207
    HRESULT       dsresult;
    WAVEFORMATEX  *p_waveformat;
Sam Hocevar's avatar
 
Sam Hocevar committed
208
    unsigned int  i_size_struct;
Sam Hocevar's avatar
 
Sam Hocevar committed
209

Sam Hocevar's avatar
 
Sam Hocevar committed
210 211
    intf_WarnMsg( 3, "aout: DirectX aout_SetFormat ");

Gildas Bazin's avatar
 
Gildas Bazin committed
212 213 214 215 216 217
    /* 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
218
    {
Gildas Bazin's avatar
 
Gildas Bazin committed
219 220 221 222 223
        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
224 225
    }

Gildas Bazin's avatar
 
Gildas Bazin committed
226 227 228 229
    if( dsresult == DS_OK )
    {
        /* Here we'll change the format */
        p_waveformat->nChannels        = 2; 
Sam Hocevar's avatar
 
Sam Hocevar committed
230 231
        p_waveformat->nSamplesPerSec   = (p_aout->i_rate < 44100) ? 44100
                                             : p_aout->i_rate; 
Gildas Bazin's avatar
 
Gildas Bazin committed
232 233 234 235 236 237 238 239 240 241 242 243
        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 );
    }
    else intf_WarnMsg( 3, "aout: can't get primary buffer format" );

Sam Hocevar's avatar
 
Sam Hocevar committed
244
    if( dsresult != DS_OK )
Gildas Bazin's avatar
 
Gildas Bazin committed
245 246 247 248 249 250 251 252 253 254 255 256
        intf_WarnMsg( 3, "aout: can't set primary buffer format" );


    /* 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
257
    {
Sam Hocevar's avatar
 
Sam Hocevar committed
258
        intf_WarnMsg( 3, "aout: DirectX aout_SetFormat cannot create buffer");
Gildas Bazin's avatar
 
Gildas Bazin committed
259
        vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
260 261
        return( 1 );
    }
262

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

Sam Hocevar's avatar
 
Sam Hocevar committed
265 266 267 268 269 270
    return( 0 );
}

/*****************************************************************************
 * aout_GetBufInfo: buffer status query
 *****************************************************************************
Gildas Bazin's avatar
 
Gildas Bazin committed
271 272
 * 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
273
 *****************************************************************************/
Sam Hocevar's avatar
 
Sam Hocevar committed
274
static int aout_GetBufInfo( aout_thread_t *p_aout, int i_buffer_limit )
Sam Hocevar's avatar
 
Sam Hocevar committed
275
{
Sam Hocevar's avatar
 
Sam Hocevar committed
276
    int i_play_position, i_notused, i_result;
Sam Hocevar's avatar
 
Sam Hocevar committed
277 278
    HRESULT dsresult;

Gildas Bazin's avatar
 
Gildas Bazin committed
279
    if( p_aout->p_sys->b_buffer_underflown )
Sam Hocevar's avatar
 
Sam Hocevar committed
280
    {
Gildas Bazin's avatar
 
Gildas Bazin committed
281
        intf_WarnMsg( 3, "aout: DirectX aout_GetBufInfo underflow");
Sam Hocevar's avatar
 
Sam Hocevar committed
282
        return( i_buffer_limit );
Sam Hocevar's avatar
 
Sam Hocevar committed
283
    }
Gildas Bazin's avatar
 
Gildas Bazin committed
284 285

    dsresult = IDirectSoundBuffer_GetCurrentPosition(p_aout->p_sys->p_dsbuffer,
Sam Hocevar's avatar
 
Sam Hocevar committed
286
                                                 &i_play_position, &i_notused);
Sam Hocevar's avatar
 
Sam Hocevar committed
287 288
    if( dsresult != DS_OK )
    {
Sam Hocevar's avatar
 
Sam Hocevar committed
289
        intf_WarnMsg(3,"aout: DirectX aout_GetBufInfo cannot get current pos");
Sam Hocevar's avatar
 
Sam Hocevar committed
290
        return( i_buffer_limit );
Sam Hocevar's avatar
 
Sam Hocevar committed
291 292
    }

Sam Hocevar's avatar
 
Sam Hocevar committed
293 294 295 296
    i_result = (p_aout->p_sys->i_write_position >= i_play_position) ?
      (p_aout->p_sys->i_write_position - i_play_position)
               : (p_aout->p_sys->i_buffer_size - i_play_position
                  + p_aout->p_sys->i_write_position);
Sam Hocevar's avatar
 
Sam Hocevar committed
297

Gildas Bazin's avatar
 
Gildas Bazin committed
298
#if 0
Sam Hocevar's avatar
 
Sam Hocevar committed
299
    intf_WarnMsg( 3, "aout: DirectX aout_GetBufInfo: %i", i_result);
Gildas Bazin's avatar
 
Gildas Bazin committed
300
#endif
Sam Hocevar's avatar
 
Sam Hocevar committed
301
    return i_result;
Sam Hocevar's avatar
 
Sam Hocevar committed
302 303 304 305 306 307
}

/*****************************************************************************
 * aout_Play: play a sound buffer
 *****************************************************************************
 * This function writes a buffer of i_length bytes
Gildas Bazin's avatar
 
Gildas Bazin committed
308
 * Don't forget that DirectSound buffers are circular buffers.
Sam Hocevar's avatar
 
Sam Hocevar committed
309 310 311 312
 *****************************************************************************/
static void aout_Play( aout_thread_t *p_aout, byte_t *buffer, int i_size )
{
    VOID            *p_write_position, *p_start_buffer;
Sam Hocevar's avatar
 
Sam Hocevar committed
313
    int             i_bytes1, i_bytes2, i_play_position;
Sam Hocevar's avatar
 
Sam Hocevar committed
314 315
    HRESULT         dsresult;

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

Gildas Bazin's avatar
 
Gildas Bazin committed
319
    if( p_aout->p_sys->b_buffer_underflown )
Sam Hocevar's avatar
 
Sam Hocevar committed
320
    {
Gildas Bazin's avatar
 
Gildas Bazin committed
321 322 323 324
        /*  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,
Sam Hocevar's avatar
 
Sam Hocevar committed
325 326
                                            &i_play_position,
                                            &p_aout->p_sys->i_write_position );
Gildas Bazin's avatar
 
Gildas Bazin committed
327 328 329
        if( dsresult != DS_OK )
        {
            intf_WarnMsg( 3, "aout: aout_Play can'get buffer position");
Sam Hocevar's avatar
 
Sam Hocevar committed
330
            p_aout->p_sys->i_write_position = 0; 
Gildas Bazin's avatar
 
Gildas Bazin committed
331 332
        }

Gildas Bazin's avatar
 
Gildas Bazin committed
333
        intf_WarnMsg( 3, "aout: aout_Play underflow");
Gildas Bazin's avatar
 
Gildas Bazin committed
334 335
        /* reinitialise the underflow detection counters */
        p_aout->p_sys->b_buffer_underflown = 0;
Sam Hocevar's avatar
 
Sam Hocevar committed
336
        p_aout->p_sys->i_data_written_from_beginning = 0;
Gildas Bazin's avatar
 
Gildas Bazin committed
337

Sam Hocevar's avatar
 
Sam Hocevar committed
338 339 340 341
#define WRITE_P  p_aout->p_sys->i_write_position
#define PLAY_P   i_play_position
#define BUF_SIZE p_aout->p_sys->i_buffer_size
        p_aout->p_sys->i_data_played_from_beginning = -(WRITE_P %(BUF_SIZE/2));
Gildas Bazin's avatar
 
Gildas Bazin committed
342 343
        if( PLAY_P < BUF_SIZE/2 && WRITE_P > BUF_SIZE/2 )
        {
Sam Hocevar's avatar
 
Sam Hocevar committed
344
            p_aout->p_sys->i_data_played_from_beginning -= (BUF_SIZE/2);
Gildas Bazin's avatar
 
Gildas Bazin committed
345 346 347
        }
        if( PLAY_P > BUF_SIZE/2 && WRITE_P < BUF_SIZE/2 )
        {
Sam Hocevar's avatar
 
Sam Hocevar committed
348
            p_aout->p_sys->i_data_played_from_beginning -= (BUF_SIZE/2);
Gildas Bazin's avatar
 
Gildas Bazin committed
349 350 351 352
        }        
#undef WRITE_P
#undef PLAY_P
#undef BUF_SIZE
Sam Hocevar's avatar
 
Sam Hocevar committed
353 354 355 356
    }

    /* Before copying anything, we have to lock the buffer */
    dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
Sam Hocevar's avatar
 
Sam Hocevar committed
357
                   p_aout->p_sys->i_write_position,  /* Offset of lock start */
Sam Hocevar's avatar
 
Sam Hocevar committed
358 359
                   i_size,                        /* Number of bytes to lock */
                   &p_write_position,               /* Address of lock start */
Sam Hocevar's avatar
 
Sam Hocevar committed
360
                   &i_bytes1,    /* Count of bytes locked before wrap around */
Sam Hocevar's avatar
 
Sam Hocevar committed
361
                   &p_start_buffer,        /* Buffer adress (if wrap around) */
Sam Hocevar's avatar
 
Sam Hocevar committed
362
                   &i_bytes2,            /* Count of bytes after wrap around */
Sam Hocevar's avatar
 
Sam Hocevar committed
363 364 365 366 367
                   0);                                              /* Flags */
    if( dsresult == DSERR_BUFFERLOST )
    {
        IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
        dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
Sam Hocevar's avatar
 
Sam Hocevar committed
368
                                            p_aout->p_sys->i_write_position,
Sam Hocevar's avatar
 
Sam Hocevar committed
369 370
                                            i_size,
                                            &p_write_position,
Sam Hocevar's avatar
 
Sam Hocevar committed
371
                                            &i_bytes1,
Sam Hocevar's avatar
 
Sam Hocevar committed
372
                                            &p_start_buffer,
Sam Hocevar's avatar
 
Sam Hocevar committed
373
                                            &i_bytes2,
Sam Hocevar's avatar
 
Sam Hocevar committed
374 375 376 377 378
                                            0);

    }
    if( dsresult != DS_OK )
    {
Sam Hocevar's avatar
 
Sam Hocevar committed
379
        intf_WarnMsg( 3, "aout: DirectX aout_Play can't lock buffer");
Gildas Bazin's avatar
 
Gildas Bazin committed
380
        vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
381 382 383
        return;
    }

Gildas Bazin's avatar
 
Gildas Bazin committed
384
    /* Now do the actual memcpy (two memcpy because the buffer is circular) */
Sam Hocevar's avatar
 
Sam Hocevar committed
385
    memcpy( p_write_position, buffer, i_bytes1 );
Sam Hocevar's avatar
 
Sam Hocevar committed
386
    if( p_start_buffer != NULL )
Gildas Bazin's avatar
 
Gildas Bazin committed
387
    {
Sam Hocevar's avatar
 
Sam Hocevar committed
388
        memcpy( p_start_buffer, buffer + i_bytes1, i_bytes2 );
Gildas Bazin's avatar
 
Gildas Bazin committed
389
    }
Sam Hocevar's avatar
 
Sam Hocevar committed
390 391 392

    /* Now the data has been copied, unlock the buffer */
    IDirectSoundBuffer_Unlock( p_aout->p_sys->p_dsbuffer, 
Sam Hocevar's avatar
 
Sam Hocevar committed
393
            p_write_position, i_bytes1, p_start_buffer, i_bytes2 );
Sam Hocevar's avatar
 
Sam Hocevar committed
394 395

    /* Update the write position index of the buffer*/
Sam Hocevar's avatar
 
Sam Hocevar committed
396 397 398
    p_aout->p_sys->i_write_position += i_size;
    p_aout->p_sys->i_write_position %= p_aout->p_sys->i_buffer_size;
    p_aout->p_sys->i_data_written_from_beginning += i_size;
Gildas Bazin's avatar
 
Gildas Bazin committed
399 400

    vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416

    /* 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 )
    {
Sam Hocevar's avatar
 
Sam Hocevar committed
417
        intf_WarnMsg( 3, "aout: DirectX aout_Play can't play buffer");
Sam Hocevar's avatar
 
Sam Hocevar committed
418 419 420 421 422 423 424 425 426 427
        return;
    }

}

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

    intf_WarnMsg( 3, "aout: DirectX aout_Close ");

Gildas Bazin's avatar
 
Gildas Bazin committed
431 432 433 434 435
    /* kill the position notification thread */
    p_aout->p_sys->b_notification_thread_die = 1;
    SetEvent( p_aout->p_sys->notification_events[0].hEventNotify );
    vlc_thread_join( p_aout->p_sys->notification_thread_id );
    vlc_mutex_destroy( &p_aout->p_sys->buffer_lock );
Sam Hocevar's avatar
 
Sam Hocevar committed
436

Gildas Bazin's avatar
 
Gildas Bazin committed
437 438
    /* release the secondary buffer */
    DirectxDestroySecondaryBuffer( p_aout );
Sam Hocevar's avatar
 
Sam Hocevar committed
439 440 441 442 443 444 445 446 447 448 449 450 451 452 453

    /* 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
454 455 456 457 458 459 460
    /* 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
461 462 463 464 465 466 467 468 469
    /* 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
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526
 * 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 )
    {
      intf_WarnMsg( 3, "aout: can't open DSOUND.DLL ");
      return( 1 );
    }

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

    if( OurDirectSoundCreate == NULL )
    {
      intf_WarnMsg( 3, "aout: GetProcAddress FAILED ");
      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 )
    {
        intf_WarnMsg( 3, "aout: can't create a direct sound device ");
        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) )
    {
        intf_WarnMsg( 3, "aout: can't set direct sound cooperative level ");
    }

    return( 0 );
}

/*****************************************************************************
 * DirectxCreateSecondaryBuffer
Sam Hocevar's avatar
 
Sam Hocevar committed
527 528 529 530 531 532 533 534 535 536
 *****************************************************************************
 * 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
537
static int DirectxCreateSecondaryBuffer( aout_thread_t *p_aout )
Sam Hocevar's avatar
 
Sam Hocevar committed
538
{
Gildas Bazin's avatar
 
Gildas Bazin committed
539 540 541
    WAVEFORMATEX         waveformat;
    DSBUFFERDESC         dsbdesc;
    DSBCAPS              dsbcaps;
Sam Hocevar's avatar
 
Sam Hocevar committed
542 543 544

    /* First set the buffer format */
    memset(&waveformat, 0, sizeof(WAVEFORMATEX)); 
Gildas Bazin's avatar
 
Gildas Bazin committed
545 546
    waveformat.wFormatTag      = WAVE_FORMAT_PCM; 
    waveformat.nChannels       = p_aout->i_channels; 
Sam Hocevar's avatar
 
Sam Hocevar committed
547
    waveformat.nSamplesPerSec  = p_aout->i_rate; 
Gildas Bazin's avatar
 
Gildas Bazin committed
548 549
    waveformat.wBitsPerSample  = 16; 
    waveformat.nBlockAlign     = waveformat.wBitsPerSample / 8 *
Sam Hocevar's avatar
 
Sam Hocevar committed
550 551 552 553 554 555 556 557
                                 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
558
                    | DSBCAPS_CTRLPOSITIONNOTIFY     /* We need notification */
Sam Hocevar's avatar
 
Sam Hocevar committed
559
                    | DSBCAPS_GLOBALFOCUS;      /* Allows background playing */
Gildas Bazin's avatar
 
Gildas Bazin committed
560
    dsbdesc.dwBufferBytes = waveformat.nAvgBytesPerSec * 2;  /* 2 sec buffer */
Sam Hocevar's avatar
 
Sam Hocevar committed
561 562 563 564 565 566 567 568 569 570 571 572 573 574
    dsbdesc.lpwfxFormat = &waveformat; 
 
    if( IDirectSound_CreateSoundBuffer( p_aout->p_sys->p_dsobject,
                                        &dsbdesc,
                                        &p_aout->p_sys->p_dsbuffer,
                                        NULL) != DS_OK )
    {
        intf_WarnMsg( 3, "aout: can't create direct sound secondary buffer ");
        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
575
    dsbcaps.dwSize = sizeof(DSBCAPS);
Sam Hocevar's avatar
 
Sam Hocevar committed
576
    IDirectSoundBuffer_GetCaps( p_aout->p_sys->p_dsbuffer, &dsbcaps  );
Sam Hocevar's avatar
 
Sam Hocevar committed
577 578
    p_aout->p_sys->i_buffer_size = dsbcaps.dwBufferBytes;
    p_aout->p_sys->i_write_position = 0;
Gildas Bazin's avatar
 
Gildas Bazin committed
579

Sam Hocevar's avatar
 
Sam Hocevar committed
580 581
    intf_WarnMsg( 3, "aout: DirectX DirectxCreateSecondaryBuffer: %i",
                  p_aout->p_sys->i_buffer_size);
Sam Hocevar's avatar
 
Sam Hocevar committed
582

Gildas Bazin's avatar
 
Gildas Bazin committed
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
    /* Now the secondary buffer is created, we need to setup its position
     * notification */
    p_aout->p_sys->notification_events[0].dwOffset = 0;    /* notif position */
    p_aout->p_sys->notification_events[1].dwOffset = dsbcaps.dwBufferBytes / 2;

    /* Get the IDirectSoundNotify interface */
    if FAILED( IDirectSoundBuffer_QueryInterface( p_aout->p_sys->p_dsbuffer,
                                                  &IID_IDirectSoundNotify,
                                       (LPVOID *)&p_aout->p_sys->p_dsnotify ) )
    {
        intf_WarnMsg( 3, "aout: DirectX can't get Notify interface" );
        /* Go on anyway */
        p_aout->p_sys->p_dsnotify = NULL;
        return( 0 );
    }
        
    if FAILED( IDirectSoundNotify_SetNotificationPositions(
                                        p_aout->p_sys->p_dsnotify,
                                        2,
                                        p_aout->p_sys->notification_events ) )
    {
        intf_WarnMsg( 3, "aout: DirectX can't set position Notification" );
        /* Go on anyway */
    }

Sam Hocevar's avatar
 
Sam Hocevar committed
608 609
    return( 0 );
}
Gildas Bazin's avatar
 
Gildas Bazin committed
610 611 612 613 614 615 616 617 618 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

/*****************************************************************************
 * 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.
 *****************************************************************************/
static void DirectSoundThread( aout_thread_t *p_aout )
{
    HANDLE  notification_events[2];
    VOID    *p_write_position, *p_start_buffer;
Sam Hocevar's avatar
 
Sam Hocevar committed
649
    int     i_bytes1, i_bytes2;
Gildas Bazin's avatar
 
Gildas Bazin committed
650
    HRESULT dsresult;
Sam Hocevar's avatar
 
Sam Hocevar committed
651
    int     i_buffer_size, i_play_position, i_data_in_buffer;
Gildas Bazin's avatar
 
Gildas Bazin committed
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667

    notification_events[0]=p_aout->p_sys->notification_events[0].hEventNotify;
    notification_events[1]=p_aout->p_sys->notification_events[1].hEventNotify;

    /* this thread must be high-priority */
    if( !SetThreadPriority( GetCurrentThread(),
                            THREAD_PRIORITY_ABOVE_NORMAL ) )
    {
        intf_WarnMsg( 3, "aout: DirectSoundThread could not renice itself" );
    }

    intf_WarnMsg( 3, "aout: DirectSoundThread ready" );

    while( !p_aout->p_sys->b_notification_thread_die )
    {
        /* wait for the position notification */
Sam Hocevar's avatar
 
Sam Hocevar committed
668
        i_play_position = WaitForMultipleObjects( 2, notification_events,
Gildas Bazin's avatar
 
Gildas Bazin committed
669 670 671 672 673 674 675 676 677
                                                  0, INFINITE ); 
        vlc_mutex_lock( &p_aout->p_sys->buffer_lock );

        if( p_aout->p_sys->b_notification_thread_die )
        {
            break;
        }

        /* check for buffer underflow (bodge for wrap around) */
Sam Hocevar's avatar
 
Sam Hocevar committed
678 679 680 681 682
        i_buffer_size = p_aout->p_sys->i_buffer_size;
        i_play_position = (i_play_position - WAIT_OBJECT_0) * i_buffer_size/2;
        p_aout->p_sys->i_data_played_from_beginning += (i_buffer_size/2);
        i_data_in_buffer = p_aout->p_sys->i_data_written_from_beginning -
                               p_aout->p_sys->i_data_played_from_beginning; 
Gildas Bazin's avatar
 
Gildas Bazin committed
683 684

        /* detect wrap-around */
Sam Hocevar's avatar
 
Sam Hocevar committed
685
        if( i_data_in_buffer < (-i_buffer_size/2) )
Gildas Bazin's avatar
 
Gildas Bazin committed
686
        {
Sam Hocevar's avatar
 
Sam Hocevar committed
687 688
            intf_WarnMsg(3,"aout: DirectSoundThread wrap around: %i", i_data_in_buffer);
            i_data_in_buffer += i_buffer_size;
Gildas Bazin's avatar
 
Gildas Bazin committed
689 690 691
        }

        /* detect underflow */
Sam Hocevar's avatar
 
Sam Hocevar committed
692
        if( i_data_in_buffer <= 0 )
Gildas Bazin's avatar
 
Gildas Bazin committed
693
        {
Sam Hocevar's avatar
 
Sam Hocevar committed
694
            intf_WarnMsg(3,"aout: DirectSoundThread underflow: %li", i_data_in_buffer);
Gildas Bazin's avatar
 
Gildas Bazin committed
695
            p_aout->p_sys->b_buffer_underflown = 1;
Sam Hocevar's avatar
 
Sam Hocevar committed
696 697 698 699
            p_aout->p_sys->i_write_position =
                  (i_play_position + i_buffer_size/2) % i_buffer_size;
            i_data_in_buffer = i_buffer_size / 2;
            p_aout->p_sys->i_data_played_from_beginning -= (i_buffer_size/2);
Gildas Bazin's avatar
 
Gildas Bazin committed
700 701 702 703 704 705 706
        }


        /* 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,
Sam Hocevar's avatar
 
Sam Hocevar committed
707 708
                   p_aout->p_sys->i_write_position,  /* Offset of lock start */
                   i_buffer_size - i_data_in_buffer,      /* Number of bytes */
Gildas Bazin's avatar
 
Gildas Bazin committed
709
                   &p_write_position,               /* Address of lock start */
Sam Hocevar's avatar
 
Sam Hocevar committed
710
                   &i_bytes1,    /* Count of bytes locked before wrap around */
Gildas Bazin's avatar
 
Gildas Bazin committed
711
                   &p_start_buffer,        /* Buffer adress (if wrap around) */
Sam Hocevar's avatar
 
Sam Hocevar committed
712
                   &i_bytes2,            /* Count of bytes after wrap around */
Gildas Bazin's avatar
 
Gildas Bazin committed
713 714 715 716 717
                   0);                                              /* Flags */
        if( dsresult == DSERR_BUFFERLOST )
        {
            IDirectSoundBuffer_Restore( p_aout->p_sys->p_dsbuffer );
            dsresult = IDirectSoundBuffer_Lock( p_aout->p_sys->p_dsbuffer,
Sam Hocevar's avatar
 
Sam Hocevar committed
718 719
                                          p_aout->p_sys->i_write_position,
                                          i_buffer_size - i_data_in_buffer,
Gildas Bazin's avatar
 
Gildas Bazin committed
720
                                          &p_write_position,
Sam Hocevar's avatar
 
Sam Hocevar committed
721
                                          &i_bytes1,
Gildas Bazin's avatar
 
Gildas Bazin committed
722
                                          &p_start_buffer,
Sam Hocevar's avatar
 
Sam Hocevar committed
723
                                          &i_bytes2,
Gildas Bazin's avatar
 
Gildas Bazin committed
724 725 726 727 728 729 730 731 732 733
                                          0);
        }
        if( dsresult != DS_OK )
        {
            intf_WarnMsg( 3, "aout: DirectX aout_Play can't lock buffer");
            vlc_mutex_unlock( &p_aout->p_sys->buffer_lock );
            return;
        }

        /* Now do the actual memcpy (two because the buffer is circular) */
Sam Hocevar's avatar
 
Sam Hocevar committed
734
        memset( p_write_position, 0, i_bytes1 );
Gildas Bazin's avatar
 
Gildas Bazin committed
735 736
        if( p_start_buffer != NULL )
        {
Sam Hocevar's avatar
 
Sam Hocevar committed
737
            memset( p_start_buffer, 0, i_bytes2 );
Gildas Bazin's avatar
 
Gildas Bazin committed
738 739 740 741
        }

        /* Now the data has been copied, unlock the buffer */
        IDirectSoundBuffer_Unlock( p_aout->p_sys->p_dsbuffer, 
Sam Hocevar's avatar
 
Sam Hocevar committed
742
                        p_write_position, i_bytes1, p_start_buffer, i_bytes2 );
Gildas Bazin's avatar
 
Gildas Bazin committed
743 744 745 746 747 748 749 750 751 752 753 754

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

    }

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

    intf_WarnMsg( 3, "aout: DirectSoundThread exiting" );

}