From b8c0aed0438e02a41839b513cdd67b75ccb88eee Mon Sep 17 00:00:00 2001 From: faust3 Date: Sat, 25 Sep 2004 15:34:42 +0000 Subject: [PATCH] directsound audio output plugin, patch by Gabor Szecsi some minor modifications by me git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@13461 b3059339-0415-0410-9bf9-f77b7e298cf2 --- configure | 4 + libao2/ao_dsound.c | 489 +++++++++++++++++++++++++++++++++++++++++++++ libao2/audio_out.c | 6 + 3 files changed, 499 insertions(+) create mode 100644 libao2/ao_dsound.c diff --git a/configure b/configure index e9e686fac4..d05126833e 100755 --- a/configure +++ b/configure @@ -4025,6 +4025,7 @@ if test "$_directx" = auto ; then cat > $TMPC << EOF #include #include +#include int main(void) { return 0; } EOF _directx=no @@ -4035,9 +4036,12 @@ if test "$_directx" = yes ; then _ld_win32libs="-lgdi32 $_ld_win32libs" _vosrc="$_vosrc vo_directx.c" _vomodules="directx $_vomodules" + _aosrc="$_aosrc ao_dsound.c" + _aomodules="dsound $_aomodules" else _def_directx='#undef HAVE_DIRECTX' _novomodules="directx $_novomodules" + _noaomodules="dsound $_noaomodules" fi echores "$_directx" diff --git a/libao2/ao_dsound.c b/libao2/ao_dsound.c new file mode 100644 index 0000000000..b279d5f89e --- /dev/null +++ b/libao2/ao_dsound.c @@ -0,0 +1,489 @@ +/****************************************************************************** + * ao_dsound.c: Windows DirectSound interface for MPlayer + * Copyright (c) 2004 Gabor Szecsi + * + * 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. + * + *****************************************************************************/ +/** +\todo verify/extend multichannel support +*/ + + +#include +#include +#include +#include +#include + +#include "afmt.h" +#include "audio_out.h" +#include "audio_out_internal.h" +#include "../mp_msg.h" +#include "../libvo/fastmemcpy.h" +#include "osdep/timer.h" + + +static ao_info_t info = +{ + "Windows DirectSound audio output", + "dsound", + "Gabor Szecsi ", + "" +}; + +LIBAO_EXTERN(dsound) + +/** +\todo use the definitions from the win32 api headers when they define these +*/ +#if 1 +#define WAVE_FORMAT_IEEE_FLOAT 0x0003 +#define WAVE_FORMAT_DOLBY_AC3_SPDIF 0x0092 +#define WAVE_FORMAT_EXTENSIBLE 0xFFFE + +static const GUID KSDATAFORMAT_SUBTYPE_PCM = {0x1,0x0000,0x0010, {0x80,0x00,0x00,0xaa,0x00,0x38,0x9b,0x71}}; + +#define SPEAKER_FRONT_LEFT 0x1 +#define SPEAKER_FRONT_RIGHT 0x2 +#define SPEAKER_FRONT_CENTER 0x4 +#define SPEAKER_LOW_FREQUENCY 0x8 +#define SPEAKER_BACK_LEFT 0x10 +#define SPEAKER_BACK_RIGHT 0x20 +#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40 +#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80 +#define SPEAKER_BACK_CENTER 0x100 +#define SPEAKER_SIDE_LEFT 0x200 +#define SPEAKER_SIDE_RIGHT 0x400 +#define SPEAKER_TOP_CENTER 0x800 +#define SPEAKER_TOP_FRONT_LEFT 0x1000 +#define SPEAKER_TOP_FRONT_CENTER 0x2000 +#define SPEAKER_TOP_FRONT_RIGHT 0x4000 +#define SPEAKER_TOP_BACK_LEFT 0x8000 +#define SPEAKER_TOP_BACK_CENTER 0x10000 +#define SPEAKER_TOP_BACK_RIGHT 0x20000 +#define SPEAKER_RESERVED 0x80000000 + +#define DSSPEAKER_HEADPHONE 0x00000001 +#define DSSPEAKER_MONO 0x00000002 +#define DSSPEAKER_QUAD 0x00000003 +#define DSSPEAKER_STEREO 0x00000004 +#define DSSPEAKER_SURROUND 0x00000005 +#define DSSPEAKER_5POINT1 0x00000006 + +#ifndef _WAVEFORMATEXTENSIBLE_ +typedef struct { + WAVEFORMATEX Format; + union { + WORD wValidBitsPerSample; /* bits of precision */ + WORD wSamplesPerBlock; /* valid if wBitsPerSample==0 */ + WORD wReserved; /* If neither applies, set to zero. */ + } Samples; + DWORD dwChannelMask; /* which channels are */ + /* present in stream */ + GUID SubFormat; +} WAVEFORMATEXTENSIBLE, *PWAVEFORMATEXTENSIBLE; +#endif + +#endif + +static const int channel_mask[] = { + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_LOW_FREQUENCY, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY, + SPEAKER_FRONT_LEFT | SPEAKER_FRONT_CENTER | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_LOW_FREQUENCY +}; + +static HINSTANCE hdsound_dll = NULL; ///handle to the dll +static LPDIRECTSOUND hds = NULL; ///direct sound object +static LPDIRECTSOUNDBUFFER hdsbuf = NULL; ///direct sound buffer +static int buffer_size = 0; ///size in bytes of the direct sound buffer +static int write_offset = 0; ///offset of the write cursor in the direct sound buffer +static int min_free_space = 4096; ///if the free space is below this value get_space() will return 0 + +#define BUFFERSIZE 32767 /// in samples - at 48khz 0.6 sec buffer, gets multiplied with nBlockAlign + +/***************************************************************************************/ + +/** +\brief output error message +\param err error code +\return string with the error message +*/ +static char * dserr2str(int err) +{ + switch (err) { + case DS_OK: return "DS_OK"; + case DS_NO_VIRTUALIZATION: return "DS_NO_VIRTUALIZATION"; + case DSERR_ALLOCATED: return "DS_NO_VIRTUALIZATION"; + case DSERR_CONTROLUNAVAIL: return "DSERR_CONTROLUNAVAIL"; + case DSERR_INVALIDPARAM: return "DSERR_INVALIDPARAM"; + case DSERR_INVALIDCALL: return "DSERR_INVALIDCALL"; + case DSERR_GENERIC: return "DSERR_GENERIC"; + case DSERR_PRIOLEVELNEEDED: return "DSERR_PRIOLEVELNEEDED"; + case DSERR_OUTOFMEMORY: return "DSERR_OUTOFMEMORY"; + case DSERR_BADFORMAT: return "DSERR_BADFORMAT"; + case DSERR_UNSUPPORTED: return "DSERR_UNSUPPORTED"; + case DSERR_NODRIVER: return "DSERR_NODRIVER"; + case DSERR_ALREADYINITIALIZED: return "DSERR_ALREADYINITIALIZED"; + case DSERR_NOAGGREGATION: return "DSERR_NOAGGREGATION"; + case DSERR_BUFFERLOST: return "DSERR_BUFFERLOST"; + case DSERR_OTHERAPPHASPRIO: return "DSERR_OTHERAPPHASPRIO"; + case DSERR_UNINITIALIZED: return "DSERR_UNINITIALIZED"; + case DSERR_NOINTERFACE: return "DSERR_NOINTERFACE"; + case DSERR_ACCESSDENIED: return "DSERR_ACCESSDENIED"; + default: return "unknown"; + } +} + +/** +\brief uninitialize direct sound +*/ +static void UninitDirectSound(void) +{ + // finally release the DirectSound object + if (hds) { + IDirectSound_Release(hds); + hds = NULL; + } + // free DSOUND.DLL + if (hdsound_dll) { + FreeLibrary(hdsound_dll); + hdsound_dll = NULL; + } + mp_msg(MSGT_AO, MSGL_V, "ao_dsound: DirectSound uninitialized\n"); +} + +/** +\brief initilize direct sound +\return 0 if error, 1 if ok +*/ +static int InitDirectSound(void) +{ + DSCAPS dscaps; + + // initialize directsound + HRESULT (WINAPI *OurDirectSoundCreate)(LPGUID, LPDIRECTSOUND *, LPUNKNOWN); + hdsound_dll = LoadLibrary("DSOUND.DLL"); + if (hdsound_dll == NULL) { + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot load DSOUND.DLL\n"); + return 0; + } + OurDirectSoundCreate = (void*)GetProcAddress(hdsound_dll, "DirectSoundCreate"); + + if (OurDirectSoundCreate == NULL) { + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: GetProcAddress FAILED\n"); + FreeLibrary(hdsound_dll); + return 0; + } + + // Create the direct sound object + if FAILED(OurDirectSoundCreate(NULL, &hds, NULL )) { + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot create a DirectSound device\n"); + FreeLibrary(hdsound_dll); + return 0; + } + + /* 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(hds, GetDesktopWindow(), DSSCL_EXCLUSIVE)) { + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot set direct sound cooperative level\n"); + IDirectSound_Release(hds); + FreeLibrary(hdsound_dll); + return 0; + } + mp_msg(MSGT_AO, MSGL_V, "ao_dsound: DirectSound initialized\n"); + + memset(&dscaps, 0, sizeof(DSCAPS)); + dscaps.dwSize = sizeof(DSCAPS); + if (DS_OK == IDirectSound_GetCaps(hds, &dscaps)) { + if (dscaps.dwFlags & DSCAPS_EMULDRIVER) mp_msg(MSGT_AO, MSGL_V, "ao_dsound: DirectSound is emulated, waveOut may give better performance\n"); + } else { + mp_msg(MSGT_AO, MSGL_V, "ao_dsound: cannot get device capabilities\n"); + } + + return 1; +} + +/** +\brief destroy the direct sound buffer +*/ +static void DestroyBuffer(void) +{ + if (hdsbuf) { + IDirectSoundBuffer_Release(hdsbuf); + hdsbuf = NULL; + } +} + +/** +\brief fill sound buffer +\param data pointer to the sound data to copy +\param len length of the data to copy in bytes +\return number of copyed bytes +*/ +static int write_buffer(unsigned char *data, int len) +{ + HRESULT res; + LPVOID lpvPtr1; + DWORD dwBytes1; + LPVOID lpvPtr2; + DWORD dwBytes2; + + // Lock the buffer + res = IDirectSoundBuffer_Lock(hdsbuf,write_offset, len, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0); + // If the buffer was lost, restore and retry lock. + if (DSERR_BUFFERLOST == res) + { + IDirectSoundBuffer_Restore(hdsbuf); + res = IDirectSoundBuffer_Lock(hdsbuf,write_offset, len, &lpvPtr1, &dwBytes1, &lpvPtr2, &dwBytes2, 0); + } + + + if (SUCCEEDED(res)) + { + // Write to pointers. + memcpy(lpvPtr1,data,dwBytes1); + if (NULL != lpvPtr2 )memcpy(lpvPtr2,data+dwBytes1,dwBytes2); + write_offset+=dwBytes1+dwBytes2; + if(write_offset>=buffer_size)write_offset-=buffer_size; + + // Release the data back to DirectSound. + res = IDirectSoundBuffer_Unlock(hdsbuf,lpvPtr1,dwBytes1,lpvPtr2,dwBytes2); + if (SUCCEEDED(res)) + { + // Success. + DWORD status; + IDirectSoundBuffer_GetStatus(hdsbuf, &status); + if (!(status & DSBSTATUS_PLAYING)){ + res = IDirectSoundBuffer_Play(hdsbuf, 0, 0, DSBPLAY_LOOPING); + } + return dwBytes1+dwBytes2; + } + } + // Lock, Unlock, or Restore failed. + return 0; +} + +/***************************************************************************************/ + +/** +\brief handle control commands +\param cmd command +\param arg argument +\return CONTROL_OK or -1 in case the command can't be handled +*/ +static int control(int cmd, void *arg) +{ + DWORD volume; + switch (cmd) { + case AOCONTROL_GET_VOLUME: { + ao_control_vol_t* vol = (ao_control_vol_t*)arg; + IDirectSoundBuffer_GetVolume(hdsbuf, &volume); + vol->left = vol->right = (float)(volume+10000) / 100.0; + //printf("ao_dsound: volume: %f\n",vol->left); + return CONTROL_OK; + } + case AOCONTROL_SET_VOLUME: { + ao_control_vol_t* vol = (ao_control_vol_t*)arg; + volume = (vol->right * 100.0)-10000; + IDirectSoundBuffer_SetVolume(hdsbuf, volume); + //printf("ao_dsound: volume: %f\n",vol->left); + return CONTROL_OK; + } + } + return -1; +} + +/** +\brief setup sound device +\param rate samplerate +\param channels number of channels +\param format format +\param flags unused +\return 1=success 0=fail +*/ +static int init(int rate, int channels, int format, int flags) +{ + int res; + if (!InitDirectSound()) return 0; + + // ok, now create the primary buffer + WAVEFORMATEXTENSIBLE wformat; + DSBUFFERDESC dsbdesc; + + //fill global ao_data + ao_data.channels = channels; + ao_data.samplerate = rate; + ao_data.format = format; + ao_data.bps = channels * rate * (audio_out_format_bits(format)>>3); + if(ao_data.buffersize==-1) + { + ao_data.buffersize = audio_out_format_bits(format) >> 3; + ao_data.buffersize *= channels; + ao_data.buffersize *= BUFFERSIZE; + } + mp_msg(MSGT_AO, MSGL_V,"ao_dsound: Samplerate:%iHz Channels:%i Format:%s\n", rate, channels, audio_out_format_name(format)); + mp_msg(MSGT_AO, MSGL_V,"ao_dsound: Buffersize:%d bytes (%d msec)\n", ao_data.buffersize, BUFFERSIZE * 1000 / rate); + + //fill waveformatex + ZeroMemory(&wformat, sizeof(WAVEFORMATEXTENSIBLE)); + wformat.Format.cbSize = (channels > 2) ? sizeof(WAVEFORMATEXTENSIBLE) : 0; + wformat.Format.nChannels = channels; + wformat.Format.nSamplesPerSec = rate; + if (format == AFMT_AC3) { + wformat.Format.wFormatTag = WAVE_FORMAT_DOLBY_AC3_SPDIF; + wformat.Format.wBitsPerSample = 16; + wformat.Format.nBlockAlign = 4; + } else { + wformat.Format.wFormatTag = (channels > 2) ? WAVE_FORMAT_EXTENSIBLE : WAVE_FORMAT_PCM; + wformat.Format.wBitsPerSample = audio_out_format_bits(format); + wformat.Format.nBlockAlign = wformat.Format.nChannels * (wformat.Format.wBitsPerSample >> 3); + } + + // fill in the direct sound buffer descriptor + memset(&dsbdesc, 0, sizeof(DSBUFFERDESC)); + dsbdesc.dwSize = sizeof(DSBUFFERDESC); + dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 /** Better position accuracy */ + | DSBCAPS_GLOBALFOCUS /** Allows background playing */ + | DSBCAPS_CTRLVOLUME; /** volume control enabled */ + + if (channels > 2) { + wformat.dwChannelMask = channel_mask[channels - 3]; + wformat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + wformat.Samples.wValidBitsPerSample = audio_out_format_bits(format); + // Needed for 5.1 on emu101k - shit soundblaster + dsbdesc.dwFlags |= DSBCAPS_LOCHARDWARE; + } + wformat.Format.nAvgBytesPerSec = wformat.Format.nSamplesPerSec * wformat.Format.nBlockAlign; + + dsbdesc.dwBufferBytes = wformat.Format.nBlockAlign * BUFFERSIZE; + dsbdesc.lpwfxFormat = (WAVEFORMATEX *)&wformat; + buffer_size = dsbdesc.dwBufferBytes; + ao_data.outburst = wformat.Format.nBlockAlign * 512; + + // now create the sound buffer + + res = IDirectSound_CreateSoundBuffer(hds, &dsbdesc, &hdsbuf, NULL); + if (res != DS_OK) { + if (dsbdesc.dwFlags & DSBCAPS_LOCHARDWARE) { + // Try without DSBCAPS_LOCHARDWARE + dsbdesc.dwFlags &= ~DSBCAPS_LOCHARDWARE; + res = IDirectSound_CreateSoundBuffer(hds, &dsbdesc, &hdsbuf, NULL); + } + if (res != DS_OK) { + UninitDirectSound(); + mp_msg(MSGT_AO, MSGL_ERR, "ao_dsound: cannot create secondary buffer (%s)\n", dserr2str(res)); + return 0; + } + } + mp_msg(MSGT_AO, MSGL_V, "ao_dsound: secondary buffer created\n"); + return 1; +} + + + +/** +\brief stop playing and empty buffers (for seeking/pause) +*/ +static void reset() +{ + IDirectSoundBuffer_Stop(hdsbuf); + // reset directsound buffer + IDirectSoundBuffer_SetCurrentPosition(hdsbuf, 0); + write_offset=0; +} + +/** +\brief stop playing, keep buffers (for pause) +*/ +static void audio_pause() +{ + IDirectSoundBuffer_Stop(hdsbuf); +} + +/** +\brief resume playing, after audio_pause() +*/ +static void audio_resume() +{ + IDirectSoundBuffer_Play(hdsbuf, 0, 0, DSBPLAY_LOOPING); +} + +/** +\brief close audio device +\param immed stop playback immediately, currently not supported +*/ +static void uninit(int immed) +{ + reset(); + DestroyBuffer(); + UninitDirectSound(); +} + +/** +\brief find out how many bytes can be written into the audio buffer without +\return free space in bytes, has to return 0 if the buffer is almost full +*/ +static int get_space() +{ + int space; + DWORD play_offset; + IDirectSoundBuffer_GetCurrentPosition(hdsbuf,&play_offset,NULL); + space=buffer_size-(write_offset-play_offset); + // | | <-- const --> | | | + // buffer start play_cursor write_cursor write_offset buffer end + // play_cursor is the actual postion of the play cursor + // write_cursor is the position after which it is assumed to be save to write data + // write_offset is the postion where we actually write the data to + if(space > buffer_size)space -= buffer_size; // write_offset < play_offset + if(space < min_free_space)return 0; + return space; +} + +/** +\brief play 'len' bytes of 'data' +\param data pointer to the data to play +\param len size in bytes of the data buffer, gets rounded down to outburst*n +\param flags currently unused +\return number of played bytes +*/ +static int play(void* data, int len, int flags) +{ + len = (len / ao_data.outburst) * ao_data.outburst; + return write_buffer(data, len); +} + +/** +\brief get the delay between the first and last sample in the buffer +\return delay in seconds +*/ +static float get_delay() +{ + DWORD play_offset; + int space; + IDirectSoundBuffer_GetCurrentPosition(hdsbuf,&play_offset,NULL); + space=play_offset-write_offset; + if(space <= 0)space += buffer_size; + return (float)(buffer_size - space) / (float)ao_data.bps; +} diff --git a/libao2/audio_out.c b/libao2/audio_out.c index 55a133b35a..b12c6403a4 100644 --- a/libao2/audio_out.c +++ b/libao2/audio_out.c @@ -53,6 +53,9 @@ extern ao_functions_t audio_out_sgi; #ifdef HAVE_WIN32WAVEOUT extern ao_functions_t audio_out_win32; #endif +#ifdef HAVE_DIRECTX +extern ao_functions_t audio_out_dsound; +#endif #ifdef HAVE_DXR2 extern ao_functions_t audio_out_dxr2; #endif @@ -69,6 +72,9 @@ ao_functions_t* audio_out_drivers[] = &audio_out_dxr2, #endif // native: +#ifdef HAVE_DIRECTX + &audio_out_dsound, +#endif #ifdef HAVE_WIN32WAVEOUT &audio_out_win32, #endif