smsvn -> ssc-hg glue: rearrange directory structure
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#include "global.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
|
||||
#define ALSA_PCM_NEW_HW_PARAMS_API
|
||||
#define ALSA_PCM_NEW_SW_PARAMS_API
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
static void *Handle = NULL;
|
||||
|
||||
#include "RageUtil.h"
|
||||
#include "ALSA9Dynamic.h"
|
||||
|
||||
/* foo_f dfoo = NULL */
|
||||
#define FUNC(ret, name, proto) name##_f d##name = NULL
|
||||
#include "ALSA9Functions.h"
|
||||
#undef FUNC
|
||||
|
||||
static const RString lib = "libasound.so.2";
|
||||
RString LoadALSA()
|
||||
{
|
||||
/* If /proc/asound/ doesn't exist, chances are we're on an OSS system. We shouldn't
|
||||
* touch ALSA at all, since many OSS systems have old, broken versions of ALSA lying
|
||||
* around; we're likely to crash if we go near it. Do this first, before loading
|
||||
* the ALSA library, since making any ALSA calls may load ALSA core modules.
|
||||
*
|
||||
* It's vaguely possible that a module autoloader would load the entire ALSA module set
|
||||
* on use, and this would prevent that from happening. I don't know if anyone actually
|
||||
* does that, though: they're often configured to load snd (the core module) if ALSA
|
||||
* devices are accessed, but hardware drivers are typically loaded on boot. */
|
||||
if( !IsADirectory("/rootfs/proc/asound/") )
|
||||
return "/proc/asound/ does not exist";
|
||||
|
||||
ASSERT( Handle == NULL );
|
||||
|
||||
Handle = dlopen( lib, RTLD_NOW );
|
||||
if( Handle == NULL )
|
||||
return ssprintf("dlopen(%s): %s", lib.c_str(), dlerror());
|
||||
|
||||
RString error;
|
||||
/* Eww. The "new" HW and SW API functions are really prefixed by __,
|
||||
* eg. __snd_pcm_hw_params_set_rate_near. */
|
||||
#define FUNC(ret, name, proto) \
|
||||
d##name = (name##_f) dlsym(Handle, "__" #name); \
|
||||
if( !d##name ) { \
|
||||
d##name = (name##_f) dlsym(Handle, #name); \
|
||||
if( !d##name ) { \
|
||||
error="Couldn't load symbol " #name; \
|
||||
goto error; \
|
||||
} \
|
||||
}
|
||||
#include "ALSA9Functions.h"
|
||||
#undef FUNC
|
||||
|
||||
return "";
|
||||
error:
|
||||
UnloadALSA();
|
||||
return error;
|
||||
}
|
||||
|
||||
void UnloadALSA()
|
||||
{
|
||||
if( Handle )
|
||||
dlclose( Handle );
|
||||
Handle = NULL;
|
||||
#define FUNC(ret, name, proto) d##name = NULL;
|
||||
#include "ALSA9Functions.h"
|
||||
#undef FUNC
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2003-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef ALSA9_DYNAMIC_H
|
||||
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
/* typedef int (*foo_f)(char c) */
|
||||
#define FUNC(ret, name, proto) typedef ret (*name##_f) proto
|
||||
#include "ALSA9Functions.h"
|
||||
#undef FUNC
|
||||
|
||||
/* extern foo_f dfoo */
|
||||
#define FUNC(ret, name, proto) extern name##_f d##name
|
||||
#include "ALSA9Functions.h"
|
||||
#undef FUNC
|
||||
|
||||
#define dsnd_pcm_hw_params_alloca(ptr) { assert(ptr); *ptr = (snd_pcm_hw_params_t *) alloca(dsnd_pcm_hw_params_sizeof()); memset(*ptr, 0, dsnd_pcm_hw_params_sizeof()); }
|
||||
#define dsnd_pcm_sw_params_alloca(ptr) { assert(ptr); *ptr = (snd_pcm_sw_params_t *) alloca(dsnd_pcm_sw_params_sizeof()); memset(*ptr, 0, dsnd_pcm_sw_params_sizeof()); }
|
||||
#define dsnd_pcm_info_alloca(ptr) { assert(ptr); *ptr = (snd_pcm_info_t *) alloca(dsnd_pcm_info_sizeof()); memset(*ptr, 0, dsnd_pcm_info_sizeof()); }
|
||||
#define dsnd_ctl_card_info_alloca(ptr) { assert(ptr); *ptr = (snd_ctl_card_info_t *) alloca(dsnd_ctl_card_info_sizeof()); memset(*ptr, 0, dsnd_ctl_card_info_sizeof()); }
|
||||
#define dsnd_pcm_status_alloca(ptr) do { assert(ptr); *ptr = (snd_pcm_status_t *) alloca(dsnd_pcm_status_sizeof()); memset(*ptr, 0, dsnd_pcm_status_sizeof()); } while (0)
|
||||
|
||||
RString LoadALSA();
|
||||
void UnloadALSA();
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2003-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,81 @@
|
||||
FUNC(size_t, snd_pcm_hw_params_sizeof, (void));
|
||||
FUNC(size_t, snd_pcm_sw_params_sizeof, (void));
|
||||
FUNC(size_t, snd_pcm_info_sizeof, (void));
|
||||
FUNC(size_t, snd_ctl_card_info_sizeof, (void));
|
||||
FUNC(int, snd_ctl_card_info, (snd_ctl_t *ctl, snd_ctl_card_info_t *info));
|
||||
FUNC(int, snd_card_next, (int *card));
|
||||
FUNC(const char *, snd_ctl_card_info_get_id, (const snd_ctl_card_info_t *obj));
|
||||
FUNC(const char *, snd_ctl_card_info_get_name, (const snd_ctl_card_info_t *obj));
|
||||
FUNC(snd_pcm_state_t, snd_pcm_state, (snd_pcm_t *pcm));
|
||||
FUNC(const char *,snd_strerror, (int errnum));
|
||||
FUNC(int, snd_ctl_close, (snd_ctl_t *ctl));
|
||||
FUNC(int, snd_ctl_open, (snd_ctl_t **ctl, const char *name, int mode));
|
||||
FUNC(int, snd_lib_error_set_handler, (snd_lib_error_handler_t handler));
|
||||
FUNC(int, snd_output_buffer_open, (snd_output_t **outputp));
|
||||
FUNC(size_t, snd_output_buffer_string, (snd_output_t *output, char **buf));
|
||||
FUNC(int, snd_output_close, (snd_output_t *output));
|
||||
FUNC(int, snd_output_flush, (snd_output_t *output));
|
||||
FUNC(snd_pcm_sframes_t, snd_pcm_avail_update, (snd_pcm_t *pcm));
|
||||
FUNC(int, snd_pcm_close, (snd_pcm_t *pcm));
|
||||
FUNC(int, snd_pcm_delay, (snd_pcm_t *pcm, snd_pcm_sframes_t *delayp));
|
||||
FUNC(int, snd_pcm_drop, (snd_pcm_t *pcm));
|
||||
FUNC(int, snd_pcm_dump, (snd_pcm_t *pcm, snd_output_t *out));
|
||||
FUNC(snd_pcm_sframes_t, snd_pcm_forward, (snd_pcm_t *pcm, snd_pcm_uframes_t frames));
|
||||
FUNC(int, snd_pcm_hw_free, (snd_pcm_t *pcm));
|
||||
FUNC(int, snd_pcm_hw_params, (snd_pcm_t *pcm, snd_pcm_hw_params_t *params));
|
||||
FUNC(int, snd_pcm_hw_params_any, (snd_pcm_t *pcm, snd_pcm_hw_params_t *params));
|
||||
FUNC(int, snd_pcm_hw_params_set_access, (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_access_t access));
|
||||
FUNC(int, snd_pcm_hw_params_set_channels, (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int val));
|
||||
FUNC(int, snd_pcm_hw_params_set_format, (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_format_t val));
|
||||
FUNC(int, snd_pcm_hw_params_set_rate_near, (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, unsigned int *val, int *dir));
|
||||
FUNC(int, snd_pcm_hw_params_set_buffer_size_near, (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val));
|
||||
FUNC(int, snd_pcm_hw_params_set_period_size_near, (snd_pcm_t *pcm, snd_pcm_hw_params_t *params, snd_pcm_uframes_t *val, int *dir));
|
||||
FUNC(int, snd_pcm_status, (snd_pcm_t *pcm, snd_pcm_status_t *status));
|
||||
FUNC(snd_pcm_uframes_t, snd_pcm_status_get_avail, (const snd_pcm_status_t *obj));
|
||||
FUNC(size_t, snd_pcm_status_sizeof, (void));
|
||||
FUNC(int, snd_pcm_hwsync, (snd_pcm_t *pcm));
|
||||
FUNC(int, snd_ctl_pcm_next_device, (snd_ctl_t *ctl, int *device));
|
||||
FUNC(int, snd_ctl_pcm_info, (snd_ctl_t *ctl, snd_pcm_info_t * info));
|
||||
FUNC(const char *,snd_pcm_info_get_id, (const snd_pcm_info_t *obj));
|
||||
FUNC(const char *, snd_pcm_info_get_name, (const snd_pcm_info_t *obj));
|
||||
FUNC(unsigned int, snd_pcm_info_get_subdevices_avail, (const snd_pcm_info_t *obj));
|
||||
FUNC(unsigned int, snd_pcm_info_get_subdevices_count, (const snd_pcm_info_t *obj));
|
||||
FUNC(void, snd_pcm_info_set_device, (snd_pcm_info_t *obj, unsigned int val));
|
||||
FUNC(void, snd_pcm_info_set_stream, (snd_pcm_info_t *obj, snd_pcm_stream_t val));
|
||||
FUNC(snd_pcm_sframes_t, snd_pcm_mmap_writei, (snd_pcm_t *pcm, const void *buffer, snd_pcm_uframes_t size));
|
||||
FUNC(int, snd_pcm_open, (snd_pcm_t **pcm, const char *name, snd_pcm_stream_t stream, int mode));
|
||||
FUNC(int, snd_pcm_prepare, (snd_pcm_t *pcm));
|
||||
FUNC(int, snd_pcm_resume, (snd_pcm_t *pcm));
|
||||
FUNC(int, snd_pcm_wait, (snd_pcm_t *pcm, int timeout));
|
||||
FUNC(int, snd_pcm_sw_params, (snd_pcm_t *pcm, snd_pcm_sw_params_t *params));
|
||||
FUNC(int, snd_pcm_sw_params_current, (snd_pcm_t *pcm, snd_pcm_sw_params_t *params));
|
||||
FUNC(int, snd_pcm_sw_params_get_boundary, (const snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val));
|
||||
FUNC(int, snd_pcm_sw_params_set_xfer_align, (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val));
|
||||
FUNC(int, snd_pcm_sw_params_set_stop_threshold, (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val));
|
||||
FUNC(int, snd_pcm_sw_params_get_avail_min, (snd_pcm_sw_params_t *params, snd_pcm_uframes_t *val));
|
||||
FUNC(int, snd_pcm_sw_params_set_avail_min, (snd_pcm_t *pcm, snd_pcm_sw_params_t *params, snd_pcm_uframes_t val));
|
||||
|
||||
/*
|
||||
* (c) 2003-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,462 @@
|
||||
#include "global.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageUtil.h"
|
||||
#include "ALSA9Helpers.h"
|
||||
#include "ALSA9Dynamic.h"
|
||||
#include "PrefsManager.h"
|
||||
|
||||
/* int err; must be defined before using this macro */
|
||||
#define ALSA_CHECK(x) \
|
||||
if ( err < 0 ) { LOG->Info("ALSA: %s: %s", x, dsnd_strerror(err)); return false; }
|
||||
#define ALSA_ASSERT(x) \
|
||||
if (err < 0) { LOG->Warn("ALSA: %s: %s", x, dsnd_strerror(err)); }
|
||||
|
||||
bool Alsa9Buf::SetHWParams()
|
||||
{
|
||||
int err;
|
||||
|
||||
if( dsnd_pcm_state(pcm) == SND_PCM_STATE_PREPARED )
|
||||
dsnd_pcm_drop( pcm );
|
||||
|
||||
if( dsnd_pcm_state(pcm) != SND_PCM_STATE_OPEN )
|
||||
{
|
||||
/* Reset the stream to SND_PCM_STATE_OPEN. */
|
||||
err = dsnd_pcm_hw_free( pcm );
|
||||
ALSA_ASSERT("dsnd_pcm_hw_free");
|
||||
}
|
||||
// ASSERT_M( dsnd_pcm_state(pcm) == SND_PCM_STATE_OPEN, ssprintf("(%s)", dsnd_pcm_state_name(dsnd_pcm_state(pcm))) );
|
||||
|
||||
/* allocate the hardware parameters structure */
|
||||
snd_pcm_hw_params_t *hwparams;
|
||||
dsnd_pcm_hw_params_alloca( &hwparams );
|
||||
|
||||
err = dsnd_pcm_hw_params_any(pcm, hwparams);
|
||||
ALSA_CHECK("dsnd_pcm_hw_params_any");
|
||||
|
||||
/* Set to interleaved mmap mode. */
|
||||
err = dsnd_pcm_hw_params_set_access(pcm, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED);
|
||||
ALSA_CHECK("dsnd_pcm_hw_params_set_access");
|
||||
|
||||
/* Set the PCM format: signed 16bit, native endian. */
|
||||
err = dsnd_pcm_hw_params_set_format(pcm, hwparams, SND_PCM_FORMAT_S16);
|
||||
ALSA_CHECK("dsnd_pcm_hw_params_set_format");
|
||||
|
||||
/* Set the number of channels. */
|
||||
err = dsnd_pcm_hw_params_set_channels(pcm, hwparams, 2);
|
||||
ALSA_CHECK("dsnd_pcm_hw_params_set_channels");
|
||||
|
||||
/* Set the sample rate. */
|
||||
err = dsnd_pcm_hw_params_set_rate_near(pcm, hwparams, &samplerate, 0);
|
||||
ALSA_CHECK("dsnd_pcm_hw_params_set_rate_near");
|
||||
|
||||
/* Set the buffersize to the writeahead, and then copy back the actual value
|
||||
* we got. */
|
||||
writeahead = preferred_writeahead;
|
||||
err = dsnd_pcm_hw_params_set_buffer_size_near( pcm, hwparams, &writeahead );
|
||||
ALSA_CHECK("dsnd_pcm_hw_params_set_buffer_size_near");
|
||||
|
||||
/* The period size is roughly equivalent to what we call the chunksize. */
|
||||
int dir = 0;
|
||||
chunksize = preferred_chunksize;
|
||||
err = dsnd_pcm_hw_params_set_period_size_near( pcm, hwparams, &chunksize, &dir );
|
||||
ALSA_CHECK("dsnd_pcm_hw_params_set_period_size_near");
|
||||
|
||||
// LOG->Info("asked for %i period, got %i", chunksize, period_size);
|
||||
|
||||
/* write the hardware parameters to the device */
|
||||
err = dsnd_pcm_hw_params( pcm, hwparams );
|
||||
ALSA_CHECK("dsnd_pcm_hw_params");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Alsa9Buf::SetSWParams()
|
||||
{
|
||||
snd_pcm_sw_params_t *swparams;
|
||||
dsnd_pcm_sw_params_alloca( &swparams );
|
||||
dsnd_pcm_sw_params_current( pcm, swparams );
|
||||
|
||||
int err = dsnd_pcm_sw_params_set_xfer_align( pcm, swparams, 1 );
|
||||
ALSA_ASSERT("dsnd_pcm_sw_params_set_xfer_align");
|
||||
|
||||
/* chunksize has been set to the period size. Set avail_min to the period
|
||||
* size, too, so poll() wakes up once per chunk. */
|
||||
err = dsnd_pcm_sw_params_set_avail_min( pcm, swparams, chunksize );
|
||||
ALSA_ASSERT("dsnd_pcm_sw_params_set_avail_min");
|
||||
|
||||
/* If this fails, we might have bound dsnd_pcm_sw_params_set_avail_min to
|
||||
* the old SW API. */
|
||||
// ASSERT( err <= 0 );
|
||||
|
||||
/* Disable SND_PCM_STATE_XRUN. */
|
||||
snd_pcm_uframes_t boundary = 0;
|
||||
err = dsnd_pcm_sw_params_get_boundary( swparams, &boundary );
|
||||
ALSA_ASSERT("dsnd_pcm_sw_params_get_boundary");
|
||||
|
||||
err = dsnd_pcm_sw_params_set_stop_threshold( pcm, swparams, boundary );
|
||||
ALSA_ASSERT("dsnd_pcm_sw_params_set_stop_threshold");
|
||||
|
||||
err = dsnd_pcm_sw_params(pcm, swparams);
|
||||
ALSA_ASSERT("dsnd_pcm_sw_params");
|
||||
|
||||
err = dsnd_pcm_prepare(pcm);
|
||||
ALSA_ASSERT("dsnd_pcm_prepare");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Alsa9Buf::ErrorHandler(const char *file, int line, const char *function, int err, const char *fmt, ...)
|
||||
{
|
||||
va_list va;
|
||||
va_start( va, fmt );
|
||||
RString str = vssprintf(fmt, va);
|
||||
va_end( va );
|
||||
|
||||
if( err )
|
||||
str += ssprintf( " (%s)", dsnd_strerror(err) );
|
||||
|
||||
/* Annoying: these happen both normally (eg. "out of memory" when allocating too many PCM
|
||||
* slots) and abnormally, and there's no way to tell which is which. I don't want to
|
||||
* pollute the warning output. */
|
||||
LOG->Trace( "ALSA error: %s:%i %s: %s", file, line, function, str.c_str() );
|
||||
}
|
||||
|
||||
void Alsa9Buf::InitializeErrorHandler()
|
||||
{
|
||||
dsnd_lib_error_set_handler( ErrorHandler );
|
||||
}
|
||||
|
||||
static RString DeviceName()
|
||||
{
|
||||
if( !PREFSMAN->m_iSoundDevice.Get().empty() )
|
||||
return PREFSMAN->m_iSoundDevice;
|
||||
return "hw:0";
|
||||
}
|
||||
|
||||
void Alsa9Buf::GetSoundCardDebugInfo()
|
||||
{
|
||||
static bool done = false;
|
||||
if( done )
|
||||
return;
|
||||
done = true;
|
||||
|
||||
if( DoesFileExist("/rootfs/proc/asound/version") )
|
||||
{
|
||||
RString sVersion;
|
||||
GetFileContents( "/rootfs/proc/asound/version", sVersion, true );
|
||||
LOG->Info( "ALSA: %s", sVersion.c_str() );
|
||||
}
|
||||
|
||||
InitializeErrorHandler();
|
||||
|
||||
int card = -1;
|
||||
while( dsnd_card_next( &card ) >= 0 && card >= 0 )
|
||||
{
|
||||
const RString id = ssprintf( "hw:%d", card );
|
||||
snd_ctl_t *handle;
|
||||
int err;
|
||||
err = dsnd_ctl_open( &handle, id, 0 );
|
||||
if ( err < 0 )
|
||||
{
|
||||
LOG->Info( "Couldn't open card #%i (\"%s\") to probe: %s", card, id.c_str(), dsnd_strerror(err) );
|
||||
continue;
|
||||
}
|
||||
|
||||
snd_ctl_card_info_t *info;
|
||||
dsnd_ctl_card_info_alloca(&info);
|
||||
err = dsnd_ctl_card_info( handle, info );
|
||||
if ( err < 0 )
|
||||
{
|
||||
LOG->Info( "Couldn't get card info for card #%i (\"%s\"): %s", card, id.c_str(), dsnd_strerror(err) );
|
||||
dsnd_ctl_close( handle );
|
||||
continue;
|
||||
}
|
||||
|
||||
int dev = -1;
|
||||
while ( dsnd_ctl_pcm_next_device( handle, &dev ) >= 0 && dev >= 0 )
|
||||
{
|
||||
snd_pcm_info_t *pcminfo;
|
||||
dsnd_pcm_info_alloca(&pcminfo);
|
||||
dsnd_pcm_info_set_device(pcminfo, dev);
|
||||
dsnd_pcm_info_set_stream(pcminfo, SND_PCM_STREAM_PLAYBACK);
|
||||
|
||||
err = dsnd_ctl_pcm_info(handle, pcminfo);
|
||||
if ( err < 0 )
|
||||
{
|
||||
if (err != -ENOENT)
|
||||
LOG->Info("dsnd_ctl_pcm_info(%i) (%s) failed: %s", card, id.c_str(), dsnd_strerror(err));
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG->Info( "ALSA Driver: %i: %s [%s], device %i: %s [%s], %i/%i subdevices avail",
|
||||
card, dsnd_ctl_card_info_get_name(info), dsnd_ctl_card_info_get_id(info), dev,
|
||||
dsnd_pcm_info_get_id(pcminfo), dsnd_pcm_info_get_name(pcminfo),
|
||||
dsnd_pcm_info_get_subdevices_avail(pcminfo),
|
||||
dsnd_pcm_info_get_subdevices_count(pcminfo) );
|
||||
|
||||
}
|
||||
dsnd_ctl_close(handle);
|
||||
}
|
||||
|
||||
if( card == 0 )
|
||||
LOG->Info( "No ALSA sound cards were found.");
|
||||
|
||||
if( !PREFSMAN->m_iSoundDevice.Get().empty() )
|
||||
LOG->Info( "ALSA device overridden to \"%s\"", PREFSMAN->m_iSoundDevice.Get().c_str() );
|
||||
}
|
||||
|
||||
Alsa9Buf::Alsa9Buf()
|
||||
{
|
||||
samplerate = 44100;
|
||||
samplebits = 16;
|
||||
last_cursor_pos = 0;
|
||||
preferred_writeahead = 8192;
|
||||
preferred_chunksize = 1024;
|
||||
pcm = NULL;
|
||||
}
|
||||
|
||||
RString Alsa9Buf::Init( int channels_,
|
||||
int iWriteahead,
|
||||
int iChunkSize,
|
||||
int iSampleRate )
|
||||
{
|
||||
channels = channels_;
|
||||
preferred_writeahead = iWriteahead;
|
||||
preferred_chunksize = iChunkSize;
|
||||
if( iSampleRate == 0 )
|
||||
samplerate = 44100;
|
||||
else
|
||||
samplerate = iSampleRate;
|
||||
|
||||
GetSoundCardDebugInfo();
|
||||
|
||||
InitializeErrorHandler();
|
||||
|
||||
/* Open the device. */
|
||||
int err;
|
||||
err = dsnd_pcm_open( &pcm, DeviceName(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK );
|
||||
if( err < 0 )
|
||||
return ssprintf( "dsnd_pcm_open(%s): %s", DeviceName().c_str(), dsnd_strerror(err) );
|
||||
|
||||
if( !SetHWParams() )
|
||||
{
|
||||
CHECKPOINT;
|
||||
return "SetHWParams failed";
|
||||
}
|
||||
|
||||
SetSWParams();
|
||||
|
||||
LOG->Info( "ALSA: Mixing at %ihz", samplerate );
|
||||
|
||||
if( preferred_writeahead != writeahead )
|
||||
LOG->Info( "ALSA: writeahead adjusted from %u to %u", (unsigned) preferred_writeahead, (unsigned) writeahead );
|
||||
if( preferred_chunksize != chunksize )
|
||||
LOG->Info( "ALSA: chunksize adjusted from %u to %u", (unsigned) preferred_chunksize, (unsigned) chunksize );
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
Alsa9Buf::~Alsa9Buf()
|
||||
{
|
||||
if( pcm != NULL )
|
||||
dsnd_pcm_close( pcm );
|
||||
}
|
||||
|
||||
|
||||
/* Don't fill the buffer any more than than "writeahead" frames. Prefer to
|
||||
* write "chunksize" frames at a time. (These numbers are hints; if the
|
||||
* hardware parameters require it, they can be ignored.) */
|
||||
int Alsa9Buf::GetNumFramesToFill()
|
||||
{
|
||||
/* Make sure we can write ahead at least two chunks. Otherwise, we'll only
|
||||
* fill one chunk ahead, and underrun. */
|
||||
int ActualWriteahead = max( writeahead, chunksize*2 );
|
||||
|
||||
snd_pcm_sframes_t avail_frames = dsnd_pcm_avail_update(pcm);
|
||||
|
||||
int total_frames = writeahead;
|
||||
if( avail_frames > total_frames )
|
||||
{
|
||||
/* underrun */
|
||||
const int size = avail_frames-total_frames;
|
||||
LOG->Trace("underrun (%i frames)", size);
|
||||
int large_skip_threshold = 2 * samplerate;
|
||||
|
||||
/* For small underruns, ignore them. We'll return the maximum writeahead and ALSA will
|
||||
* just discard the data. GetPosition will return consistent values during this time,
|
||||
* so arrows will continue to scroll smoothly until the music catches up. */
|
||||
if( size >= large_skip_threshold )
|
||||
{
|
||||
/* It's a large skip. Catch up. If we fall too far behind, the sound thread will
|
||||
* be decoding as fast as it can, which will steal too many cycles from the rendering
|
||||
* thread. */
|
||||
dsnd_pcm_forward( pcm, size );
|
||||
}
|
||||
}
|
||||
|
||||
if( avail_frames < 0 )
|
||||
avail_frames = dsnd_pcm_avail_update(pcm);
|
||||
|
||||
if( avail_frames < 0 )
|
||||
{
|
||||
LOG->Trace( "RageSoundDriver_ALSA9::GetData: dsnd_pcm_avail_update: %s", dsnd_strerror(avail_frames) );
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Number of frames that have data: */
|
||||
const snd_pcm_sframes_t filled_frames = max( 0l, total_frames - avail_frames );
|
||||
|
||||
/* Number of frames that don't have data, that are within the writeahead: */
|
||||
snd_pcm_sframes_t unfilled_frames = clamp( ActualWriteahead - filled_frames, 0l, (snd_pcm_sframes_t)ActualWriteahead );
|
||||
|
||||
// LOG->Trace( "total_fr: %i; avail_fr: %i; filled_fr: %i; ActualWr %i; chunksize %i; unfilled_frames %i ",
|
||||
// total_frames, avail_frames, filled_frames, ActualWriteahead, chunksize, unfilled_frames );
|
||||
|
||||
/* If we have less than a chunk empty, don't fill at all. Otherwise, we'll
|
||||
* spend a lot of CPU filling in partial chunks, instead of waiting for some
|
||||
* sound to play and then filling a whole chunk at once. */
|
||||
if( unfilled_frames < (int) chunksize )
|
||||
return 0;
|
||||
|
||||
return chunksize;
|
||||
}
|
||||
|
||||
bool Alsa9Buf::WaitUntilFramesCanBeFilled( int timeout_ms )
|
||||
{
|
||||
int err = dsnd_pcm_wait( pcm, timeout_ms );
|
||||
/* EINTR is normal; don't warn. */
|
||||
if( err == -EINTR )
|
||||
return false;
|
||||
ALSA_ASSERT("snd_pcm_wait");
|
||||
|
||||
return err == 1;
|
||||
}
|
||||
|
||||
void Alsa9Buf::Write( const int16_t *buffer, int frames )
|
||||
{
|
||||
/* We should be able to write it all. If we don't, treat it as an error. */
|
||||
int wrote;
|
||||
do
|
||||
{
|
||||
wrote = dsnd_pcm_mmap_writei( pcm, (const char *) buffer, frames );
|
||||
}
|
||||
while( wrote == -EAGAIN );
|
||||
|
||||
if( wrote < 0 )
|
||||
{
|
||||
LOG->Trace( "RageSoundDriver_ALSA9::GetData: dsnd_pcm_mmap_writei: %s (%i)", dsnd_strerror(wrote), wrote );
|
||||
return;
|
||||
}
|
||||
|
||||
last_cursor_pos += wrote;
|
||||
if( wrote < frames )
|
||||
LOG->Trace("Couldn't write whole buffer? (%i < %i)", wrote, frames );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* When the play buffer underruns, subsequent writes to the buffer
|
||||
* return -EPIPE. When this happens, call Recover() to restart playback.
|
||||
*/
|
||||
bool Alsa9Buf::Recover( int r )
|
||||
{
|
||||
if( r == -EPIPE )
|
||||
{
|
||||
LOG->Trace("RageSound_ALSA9::Recover (prepare)");
|
||||
int err = dsnd_pcm_prepare(pcm);
|
||||
ALSA_ASSERT("dsnd_pcm_prepare (Recover)");
|
||||
return true;
|
||||
}
|
||||
|
||||
if( r == -ESTRPIPE )
|
||||
{
|
||||
LOG->Trace("RageSound_ALSA9::Recover (resume)");
|
||||
int err;
|
||||
while ((err = dsnd_pcm_resume(pcm)) == -EAGAIN)
|
||||
usleep(10000); // 10ms
|
||||
|
||||
ALSA_ASSERT("dsnd_pcm_resume (Recover)");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int64_t Alsa9Buf::GetPosition() const
|
||||
{
|
||||
if( dsnd_pcm_state(pcm) == SND_PCM_STATE_PREPARED )
|
||||
return last_cursor_pos;
|
||||
|
||||
dsnd_pcm_hwsync( pcm );
|
||||
|
||||
/* delay is returned in frames */
|
||||
snd_pcm_sframes_t delay;
|
||||
int err = dsnd_pcm_delay( pcm, &delay );
|
||||
ALSA_ASSERT("dsnd_pcm_delay");
|
||||
|
||||
return last_cursor_pos - delay;
|
||||
}
|
||||
|
||||
void Alsa9Buf::Play()
|
||||
{
|
||||
/* NOP. It'll start playing when it gets some data. */
|
||||
}
|
||||
|
||||
void Alsa9Buf::Stop()
|
||||
{
|
||||
dsnd_pcm_drop( pcm );
|
||||
dsnd_pcm_prepare( pcm );
|
||||
last_cursor_pos = 0;
|
||||
}
|
||||
|
||||
RString Alsa9Buf::GetHardwareID( RString name )
|
||||
{
|
||||
InitializeErrorHandler();
|
||||
|
||||
if( name.empty() )
|
||||
name = DeviceName();
|
||||
|
||||
snd_ctl_t *handle;
|
||||
int err;
|
||||
err = dsnd_ctl_open( &handle, name, 0 );
|
||||
if ( err < 0 )
|
||||
{
|
||||
LOG->Info( "Couldn't open card \"%s\" to get ID: %s", name.c_str(), dsnd_strerror(err) );
|
||||
return "???";
|
||||
}
|
||||
|
||||
snd_ctl_card_info_t *info;
|
||||
dsnd_ctl_card_info_alloca(&info);
|
||||
err = dsnd_ctl_card_info( handle, info );
|
||||
RString ret = dsnd_ctl_card_info_get_id( info );
|
||||
dsnd_ctl_close(handle);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard, Aaron VonderHaar
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,76 @@
|
||||
#ifndef ALSA9_HELPERS_H
|
||||
#define ALSA9_HELPERS_H
|
||||
|
||||
#define ALSA_PCM_NEW_HW_PARAMS_API
|
||||
#define ALSA_PCM_NEW_SW_PARAMS_API
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
class Alsa9Buf
|
||||
{
|
||||
private:
|
||||
int channels, samplebits;
|
||||
unsigned samplerate;
|
||||
int buffersize;
|
||||
int64_t last_cursor_pos;
|
||||
|
||||
snd_pcm_uframes_t preferred_writeahead, preferred_chunksize;
|
||||
snd_pcm_uframes_t writeahead, chunksize;
|
||||
|
||||
snd_pcm_t *pcm;
|
||||
|
||||
bool Recover( int r );
|
||||
bool SetHWParams();
|
||||
bool SetSWParams();
|
||||
|
||||
static void ErrorHandler(const char *file, int line, const char *function, int err, const char *fmt, ...);
|
||||
|
||||
public:
|
||||
static void InitializeErrorHandler();
|
||||
static void GetSoundCardDebugInfo();
|
||||
static RString GetHardwareID( RString name="" );
|
||||
|
||||
Alsa9Buf();
|
||||
RString Init( int channels,
|
||||
int iWriteahead,
|
||||
int iChunkSize,
|
||||
int iSampleRate );
|
||||
~Alsa9Buf();
|
||||
|
||||
int GetNumFramesToFill();
|
||||
bool WaitUntilFramesCanBeFilled( int timeout_ms );
|
||||
void Write( const int16_t *buffer, int frames );
|
||||
|
||||
void Play();
|
||||
void Stop();
|
||||
void SetVolume(float vol);
|
||||
int GetSampleRate() const { return samplerate; }
|
||||
|
||||
int64_t GetPosition() const;
|
||||
int64_t GetPlayPos() const { return last_cursor_pos; }
|
||||
};
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard, Aaron VonderHaar
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,687 @@
|
||||
#include "global.h"
|
||||
#include "DSoundHelpers.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageLog.h"
|
||||
#include "archutils/Win32/DirectXHelpers.h"
|
||||
#include "archutils/Win32/GetFileInformation.h"
|
||||
|
||||
#if defined(_WINDOWS)
|
||||
#include <mmsystem.h>
|
||||
#endif
|
||||
#define DIRECTSOUND_VERSION 0x0700
|
||||
#include <dsound.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma comment(lib, "dsound.lib")
|
||||
#endif
|
||||
|
||||
BOOL CALLBACK DSound::EnumCallback( LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext )
|
||||
{
|
||||
RString sLine = ssprintf( "DirectSound Driver: %s", lpcstrDescription );
|
||||
if( lpcstrModule[0] )
|
||||
{
|
||||
sLine += ssprintf( " %s", lpcstrModule );
|
||||
|
||||
#ifndef _XBOX
|
||||
RString sPath = FindSystemFile( lpcstrModule );
|
||||
if( sPath != "" )
|
||||
{
|
||||
RString sVersion;
|
||||
if( GetFileVersion(sPath, sVersion) )
|
||||
sLine += ssprintf( " %s", sVersion.c_str() );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
LOG->Info( "%s", sLine.c_str() );
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void DSound::SetPrimaryBufferMode()
|
||||
{
|
||||
#ifndef _XBOX
|
||||
DSBUFFERDESC format;
|
||||
memset( &format, 0, sizeof(format) );
|
||||
format.dwSize = sizeof(format);
|
||||
format.dwFlags = DSBCAPS_PRIMARYBUFFER;
|
||||
format.dwBufferBytes = 0;
|
||||
format.lpwfxFormat = NULL;
|
||||
|
||||
IDirectSoundBuffer *pBuffer;
|
||||
HRESULT hr = this->GetDS()->CreateSoundBuffer( &format, &pBuffer, NULL );
|
||||
if( FAILED(hr) )
|
||||
{
|
||||
LOG->Warn(hr_ssprintf(hr, "Couldn't create primary buffer"));
|
||||
return;
|
||||
}
|
||||
|
||||
WAVEFORMATEX waveformat;
|
||||
memset( &waveformat, 0, sizeof(waveformat) );
|
||||
waveformat.cbSize = 0;
|
||||
waveformat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
waveformat.wBitsPerSample = 16;
|
||||
waveformat.nChannels = 2;
|
||||
waveformat.nSamplesPerSec = 44100;
|
||||
waveformat.nBlockAlign = 4;
|
||||
waveformat.nAvgBytesPerSec = waveformat.nSamplesPerSec * waveformat.nBlockAlign;
|
||||
|
||||
// Set the primary buffer's format
|
||||
hr = IDirectSoundBuffer_SetFormat( pBuffer, &waveformat );
|
||||
if( FAILED(hr) )
|
||||
LOG->Warn( hr_ssprintf(hr, "SetFormat on primary buffer") );
|
||||
|
||||
DWORD got;
|
||||
hr = pBuffer->GetFormat( &waveformat, sizeof(waveformat), &got );
|
||||
if( FAILED(hr) )
|
||||
LOG->Warn( hr_ssprintf(hr, "GetFormat on primary buffer") );
|
||||
else if( waveformat.nSamplesPerSec != 44100 )
|
||||
LOG->Warn( "Primary buffer set to %i instead of 44100", waveformat.nSamplesPerSec );
|
||||
|
||||
/*
|
||||
* MS docs:
|
||||
*
|
||||
* When there are no sounds playing, DirectSound stops the mixer engine and halts DMA
|
||||
* (direct memory access) activity. If your application has frequent short intervals of
|
||||
* silence, the overhead of starting and stopping the mixer each time a sound is played
|
||||
* may be worse than the DMA overhead if you kept the mixer active. Also, some sound
|
||||
* hardware or drivers may produce unwanted audible artifacts from frequent starting and
|
||||
* stopping of playback. If your application is playing audio almost continuously with only
|
||||
* short breaks of silence, you can force the mixer engine to remain active by calling the
|
||||
* IDirectSoundBuffer::Play method for the primary buffer. The mixer will continue to run
|
||||
* silently.
|
||||
*
|
||||
* However, I just added the above code and I don't want to change more until it's tested.
|
||||
*/
|
||||
// pBuffer->Play( 0, 0, DSBPLAY_LOOPING );
|
||||
|
||||
pBuffer->Release();
|
||||
#endif
|
||||
}
|
||||
|
||||
DSound::DSound()
|
||||
{
|
||||
HRESULT hr;
|
||||
if( FAILED( hr = CoInitialize(NULL) ) )
|
||||
RageException::Throw( hr_ssprintf(hr, "CoInitialize") );
|
||||
m_pDS = NULL;
|
||||
}
|
||||
|
||||
RString DSound::Init()
|
||||
{
|
||||
HRESULT hr;
|
||||
if( FAILED( hr = DirectSoundCreate(NULL, &m_pDS, NULL) ) )
|
||||
return hr_ssprintf( hr, "DirectSoundCreate" );
|
||||
|
||||
#ifndef _XBOX
|
||||
static bool bShownInfo = false;
|
||||
if( !bShownInfo )
|
||||
{
|
||||
bShownInfo = true;
|
||||
DirectSoundEnumerate( EnumCallback, 0 );
|
||||
|
||||
DSCAPS Caps;
|
||||
Caps.dwSize = sizeof(Caps);
|
||||
HRESULT hr;
|
||||
if( FAILED(hr = m_pDS->GetCaps(&Caps)) )
|
||||
{
|
||||
LOG->Warn( hr_ssprintf(hr, "m_pDS->GetCaps failed") );
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG->Info( "DirectSound sample rates: %i..%i %s", Caps.dwMinSecondarySampleRate, Caps.dwMaxSecondarySampleRate,
|
||||
(Caps.dwFlags & DSCAPS_CONTINUOUSRATE)?"(continuous)":"" );
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to set primary mixing privileges */
|
||||
hr = m_pDS->SetCooperativeLevel( GetDesktopWindow(), DSSCL_PRIORITY );
|
||||
#endif
|
||||
|
||||
SetPrimaryBufferMode();
|
||||
|
||||
return RString();
|
||||
}
|
||||
|
||||
DSound::~DSound()
|
||||
{
|
||||
if( m_pDS != NULL )
|
||||
m_pDS->Release();
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
bool DSound::IsEmulated() const
|
||||
{
|
||||
#ifndef _XBOX
|
||||
/* Don't bother wasting time trying to create buffers if we're
|
||||
* emulated. This also gives us better diagnostic information. */
|
||||
DSCAPS Caps;
|
||||
Caps.dwSize = sizeof(Caps);
|
||||
HRESULT hr;
|
||||
if( FAILED(hr = m_pDS->GetCaps(&Caps)) )
|
||||
{
|
||||
LOG->Warn( hr_ssprintf(hr, "m_pDS->GetCaps failed") );
|
||||
/* This is strange, so let's be conservative. */
|
||||
return true;
|
||||
}
|
||||
|
||||
return !!(Caps.dwFlags & DSCAPS_EMULDRIVER);
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
DSoundBuf::DSoundBuf()
|
||||
{
|
||||
m_pBuffer = NULL;
|
||||
m_pTempBuffer = NULL;
|
||||
}
|
||||
|
||||
RString DSoundBuf::Init( DSound &ds, DSoundBuf::hw hardware,
|
||||
int iChannels, int iSampleRate, int iSampleBits, int iWriteAhead )
|
||||
{
|
||||
m_iChannels = iChannels;
|
||||
m_iSampleRate = iSampleRate;
|
||||
m_iSampleBits = iSampleBits;
|
||||
m_iWriteAhead = iWriteAhead * bytes_per_frame();
|
||||
m_iVolume = -1; /* unset */
|
||||
m_bBufferLocked = false;
|
||||
m_iWriteCursorPos = m_iWriteCursor = m_iBufferBytesFilled = 0;
|
||||
m_iExtraWriteahead = 0;
|
||||
m_iLastPosition = 0;
|
||||
m_bPlaying = false;
|
||||
ZERO( m_iLastCursors );
|
||||
|
||||
/* The size of the actual DSound buffer. This can be large; we generally
|
||||
* won't fill it completely. */
|
||||
m_iBufferSize = 1024*64;
|
||||
m_iBufferSize = max( m_iBufferSize, m_iWriteAhead );
|
||||
|
||||
WAVEFORMATEX waveformat;
|
||||
memset( &waveformat, 0, sizeof(waveformat) );
|
||||
waveformat.cbSize = 0;
|
||||
waveformat.wFormatTag = WAVE_FORMAT_PCM;
|
||||
|
||||
bool bNeedCtrlFrequency = false;
|
||||
if( m_iSampleRate == DYNAMIC_SAMPLERATE )
|
||||
{
|
||||
m_iSampleRate = 44100;
|
||||
bNeedCtrlFrequency = true;
|
||||
}
|
||||
|
||||
int bytes = m_iSampleBits / 8;
|
||||
waveformat.wBitsPerSample = WORD(m_iSampleBits);
|
||||
waveformat.nChannels = WORD(m_iChannels);
|
||||
waveformat.nSamplesPerSec = DWORD(m_iSampleRate);
|
||||
waveformat.nBlockAlign = WORD(bytes*m_iChannels);
|
||||
waveformat.nAvgBytesPerSec = m_iSampleRate * bytes*m_iChannels;
|
||||
|
||||
/* Try to create the secondary buffer */
|
||||
DSBUFFERDESC format;
|
||||
memset( &format, 0, sizeof(format) );
|
||||
format.dwSize = sizeof(format);
|
||||
#ifdef _XBOX
|
||||
format.dwFlags = 0;
|
||||
#else
|
||||
format.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLVOLUME;
|
||||
#endif
|
||||
|
||||
#ifndef _XBOX
|
||||
/* Don't use DSBCAPS_STATIC. It's meant for static buffers, and we
|
||||
* only use streaming buffers. */
|
||||
if( hardware == HW_HARDWARE )
|
||||
format.dwFlags |= DSBCAPS_LOCHARDWARE;
|
||||
else
|
||||
format.dwFlags |= DSBCAPS_LOCSOFTWARE;
|
||||
#endif
|
||||
|
||||
if( bNeedCtrlFrequency )
|
||||
format.dwFlags |= DSBCAPS_CTRLFREQUENCY;
|
||||
|
||||
format.dwBufferBytes = m_iBufferSize;
|
||||
#ifndef _XBOX
|
||||
format.dwReserved = 0;
|
||||
#else
|
||||
DSMIXBINVOLUMEPAIR dsmbvp[8] =
|
||||
{
|
||||
{ DSMIXBIN_FRONT_LEFT, DSBVOLUME_MAX }, // left channel
|
||||
{ DSMIXBIN_FRONT_RIGHT, DSBVOLUME_MAX }, // right channel
|
||||
{ DSMIXBIN_FRONT_CENTER, DSBVOLUME_MAX }, // left channel
|
||||
{ DSMIXBIN_FRONT_CENTER, DSBVOLUME_MAX }, // right channel
|
||||
{ DSMIXBIN_BACK_LEFT, DSBVOLUME_MAX }, // left channel
|
||||
{ DSMIXBIN_BACK_RIGHT, DSBVOLUME_MAX }, // right channel
|
||||
{ DSMIXBIN_LOW_FREQUENCY, DSBVOLUME_MAX }, // left channel
|
||||
{ DSMIXBIN_LOW_FREQUENCY, DSBVOLUME_MAX } // right channel
|
||||
};
|
||||
DSMIXBINS dsmb;
|
||||
dsmb.dwMixBinCount = 8;
|
||||
dsmb.lpMixBinVolumePairs = dsmbvp;
|
||||
|
||||
format.lpMixBins = &dsmb;
|
||||
#endif
|
||||
|
||||
format.lpwfxFormat = &waveformat;
|
||||
|
||||
HRESULT hr = ds.GetDS()->CreateSoundBuffer( &format, &m_pBuffer, NULL );
|
||||
if( FAILED(hr) )
|
||||
return hr_ssprintf( hr, "CreateSoundBuffer failed (%i hz)", m_iSampleBits );
|
||||
|
||||
#ifndef _XBOX
|
||||
/* I'm not sure this should ever be needed, but ... */
|
||||
DSBCAPS bcaps;
|
||||
bcaps.dwSize=sizeof(bcaps);
|
||||
hr = m_pBuffer->GetCaps( &bcaps );
|
||||
if( FAILED(hr) )
|
||||
return hr_ssprintf( hr, "m_pBuffer->GetCaps" );
|
||||
if( int(bcaps.dwBufferBytes) != m_iBufferSize )
|
||||
{
|
||||
LOG->Warn( "bcaps.dwBufferBytes (%i) != m_iBufferSize(%i); adjusting", bcaps.dwBufferBytes, m_iBufferSize );
|
||||
m_iBufferSize = bcaps.dwBufferBytes;
|
||||
m_iWriteAhead = min( m_iWriteAhead, m_iBufferSize );
|
||||
}
|
||||
|
||||
if( !(bcaps.dwFlags & DSBCAPS_CTRLVOLUME) )
|
||||
LOG->Warn( "Sound channel missing DSBCAPS_CTRLVOLUME" );
|
||||
if( !(bcaps.dwFlags & DSBCAPS_GETCURRENTPOSITION2) )
|
||||
LOG->Warn( "Sound channel missing DSBCAPS_GETCURRENTPOSITION2" );
|
||||
|
||||
DWORD got;
|
||||
hr = m_pBuffer->GetFormat( &waveformat, sizeof(waveformat), &got );
|
||||
if( FAILED(hr) )
|
||||
LOG->Warn( hr_ssprintf(hr, "GetFormat on secondary buffer") );
|
||||
else if( (int) waveformat.nSamplesPerSec != m_iSampleRate )
|
||||
LOG->Warn( "Secondary buffer set to %i instead of %i", waveformat.nSamplesPerSec, m_iSampleRate );
|
||||
#endif
|
||||
|
||||
m_pTempBuffer = new char[m_iBufferSize];
|
||||
|
||||
return RString();
|
||||
}
|
||||
|
||||
void DSoundBuf::SetSampleRate( int hz )
|
||||
{
|
||||
m_iSampleRate = hz;
|
||||
HRESULT hr = m_pBuffer->SetFrequency( hz );
|
||||
if( FAILED(hr) )
|
||||
RageException::Throw( hr_ssprintf(hr, "m_pBuffer->SetFrequency(%i)", hz) );
|
||||
}
|
||||
|
||||
void DSoundBuf::SetVolume( float fVolume )
|
||||
{
|
||||
ASSERT_M( fVolume >= 0 && fVolume <= 1, ssprintf("%f",fVolume) );
|
||||
|
||||
if( fVolume == 0 )
|
||||
fVolume = 0.001f; // fix log10f(0) == -INF
|
||||
float iVolumeLog2 = log10f(fVolume) / log10f(2); /* vol log 2 */
|
||||
|
||||
/* Volume is a multiplier; SetVolume wants attenuation in hundredths of a decibel. */
|
||||
const int iNewVolume = max( int(1000 * iVolumeLog2), DSBVOLUME_MIN );
|
||||
|
||||
if( m_iVolume == iNewVolume )
|
||||
return;
|
||||
|
||||
HRESULT hr = m_pBuffer->SetVolume( iNewVolume );
|
||||
if( FAILED(hr) )
|
||||
{
|
||||
static bool bWarned = false;
|
||||
if( !bWarned )
|
||||
LOG->Warn( hr_ssprintf(hr, "DirectSoundBuffer::SetVolume(%i) failed", iNewVolume) );
|
||||
bWarned = true;
|
||||
return;
|
||||
}
|
||||
|
||||
m_iVolume = iNewVolume;
|
||||
}
|
||||
|
||||
/* Determine if "pos" is between "start" and "end", for a circular buffer. Note that
|
||||
* a start/end pos is ambiguous when start == end; it can mean that the buffer is
|
||||
* completely full or completely empty; this function treats it as completely empty. */
|
||||
static bool contained( int iStart, int iEnd, int iPos )
|
||||
{
|
||||
if( iEnd >= iStart ) /* iStart ... iPos ... iEnd */
|
||||
return iStart <= iPos && iPos < iEnd;
|
||||
else
|
||||
return iPos >= iStart || iPos < iEnd;
|
||||
}
|
||||
|
||||
DSoundBuf::~DSoundBuf()
|
||||
{
|
||||
if( m_pBuffer != NULL )
|
||||
m_pBuffer->Release();
|
||||
delete [] m_pTempBuffer;
|
||||
}
|
||||
|
||||
/* Check to make sure that, given the current writeahead and chunksize, we're
|
||||
* capable of filling the prefetch region entirely. If we aren't, increase
|
||||
* the writeahead. If this happens, we're underruning. */
|
||||
void DSoundBuf::CheckWriteahead( int iCursorStart, int iCursorEnd )
|
||||
{
|
||||
/* If we're in a recovering-from-underrun state, stop. */
|
||||
if( m_iExtraWriteahead )
|
||||
return;
|
||||
|
||||
/* If the driver is requesting an unreasonably large prefetch, ignore it entirely.
|
||||
* Some drivers seem to give broken write cursors sporadically, requesting that
|
||||
* almost the entire buffer be filled. There's no reason a driver should ever need
|
||||
* more than 8k frames of writeahead. */
|
||||
int iPrefetch = iCursorEnd - iCursorStart;
|
||||
wrap( iPrefetch, m_iBufferSize );
|
||||
|
||||
if( iPrefetch >= 1024*32 )
|
||||
{
|
||||
static bool bLogged = false;
|
||||
if( bLogged )
|
||||
return;
|
||||
bLogged = true;
|
||||
|
||||
LOG->Warn("Sound driver is requesting an overly large prefetch: wants %i (cursor at %i..%i), writeahead not adjusted",
|
||||
iPrefetch / bytes_per_frame(), iCursorStart, iCursorEnd );
|
||||
return;
|
||||
}
|
||||
|
||||
if( m_iWriteAhead >= iPrefetch )
|
||||
return;
|
||||
|
||||
/* We need to increase the writeahead. */
|
||||
LOG->Trace("insufficient writeahead: wants %i (cursor at %i..%i), writeahead adjusted from %i to %i",
|
||||
iPrefetch / bytes_per_frame(), iCursorStart, iCursorEnd, m_iWriteAhead, iPrefetch );
|
||||
|
||||
m_iWriteAhead = iPrefetch;
|
||||
}
|
||||
|
||||
/* Figure out if we've underrun, and act if appropriate. */
|
||||
void DSoundBuf::CheckUnderrun( int iCursorStart, int iCursorEnd )
|
||||
{
|
||||
/* If the buffer is full, we can't be underrunning. */
|
||||
if( m_iBufferBytesFilled >= m_iBufferSize )
|
||||
return;
|
||||
|
||||
/* If nothing is expected to be filled, we can't underrun. */
|
||||
if( iCursorStart == iCursorEnd )
|
||||
return;
|
||||
|
||||
/* If we're already in a recovering-from-underrun state, stop. */
|
||||
if( m_iExtraWriteahead )
|
||||
return;
|
||||
|
||||
int iFirstByteFilled = m_iWriteCursor - m_iBufferBytesFilled;
|
||||
wrap( iFirstByteFilled, m_iBufferSize );
|
||||
|
||||
/* If the end of the play cursor has data, we haven't underrun. */
|
||||
if( m_iBufferBytesFilled > 0 && contained(iFirstByteFilled, m_iWriteCursor, iCursorEnd) )
|
||||
return;
|
||||
|
||||
/* Extend the writeahead to force fill as much as required to stop underrunning.
|
||||
* This has a major benefit: if we havn't skipped so long we've passed a whole
|
||||
* buffer (64k = ~350ms), this doesn't break stride. We'll skip forward, but
|
||||
* the beat won't be lost, which is a lot easier to recover from in play. */
|
||||
/* XXX: If this happens repeatedly over a period of time, increase writeahead. */
|
||||
/* XXX: What was I doing here? This isn't working. We want to know the writeahead
|
||||
* value needed to fill from the current iFirstByteFilled all the way to iCursorEnd. */
|
||||
// int iNeededWriteahead = (iCursorStart + writeahead) - m_iWriteCursor;
|
||||
int iNeededWriteahead = iCursorEnd - iFirstByteFilled;
|
||||
wrap( iNeededWriteahead, m_iBufferSize );
|
||||
if( iNeededWriteahead > m_iWriteAhead )
|
||||
{
|
||||
m_iExtraWriteahead = iNeededWriteahead - m_iWriteAhead;
|
||||
m_iWriteAhead = iNeededWriteahead;
|
||||
}
|
||||
|
||||
int iMissedBy = iCursorEnd - m_iWriteCursor;
|
||||
wrap( iMissedBy, m_iBufferSize );
|
||||
|
||||
RString s = ssprintf( "underrun: %i..%i (%i) filled but cursor at %i..%i; missed it by %i",
|
||||
iFirstByteFilled, m_iWriteCursor, m_iBufferBytesFilled, iCursorStart, iCursorEnd, iMissedBy );
|
||||
|
||||
if( m_iExtraWriteahead )
|
||||
s += ssprintf( "; extended writeahead by %i to %i", m_iExtraWriteahead, m_iWriteAhead );
|
||||
|
||||
s += "; last: ";
|
||||
for( int i = 0; i < 4; ++i )
|
||||
s += ssprintf( "%i, %i; ", m_iLastCursors[i][0], m_iLastCursors[i][1] );
|
||||
|
||||
LOG->Trace( "%s", s.c_str() );
|
||||
}
|
||||
|
||||
bool DSoundBuf::get_output_buf( char **pBuffer, unsigned *pBufferSize, int iChunksize )
|
||||
{
|
||||
ASSERT( !m_bBufferLocked );
|
||||
|
||||
iChunksize *= bytes_per_frame();
|
||||
|
||||
DWORD iCursorStart, iCursorEnd;
|
||||
|
||||
HRESULT result;
|
||||
|
||||
/* It's easiest to think of the cursor as a block, starting and ending at
|
||||
* the two values returned by GetCurrentPosition, that we can't write to. */
|
||||
result = m_pBuffer->GetCurrentPosition( &iCursorStart, &iCursorEnd );
|
||||
#ifndef _XBOX
|
||||
if( result == DSERR_BUFFERLOST )
|
||||
{
|
||||
m_pBuffer->Restore();
|
||||
result = m_pBuffer->GetCurrentPosition( &iCursorStart, &iCursorEnd );
|
||||
}
|
||||
if( result != DS_OK )
|
||||
{
|
||||
LOG->Warn( hr_ssprintf(result, "DirectSound::GetCurrentPosition failed") );
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
memmove( &m_iLastCursors[0][0], &m_iLastCursors[1][0], sizeof(int)*6 );
|
||||
m_iLastCursors[3][0] = iCursorStart;
|
||||
m_iLastCursors[3][1] = iCursorEnd;
|
||||
|
||||
/* Some cards (Creative AudioPCI) have a no-write area even when not playing. I'm not
|
||||
* sure what that means, but it breaks the assumption that we can fill the whole writeahead
|
||||
* when prebuffering. */
|
||||
if( !m_bPlaying )
|
||||
iCursorEnd = iCursorStart;
|
||||
|
||||
/*
|
||||
* Some cards (Game Theater XP 7.1 hercwdm.sys 5.12.01.4101 [466688b, 01-10-2003])
|
||||
* have odd behavior when starting a sound: the start/end cursors go:
|
||||
*
|
||||
* 0,0 end cursor forced equal to start above (normal)
|
||||
* 4608, 1764 end cursor trailing the write cursor; except with old emulated
|
||||
* WaveOut devices, this shouldn't happen; it indicates that the
|
||||
* driver expects almost the whole buffer to be filled. Also, the
|
||||
* play cursor is too far ahead from the last call for the amount
|
||||
* of actual time passed.
|
||||
* 704, XXX start cursor moves back to where it should be. I don't have an exact
|
||||
* end cursor position, but in general from now on it stays about 5kb
|
||||
* ahead of start (which is where it should be).
|
||||
*
|
||||
* The second call is completely wrong; both the start and end cursors are meaningless.
|
||||
* Detect this: if the end cursor is close behind the start cursor, don't do anything.
|
||||
* (We can't; we have no idea what the cursors actually are.)
|
||||
*/
|
||||
{
|
||||
int iPrefetch = iCursorEnd - iCursorStart;
|
||||
wrap( iPrefetch, m_iBufferSize );
|
||||
|
||||
if( m_iBufferSize - iPrefetch < 1024*4 )
|
||||
{
|
||||
LOG->Trace( "Strange DirectSound cursor ignored: %i..%i", iCursorStart, iCursorEnd );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update m_iBufferBytesFilled. */
|
||||
{
|
||||
int iFirstByteFilled = m_iWriteCursor - m_iBufferBytesFilled;
|
||||
wrap( iFirstByteFilled, m_iBufferSize );
|
||||
|
||||
/* The number of bytes that have been played since the last time we got here: */
|
||||
int bytes_played = iCursorStart - iFirstByteFilled;
|
||||
wrap( bytes_played, m_iBufferSize );
|
||||
|
||||
m_iBufferBytesFilled -= bytes_played;
|
||||
m_iBufferBytesFilled = max( 0, m_iBufferBytesFilled );
|
||||
|
||||
if( m_iExtraWriteahead )
|
||||
{
|
||||
int used = min( m_iExtraWriteahead, bytes_played );
|
||||
RString s = ssprintf("used %i of %i (%i..%i)", used, m_iExtraWriteahead, iCursorStart, iCursorEnd );
|
||||
s += "; last: ";
|
||||
for( int i = 0; i < 4; ++i )
|
||||
s += ssprintf( "%i, %i; ", m_iLastCursors[i][0], m_iLastCursors[i][1] );
|
||||
LOG->Trace("%s", s.c_str());
|
||||
m_iWriteAhead -= used;
|
||||
m_iExtraWriteahead -= used;
|
||||
}
|
||||
}
|
||||
|
||||
CheckWriteahead( iCursorStart, iCursorEnd );
|
||||
CheckUnderrun( iCursorStart, iCursorEnd );
|
||||
|
||||
/* If we already have enough bytes written ahead, stop. */
|
||||
if( m_iBufferBytesFilled >= m_iWriteAhead )
|
||||
return false;
|
||||
|
||||
/* If we don't have enough free space in the buffer to fill a whole chunk, stop. */
|
||||
if( m_iBufferSize - m_iBufferBytesFilled < iChunksize )
|
||||
return false;
|
||||
|
||||
int iNumBytesEmpty = m_iWriteAhead - m_iBufferBytesFilled;
|
||||
iNumBytesEmpty = QuantizeUp( iNumBytesEmpty, iChunksize );
|
||||
|
||||
// LOG->Trace("gave %i at %i (%i, %i) %i filled", iNumBytesEmpty, m_iWriteCursor, cursor, write, m_iBufferBytesFilled);
|
||||
|
||||
/* Lock the audio buffer. */
|
||||
result = m_pBuffer->Lock( m_iWriteCursor, iNumBytesEmpty, (LPVOID *) &m_pLockedBuf1, (DWORD *) &m_iLockedSize1, (LPVOID *) &m_pLockedBuf2, (DWORD *) &m_iLockedSize2, 0 );
|
||||
|
||||
#ifndef _XBOX
|
||||
if( result == DSERR_BUFFERLOST )
|
||||
{
|
||||
m_pBuffer->Restore();
|
||||
result = m_pBuffer->Lock( m_iWriteCursor, iNumBytesEmpty, (LPVOID *) &m_pLockedBuf1, (DWORD *) &m_iLockedSize1, (LPVOID *) &m_pLockedBuf2, (DWORD *) &m_iLockedSize2, 0 );
|
||||
}
|
||||
#endif
|
||||
if( result != DS_OK )
|
||||
{
|
||||
LOG->Warn( hr_ssprintf(result, "Couldn't lock the DirectSound buffer.") );
|
||||
return false;
|
||||
}
|
||||
|
||||
*pBuffer = m_pTempBuffer;
|
||||
*pBufferSize = m_iLockedSize1 + m_iLockedSize2;
|
||||
|
||||
m_iWriteCursor += iNumBytesEmpty;
|
||||
if( m_iWriteCursor >= m_iBufferSize )
|
||||
m_iWriteCursor -= m_iBufferSize;
|
||||
|
||||
m_iBufferBytesFilled += iNumBytesEmpty;
|
||||
m_iWriteCursorPos += iNumBytesEmpty / bytes_per_frame();
|
||||
|
||||
m_bBufferLocked = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DSoundBuf::release_output_buf( char *pBuffer, unsigned iBufferSize )
|
||||
{
|
||||
memcpy( m_pLockedBuf1, pBuffer, m_iLockedSize1 );
|
||||
memcpy( m_pLockedBuf2, pBuffer+m_iLockedSize1, m_iLockedSize2 );
|
||||
m_pBuffer->Unlock( m_pLockedBuf1, m_iLockedSize1, m_pLockedBuf2, m_iLockedSize2 );
|
||||
m_bBufferLocked = false;
|
||||
}
|
||||
|
||||
int64_t DSoundBuf::GetPosition() const
|
||||
{
|
||||
DWORD iCursor, iJunk;
|
||||
HRESULT hr = m_pBuffer->GetCurrentPosition( &iCursor, &iJunk );
|
||||
|
||||
#ifndef _XBOX
|
||||
if( hr == DSERR_BUFFERLOST )
|
||||
{
|
||||
m_pBuffer->Restore();
|
||||
hr = m_pBuffer->GetCurrentPosition( &iCursor, &iJunk );
|
||||
}
|
||||
if( hr != DS_OK )
|
||||
{
|
||||
LOG->Warn( hr_ssprintf(hr, "DirectSound::GetPosition failed") );
|
||||
iCursor = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* This happens occasionally on "Realtek AC97 Audio". */
|
||||
if( (int) iCursor == m_iBufferSize )
|
||||
iCursor = 0;
|
||||
ASSERT_M( (int) iCursor < m_iBufferSize, ssprintf("%i, %i", iCursor, m_iBufferSize) );
|
||||
|
||||
int iCursorFrames = int(iCursor) / bytes_per_frame();
|
||||
int iWriteCursorFrames = m_iWriteCursor / bytes_per_frame();
|
||||
|
||||
int iFramesBehind = iWriteCursorFrames - iCursorFrames;
|
||||
/* iFramesBehind will be 0 if we're called before the buffer starts playing:
|
||||
* both iWriteCursorFrames and iCursorFrames will be 0. */
|
||||
if( iFramesBehind < 0 )
|
||||
iFramesBehind += buffersize_frames(); /* unwrap */
|
||||
|
||||
int64_t iRet = m_iWriteCursorPos - iFramesBehind;
|
||||
|
||||
/* Failsafe: never return a value smaller than we've already returned.
|
||||
* This can happen once in a while in underrun conditions. */
|
||||
iRet = max( m_iLastPosition, iRet );
|
||||
m_iLastPosition = iRet;
|
||||
|
||||
return iRet;
|
||||
}
|
||||
|
||||
void DSoundBuf::Play()
|
||||
{
|
||||
if( m_bPlaying )
|
||||
return;
|
||||
m_pBuffer->Play( 0, 0, DSBPLAY_LOOPING );
|
||||
m_bPlaying = true;
|
||||
}
|
||||
|
||||
void DSoundBuf::Stop()
|
||||
{
|
||||
if( !m_bPlaying )
|
||||
return;
|
||||
|
||||
m_pBuffer->Stop();
|
||||
m_pBuffer->SetCurrentPosition(0);
|
||||
|
||||
m_iWriteCursorPos = m_iWriteCursor = m_iBufferBytesFilled = 0;
|
||||
m_iLastPosition = 0;
|
||||
|
||||
m_iWriteAhead -= m_iExtraWriteahead;
|
||||
m_iExtraWriteahead = 0;
|
||||
|
||||
/* When stopped and rewound, the play and write cursors should both be 0. */
|
||||
/* This isn't true on some broken cards. */
|
||||
// DWORD iPlay, iWrite;
|
||||
// m_pBuffer->GetCurrentPosition( &iPlay, &iWrite );
|
||||
// ASSERT_M( iPlay == 0 && iWrite == 0, ssprintf("%i, %i", iPlay, iWrite) );
|
||||
|
||||
m_bPlaying = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,108 @@
|
||||
#ifndef DSOUND_HELPERS
|
||||
#define DSOUND_HELPERS 1
|
||||
|
||||
#if defined(_WINDOWS)
|
||||
#include <windows.h>
|
||||
#include <wtypes.h>
|
||||
#endif
|
||||
|
||||
struct IDirectSound;
|
||||
struct IDirectSoundBuffer;
|
||||
|
||||
class DSound
|
||||
{
|
||||
public:
|
||||
IDirectSound *GetDS() const { return m_pDS; }
|
||||
bool IsEmulated() const;
|
||||
|
||||
DSound();
|
||||
~DSound();
|
||||
RString Init();
|
||||
|
||||
private:
|
||||
IDirectSound *m_pDS;
|
||||
static BOOL CALLBACK EnumCallback( LPGUID lpGuid, LPCSTR lpcstrDescription, LPCSTR lpcstrModule, LPVOID lpContext);
|
||||
|
||||
void SetPrimaryBufferMode();
|
||||
};
|
||||
|
||||
class DSoundBuf
|
||||
{
|
||||
public:
|
||||
enum hw { HW_HARDWARE, HW_SOFTWARE, HW_DONT_CARE };
|
||||
|
||||
/* If samplerate is DYNAMIC_SAMPLERATE, then call SetSampleRate before
|
||||
* you use the sample. */
|
||||
enum { DYNAMIC_SAMPLERATE = -1 };
|
||||
|
||||
DSoundBuf();
|
||||
RString Init( DSound &ds, hw hardware,
|
||||
int iChannels, int iSampleRate, int iSampleBits, int iWriteAhead );
|
||||
|
||||
bool get_output_buf( char **pBuffer, unsigned *iBuffersize, int iChunksize );
|
||||
void release_output_buf( char *pBuffer, unsigned iBuffersize );
|
||||
|
||||
void Play();
|
||||
void Stop();
|
||||
void SetVolume( float fVolume );
|
||||
void SetSampleRate( int iRate );
|
||||
int GetSampleRate() const { return m_iSampleRate; }
|
||||
|
||||
~DSoundBuf();
|
||||
int64_t GetPosition() const;
|
||||
int64_t GetOutputPosition() const { return m_iWriteCursorPos; }
|
||||
|
||||
private:
|
||||
int buffersize_frames() const { return m_iBufferSize / bytes_per_frame(); }
|
||||
int bytes_per_frame() const { return m_iChannels*m_iSampleBits/8; }
|
||||
|
||||
void CheckWriteahead( int iCursorStart, int iCursorEnd );
|
||||
void CheckUnderrun( int iCursorStart, int iCursorEnd );
|
||||
|
||||
IDirectSoundBuffer *m_pBuffer;
|
||||
|
||||
int m_iChannels, m_iSampleRate, m_iSampleBits, m_iWriteAhead;
|
||||
int m_iVolume;
|
||||
|
||||
int m_iBufferSize;
|
||||
|
||||
int m_iWriteCursor, m_iBufferBytesFilled; /* bytes */
|
||||
int m_iExtraWriteahead;
|
||||
int64_t m_iWriteCursorPos; /* frames */
|
||||
mutable int64_t m_iLastPosition;
|
||||
bool m_bPlaying;
|
||||
|
||||
bool m_bBufferLocked;
|
||||
char *m_pLockedBuf1, *m_pLockedBuf2;
|
||||
int m_iLockedSize1, m_iLockedSize2;
|
||||
char *m_pTempBuffer;
|
||||
|
||||
int m_iLastCursors[4][2];
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,62 @@
|
||||
#include "global.h"
|
||||
#include "RageSoundDriver.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageUtil.h"
|
||||
#include "Foreach.h"
|
||||
#include "arch/arch_default.h"
|
||||
|
||||
DriverList RageSoundDriver::m_pDriverList;
|
||||
|
||||
RageSoundDriver *RageSoundDriver::Create( const RString& sDrivers )
|
||||
{
|
||||
vector<RString> DriversToTry;
|
||||
split( sDrivers.empty()? DEFAULT_SOUND_DRIVER_LIST:sDrivers, ",", DriversToTry, true );
|
||||
|
||||
FOREACH_CONST( RString, DriversToTry, Driver )
|
||||
{
|
||||
RageDriver *pDriver = m_pDriverList.Create( *Driver );
|
||||
if( pDriver == NULL )
|
||||
{
|
||||
LOG->Trace( "Unknown sound driver: %s", Driver->c_str() );
|
||||
continue;
|
||||
}
|
||||
|
||||
RageSoundDriver *pRet = dynamic_cast<RageSoundDriver *>( pDriver );
|
||||
ASSERT( pRet != NULL );
|
||||
|
||||
const RString sError = pRet->Init();
|
||||
if( sError.empty() )
|
||||
{
|
||||
LOG->Info( "Sound driver: %s", Driver->c_str() );
|
||||
return pRet;
|
||||
}
|
||||
LOG->Info( "Couldn't load driver %s: %s", Driver->c_str(), sError.c_str() );
|
||||
SAFE_DELETE( pRet );
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2002-2006 Glenn Maynard, Steve Checkoway
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,240 @@
|
||||
#ifndef RAGE_SOUND_DRIVER
|
||||
#define RAGE_SOUND_DRIVER
|
||||
|
||||
#include "RageUtil.h"
|
||||
#include "arch/RageDriver.h"
|
||||
#include "RageThreads.h"
|
||||
#include "RageTimer.h"
|
||||
#include "RageUtil_CircularBuffer.h"
|
||||
|
||||
class RageSoundBase;
|
||||
class RageTimer;
|
||||
class RageSoundMixBuffer;
|
||||
static const int samples_per_block = 512;
|
||||
|
||||
class RageSoundDriver: public RageDriver
|
||||
{
|
||||
public:
|
||||
/* Pass an empty string to get the default sound driver list. */
|
||||
static RageSoundDriver *Create( const RString &sDrivers );
|
||||
static DriverList m_pDriverList;
|
||||
|
||||
friend class RageSoundManager;
|
||||
|
||||
RageSoundDriver();
|
||||
virtual ~RageSoundDriver();
|
||||
|
||||
/* Initialize. On failure, an error message is returned. */
|
||||
virtual RString Init() { return RString(); }
|
||||
|
||||
/* A RageSound calls this to request to be played.
|
||||
* XXX: define what we should do when it can't be played (eg. out of
|
||||
* channels) */
|
||||
void StartMixing( RageSoundBase *pSound );
|
||||
|
||||
/* A RageSound calls this to request it not be played. When this function
|
||||
* returns, snd is no longer valid; ensure no running threads are still
|
||||
* accessing it before returning. This must handle gracefully the case where
|
||||
* snd was not actually being played, though it may print a warning. */
|
||||
void StopMixing( RageSoundBase *pSound );
|
||||
|
||||
/* Pause or unpause the given sound. If the sound was stopped (not paused),
|
||||
* return false and do nothing; otherwise return true and pause or unpause
|
||||
* the sound. Unlike StopMixing, pausing and unpause a sound will not lose
|
||||
* any buffered sound (but will not release any resources associated with
|
||||
* playing the sound, either). */
|
||||
bool PauseMixing( RageSoundBase *pSound, bool bStop );
|
||||
|
||||
/* Get the current hardware frame position, in the same time base as passed to
|
||||
* RageSound::CommitPlayingPosition. */
|
||||
int64_t GetHardwareFrame( RageTimer *pTimer ) const;
|
||||
virtual int64_t GetPosition() const = 0;
|
||||
|
||||
/* When a sound is finished playing (GetDataToPlay returns 0) and the sound has
|
||||
* been completely flushed (so GetPosition is no longer meaningful), call
|
||||
* RageSoundBase::SoundIsFinishedPlaying(). */
|
||||
|
||||
/* Optional, if needed: */
|
||||
virtual void Update();
|
||||
|
||||
/* Sound startup latency--delay between Play() being called and actually
|
||||
* hearing it. (This isn't necessarily the same as the buffer latency.) */
|
||||
virtual float GetPlayLatency() const { return 0.0f; }
|
||||
|
||||
virtual int GetSampleRate() const { return 44100; }
|
||||
|
||||
protected:
|
||||
/* Start the decoding. This should be called once the hardware is set up and
|
||||
* GetSampleRate will return the correct value. */
|
||||
void StartDecodeThread();
|
||||
|
||||
/* Call this before calling StartDecodeThread to set the desired decoding buffer
|
||||
* size. This is the number of frames that Mix() will try to be able to return
|
||||
* at once. This should generally be slightly larger than the sound writeahead,
|
||||
* to allow filling the buffer after an underrun. The default is 4096 frames. */
|
||||
void SetDecodeBufferSize( int frames );
|
||||
|
||||
/* Override this to set the priority of the decoding thread, which should be above
|
||||
* normal priority but not realtime. */
|
||||
virtual void SetupDecodingThread() { }
|
||||
|
||||
/*
|
||||
* Read mixed data.
|
||||
*
|
||||
* pBuf: buffer to read into
|
||||
* iFrames: number of frames (not samples) to read
|
||||
* frameno: frame number at which this sound will be heard
|
||||
* iCurrentFrame: frame number that is currently being heard
|
||||
*
|
||||
* iCurrentFrame is used for handling start timing.
|
||||
*
|
||||
* This function only mixes data; it will not lock any mutexes or do any file access, and
|
||||
* is safe to call from a realtime thread.
|
||||
*/
|
||||
void Mix( int16_t *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame );
|
||||
void Mix( float *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame );
|
||||
|
||||
private:
|
||||
/* This mutex is used for serializing with the decoder thread. Locking this mutex
|
||||
* can take a while. */
|
||||
RageMutex m_Mutex;
|
||||
|
||||
/* This mutex locks all sounds[] which are "available". (Other sound may safely
|
||||
* be accessed, and sounds may be set to available, without locking this.) */
|
||||
RageMutex m_SoundListMutex;
|
||||
|
||||
/*
|
||||
* Thread safety and state transitions:
|
||||
*
|
||||
* AVAILABLE: The sound is available to play a new sound. The decoding and mixing threads
|
||||
* will not touch a sound in this state.
|
||||
*
|
||||
* BUFFERING: The sound is stopped but StartMixing() is prebuffering. No other threads
|
||||
* will touch a sound that is BUFFERING. This isn't necessary if only the main thread
|
||||
* can call StartMixing().
|
||||
*
|
||||
* STOPPED: The sound is idle, but memory is still allocated for its buffer. Update()
|
||||
* will deallocate memory and the sound will be changed to AVAILABLE.
|
||||
*
|
||||
* PLAYING: The sound is being decoded by the decoding thread, and played by the mixing
|
||||
* thread. If the decoding thread hits EOF, the decoding thread will change the state
|
||||
* to STOPPING.
|
||||
*
|
||||
* STOPPING: The sound is being played by the mixing thread. No new data will be decoded.
|
||||
* Once the data buffer is empty (all sound has been played), Update() will change the
|
||||
* sound to HALTING.
|
||||
*
|
||||
* HALTING: The main thread has called StopMixing or the data buffer is empty. The mixing
|
||||
* thread will flush any remaining buffered data without playing it, and then move the
|
||||
* sound to STOPPED.
|
||||
*
|
||||
* The mixing thread operates without any locks. This can lead to a little overlap. For
|
||||
* example, if StopMixing() is called, moving the sound from PLAYING to HALTING, the mixing
|
||||
* thread might be in the middle of mixing data. Although HALTING means "discard buffered
|
||||
* data", some data will still be mixed. This is OK; the data is valid, and the flush will
|
||||
* happen on the next iteration.
|
||||
*
|
||||
* The only state change made by the decoding thread is on EOF: the state is changed
|
||||
* from PLAYING to STOPPING. This is done while m_Mutex is held, to prevent
|
||||
* races with other threads.
|
||||
*
|
||||
* The only state change made by the mixing thread is from HALTING to STOPPED.
|
||||
* This is done with no locks; no other thread can take a sound out of the HALTING state.
|
||||
*
|
||||
* Do not allocate or deallocate memory in the mixing thread since allocating memory
|
||||
* involves taking a lock. Instead, push the deallocation to the main thread.
|
||||
*/
|
||||
struct sound_block
|
||||
{
|
||||
float m_Buffer[samples_per_block];
|
||||
float *m_BufferNext; // beginning of the unread data
|
||||
int m_FramesInBuffer; // total number of frames at m_BufferNext
|
||||
int64_t m_iPosition; // stream frame of m_BufferNext
|
||||
sound_block() { m_FramesInBuffer = 0; m_iPosition = 0; m_BufferNext = m_Buffer; }
|
||||
};
|
||||
|
||||
struct Sound
|
||||
{
|
||||
Sound();
|
||||
void Allocate( int iFrames );
|
||||
void Deallocate();
|
||||
|
||||
RageSoundBase *m_pSound;
|
||||
RageTimer m_StartTime;
|
||||
CircBuf<sound_block> m_Buffer;
|
||||
|
||||
bool m_bPaused;
|
||||
|
||||
struct QueuedPosMap
|
||||
{
|
||||
int iFrames;
|
||||
int64_t iStreamFrame;
|
||||
int64_t iHardwareFrame;
|
||||
};
|
||||
|
||||
CircBuf<QueuedPosMap> m_PosMapQueue;
|
||||
|
||||
enum
|
||||
{
|
||||
AVAILABLE,
|
||||
BUFFERING,
|
||||
STOPPED, /* idle */
|
||||
|
||||
/* This state is set by the decoder thread, indicating that the sound has just
|
||||
* reached EOF. Once the mixing thread finishes flushing buffer, it'll change
|
||||
* to the STOPPING_FINISH state. */
|
||||
STOPPING,
|
||||
|
||||
HALTING, /* stop immediately */
|
||||
PLAYING
|
||||
} m_State;
|
||||
};
|
||||
|
||||
/* List of currently playing sounds: XXX no vector */
|
||||
Sound m_Sounds[32];
|
||||
|
||||
int64_t ClampHardwareFrame( int64_t iHardwareFrame ) const;
|
||||
mutable int64_t m_iMaxHardwareFrame;
|
||||
|
||||
bool m_bShutdownDecodeThread;
|
||||
|
||||
static int DecodeThread_start( void *p );
|
||||
void DecodeThread();
|
||||
RageSoundMixBuffer &MixIntoBuffer( int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame );
|
||||
RageThread m_DecodeThread;
|
||||
|
||||
int GetDataForSound( Sound &s );
|
||||
};
|
||||
|
||||
// Can't use Create##name because many of these have -sw suffixes.
|
||||
#define REGISTER_SOUND_DRIVER_CLASS2( name, x ) \
|
||||
static RegisterRageDriver register_##x( &RageSoundDriver::m_pDriverList, #name, CreateClass<RageSoundDriver_##x, RageDriver> )
|
||||
#define REGISTER_SOUND_DRIVER_CLASS( name ) REGISTER_SOUND_DRIVER_CLASS2( name, name )
|
||||
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,175 @@
|
||||
#include "global.h"
|
||||
#include "RageSoundDriver_ALSA9_Software.h"
|
||||
|
||||
#include "RageLog.h"
|
||||
#include "RageSound.h"
|
||||
#include "RageSoundManager.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageTimer.h"
|
||||
#include "ALSA9Dynamic.h"
|
||||
#include "PrefsManager.h"
|
||||
|
||||
#include "archutils/Unix/GetSysInfo.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
REGISTER_SOUND_DRIVER_CLASS2( ALSA-sw, ALSA9_Software );
|
||||
|
||||
static const int channels = 2;
|
||||
static const int samples_per_frame = channels;
|
||||
static const int bytes_per_frame = sizeof(int16_t) * samples_per_frame;
|
||||
|
||||
/* Linux 2.6 has a fine-grained scheduler. We can almost always use a smaller buffer
|
||||
* size than in 2.4. XXX: Some cards can handle smaller buffer sizes than others. */
|
||||
static const unsigned g_iMaxWriteahead_linux_26 = 512;
|
||||
static const unsigned safe_writeahead = 1024*4;
|
||||
static unsigned g_iMaxWriteahead;
|
||||
const int num_chunks = 8;
|
||||
|
||||
int RageSoundDriver_ALSA9_Software::MixerThread_start( void *p )
|
||||
{
|
||||
((RageSoundDriver_ALSA9_Software *) p)->MixerThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RageSoundDriver_ALSA9_Software::MixerThread()
|
||||
{
|
||||
setpriority( PRIO_PROCESS, 0, -15 );
|
||||
|
||||
while( !m_bShutdown )
|
||||
{
|
||||
while( !m_bShutdown && GetData() )
|
||||
;
|
||||
|
||||
m_pPCM->WaitUntilFramesCanBeFilled( 100 );
|
||||
}
|
||||
}
|
||||
|
||||
/* Returns the number of frames processed */
|
||||
bool RageSoundDriver_ALSA9_Software::GetData()
|
||||
{
|
||||
const int frames_to_fill = m_pPCM->GetNumFramesToFill();
|
||||
if( frames_to_fill <= 0 )
|
||||
return false;
|
||||
|
||||
static int16_t *buf = NULL;
|
||||
static int bufsize = 0;
|
||||
if( buf && bufsize < frames_to_fill )
|
||||
{
|
||||
delete[] buf;
|
||||
buf = NULL;
|
||||
}
|
||||
if( !buf )
|
||||
{
|
||||
buf = new int16_t[frames_to_fill*samples_per_frame];
|
||||
bufsize = frames_to_fill;
|
||||
}
|
||||
|
||||
const int64_t play_pos = m_pPCM->GetPlayPos();
|
||||
const int64_t cur_play_pos = m_pPCM->GetPosition();
|
||||
|
||||
this->Mix( buf, frames_to_fill, play_pos, cur_play_pos );
|
||||
m_pPCM->Write( buf, frames_to_fill );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
int64_t RageSoundDriver_ALSA9_Software::GetPosition() const
|
||||
{
|
||||
return m_pPCM->GetPosition();
|
||||
}
|
||||
|
||||
void RageSoundDriver_ALSA9_Software::SetupDecodingThread()
|
||||
{
|
||||
setpriority( PRIO_PROCESS, 0, -5 );
|
||||
}
|
||||
|
||||
|
||||
RageSoundDriver_ALSA9_Software::RageSoundDriver_ALSA9_Software()
|
||||
{
|
||||
m_pPCM = NULL;
|
||||
m_bShutdown = false;
|
||||
}
|
||||
|
||||
RString RageSoundDriver_ALSA9_Software::Init()
|
||||
{
|
||||
RString sError = LoadALSA();
|
||||
if( sError != "" )
|
||||
return ssprintf( "Driver unusable: %s", sError.c_str() );
|
||||
|
||||
g_iMaxWriteahead = safe_writeahead;
|
||||
RString sys;
|
||||
int vers;
|
||||
GetKernel( sys, vers );
|
||||
LOG->Trace( "OS: %s ver %06i", sys.c_str(), vers );
|
||||
if( sys == "Linux" && vers >= 20600 )
|
||||
g_iMaxWriteahead = g_iMaxWriteahead_linux_26;
|
||||
|
||||
if( PREFSMAN->m_iSoundWriteAhead )
|
||||
g_iMaxWriteahead = PREFSMAN->m_iSoundWriteAhead;
|
||||
|
||||
m_pPCM = new Alsa9Buf();
|
||||
sError = m_pPCM->Init( channels,
|
||||
g_iMaxWriteahead,
|
||||
g_iMaxWriteahead / num_chunks,
|
||||
PREFSMAN->m_iSoundPreferredSampleRate );
|
||||
if( sError != "" )
|
||||
return sError;
|
||||
|
||||
m_iSampleRate = m_pPCM->GetSampleRate();
|
||||
|
||||
StartDecodeThread();
|
||||
|
||||
m_MixingThread.SetName( "RageSoundDriver_ALSA9_Software" );
|
||||
m_MixingThread.Create( MixerThread_start, this );
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
RageSoundDriver_ALSA9_Software::~RageSoundDriver_ALSA9_Software()
|
||||
{
|
||||
if( m_MixingThread.IsCreated() )
|
||||
{
|
||||
/* Signal the mixing thread to quit. */
|
||||
m_bShutdown = true;
|
||||
LOG->Trace("Shutting down mixer thread ...");
|
||||
m_MixingThread.Wait();
|
||||
LOG->Trace("Mixer thread shut down.");
|
||||
}
|
||||
|
||||
delete m_pPCM;
|
||||
|
||||
UnloadALSA();
|
||||
}
|
||||
|
||||
float RageSoundDriver_ALSA9_Software::GetPlayLatency() const
|
||||
{
|
||||
return float(g_iMaxWriteahead) / m_iSampleRate;
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard, Aaron VonderHaar
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef RAGE_SOUND_ALSA9_SOFTWARE_H
|
||||
#define RAGE_SOUND_ALSA9_SOFTWARE_H
|
||||
|
||||
#include "RageSound.h"
|
||||
#include "RageThreads.h"
|
||||
#include "RageSoundDriver.h"
|
||||
|
||||
#include "ALSA9Helpers.h"
|
||||
|
||||
class RageSoundDriver_ALSA9_Software: public RageSoundDriver
|
||||
{
|
||||
public:
|
||||
RageSoundDriver_ALSA9_Software();
|
||||
~RageSoundDriver_ALSA9_Software();
|
||||
RString Init();
|
||||
|
||||
/* virtuals: */
|
||||
int64_t GetPosition() const;
|
||||
float GetPlayLatency() const;
|
||||
int GetSampleRate() const { return m_iSampleRate; }
|
||||
|
||||
void SetupDecodingThread();
|
||||
|
||||
private:
|
||||
static int MixerThread_start( void *p );
|
||||
void MixerThread();
|
||||
bool GetData();
|
||||
|
||||
bool m_bShutdown;
|
||||
int m_iSampleRate;
|
||||
Alsa9Buf *m_pPCM;
|
||||
RageThread m_MixingThread;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard, Aaron VonderHaar
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,381 @@
|
||||
#include "global.h"
|
||||
#include "RageSoundDriver_AU.h"
|
||||
#include "RageLog.h"
|
||||
#include "PrefsManager.h"
|
||||
#include "archutils/Darwin/DarwinThreadHelpers.h"
|
||||
#include <CoreServices/CoreServices.h>
|
||||
|
||||
REGISTER_SOUND_DRIVER_CLASS2( AudioUnit, AU );
|
||||
|
||||
static const UInt32 kFramesPerPacket = 1;
|
||||
static const UInt32 kChannelsPerFrame = 2;
|
||||
static const UInt32 kBitsPerChannel = 32;
|
||||
static const UInt32 kBytesPerPacket = kChannelsPerFrame * kBitsPerChannel / 8;
|
||||
static const UInt32 kBytesPerFrame = kBytesPerPacket;
|
||||
static const UInt32 kFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsFloat;
|
||||
|
||||
#define WERROR(str, num, extra...) str ": '%s' (%lu).", ## extra, FourCCToString(num).c_str(), (num)
|
||||
#define ERROR(str, num, extra...) (ssprintf(WERROR(str, (num), ## extra)))
|
||||
|
||||
static inline RString FourCCToString( uint32_t num )
|
||||
{
|
||||
RString s( 4, '?' );
|
||||
char c;
|
||||
|
||||
c = (num >> 24) & 0xFF;
|
||||
if( c >='\x20' && c <= '\x7e' )
|
||||
s[0] = c;
|
||||
c = (num >> 16) & 0xFF;
|
||||
if( c >='\x20' && c <= '\x7e' )
|
||||
s[1] = c;
|
||||
c = (num >> 8) & 0xFF;
|
||||
if( c >='\x20' && c <= '\x7e' )
|
||||
s[2] = c;
|
||||
c = num & 0xFF;
|
||||
if( c >= '\x20' && c <= '\x7e' )
|
||||
s[3] = c;
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
RageSoundDriver_AU::RageSoundDriver_AU() : m_OutputUnit(NULL), m_iSampleRate(0), m_bDone(false), m_bStarted(false),
|
||||
m_pIOThread(NULL), m_pNotificationThread(NULL), m_Semaphore("Sound")
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void RageSoundDriver_AU::NameHALThread( CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *inRefCon )
|
||||
{
|
||||
RageSoundDriver_AU *This = (RageSoundDriver_AU *)inRefCon;
|
||||
CFRunLoopObserverInvalidate( observer );
|
||||
CFRelease( observer );
|
||||
This->m_pNotificationThread = new RageThreadRegister( "HAL notification thread" );
|
||||
This->m_Semaphore.Post();
|
||||
}
|
||||
|
||||
static void SetSampleRate( AudioUnit au, Float64 desiredRate )
|
||||
{
|
||||
AudioDeviceID OutputDevice;
|
||||
OSStatus error;
|
||||
UInt32 size = sizeof( AudioDeviceID );
|
||||
|
||||
if( (error = AudioUnitGetProperty(au, kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0, &OutputDevice, &size)) )
|
||||
{
|
||||
LOG->Warn( WERROR("No output device", error) );
|
||||
return;
|
||||
}
|
||||
|
||||
Float64 rate = 0.0;
|
||||
size = sizeof( Float64 );
|
||||
if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyNominalSampleRate,
|
||||
&size, &rate)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Couldn't get the device's sample rate", error) );
|
||||
return;
|
||||
}
|
||||
if( rate == desiredRate )
|
||||
return;
|
||||
|
||||
if( (error = AudioDeviceGetPropertyInfo(OutputDevice, 0, false, kAudioDevicePropertyAvailableNominalSampleRates,
|
||||
&size, NULL)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Couldn't get available nominal sample rates info", error) );
|
||||
return;
|
||||
}
|
||||
|
||||
const int num = size/sizeof(AudioValueRange);
|
||||
AudioValueRange *ranges = new AudioValueRange[num];
|
||||
|
||||
if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyAvailableNominalSampleRates,
|
||||
&size, ranges)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Couldn't get available nominal sample rates", error) );
|
||||
delete[] ranges;
|
||||
return;
|
||||
}
|
||||
|
||||
Float64 bestRate = 0.0;
|
||||
for( int i = 0; i < num; ++i )
|
||||
{
|
||||
if( desiredRate >= ranges[i].mMinimum && desiredRate <= ranges[i].mMaximum )
|
||||
{
|
||||
bestRate = desiredRate;
|
||||
break;
|
||||
}
|
||||
/* XXX: If the desired rate is supported by the device, then change it, if not
|
||||
* then we should select the "best" rate. I don't really know what such a best
|
||||
* rate would be. The rate closest to the desired value? A multiple of 2?
|
||||
* For now give up if the desired sample rate isn't available. */
|
||||
}
|
||||
delete[] ranges;
|
||||
if( bestRate == 0.0 )
|
||||
return;
|
||||
|
||||
if( (error = AudioDeviceSetProperty(OutputDevice, NULL, 0, false, kAudioDevicePropertyNominalSampleRate,
|
||||
sizeof(Float64), &bestRate)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Couldn't set the device's sample rate", error) );
|
||||
}
|
||||
}
|
||||
|
||||
RString RageSoundDriver_AU::Init()
|
||||
{
|
||||
ComponentDescription desc;
|
||||
|
||||
desc.componentType = kAudioUnitType_Output;
|
||||
desc.componentSubType = kAudioUnitSubType_DefaultOutput;
|
||||
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
|
||||
desc.componentFlags = 0;
|
||||
desc.componentFlagsMask = 0;
|
||||
|
||||
Component comp = FindNextComponent( NULL, &desc );
|
||||
|
||||
if( comp == NULL )
|
||||
return "Failed to find the default output unit.";
|
||||
|
||||
OSStatus error = OpenAComponent( comp, &m_OutputUnit );
|
||||
|
||||
if( error != noErr || m_OutputUnit == NULL )
|
||||
return ERROR( "Could not open the default output unit", error );
|
||||
|
||||
// Set up a callback function to generate output to the output unit
|
||||
AURenderCallbackStruct input;
|
||||
input.inputProc = Render;
|
||||
input.inputProcRefCon = this;
|
||||
|
||||
error = AudioUnitSetProperty( m_OutputUnit,
|
||||
kAudioUnitProperty_SetRenderCallback,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&input,
|
||||
sizeof(input) );
|
||||
if( error != noErr )
|
||||
return ERROR( "Failed to set render callback", error );
|
||||
|
||||
AudioStreamBasicDescription streamFormat;
|
||||
|
||||
streamFormat.mSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
streamFormat.mFormatID = kAudioFormatLinearPCM;
|
||||
streamFormat.mFormatFlags = kFormatFlags;
|
||||
streamFormat.mBytesPerPacket = kBytesPerPacket;
|
||||
streamFormat.mFramesPerPacket = kFramesPerPacket;
|
||||
streamFormat.mBytesPerFrame = kBytesPerFrame;
|
||||
streamFormat.mChannelsPerFrame = kChannelsPerFrame;
|
||||
streamFormat.mBitsPerChannel = kBitsPerChannel;
|
||||
|
||||
if( streamFormat.mSampleRate <= 0.0 )
|
||||
streamFormat.mSampleRate = 44100.0;
|
||||
m_iSampleRate = int( streamFormat.mSampleRate );
|
||||
m_TimeScale = streamFormat.mSampleRate / AudioGetHostClockFrequency();
|
||||
|
||||
// Try to set the hardware sample rate.
|
||||
SetSampleRate( m_OutputUnit, streamFormat.mSampleRate );
|
||||
|
||||
|
||||
error = AudioUnitSetProperty( m_OutputUnit,
|
||||
kAudioUnitProperty_StreamFormat,
|
||||
kAudioUnitScope_Input,
|
||||
0,
|
||||
&streamFormat,
|
||||
sizeof(AudioStreamBasicDescription) );
|
||||
if( error != noErr )
|
||||
return ERROR( "Failed to set AU stream format", error );
|
||||
UInt32 renderQuality = kRenderQuality_Max;
|
||||
|
||||
error = AudioUnitSetProperty( m_OutputUnit,
|
||||
kAudioUnitProperty_RenderQuality,
|
||||
kAudioUnitScope_Global,
|
||||
0,
|
||||
&renderQuality,
|
||||
sizeof(renderQuality) );
|
||||
if( error != noErr )
|
||||
LOG->Warn( WERROR("Failed to set the maximum render quality", error) );
|
||||
|
||||
// Initialize the AU.
|
||||
if( (error = AudioUnitInitialize(m_OutputUnit)) )
|
||||
return ERROR( "Could not initialize the AudioUnit", error );
|
||||
|
||||
StartDecodeThread();
|
||||
|
||||
// Get the HAL's runloop and attach an observer.
|
||||
{
|
||||
CFRunLoopObserverRef observerRef;
|
||||
CFRunLoopRef runLoopRef;
|
||||
CFRunLoopObserverContext context = { 0, this, NULL, NULL, NULL };
|
||||
UInt32 size = sizeof( CFRunLoopRef );
|
||||
|
||||
if( (error = AudioHardwareGetProperty(kAudioHardwarePropertyRunLoop, &size, &runLoopRef)) )
|
||||
return ERROR( "Couldn't get the HAL's run loop", error);
|
||||
|
||||
observerRef = CFRunLoopObserverCreate( kCFAllocatorDefault, kCFRunLoopAllActivities, false, 0, NameHALThread, &context );
|
||||
CFRunLoopAddObserver( runLoopRef, observerRef, kCFRunLoopDefaultMode );
|
||||
CFRunLoopWakeUp( runLoopRef );
|
||||
m_Semaphore.Wait();
|
||||
}
|
||||
|
||||
if( (error = AudioOutputUnitStart(m_OutputUnit)) )
|
||||
return ERROR( "Could not start the AudioUnit", error );
|
||||
m_bStarted = true;
|
||||
return RString();
|
||||
}
|
||||
|
||||
RageSoundDriver_AU::~RageSoundDriver_AU()
|
||||
{
|
||||
if( !m_OutputUnit )
|
||||
return;
|
||||
if( m_bStarted )
|
||||
{
|
||||
m_bDone = true;
|
||||
m_Semaphore.Wait();
|
||||
}
|
||||
AudioUnitUninitialize( m_OutputUnit );
|
||||
CloseComponent( m_OutputUnit );
|
||||
delete m_pIOThread;
|
||||
delete m_pNotificationThread;
|
||||
}
|
||||
|
||||
int64_t RageSoundDriver_AU::GetPosition() const
|
||||
{
|
||||
return int64_t( m_TimeScale * AudioGetCurrentHostTime() );
|
||||
}
|
||||
|
||||
|
||||
void RageSoundDriver_AU::SetupDecodingThread()
|
||||
{
|
||||
/* Increase the scheduling precedence of the decoder thread. */
|
||||
const RString sError = SetThreadPrecedence( 0.75f );
|
||||
if( !sError.empty() )
|
||||
LOG->Warn( "Could not set precedence of the decoding thread: %s", sError.c_str() );
|
||||
}
|
||||
|
||||
float RageSoundDriver_AU::GetPlayLatency() const
|
||||
{
|
||||
OSStatus error;
|
||||
UInt32 bufferSize;
|
||||
AudioDeviceID OutputDevice;
|
||||
UInt32 size = sizeof( AudioDeviceID );
|
||||
Float64 sampleRate;
|
||||
|
||||
if( (error = AudioUnitGetProperty(m_OutputUnit, kAudioOutputUnitProperty_CurrentDevice,
|
||||
kAudioUnitScope_Global, 0, &OutputDevice, &size)) )
|
||||
{
|
||||
LOG->Warn( WERROR("No output device", error) );
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
size = sizeof( Float64 );
|
||||
if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyNominalSampleRate, &size, &sampleRate)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Couldn't get the device sample rate", error) );
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
size = sizeof( UInt32 );
|
||||
if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyBufferFrameSize, &size, &bufferSize)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Couldn't determine buffer size", error) );
|
||||
bufferSize = 0;
|
||||
}
|
||||
|
||||
UInt32 frames;
|
||||
|
||||
size = sizeof( UInt32 );
|
||||
if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyLatency, &size, &frames)) )
|
||||
{
|
||||
LOG->Warn( WERROR( "Couldn't get device latency", error) );
|
||||
frames = 0;
|
||||
}
|
||||
|
||||
bufferSize += frames;
|
||||
size = sizeof( UInt32 );
|
||||
if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertySafetyOffset, &size, &frames)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Couldn't get device safety offset", error) );
|
||||
frames = 0;
|
||||
}
|
||||
bufferSize += frames;
|
||||
size = sizeof( UInt32 );
|
||||
|
||||
do {
|
||||
if( (error = AudioDeviceGetPropertyInfo(OutputDevice, 0, false, kAudioDevicePropertyStreams, &size, NULL)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Device has no streams", error) );
|
||||
break;
|
||||
}
|
||||
int num = size / sizeof( AudioStreamID );
|
||||
if( num == 0 )
|
||||
{
|
||||
LOG->Warn( "Device has no streams." );
|
||||
break;
|
||||
}
|
||||
AudioStreamID *streams = new AudioStreamID[num];
|
||||
|
||||
if( (error = AudioDeviceGetProperty(OutputDevice, 0, false, kAudioDevicePropertyStreams, &size, streams)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Cannot get device's streams", error) );
|
||||
delete[] streams;
|
||||
break;
|
||||
}
|
||||
if( (error = AudioStreamGetProperty(streams[0], 0, kAudioDevicePropertyLatency, &size, &frames)) )
|
||||
{
|
||||
LOG->Warn( WERROR("Stream does not report latency", error) );
|
||||
frames = 0;
|
||||
}
|
||||
delete[] streams;
|
||||
bufferSize += frames;
|
||||
} while( false );
|
||||
|
||||
return float( bufferSize / sampleRate );
|
||||
}
|
||||
|
||||
|
||||
OSStatus RageSoundDriver_AU::Render( void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData )
|
||||
{
|
||||
RageSoundDriver_AU *This = (RageSoundDriver_AU *)inRefCon;
|
||||
|
||||
if( unlikely(This->m_pIOThread == NULL) )
|
||||
This->m_pIOThread = new RageThreadRegister( "HAL I/O thread" );
|
||||
|
||||
AudioBuffer &buf = ioData->mBuffers[0];
|
||||
int64_t now = int64_t( This->m_TimeScale * AudioGetCurrentHostTime() );
|
||||
int64_t next = int64_t( This->m_TimeScale * inTimeStamp->mHostTime );
|
||||
|
||||
This->Mix( (float *)buf.mData, inNumberFrames, next, now );
|
||||
if( unlikely(This->m_bDone) )
|
||||
{
|
||||
AudioOutputUnitStop( This->m_OutputUnit );
|
||||
This->m_Semaphore.Post();
|
||||
}
|
||||
return noErr;
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2004-2007 Steve Checkoway
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,65 @@
|
||||
#ifndef RAGE_SOUND_DRIVER_AU_H
|
||||
#define RAGE_SOUND_DRIVER_AU_H
|
||||
|
||||
#include "RageSoundDriver.h"
|
||||
#include "RageThreads.h"
|
||||
#include <AudioUnit/AudioUnit.h>
|
||||
|
||||
class RageSoundDriver_AU: public RageSoundDriver
|
||||
{
|
||||
public:
|
||||
RageSoundDriver_AU();
|
||||
RString Init();
|
||||
~RageSoundDriver_AU();
|
||||
float GetPlayLatency() const;
|
||||
int GetSampleRate() const { return m_iSampleRate; }
|
||||
int64_t GetPosition() const;
|
||||
|
||||
protected:
|
||||
void SetupDecodingThread();
|
||||
|
||||
private:
|
||||
static OSStatus Render( void *inRefCon,
|
||||
AudioUnitRenderActionFlags *ioActionFlags,
|
||||
const AudioTimeStamp *inTimeStamp,
|
||||
UInt32 inBusNumber,
|
||||
UInt32 inNumberFrames,
|
||||
AudioBufferList *ioData );
|
||||
static void NameHALThread( CFRunLoopObserverRef, CFRunLoopActivity activity, void *inRefCon );
|
||||
|
||||
double m_TimeScale;
|
||||
AudioUnit m_OutputUnit;
|
||||
int m_iSampleRate;
|
||||
bool m_bDone;
|
||||
bool m_bStarted;
|
||||
RageThreadRegister *m_pIOThread;
|
||||
RageThreadRegister *m_pNotificationThread;
|
||||
RageSemaphore m_Semaphore;
|
||||
};
|
||||
|
||||
#endif
|
||||
/*
|
||||
* (c) 2004-2006 Steve Checkoway
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
#include "global.h"
|
||||
#include "RageSoundDriver_DSound_Software.h"
|
||||
#include "DSoundHelpers.h"
|
||||
|
||||
#include "RageLog.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageSoundManager.h"
|
||||
#include "PrefsManager.h"
|
||||
#include "archutils/Win32/ErrorStrings.h"
|
||||
|
||||
REGISTER_SOUND_DRIVER_CLASS2( DirectSound-sw, DSound_Software );
|
||||
|
||||
static const int channels = 2;
|
||||
static const int bytes_per_frame = channels*2; /* 16-bit */
|
||||
static const int safe_writeahead = 1024*4; /* in frames */
|
||||
static int g_iMaxWriteahead;
|
||||
|
||||
/* We'll fill the buffer in chunks this big. */
|
||||
static const int num_chunks = 8;
|
||||
static int chunksize() { return g_iMaxWriteahead / num_chunks; }
|
||||
|
||||
void RageSoundDriver_DSound_Software::MixerThread()
|
||||
{
|
||||
if( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) )
|
||||
if( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL) )
|
||||
LOG->Warn(werr_ssprintf(GetLastError(), "Failed to set sound thread priority"));
|
||||
|
||||
/* Fill a buffer before we start playing, so we don't play whatever junk is
|
||||
* in the buffer. */
|
||||
char *locked_buf;
|
||||
unsigned len;
|
||||
while( m_pPCM->get_output_buf(&locked_buf, &len, chunksize()) )
|
||||
{
|
||||
memset( locked_buf, 0, len );
|
||||
m_pPCM->release_output_buf(locked_buf, len);
|
||||
}
|
||||
|
||||
/* Start playing. */
|
||||
m_pPCM->Play();
|
||||
|
||||
while( !m_bShutdownMixerThread )
|
||||
{
|
||||
char *pLockedBuf;
|
||||
unsigned iLen;
|
||||
const int64_t iPlayPos = m_pPCM->GetOutputPosition(); /* must be called before get_output_buf */
|
||||
|
||||
if( !m_pPCM->get_output_buf(&pLockedBuf, &iLen, chunksize()) )
|
||||
{
|
||||
Sleep( chunksize()*1000 / m_iSampleRate );
|
||||
continue;
|
||||
}
|
||||
|
||||
this->Mix( (int16_t *) pLockedBuf, iLen/bytes_per_frame, iPlayPos, m_pPCM->GetPosition() );
|
||||
|
||||
m_pPCM->release_output_buf( pLockedBuf, iLen );
|
||||
}
|
||||
|
||||
/* I'm not sure why, but if we don't stop the stream now, then the thread will take
|
||||
* 90ms (our buffer size) longer to close. */
|
||||
m_pPCM->Stop();
|
||||
}
|
||||
|
||||
int64_t RageSoundDriver_DSound_Software::GetPosition() const
|
||||
{
|
||||
return m_pPCM->GetPosition();
|
||||
}
|
||||
|
||||
int RageSoundDriver_DSound_Software::MixerThread_start(void *p)
|
||||
{
|
||||
((RageSoundDriver_DSound_Software *) p)->MixerThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
RageSoundDriver_DSound_Software::RageSoundDriver_DSound_Software()
|
||||
{
|
||||
m_bShutdownMixerThread = false;
|
||||
m_pPCM = NULL;
|
||||
}
|
||||
|
||||
RString RageSoundDriver_DSound_Software::Init()
|
||||
{
|
||||
RString sError = ds.Init();
|
||||
if( sError != "" )
|
||||
return sError;
|
||||
|
||||
/* If we're emulated, we're better off with the WaveOut driver; DS
|
||||
* emulation tends to be desynced. */
|
||||
if( ds.IsEmulated() )
|
||||
return "Driver unusable (emulated device)";
|
||||
|
||||
g_iMaxWriteahead = safe_writeahead;
|
||||
if( PREFSMAN->m_iSoundWriteAhead )
|
||||
g_iMaxWriteahead = PREFSMAN->m_iSoundWriteAhead;
|
||||
|
||||
/* Create a DirectSound stream, but don't force it into hardware. */
|
||||
m_pPCM = new DSoundBuf;
|
||||
m_iSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if( m_iSampleRate == 0 )
|
||||
m_iSampleRate = 44100;
|
||||
sError = m_pPCM->Init( ds, DSoundBuf::HW_DONT_CARE, channels, m_iSampleRate, 16, g_iMaxWriteahead );
|
||||
if( sError != "" )
|
||||
return sError;
|
||||
|
||||
LOG->Info( "Software mixing at %i hz", m_iSampleRate );
|
||||
|
||||
StartDecodeThread();
|
||||
|
||||
m_MixingThread.SetName("Mixer thread");
|
||||
m_MixingThread.Create( MixerThread_start, this );
|
||||
|
||||
return RString();
|
||||
}
|
||||
|
||||
RageSoundDriver_DSound_Software::~RageSoundDriver_DSound_Software()
|
||||
{
|
||||
/* Signal the mixing thread to quit. */
|
||||
if( m_MixingThread.IsCreated() )
|
||||
{
|
||||
m_bShutdownMixerThread = true;
|
||||
LOG->Trace("Shutting down mixer thread ...");
|
||||
LOG->Flush();
|
||||
m_MixingThread.Wait();
|
||||
LOG->Trace("Mixer thread shut down.");
|
||||
LOG->Flush();
|
||||
}
|
||||
|
||||
delete m_pPCM;
|
||||
}
|
||||
|
||||
void RageSoundDriver_DSound_Software::SetupDecodingThread()
|
||||
{
|
||||
if( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL) )
|
||||
LOG->Warn( werr_ssprintf(GetLastError(), "Failed to set decoding thread priority") );
|
||||
}
|
||||
|
||||
float RageSoundDriver_DSound_Software::GetPlayLatency() const
|
||||
{
|
||||
return (1.0f / m_iSampleRate) * g_iMaxWriteahead;
|
||||
}
|
||||
|
||||
int RageSoundDriver_DSound_Software::GetSampleRate() const
|
||||
{
|
||||
return m_iSampleRate;
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,59 @@
|
||||
#ifndef RAGE_SOUND_GENERIC_TEST
|
||||
#define RAGE_SOUND_GENERIC_TEST
|
||||
|
||||
#include "DSoundHelpers.h"
|
||||
#include "RageThreads.h"
|
||||
#include "RageSoundDriver.h"
|
||||
|
||||
class RageSoundDriver_DSound_Software: public RageSoundDriver
|
||||
{
|
||||
public:
|
||||
RageSoundDriver_DSound_Software();
|
||||
virtual ~RageSoundDriver_DSound_Software();
|
||||
RString Init();
|
||||
|
||||
int64_t GetPosition() const;
|
||||
float GetPlayLatency() const;
|
||||
int GetSampleRate() const;
|
||||
|
||||
protected:
|
||||
void SetupDecodingThread();
|
||||
|
||||
private:
|
||||
DSound ds;
|
||||
DSoundBuf *m_pPCM;
|
||||
int m_iSampleRate;
|
||||
|
||||
bool m_bShutdownMixerThread;
|
||||
|
||||
static int MixerThread_start(void *p);
|
||||
void MixerThread();
|
||||
RageThread m_MixingThread;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,541 @@
|
||||
#include "global.h"
|
||||
#include "RageSoundDriver.h"
|
||||
|
||||
#include "RageLog.h"
|
||||
#include "RageSound.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageSoundMixBuffer.h"
|
||||
#include "RageSoundReader.h"
|
||||
|
||||
static const int channels = 2;
|
||||
|
||||
static int frames_to_buffer;
|
||||
|
||||
/* 512 is about 10ms, which is big enough for the tolerance of most schedulers. */
|
||||
static int chunksize() { return 512; }
|
||||
|
||||
static int underruns = 0, logged_underruns = 0;
|
||||
|
||||
RageSoundDriver::Sound::Sound()
|
||||
{
|
||||
m_pSound = NULL;
|
||||
m_State = AVAILABLE;
|
||||
m_bPaused = false;
|
||||
}
|
||||
|
||||
void RageSoundDriver::Sound::Allocate( int iFrames )
|
||||
{
|
||||
/* Reserve enough blocks in the buffer to hold the buffer. Add one, to account for
|
||||
* the fact that we may have a partial block due to a previous Mix() call. */
|
||||
const int iFramesPerBlock = samples_per_block / channels;
|
||||
const int iBlocksToPrebuffer = iFrames / iFramesPerBlock;
|
||||
m_Buffer.reserve( iBlocksToPrebuffer + 1 );
|
||||
m_PosMapQueue.reserve( 32 );
|
||||
}
|
||||
|
||||
void RageSoundDriver::Sound::Deallocate()
|
||||
{
|
||||
m_Buffer.reserve( 0 );
|
||||
m_PosMapQueue.reserve( 0 );
|
||||
}
|
||||
|
||||
int RageSoundDriver::DecodeThread_start( void *p )
|
||||
{
|
||||
((RageSoundDriver *) p)->DecodeThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int g_iTotalAhead = 0;
|
||||
static int g_iTotalAheadCount = 0;
|
||||
|
||||
RageSoundMixBuffer &RageSoundDriver::MixIntoBuffer( int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
|
||||
{
|
||||
ASSERT_M( m_DecodeThread.IsCreated(), "RageSoundDriver::StartDecodeThread() was never called" );
|
||||
|
||||
if( iFrameNumber - iCurrentFrame + iFrames > 0 )
|
||||
{
|
||||
g_iTotalAhead += (int) (iFrameNumber - iCurrentFrame + iFrames);
|
||||
++g_iTotalAheadCount;
|
||||
}
|
||||
|
||||
static RageSoundMixBuffer mix;
|
||||
|
||||
for( unsigned i = 0; i < ARRAYLEN(m_Sounds); ++i )
|
||||
{
|
||||
/* s.m_pSound can not safely be accessed from here. */
|
||||
Sound &s = m_Sounds[i];
|
||||
if( s.m_State == Sound::HALTING )
|
||||
{
|
||||
/* This indicates that this stream can be reused. */
|
||||
s.m_bPaused = false;
|
||||
s.m_State = Sound::STOPPED;
|
||||
|
||||
// LOG->Trace("set %p from HALTING to STOPPED", m_Sounds[i].m_pSound);
|
||||
continue;
|
||||
}
|
||||
|
||||
if( s.m_State != Sound::STOPPING && s.m_State != Sound::PLAYING )
|
||||
continue;
|
||||
|
||||
/* STOPPING or PLAYING. Read sound data. */
|
||||
if( m_Sounds[i].m_bPaused )
|
||||
continue;
|
||||
|
||||
int iGotFrames = 0;
|
||||
int iFramesLeft = iFrames;
|
||||
|
||||
/* Does the sound have a start time? */
|
||||
if( !s.m_StartTime.IsZero() && iCurrentFrame != -1 )
|
||||
{
|
||||
/* If the sound is supposed to start at a time past this buffer, insert silence. */
|
||||
const int64_t iFramesUntilThisBuffer = iFrameNumber - iCurrentFrame;
|
||||
const float fSecondsBeforeStart = -s.m_StartTime.Ago();
|
||||
const int64_t iFramesBeforeStart = int64_t(fSecondsBeforeStart * GetSampleRate());
|
||||
const int iSilentFramesInThisBuffer = clamp( int(iFramesBeforeStart-iFramesUntilThisBuffer), 0, iFramesLeft );
|
||||
|
||||
iGotFrames += iSilentFramesInThisBuffer;
|
||||
iFramesLeft -= iSilentFramesInThisBuffer;
|
||||
|
||||
/* If we didn't completely fill the buffer, then we've written all of the silence. */
|
||||
if( iFramesLeft )
|
||||
s.m_StartTime.SetZero();
|
||||
}
|
||||
|
||||
/* Fill actual data. */
|
||||
sound_block *p[2];
|
||||
unsigned pSize[2];
|
||||
s.m_Buffer.get_read_pointers( p, pSize );
|
||||
|
||||
while( iFramesLeft && pSize[0] )
|
||||
{
|
||||
if( !p[0]->m_FramesInBuffer )
|
||||
{
|
||||
/* We've processed all of the sound in this block. Mark it read. */
|
||||
s.m_Buffer.advance_read_pointer( 1 );
|
||||
++p[0];
|
||||
--pSize[0];
|
||||
|
||||
/* If we have more data in p[0], keep going. */
|
||||
if( pSize[0] )
|
||||
continue; // more data
|
||||
|
||||
/* We've used up p[0]. Try p[1]. */
|
||||
swap( p[0], p[1] );
|
||||
swap( pSize[0], pSize[1] );
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Note that, until we call advance_read_pointer, we can safely write to p[0]. */
|
||||
const int frames_to_read = min( iFramesLeft, p[0]->m_FramesInBuffer );
|
||||
mix.SetWriteOffset( iGotFrames*channels );
|
||||
mix.write( p[0]->m_BufferNext, frames_to_read * channels );
|
||||
|
||||
{
|
||||
Sound::QueuedPosMap pos;
|
||||
pos.iStreamFrame = iFrameNumber+iGotFrames;
|
||||
pos.iHardwareFrame = p[0]->m_iPosition;
|
||||
pos.iFrames = frames_to_read;
|
||||
|
||||
s.m_PosMapQueue.write( &pos, 1 );
|
||||
}
|
||||
|
||||
p[0]->m_BufferNext += frames_to_read*channels;
|
||||
p[0]->m_FramesInBuffer -= frames_to_read;
|
||||
p[0]->m_iPosition += frames_to_read;
|
||||
|
||||
// LOG->Trace( "incr fr rd += %i (state %i) (%p)",
|
||||
// (int) frames_to_read, s.m_State, s.m_pSound );
|
||||
|
||||
iGotFrames += frames_to_read;
|
||||
iFramesLeft -= frames_to_read;
|
||||
}
|
||||
|
||||
/* If we don't have enough to fill the buffer, we've underrun. */
|
||||
if( iGotFrames < iFrames && s.m_State == Sound::PLAYING )
|
||||
++underruns;
|
||||
}
|
||||
|
||||
return mix;
|
||||
}
|
||||
|
||||
void RageSoundDriver::Mix( int16_t *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
|
||||
{
|
||||
memset( pBuf, 0, iFrames*channels*sizeof(int16_t) );
|
||||
MixIntoBuffer( iFrames, iFrameNumber, iCurrentFrame ).read( pBuf );
|
||||
}
|
||||
|
||||
void RageSoundDriver::Mix( float *pBuf, int iFrames, int64_t iFrameNumber, int64_t iCurrentFrame )
|
||||
{
|
||||
memset( pBuf, 0, iFrames*channels*sizeof(float) );
|
||||
MixIntoBuffer( iFrames, iFrameNumber, iCurrentFrame ).read( pBuf );
|
||||
}
|
||||
|
||||
void RageSoundDriver::DecodeThread()
|
||||
{
|
||||
SetupDecodingThread();
|
||||
|
||||
while( !m_bShutdownDecodeThread )
|
||||
{
|
||||
/* Fill each playing sound, round-robin. */
|
||||
{
|
||||
int iSampleRate = GetSampleRate();
|
||||
ASSERT_M( iSampleRate > 0, ssprintf("%i", iSampleRate) );
|
||||
int iUsecs = 1000000*chunksize() / iSampleRate;
|
||||
usleep( iUsecs );
|
||||
}
|
||||
|
||||
LockMut( m_Mutex );
|
||||
// LOG->Trace("begin mix");
|
||||
|
||||
for( unsigned i = 0; i < ARRAYLEN(m_Sounds); ++i )
|
||||
{
|
||||
if( m_Sounds[i].m_State != Sound::PLAYING )
|
||||
continue;
|
||||
|
||||
Sound *pSound = &m_Sounds[i];
|
||||
|
||||
CHECKPOINT;
|
||||
while( pSound->m_Buffer.num_writable() )
|
||||
{
|
||||
int iWrote = GetDataForSound( *pSound );
|
||||
if( iWrote == RageSoundReader::WOULD_BLOCK )
|
||||
break;
|
||||
if( iWrote < 0 )
|
||||
{
|
||||
/* This sound is finishing. */
|
||||
pSound->m_State = Sound::STOPPING;
|
||||
break;
|
||||
// LOG->Trace("mixer: (#%i) eof (%p)", i, pSound->m_pSound );
|
||||
}
|
||||
}
|
||||
}
|
||||
// LOG->Trace("end mix");
|
||||
}
|
||||
}
|
||||
|
||||
/* Buffer a block of sound data for the given sound. Return the number of
|
||||
* frames buffered, or a RageSoundReader return code. */
|
||||
int RageSoundDriver::GetDataForSound( Sound &s )
|
||||
{
|
||||
sound_block *p[2];
|
||||
unsigned psize[2];
|
||||
s.m_Buffer.get_write_pointers( p, psize );
|
||||
|
||||
/* If we have no open buffer slot, we have a buffer overflow. */
|
||||
ASSERT( psize[0] > 0 );
|
||||
|
||||
sound_block *pBlock = p[0];
|
||||
int size = ARRAYLEN(pBlock->m_Buffer)/channels;
|
||||
int iRet = s.m_pSound->GetDataToPlay( pBlock->m_Buffer, size, pBlock->m_iPosition, pBlock->m_FramesInBuffer );
|
||||
if( iRet > 0 )
|
||||
{
|
||||
pBlock->m_BufferNext = pBlock->m_Buffer;
|
||||
s.m_Buffer.advance_write_pointer( 1 );
|
||||
}
|
||||
|
||||
// LOG->Trace( "incr fr wr %i (state %i) (%p)",
|
||||
// (int) pBlock->m_FramesInBuffer, s.m_State, s.m_pSound );
|
||||
|
||||
return iRet;
|
||||
}
|
||||
|
||||
|
||||
void RageSoundDriver::Update()
|
||||
{
|
||||
m_Mutex.Lock();
|
||||
for( unsigned i = 0; i < ARRAYLEN(m_Sounds); ++i )
|
||||
{
|
||||
{
|
||||
Sound::QueuedPosMap p;
|
||||
while( m_Sounds[i].m_PosMapQueue.read( &p, 1 ) )
|
||||
{
|
||||
RageSoundBase *pSound = m_Sounds[i].m_pSound;
|
||||
if( pSound != NULL )
|
||||
pSound->CommitPlayingPosition( p.iStreamFrame, p.iHardwareFrame, p.iFrames );
|
||||
}
|
||||
}
|
||||
|
||||
switch( m_Sounds[i].m_State )
|
||||
{
|
||||
case Sound::STOPPED:
|
||||
m_Sounds[i].Deallocate();
|
||||
m_Sounds[i].m_State = Sound::AVAILABLE;
|
||||
continue;
|
||||
case Sound::STOPPING:
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if( m_Sounds[i].m_Buffer.num_readable() != 0 )
|
||||
continue;
|
||||
|
||||
// LOG->Trace("finishing sound %i", i);
|
||||
|
||||
m_Sounds[i].m_pSound->SoundIsFinishedPlaying();
|
||||
m_Sounds[i].m_pSound = NULL;
|
||||
|
||||
/* This sound is done. Set it to HALTING, since the mixer thread might
|
||||
* be accessing it; it'll change it back to STOPPED once it's ready to
|
||||
* be used again. */
|
||||
m_Sounds[i].m_State = Sound::HALTING;
|
||||
// LOG->Trace("set (#%i) %p from STOPPING to HALTING", i, m_Sounds[i].m_pSound);
|
||||
}
|
||||
|
||||
static float fNext = 0;
|
||||
if( RageTimer::GetTimeSinceStart() >= fNext )
|
||||
{
|
||||
/* Lockless: only Mix() can write to underruns. */
|
||||
int current_underruns = underruns;
|
||||
if( current_underruns > logged_underruns )
|
||||
{
|
||||
LOG->MapLog( "GenericMixingUnderruns", "Mixing underruns: %i", current_underruns - logged_underruns );
|
||||
LOG->Trace( "Mixing underruns: %i", current_underruns - logged_underruns );
|
||||
logged_underruns = current_underruns;
|
||||
|
||||
/* Don't log again for at least a second, or we'll burst output
|
||||
* and possibly cause more underruns. */
|
||||
fNext = RageTimer::GetTimeSinceStart() + 1;
|
||||
}
|
||||
}
|
||||
|
||||
m_Mutex.Unlock();
|
||||
}
|
||||
|
||||
void RageSoundDriver::StartMixing( RageSoundBase *pSound )
|
||||
{
|
||||
/* Lock available m_Sounds[], and reserve a slot. */
|
||||
m_SoundListMutex.Lock();
|
||||
|
||||
unsigned i;
|
||||
for( i = 0; i < ARRAYLEN(m_Sounds); ++i )
|
||||
if( m_Sounds[i].m_State == Sound::AVAILABLE )
|
||||
break;
|
||||
if( i == ARRAYLEN(m_Sounds) )
|
||||
{
|
||||
m_SoundListMutex.Unlock();
|
||||
return;
|
||||
}
|
||||
|
||||
Sound &s = m_Sounds[i];
|
||||
s.m_State = Sound::BUFFERING;
|
||||
|
||||
/* We've reserved our slot; we can safely unlock now. Don't hold onto it longer
|
||||
* than needed, since prebuffering might take some time. */
|
||||
m_SoundListMutex.Unlock();
|
||||
|
||||
s.m_pSound = pSound;
|
||||
s.m_StartTime = pSound->GetStartTime();
|
||||
s.m_Buffer.clear();
|
||||
|
||||
/* Initialize the sound buffer. */
|
||||
int BufferSize = frames_to_buffer;
|
||||
|
||||
s.Allocate( BufferSize );
|
||||
|
||||
// LOG->Trace("StartMixing(%s) (%p)", s.m_pSound->GetLoadedFilePath().c_str(), s.m_pSound );
|
||||
|
||||
/* Prebuffer some frames before changing the sound to PLAYING. */
|
||||
while( s.m_Buffer.num_writable() )
|
||||
{
|
||||
// LOG->Trace("StartMixing: (#%i) buffering %i (%i writable) (%p)", i, (int) frames_to_buffer, s.buffer.num_writable(), s.m_pSound );
|
||||
int iWrote = GetDataForSound( s );
|
||||
if( iWrote < 0 )
|
||||
break;
|
||||
}
|
||||
|
||||
s.m_State = Sound::PLAYING;
|
||||
|
||||
// LOG->Trace("StartMixing: (#%i) finished prebuffering(%s) (%p)", i, s.m_pSound->GetLoadedFilePath().c_str(), s.m_pSound );
|
||||
}
|
||||
|
||||
void RageSoundDriver::StopMixing( RageSoundBase *pSound )
|
||||
{
|
||||
/* Lock, to make sure the decoder thread isn't running on this sound while we do this. */
|
||||
m_Mutex.Lock();
|
||||
|
||||
/* Find the sound. */
|
||||
unsigned i;
|
||||
for( i = 0; i < ARRAYLEN(m_Sounds); ++i )
|
||||
if( m_Sounds[i].m_State != Sound::AVAILABLE && m_Sounds[i].m_pSound == pSound )
|
||||
break;
|
||||
if( i == ARRAYLEN(m_Sounds) )
|
||||
{
|
||||
m_Mutex.Unlock();
|
||||
LOG->Trace( "not stopping a sound because it's not playing" );
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we're already in STOPPED, there's nothing to do. */
|
||||
if( m_Sounds[i].m_State == Sound::STOPPED )
|
||||
{
|
||||
m_Mutex.Unlock();
|
||||
LOG->Trace( "not stopping a sound because it's already in STOPPED" );
|
||||
return;
|
||||
}
|
||||
|
||||
// LOG->Trace("StopMixing: set %p (%s) to HALTING", m_Sounds[i].m_pSound, m_Sounds[i].m_pSound->GetLoadedFilePath().c_str());
|
||||
|
||||
/* Tell the mixing thread to flush the buffer. We don't have to worry about
|
||||
* the decoding thread, since we've locked m_Mutex. */
|
||||
m_Sounds[i].m_State = Sound::HALTING;
|
||||
|
||||
/* Invalidate the m_pSound pointer to guarantee we don't make any further references to
|
||||
* it. Once this call returns, the sound may no longer exist. */
|
||||
m_Sounds[i].m_pSound = NULL;
|
||||
// LOG->Trace("end StopMixing");
|
||||
|
||||
m_Mutex.Unlock();
|
||||
|
||||
pSound->SoundIsFinishedPlaying();
|
||||
}
|
||||
|
||||
|
||||
bool RageSoundDriver::PauseMixing( RageSoundBase *pSound, bool bStop )
|
||||
{
|
||||
LockMut( m_Mutex );
|
||||
|
||||
/* Find the sound. */
|
||||
unsigned i;
|
||||
for( i = 0; i < ARRAYLEN(m_Sounds); ++i )
|
||||
if( m_Sounds[i].m_State != Sound::AVAILABLE && m_Sounds[i].m_pSound == pSound )
|
||||
break;
|
||||
|
||||
/* A sound can be paused in PLAYING or STOPPING. (STOPPING means the sound
|
||||
* has been decoded to the end, and we're waiting for that data to finish, so
|
||||
* externally it looks and acts like PLAYING.) */
|
||||
if( i == ARRAYLEN(m_Sounds) ||
|
||||
(m_Sounds[i].m_State != Sound::PLAYING && m_Sounds[i].m_State != Sound::STOPPING) )
|
||||
{
|
||||
LOG->Trace( "not pausing a sound because it's not playing" );
|
||||
return false;
|
||||
}
|
||||
|
||||
m_Sounds[i].m_bPaused = bStop;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RageSoundDriver::StartDecodeThread()
|
||||
{
|
||||
ASSERT( !m_DecodeThread.IsCreated() );
|
||||
|
||||
m_DecodeThread.Create( DecodeThread_start, this );
|
||||
}
|
||||
|
||||
void RageSoundDriver::SetDecodeBufferSize( int iFrames )
|
||||
{
|
||||
ASSERT( !m_DecodeThread.IsCreated() );
|
||||
|
||||
frames_to_buffer = iFrames;
|
||||
}
|
||||
|
||||
RageSoundDriver::RageSoundDriver():
|
||||
m_Mutex("RageSoundDriver"),
|
||||
m_SoundListMutex("SoundListMutex")
|
||||
{
|
||||
m_bShutdownDecodeThread = false;
|
||||
m_iMaxHardwareFrame = 0;
|
||||
SetDecodeBufferSize( 4096 );
|
||||
|
||||
m_DecodeThread.SetName("Decode thread");
|
||||
}
|
||||
|
||||
RageSoundDriver::~RageSoundDriver()
|
||||
{
|
||||
/* Signal the decoding thread to quit. */
|
||||
if( m_DecodeThread.IsCreated() )
|
||||
{
|
||||
m_bShutdownDecodeThread = true;
|
||||
LOG->Trace("Shutting down decode thread ...");
|
||||
LOG->Flush();
|
||||
m_DecodeThread.Wait();
|
||||
LOG->Trace("Decode thread shut down.");
|
||||
LOG->Flush();
|
||||
|
||||
LOG->Info( "Mixing %f ahead in %i Mix() calls",
|
||||
float(g_iTotalAhead) / max( g_iTotalAheadCount, 1 ), g_iTotalAheadCount );
|
||||
}
|
||||
}
|
||||
|
||||
int64_t RageSoundDriver::ClampHardwareFrame( int64_t iHardwareFrame ) const
|
||||
{
|
||||
/* It's sometimes possible for the hardware position to move backwards, usually
|
||||
* on underrun. We can try to prevent this in each driver, but it's an obscure
|
||||
* error, so let's clamp the result here instead. */
|
||||
if( iHardwareFrame < m_iMaxHardwareFrame )
|
||||
{
|
||||
/* Clamp the output to one per second, so one underruns don't cascade due to
|
||||
* output spam. */
|
||||
static RageTimer last(RageZeroTimer);
|
||||
if( last.IsZero() || last.Ago() > 1.0f )
|
||||
{
|
||||
LOG->Trace( "RageSoundDriver: driver returned a lesser position (%d < %d)",
|
||||
(int)iHardwareFrame, (int)m_iMaxHardwareFrame );
|
||||
last.Touch();
|
||||
}
|
||||
return m_iMaxHardwareFrame;
|
||||
}
|
||||
m_iMaxHardwareFrame = iHardwareFrame = max( iHardwareFrame, m_iMaxHardwareFrame );
|
||||
return iHardwareFrame;
|
||||
}
|
||||
|
||||
int64_t RageSoundDriver::GetHardwareFrame( RageTimer *pTimestamp ) const
|
||||
{
|
||||
if( pTimestamp == NULL )
|
||||
return ClampHardwareFrame( GetPosition() );
|
||||
|
||||
/*
|
||||
* We may have unpredictable scheduling delays between updating the timestamp
|
||||
* and reading the sound position. If we're preempted while doing this and
|
||||
* it may have caused the timestamp to not match the returned time, retry.
|
||||
*
|
||||
* As a failsafe, only allow a few attempts. If this has to try more than
|
||||
* a few times, then probably we have thread contention that's causing more
|
||||
* severe performance problems, anyway.
|
||||
*/
|
||||
int iTries = 3;
|
||||
int64_t iPositionFrames;
|
||||
do
|
||||
{
|
||||
pTimestamp->Touch();
|
||||
iPositionFrames = GetPosition();
|
||||
} while( --iTries && pTimestamp->Ago() > 0.002f );
|
||||
|
||||
if( iTries == 0 )
|
||||
{
|
||||
static bool bLogged = false;
|
||||
if( !bLogged )
|
||||
{
|
||||
bLogged = true;
|
||||
LOG->Warn( "RageSoundDriver::GetHardwareFrame: too many tries" );
|
||||
}
|
||||
}
|
||||
|
||||
return ClampHardwareFrame( iPositionFrames );
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,66 @@
|
||||
#include "global.h"
|
||||
#include "RageSoundDriver_Null.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageUtil.h"
|
||||
#include "PrefsManager.h"
|
||||
|
||||
REGISTER_SOUND_DRIVER_CLASS( Null );
|
||||
|
||||
const int channels = 2;
|
||||
|
||||
void RageSoundDriver_Null::Update()
|
||||
{
|
||||
/* "Play" frames. */
|
||||
while( m_iLastCursorPos < GetPosition()+1024*4 )
|
||||
{
|
||||
int16_t buf[256*channels];
|
||||
this->Mix( buf, 256, m_iLastCursorPos, GetPosition() );
|
||||
m_iLastCursorPos += 256;
|
||||
}
|
||||
|
||||
RageSoundDriver::Update();
|
||||
}
|
||||
|
||||
int64_t RageSoundDriver_Null::GetPosition() const
|
||||
{
|
||||
return int64_t( RageTimer::GetTimeSinceStart() * m_iSampleRate );
|
||||
}
|
||||
|
||||
RageSoundDriver_Null::RageSoundDriver_Null()
|
||||
{
|
||||
m_iSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if( m_iSampleRate == 0 )
|
||||
m_iSampleRate = 44100;
|
||||
m_iLastCursorPos = GetPosition();
|
||||
StartDecodeThread();
|
||||
}
|
||||
|
||||
int RageSoundDriver_Null::GetSampleRate() const
|
||||
{
|
||||
return m_iSampleRate;
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard, Aaron VonderHaar
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef RAGE_SOUND_NULL
|
||||
#define RAGE_SOUND_NULL
|
||||
|
||||
#include "RageSoundDriver.h"
|
||||
|
||||
class RageSoundDriver_Null: public RageSoundDriver
|
||||
{
|
||||
public:
|
||||
RageSoundDriver_Null();
|
||||
int64_t GetPosition() const;
|
||||
int GetSampleRate() const;
|
||||
void Update();
|
||||
|
||||
private:
|
||||
int64_t m_iLastCursorPos;
|
||||
int m_iSampleRate;
|
||||
};
|
||||
#define USE_RAGE_SOUND_NULL
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard, Aaron VonderHaar
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,248 @@
|
||||
#include "global.h"
|
||||
#include "RageSoundDriver_OSS.h"
|
||||
|
||||
#include "RageLog.h"
|
||||
#include "RageSound.h"
|
||||
#include "RageSoundManager.h"
|
||||
#include "RageUtil.h"
|
||||
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/soundcard.h>
|
||||
#include <sys/select.h>
|
||||
|
||||
REGISTER_SOUND_DRIVER_CLASS( OSS );
|
||||
|
||||
#if !defined(SNDCTL_DSP_SPEED)
|
||||
#define SNDCTL_DSP_SPEED SOUND_PCM_WRITE_RATE
|
||||
#endif
|
||||
|
||||
/* samples */
|
||||
const int channels = 2;
|
||||
const int bytes_per_frame = channels*2; /* 16-bit */
|
||||
const int chunk_order = 12;
|
||||
const int num_chunks = 4;
|
||||
const int buffersize = num_chunks * (1 << (chunk_order-1)); /* in bytes */
|
||||
const int buffersize_frames = buffersize/bytes_per_frame; /* in frames */
|
||||
|
||||
int RageSoundDriver_OSS::MixerThread_start(void *p)
|
||||
{
|
||||
((RageSoundDriver_OSS *) p)->MixerThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RageSoundDriver_OSS::MixerThread()
|
||||
{
|
||||
/* We want to set a higher priority, but Unix only lets root renice
|
||||
* < 0, which is silly. Give it a try, anyway. */
|
||||
int status = nice( -10 );
|
||||
if( status != -1 )
|
||||
LOG->Trace( "Set MixerThread nice value to %d", status );
|
||||
|
||||
while( !shutdown )
|
||||
{
|
||||
while(GetData())
|
||||
;
|
||||
|
||||
fd_set f;
|
||||
FD_ZERO(&f);
|
||||
FD_SET(fd, &f);
|
||||
|
||||
usleep( 10000 );
|
||||
|
||||
struct timeval tv = { 0, 10000 };
|
||||
select(fd+1, NULL, &f, NULL, &tv);
|
||||
}
|
||||
}
|
||||
|
||||
void RageSoundDriver_OSS::SetupDecodingThread()
|
||||
{
|
||||
int status = nice( -5 );
|
||||
if( status != -1 )
|
||||
LOG->Trace( "Set DecodingThread nice value to %d", status );
|
||||
}
|
||||
|
||||
bool RageSoundDriver_OSS::GetData()
|
||||
{
|
||||
/* Look for a free buffer. */
|
||||
audio_buf_info ab;
|
||||
if( ioctl(fd, SNDCTL_DSP_GETOSPACE, &ab) == -1 )
|
||||
FAIL_M( ssprintf("ioctl(SNDCTL_DSP_GETOSPACE): %s", strerror(errno)) );
|
||||
|
||||
if( !ab.fragments )
|
||||
return false;
|
||||
|
||||
const int chunksize = ab.fragsize;
|
||||
|
||||
static int16_t *buf = NULL;
|
||||
if(!buf)
|
||||
buf = new int16_t[chunksize / sizeof(int16_t)];
|
||||
|
||||
this->Mix( buf, chunksize/bytes_per_frame, last_cursor_pos, GetPosition() );
|
||||
|
||||
int wrote = write( fd, buf, chunksize );
|
||||
if( wrote != chunksize )
|
||||
FAIL_M( ssprintf("write didn't: %i (%s)", wrote, wrote == -1? strerror(errno): "") );
|
||||
|
||||
/* Increment last_cursor_pos. */
|
||||
last_cursor_pos += chunksize / bytes_per_frame;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* XXX: There's a race on last_cursor_pos here: new data might be written after the
|
||||
* ioctl returns, incrementing last_cursor_pos. */
|
||||
int64_t RageSoundDriver_OSS::GetPosition() const
|
||||
{
|
||||
ASSERT( fd != -1 );
|
||||
|
||||
int delay;
|
||||
if(ioctl(fd, SNDCTL_DSP_GETODELAY, &delay) == -1)
|
||||
FAIL_M( ssprintf("RageSoundDriver_OSS: ioctl(SNDCTL_DSP_GETODELAY): %s", strerror(errno)) );
|
||||
|
||||
return last_cursor_pos - (delay / bytes_per_frame);
|
||||
}
|
||||
|
||||
RString RageSoundDriver_OSS::CheckOSSVersion( int fd )
|
||||
{
|
||||
int version = 0;
|
||||
|
||||
#if defined(HAVE_OSS_GETVERSION)
|
||||
if( ioctl(fd, OSS_GETVERSION, &version) != 0 )
|
||||
{
|
||||
LOG->Warn( "OSS_GETVERSION failed: %s", strerror(errno) );
|
||||
version = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Find out if /dev/dsp is really ALSA emulating it. ALSA's OSS emulation has
|
||||
* been buggy. If we got here, we probably failed to init ALSA. The only case
|
||||
* I've seen of this so far was not having access to /dev/snd devices.
|
||||
*/
|
||||
/* Reliable but only too recently available:
|
||||
if (ioctl(fd, OSS_ALSAEMULVER, &ver) == 0 && ver ) */
|
||||
|
||||
/*
|
||||
* Ack. We can't just check for /proc/asound, since a few systems have ALSA
|
||||
* loaded but actually use OSS. ALSA returns a specific version; check that,
|
||||
* too. It looks like that version is potentially a valid OSS version, so
|
||||
* check both.
|
||||
*/
|
||||
#ifndef FORCE_OSS
|
||||
#define ALSA_SNDRV_OSS_VERSION ((3<<16)|(8<<8)|(1<<4)|(0))
|
||||
if( version == ALSA_SNDRV_OSS_VERSION && IsADirectory("/rootfs/proc/asound") )
|
||||
return "RageSoundDriver_OSS: ALSA detected. ALSA OSS emulation is buggy; use ALSA natively.";
|
||||
#endif
|
||||
if( version )
|
||||
{
|
||||
int major, minor, rev;
|
||||
if( version < 361 )
|
||||
{
|
||||
major = (version/100)%10;
|
||||
minor = (version/10) %10;
|
||||
rev = (version/1) %10;
|
||||
} else {
|
||||
major = (version/0x10000) % 0x100;
|
||||
minor = (version/0x00100) % 0x100;
|
||||
rev = (version/0x00001) % 0x100;
|
||||
}
|
||||
|
||||
LOG->Info("OSS: %i.%i.%i", major, minor, rev );
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
RageSoundDriver_OSS::RageSoundDriver_OSS()
|
||||
{
|
||||
fd = -1;
|
||||
shutdown = false;
|
||||
last_cursor_pos = 0;
|
||||
}
|
||||
|
||||
RString RageSoundDriver_OSS::Init()
|
||||
{
|
||||
fd = open("/dev/dsp", O_WRONLY|O_NONBLOCK);
|
||||
if( fd == -1 )
|
||||
return ssprintf( "RageSoundDriver_OSS: Couldn't open /dev/dsp: %s", strerror(errno) );
|
||||
|
||||
RString sError = CheckOSSVersion( fd );
|
||||
if( sError != "" )
|
||||
return sError;
|
||||
|
||||
int i = AFMT_S16_LE;
|
||||
if(ioctl(fd, SNDCTL_DSP_SETFMT, &i) == -1)
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SETFMT, %i): %s", i, strerror(errno) );
|
||||
if(i != AFMT_S16_LE)
|
||||
return ssprintf( "RageSoundDriver_OSS: Wanted format %i, got %i instead", AFMT_S16_LE, i );
|
||||
|
||||
i = channels;
|
||||
if(ioctl(fd, SNDCTL_DSP_CHANNELS, &i) == -1)
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_CHANNELS, %i): %s", i, strerror(errno) );
|
||||
if(i != channels)
|
||||
return ssprintf( "RageSoundDriver_OSS: Wanted %i channels, got %i instead", channels, i );
|
||||
|
||||
i = 44100;
|
||||
if(ioctl(fd, SOUND_PCM_WRITE_RATE, &i) == -1 )
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SOUND_PCM_WRITE_RATE, %i): %s", i, strerror(errno) );
|
||||
samplerate = i;
|
||||
LOG->Trace("RageSoundDriver_OSS: sample rate %i", samplerate);
|
||||
i = (num_chunks << 16) + chunk_order;
|
||||
if(ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &i) == -1)
|
||||
return ssprintf( "RageSoundDriver_OSS: ioctl(SNDCTL_DSP_SETFRAGMENT, %i): %s", i, strerror(errno) );
|
||||
StartDecodeThread();
|
||||
|
||||
MixingThread.SetName( "RageSoundDriver_OSS" );
|
||||
MixingThread.Create( MixerThread_start, this );
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
RageSoundDriver_OSS::~RageSoundDriver_OSS()
|
||||
{
|
||||
if( MixingThread.IsCreated() )
|
||||
{
|
||||
/* Signal the mixing thread to quit. */
|
||||
shutdown = true;
|
||||
LOG->Trace("Shutting down mixer thread ...");
|
||||
MixingThread.Wait();
|
||||
LOG->Trace("Mixer thread shut down.");
|
||||
}
|
||||
|
||||
if( fd != -1 )
|
||||
close( fd );
|
||||
}
|
||||
|
||||
float RageSoundDriver_OSS::GetPlayLatency() const
|
||||
{
|
||||
return 0; // (1.0f / samplerate) * (buffersize_frames - chunksize_frames);
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef RAGE_SOUND_WAVEOUT
|
||||
#define RAGE_SOUND_WAVEOUT
|
||||
|
||||
#include "RageSoundDriver.h"
|
||||
#include "RageThreads.h"
|
||||
#include "RageTimer.h"
|
||||
|
||||
class RageSoundDriver_OSS: public RageSoundDriver
|
||||
{
|
||||
int fd;
|
||||
|
||||
bool shutdown;
|
||||
int last_cursor_pos;
|
||||
int samplerate;
|
||||
|
||||
static int MixerThread_start(void *p);
|
||||
void MixerThread();
|
||||
RageThread MixingThread;
|
||||
|
||||
static RString CheckOSSVersion( int fd );
|
||||
|
||||
public:
|
||||
bool GetData();
|
||||
int GetSampleRate() const { return samplerate; }
|
||||
|
||||
/* virtuals: */
|
||||
int64_t GetPosition() const;
|
||||
float GetPlayLatency() const;
|
||||
void SetupDecodingThread();
|
||||
|
||||
RageSoundDriver_OSS();
|
||||
RString Init();
|
||||
~RageSoundDriver_OSS();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,362 @@
|
||||
#include "global.h"
|
||||
#include "RageSoundDriver_PulseAudio.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageSound.h"
|
||||
#include "RageSoundManager.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageTimer.h"
|
||||
#include "PrefsManager.h"
|
||||
#include <pulse/error.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/resource.h>
|
||||
|
||||
/* Register the RageSoundDriver_Pulseaudio class as sound driver "Pulse" */
|
||||
REGISTER_SOUND_DRIVER_CLASS2( Pulse, PulseAudio );
|
||||
|
||||
/* Constructor */
|
||||
RageSoundDriver_PulseAudio::RageSoundDriver_PulseAudio()
|
||||
: RageSoundDriver(),
|
||||
m_LastPosition(0), m_SampleRate(0), m_Error(NULL),
|
||||
m_Sem("Pulseaudio Synchronization Semaphore"),
|
||||
m_PulseMainLoop(NULL), m_PulseCtx(NULL), m_PulseStream(NULL)
|
||||
{
|
||||
m_SampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if( m_SampleRate == 0 )
|
||||
m_SampleRate = 44100;
|
||||
}
|
||||
|
||||
RageSoundDriver_PulseAudio::~RageSoundDriver_PulseAudio()
|
||||
{
|
||||
pa_context_disconnect(m_PulseCtx);
|
||||
pa_context_unref(m_PulseCtx);
|
||||
pa_threaded_mainloop_stop(m_PulseMainLoop);
|
||||
pa_threaded_mainloop_free(m_PulseMainLoop);
|
||||
|
||||
if(m_Error != NULL)
|
||||
{
|
||||
free(m_Error);
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialization */
|
||||
RString RageSoundDriver_PulseAudio::Init()
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
LOG->Trace("Pulse: pa_threaded_mainloop_new()...");
|
||||
m_PulseMainLoop = pa_threaded_mainloop_new();
|
||||
if(m_PulseMainLoop == NULL)
|
||||
{
|
||||
return "pa_threaded_mainloop_new() failed!";
|
||||
}
|
||||
|
||||
#ifdef PA_PROP_APPLICATION_NAME /* proplist available only since 0.9.11 */
|
||||
pa_proplist *plist = pa_proplist_new();
|
||||
pa_proplist_sets(plist, PA_PROP_APPLICATION_NAME, PACKAGE_NAME);
|
||||
pa_proplist_sets(plist, PA_PROP_APPLICATION_VERSION, PACKAGE_VERSION);
|
||||
pa_proplist_sets(plist, PA_PROP_MEDIA_ROLE, "game");
|
||||
|
||||
LOG->Trace("Pulse: pa_context_new_with_proplist()...");
|
||||
|
||||
m_PulseCtx = pa_context_new_with_proplist(
|
||||
pa_threaded_mainloop_get_api(m_PulseMainLoop),
|
||||
"StepMania", plist);
|
||||
pa_proplist_free(plist);
|
||||
|
||||
if(m_PulseCtx == NULL)
|
||||
{
|
||||
return "pa_context_new_with_proplist() failed!";
|
||||
}
|
||||
#else
|
||||
LOG->Trace("Pulse: pa_context_new()...");
|
||||
m_PulseCtx = pa_context_new(
|
||||
pa_threaded_mainloop_get_api(m_PulseMainLoop),
|
||||
"Stepmania");
|
||||
if(m_PulseCtx == NULL)
|
||||
{
|
||||
return "pa_context_new() failed!";
|
||||
}
|
||||
#endif
|
||||
|
||||
pa_context_set_state_callback(m_PulseCtx, StaticCtxStateCb, this);
|
||||
|
||||
LOG->Trace("Pulse: pa_context_connect()...");
|
||||
error = pa_context_connect(m_PulseCtx, NULL, (pa_context_flags_t)0, NULL);
|
||||
|
||||
if(error < 0)
|
||||
{
|
||||
return ssprintf("pa_contect_connect(): %s",
|
||||
pa_strerror(pa_context_errno(m_PulseCtx)));
|
||||
}
|
||||
|
||||
LOG->Trace("Pulse: pa_threaded_mainloop_start()...");
|
||||
error = pa_threaded_mainloop_start(m_PulseMainLoop);
|
||||
if(error < 0)
|
||||
{
|
||||
return ssprintf("pa_threaded_mainloop_start() returned %i", error);
|
||||
}
|
||||
|
||||
/* Create the decode thread, this will be needed for Mix(), that we
|
||||
* will use as soon as a stream is ready. */
|
||||
StartDecodeThread();
|
||||
|
||||
/* Wait for the pulseaudio stream to be ready before returning.
|
||||
* An error may occur, if it appends, m_Error becomes non-NULL. */
|
||||
m_Sem.Wait();
|
||||
|
||||
if(m_Error == NULL)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return m_Error;
|
||||
}
|
||||
}
|
||||
|
||||
void RageSoundDriver_PulseAudio::m_InitStream(void)
|
||||
{
|
||||
int error;
|
||||
pa_sample_spec ss;
|
||||
pa_channel_map map;
|
||||
|
||||
/* init sample spec */
|
||||
ss.format = PA_SAMPLE_S16LE;
|
||||
ss.channels = 2;
|
||||
ss.rate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if(ss.rate == 0)
|
||||
{
|
||||
ss.rate = 44100;
|
||||
}
|
||||
|
||||
/* init channel map */
|
||||
pa_channel_map_init_stereo(&map);
|
||||
|
||||
/* check sample spec */
|
||||
if(!pa_sample_spec_valid(&ss))
|
||||
{
|
||||
if(asprintf(&m_Error, "invalid sample spec!") == -1)
|
||||
{
|
||||
m_Error = NULL;
|
||||
}
|
||||
m_Sem.Post();
|
||||
return;
|
||||
}
|
||||
|
||||
/* log the used sample spec */
|
||||
char specstring[PA_SAMPLE_SPEC_SNPRINT_MAX];
|
||||
pa_sample_spec_snprint(specstring, sizeof(specstring), &ss);
|
||||
LOG->Trace("Pulse: using sample spec: %s", specstring);
|
||||
|
||||
/* create the stream */
|
||||
LOG->Trace("Pulse: pa_stream_new()...");
|
||||
m_PulseStream = pa_stream_new(m_PulseCtx, "Stepmania Audio", &ss, &map);
|
||||
if(m_PulseStream == NULL)
|
||||
{
|
||||
if(asprintf(&m_Error, "pa_stream_new(): %s", pa_strerror(pa_context_errno(m_PulseCtx))) == -1)
|
||||
{
|
||||
m_Error = NULL;
|
||||
}
|
||||
m_Sem.Post();
|
||||
return;
|
||||
}
|
||||
|
||||
/* set the write callback, it will be called when the sound server
|
||||
* needs data */
|
||||
pa_stream_set_write_callback(m_PulseStream, StaticStreamWriteCb, this);
|
||||
|
||||
/* set the state callback, it will be called the the stream state will
|
||||
* change */
|
||||
pa_stream_set_state_callback(m_PulseStream, StaticStreamStateCb, this);
|
||||
|
||||
/* configure attributes of the stream */
|
||||
pa_buffer_attr attr;
|
||||
memset(&attr, 0x00, sizeof(attr));
|
||||
|
||||
/* tlength: Target length of the buffer.
|
||||
*
|
||||
* "The server tries to assure that at least tlength bytes are always
|
||||
* available in the per-stream server-side playback buffer. It is
|
||||
* recommended to set this to (uint32_t) -1, which will initialize
|
||||
* this to a value that is deemed sensible by the server. However,
|
||||
* this value will default to something like 2s, i.e. for applications
|
||||
* that have specific latency requirements this value should be set to
|
||||
* the maximum latency that the application can deal with."
|
||||
*
|
||||
* We don't want the default here, we want a small latency.
|
||||
* We use pa_usec_to_bytes() to convert a latency to a buffer size.
|
||||
*/
|
||||
attr.tlength = pa_usec_to_bytes(20*PA_USEC_PER_MSEC, &ss);
|
||||
|
||||
/* maxlength: Maximum length of the buffer
|
||||
*
|
||||
* "Setting this to (uint32_t) -1 will initialize this to the maximum
|
||||
* value supported by server, which is recommended."
|
||||
*
|
||||
* (uint32_t)-1 is NOT working here, setting it to tlength*2, like
|
||||
* openal-soft-pulseaudio does.
|
||||
*/
|
||||
attr.maxlength = attr.tlength*2;
|
||||
|
||||
/* minreq: Minimum request
|
||||
*
|
||||
* "The server does not request less than minreq bytes from the client,
|
||||
* instead waits until the buffer is free enough to request more bytes
|
||||
* at once. It is recommended to set this to (uint32_t) -1, which will
|
||||
* initialize this to a value that is deemed sensible by the server."
|
||||
*
|
||||
* (uint32_t)-1 is NOT working here, setting it to 0, like
|
||||
* openal-soft-pulseaudio does.
|
||||
*/
|
||||
attr.minreq = 0;
|
||||
|
||||
/* prebuf: Pre-buffering
|
||||
*
|
||||
* "The server does not start with playback before at least prebuf
|
||||
* bytes are available in the buffer. It is recommended to set this
|
||||
* to (uint32_t) -1, which will initialize this to the same value as
|
||||
* tlength"
|
||||
*/
|
||||
attr.prebuf = (uint32_t)-1;
|
||||
|
||||
/* log the used target buffer length */
|
||||
LOG->Trace("Pulse: using target buffer length of %i bytes", attr.tlength);
|
||||
|
||||
/* connect the stream for playback */
|
||||
LOG->Trace("Pulse: pa_stream_connect_playback()...");
|
||||
error = pa_stream_connect_playback(m_PulseStream, NULL, &attr,
|
||||
PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL);
|
||||
if(error < 0)
|
||||
{
|
||||
if(asprintf(&m_Error, "pa_stream_connect_playback(): %s",
|
||||
pa_strerror(pa_context_errno(m_PulseCtx))) == -1)
|
||||
{
|
||||
m_Error = NULL;
|
||||
}
|
||||
m_Sem.Post();
|
||||
return;
|
||||
}
|
||||
|
||||
m_SampleRate = ss.rate;
|
||||
}
|
||||
|
||||
void RageSoundDriver_PulseAudio::CtxStateCb(pa_context *c)
|
||||
{
|
||||
switch (pa_context_get_state(m_PulseCtx))
|
||||
{
|
||||
case PA_CONTEXT_CONNECTING:
|
||||
LOG->Trace("Pulse: Context connecting...");
|
||||
break;
|
||||
case PA_CONTEXT_AUTHORIZING:
|
||||
LOG->Trace("Pulse: Context authorizing...");
|
||||
break;
|
||||
case PA_CONTEXT_SETTING_NAME:
|
||||
LOG->Trace("Pulse: Context setting name...");
|
||||
break;
|
||||
case PA_CONTEXT_READY:
|
||||
LOG->Trace("Pulse: Context ready now.");
|
||||
m_InitStream();
|
||||
break;
|
||||
case PA_CONTEXT_TERMINATED:
|
||||
case PA_CONTEXT_FAILED:
|
||||
if(asprintf(&m_Error, "context connection failed: %s", pa_strerror(pa_context_errno(m_PulseCtx))) == -1)
|
||||
{
|
||||
m_Error = NULL;
|
||||
}
|
||||
m_Sem.Post();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RageSoundDriver_PulseAudio::StreamStateCb(pa_stream *s)
|
||||
{
|
||||
switch(pa_stream_get_state(m_PulseStream))
|
||||
{
|
||||
case PA_STREAM_CREATING:
|
||||
LOG->Trace("Pulse: Stream creating...");
|
||||
break;
|
||||
case PA_STREAM_READY:
|
||||
LOG->Trace("Pulse: Stream ready now/");
|
||||
m_Sem.Post();
|
||||
return;
|
||||
break;
|
||||
case PA_STREAM_UNCONNECTED:
|
||||
case PA_STREAM_TERMINATED:
|
||||
case PA_STREAM_FAILED:
|
||||
if(asprintf(&m_Error, "stream connection failed: %s",
|
||||
pa_strerror(pa_context_errno(m_PulseCtx))) == -1)
|
||||
{
|
||||
}
|
||||
m_Sem.Post();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int64_t RageSoundDriver_PulseAudio::GetPosition() const
|
||||
{
|
||||
return m_LastPosition;
|
||||
}
|
||||
|
||||
void RageSoundDriver_PulseAudio::StreamWriteCb(pa_stream *s, size_t length)
|
||||
{
|
||||
#if PA_API_VERSION <= 11
|
||||
/* We have to multiply the requested length by 2 on 0.9.10
|
||||
* maybe the requested length is given in frames instead of bytes */
|
||||
length *= 2;
|
||||
#endif
|
||||
size_t nbframes = length / sizeof(int16_t); /* we use 16-bit frames */
|
||||
int16_t buf[nbframes];
|
||||
int64_t pos1 = m_LastPosition;
|
||||
int64_t pos2 = pos1 + nbframes/2; /* Mix() position in stereo frames */
|
||||
this->Mix( buf, pos2-pos1, pos1, pos2);
|
||||
if(pa_stream_write(m_PulseStream, buf, length, NULL, 0, PA_SEEK_RELATIVE) < 0)
|
||||
{
|
||||
RageException::Throw("Pulse: pa_stream_write()");
|
||||
}
|
||||
m_LastPosition = pos2;
|
||||
}
|
||||
|
||||
/* Static wrappers, because pulseaudio is a C API, it uses callbacks.
|
||||
* So we have to write wrappers that will call our objects callbacks. */
|
||||
void RageSoundDriver_PulseAudio::StaticCtxStateCb(pa_context *c, void *user)
|
||||
{
|
||||
RageSoundDriver_PulseAudio *obj = (RageSoundDriver_PulseAudio*)user;
|
||||
obj->CtxStateCb(c);
|
||||
}
|
||||
void RageSoundDriver_PulseAudio::StaticStreamStateCb(pa_stream *s, void *user)
|
||||
{
|
||||
RageSoundDriver_PulseAudio *obj = (RageSoundDriver_PulseAudio*)user;
|
||||
obj->StreamStateCb(s);
|
||||
}
|
||||
void RageSoundDriver_PulseAudio::StaticStreamWriteCb(pa_stream *s, size_t length, void *user)
|
||||
{
|
||||
RageSoundDriver_PulseAudio *obj = (RageSoundDriver_PulseAudio*)user;
|
||||
obj->StreamWriteCb(s, length);
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2009 Damien Thebault
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef RAGE_SOUND_PULSEAUDIO_H
|
||||
#define RAGE_SOUND_PULSEAUDIO_H
|
||||
|
||||
#include "RageSound.h"
|
||||
#include "RageThreads.h"
|
||||
#include "RageSoundDriver.h"
|
||||
#include <pulse/pulseaudio.h>
|
||||
|
||||
class RageSoundDriver_PulseAudio : public RageSoundDriver
|
||||
{
|
||||
public:
|
||||
RageSoundDriver_PulseAudio();
|
||||
virtual ~RageSoundDriver_PulseAudio();
|
||||
|
||||
RString Init();
|
||||
|
||||
inline int64_t GetPosition() const;
|
||||
inline int GetSampleRate() const { return m_SampleRate; };
|
||||
|
||||
protected:
|
||||
int64_t m_LastPosition;
|
||||
int m_SampleRate;
|
||||
char *m_Error;
|
||||
|
||||
void m_InitStream();
|
||||
RageSemaphore m_Sem;
|
||||
|
||||
pa_threaded_mainloop *m_PulseMainLoop;
|
||||
pa_context *m_PulseCtx;
|
||||
pa_stream *m_PulseStream;
|
||||
|
||||
public:
|
||||
void CtxStateCb(pa_context *c);
|
||||
void StreamStateCb(pa_stream *s);
|
||||
void StreamWriteCb(pa_stream *s, size_t length);
|
||||
|
||||
static void StaticCtxStateCb(pa_context *c, void *user);
|
||||
static void StaticStreamStateCb(pa_stream *s, void *user);
|
||||
static void StaticStreamWriteCb(pa_stream *s, size_t length, void *user);
|
||||
};
|
||||
|
||||
#endif /* RAGE_SOUND_PULSEAUDIO_H */
|
||||
|
||||
/*
|
||||
* (c) 2009 Damien Thebault
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,64 @@
|
||||
#ifndef RAGE_SOUND_WAVEOUT_H
|
||||
#define RAGE_SOUND_WAVEOUT_H
|
||||
|
||||
#include "RageSoundDriver.h"
|
||||
#include "RageThreads.h"
|
||||
#include <windows.h>
|
||||
|
||||
struct WinWdmStream;
|
||||
struct WinWdmFilter;
|
||||
|
||||
class RageSoundDriver_WDMKS: public RageSoundDriver
|
||||
{
|
||||
public:
|
||||
RageSoundDriver_WDMKS();
|
||||
~RageSoundDriver_WDMKS();
|
||||
RString Init();
|
||||
|
||||
int64_t GetPosition() const;
|
||||
float GetPlayLatency() const;
|
||||
int GetSampleRate() const;
|
||||
|
||||
private:
|
||||
static int MixerThread_start( void *p );
|
||||
void MixerThread();
|
||||
bool Fill( int iPacket, RString &sError );
|
||||
void Read( void *pData, int iFrames, int iLastCursorPos, int iCurrentFrame );
|
||||
|
||||
RageThread MixingThread;
|
||||
void SetupDecodingThread();
|
||||
|
||||
bool m_bShutdown;
|
||||
int m_iLastCursorPos;
|
||||
|
||||
HANDLE m_hSignal;
|
||||
WinWdmStream *m_pStream;
|
||||
WinWdmFilter *m_pFilter;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2002-2006 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,211 @@
|
||||
#include "global.h"
|
||||
#include "RageSoundDriver_WaveOut.h"
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma comment(lib, "winmm.lib")
|
||||
#endif
|
||||
|
||||
#include "RageTimer.h"
|
||||
#include "RageLog.h"
|
||||
#include "RageSound.h"
|
||||
#include "RageUtil.h"
|
||||
#include "RageSoundManager.h"
|
||||
#include "PrefsManager.h"
|
||||
#include "archutils/Win32/ErrorStrings.h"
|
||||
|
||||
REGISTER_SOUND_DRIVER_CLASS( WaveOut );
|
||||
|
||||
const int channels = 2;
|
||||
const int bytes_per_frame = channels*2; /* 16-bit */
|
||||
const int buffersize_frames = 1024*8; /* in frames */
|
||||
const int buffersize = buffersize_frames * bytes_per_frame; /* in bytes */
|
||||
|
||||
const int num_chunks = 8;
|
||||
const int chunksize_frames = buffersize_frames / num_chunks;
|
||||
const int chunksize = buffersize / num_chunks; /* in bytes */
|
||||
|
||||
static RString wo_ssprintf( MMRESULT err, const char *szFmt, ...)
|
||||
{
|
||||
char szBuf[MAXERRORLENGTH];
|
||||
waveOutGetErrorText( err, szBuf, MAXERRORLENGTH );
|
||||
|
||||
va_list va;
|
||||
va_start( va, szFmt );
|
||||
RString s = vssprintf( szFmt, va );
|
||||
va_end( va );
|
||||
|
||||
return s += ssprintf( "(%s)", szBuf );
|
||||
}
|
||||
|
||||
int RageSoundDriver_WaveOut::MixerThread_start( void *p )
|
||||
{
|
||||
((RageSoundDriver_WaveOut *) p)->MixerThread();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void RageSoundDriver_WaveOut::MixerThread()
|
||||
{
|
||||
if( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL) )
|
||||
LOG->Warn( werr_ssprintf(GetLastError(), "Failed to set sound thread priority") );
|
||||
|
||||
while( !m_bShutdown )
|
||||
{
|
||||
while( GetData() )
|
||||
;
|
||||
|
||||
WaitForSingleObject( m_hSoundEvent, 10 );
|
||||
}
|
||||
|
||||
waveOutReset( m_hWaveOut );
|
||||
}
|
||||
|
||||
bool RageSoundDriver_WaveOut::GetData()
|
||||
{
|
||||
/* Look for a free buffer. */
|
||||
int b;
|
||||
for( b = 0; b < num_chunks; ++b )
|
||||
if( m_aBuffers[b].dwFlags & WHDR_DONE )
|
||||
break;
|
||||
if( b == num_chunks )
|
||||
return false;
|
||||
|
||||
/* Call the callback. */
|
||||
this->Mix( (int16_t *) m_aBuffers[b].lpData, chunksize_frames, m_iLastCursorPos, GetPosition() );
|
||||
|
||||
MMRESULT ret = waveOutWrite( m_hWaveOut, &m_aBuffers[b], sizeof(m_aBuffers[b]) );
|
||||
if( ret != MMSYSERR_NOERROR )
|
||||
FAIL_M( wo_ssprintf(ret, "waveOutWrite failed") );
|
||||
|
||||
/* Increment m_iLastCursorPos. */
|
||||
m_iLastCursorPos += chunksize_frames;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RageSoundDriver_WaveOut::SetupDecodingThread()
|
||||
{
|
||||
if( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL) )
|
||||
LOG->Warn( werr_ssprintf(GetLastError(), "Failed to set sound thread priority") );
|
||||
}
|
||||
|
||||
int64_t RageSoundDriver_WaveOut::GetPosition() const
|
||||
{
|
||||
MMTIME tm;
|
||||
tm.wType = TIME_SAMPLES;
|
||||
MMRESULT ret = waveOutGetPosition( m_hWaveOut, &tm, sizeof(tm) );
|
||||
if( ret != MMSYSERR_NOERROR )
|
||||
FAIL_M( wo_ssprintf(ret, "waveOutGetPosition failed") );
|
||||
|
||||
return tm.u.sample;
|
||||
}
|
||||
|
||||
RageSoundDriver_WaveOut::RageSoundDriver_WaveOut()
|
||||
{
|
||||
m_bShutdown = false;
|
||||
m_iLastCursorPos = 0;
|
||||
|
||||
m_hSoundEvent = CreateEvent( NULL, false, true, NULL );
|
||||
|
||||
m_hWaveOut = NULL;
|
||||
}
|
||||
|
||||
RString RageSoundDriver_WaveOut::Init()
|
||||
{
|
||||
m_iSampleRate = PREFSMAN->m_iSoundPreferredSampleRate;
|
||||
if( m_iSampleRate == 0 )
|
||||
m_iSampleRate = 44100;
|
||||
|
||||
WAVEFORMATEX fmt;
|
||||
fmt.wFormatTag = WAVE_FORMAT_PCM;
|
||||
fmt.nChannels = channels;
|
||||
fmt.cbSize = 0;
|
||||
fmt.nSamplesPerSec = m_iSampleRate;
|
||||
fmt.wBitsPerSample = 16;
|
||||
fmt.nBlockAlign = fmt.nChannels * fmt.wBitsPerSample / 8;
|
||||
fmt.nAvgBytesPerSec = fmt.nSamplesPerSec * fmt.nBlockAlign;
|
||||
|
||||
MMRESULT ret = waveOutOpen( &m_hWaveOut, WAVE_MAPPER, &fmt, (DWORD_PTR) m_hSoundEvent, NULL, CALLBACK_EVENT );
|
||||
if( ret != MMSYSERR_NOERROR )
|
||||
return wo_ssprintf( ret, "waveOutOpen failed" );
|
||||
|
||||
ZERO( m_aBuffers );
|
||||
for(int b = 0; b < num_chunks; ++b)
|
||||
{
|
||||
m_aBuffers[b].dwBufferLength = chunksize;
|
||||
m_aBuffers[b].lpData = new char[chunksize];
|
||||
ret = waveOutPrepareHeader( m_hWaveOut, &m_aBuffers[b], sizeof(m_aBuffers[b]) );
|
||||
if( ret != MMSYSERR_NOERROR )
|
||||
return wo_ssprintf( ret, "waveOutPrepareHeader failed" );
|
||||
m_aBuffers[b].dwFlags |= WHDR_DONE;
|
||||
}
|
||||
|
||||
LOG->Info( "WaveOut software mixing at %i hz", m_iSampleRate );
|
||||
|
||||
/* We have a very large writeahead; make sure we have a large enough decode
|
||||
* buffer to recover cleanly from underruns. */
|
||||
SetDecodeBufferSize( buffersize_frames * 3/2 );
|
||||
StartDecodeThread();
|
||||
|
||||
MixingThread.SetName( "Mixer thread" );
|
||||
MixingThread.Create( MixerThread_start, this );
|
||||
|
||||
return RString();
|
||||
}
|
||||
|
||||
RageSoundDriver_WaveOut::~RageSoundDriver_WaveOut()
|
||||
{
|
||||
/* Signal the mixing thread to quit. */
|
||||
if( MixingThread.IsCreated() )
|
||||
{
|
||||
m_bShutdown = true;
|
||||
SetEvent( m_hSoundEvent );
|
||||
LOG->Trace( "Shutting down mixer thread ..." );
|
||||
MixingThread.Wait();
|
||||
LOG->Trace( "Mixer thread shut down." );
|
||||
}
|
||||
|
||||
if( m_hWaveOut != NULL )
|
||||
{
|
||||
for( int b = 0; b < num_chunks && m_aBuffers[b].lpData != NULL; ++b )
|
||||
{
|
||||
waveOutUnprepareHeader( m_hWaveOut, &m_aBuffers[b], sizeof(m_aBuffers[b]) );
|
||||
delete [] m_aBuffers[b].lpData;
|
||||
}
|
||||
|
||||
waveOutClose( m_hWaveOut );
|
||||
}
|
||||
|
||||
CloseHandle( m_hSoundEvent );
|
||||
}
|
||||
|
||||
float RageSoundDriver_WaveOut::GetPlayLatency() const
|
||||
{
|
||||
/* If we have a 1000-byte buffer, and we fill 100 bytes at a time, we
|
||||
* almost always have between 900 and 1000 bytes filled; on average, 950. */
|
||||
return (buffersize_frames - chunksize_frames/2) * (1.0f / m_iSampleRate);
|
||||
}
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef RAGE_SOUND_WAVEOUT_H
|
||||
#define RAGE_SOUND_WAVEOUT_H
|
||||
|
||||
#include "RageSoundDriver.h"
|
||||
#include "RageThreads.h"
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
class RageSoundDriver_WaveOut: public RageSoundDriver
|
||||
{
|
||||
public:
|
||||
RageSoundDriver_WaveOut();
|
||||
~RageSoundDriver_WaveOut();
|
||||
RString Init();
|
||||
|
||||
int64_t GetPosition() const;
|
||||
float GetPlayLatency() const;
|
||||
int GetSampleRate() const { return m_iSampleRate; }
|
||||
|
||||
private:
|
||||
static int MixerThread_start( void *p );
|
||||
void MixerThread();
|
||||
RageThread MixingThread;
|
||||
bool GetData();
|
||||
void SetupDecodingThread();
|
||||
|
||||
HWAVEOUT m_hWaveOut;
|
||||
HANDLE m_hSoundEvent;
|
||||
WAVEHDR m_aBuffers[8];
|
||||
int m_iSampleRate;
|
||||
bool m_bShutdown;
|
||||
int m_iLastCursorPos;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* (c) 2002-2004 Glenn Maynard
|
||||
* All rights reserved.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, and/or sell copies of the Software, and to permit persons to
|
||||
* whom the Software is furnished to do so, provided that the above
|
||||
* copyright notice(s) and this permission notice appear in all copies of
|
||||
* the Software and that both the above copyright notice(s) and this
|
||||
* permission notice appear in supporting documentation.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
|
||||
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
|
||||
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
|
||||
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
* PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
Reference in New Issue
Block a user