Ya sin mas ideas, pego aquí el código por si algún alma piadosa puede encontrar que cosa peligrosa estoy haciendo mal.
Aclaración y explicaciones:
- El código anda perfectamente en mi computadora, pero en algunas computadoras y en circunstancias a las cuales no le encuentro un patrón, la reproducción pierde el "sync" y se siente ruido a buffer no llenado a tiempo / estática.
- Este corre sobre otro thread, con la librería standard the C en versión multithreading.
- El objeto se inicializa y destruye en el thread principal y si la inicialización no tubo ningún problema se hace correr el thread sobre la función Run() la cual tiene el loop de mezcla.
- Se cambia la prioridad al máximo sobre este thread.
- Se cambia la granularidad con timeBeginPeriod para mejorar la respuesta de WaitForMultiplesObjects.
- Usa un buffer secundario de DSound con DSBCAPS_LOCSOFTWARE y DSBCAPS_GETCURRENTPOSITION2 el cual parto en dos para usarlo como doble buffer.
- Hay un tercer buffer en un formato Fixed Point 8-24 para hacer solo una vez el clamping/saturación. La función Mix llena este buffer mezclando los sonidos en ejecución, durante el Mix hay lectura de archivos por streaming ( generalmente nunca mas de 2 )
- El cambio de modo ventana a fullscreen no es tenido en cuenta por esto, pues le handle de window no se destruye, solo se cambian sus propiedades. Esto es independiente de la falla.
- El loop de mezcla básicamente es:
1 - Esperar que se llegue a una posicion del buffer (a la mitad o al final).
2 - Lockear el buffer
3 - Volcar los datos del buffer de mezclas (el tercer buffer) en el buffer secundario en la posición adecuada.
4 - Unlockear el buffer
5 - Mezclar sobre el buffer de mezclas (el tercer buffer) los sonidos en ejecución.
6 - volver a 1
Todo lo que no entiendan supongan que esta bién.
Aquí el código:
#include "DSoundDevice.h"
#include "ScreenWindows.h"
#pragma comment (lib,"dsound.lib")
using namespace PGL;
DSoundDevice::DSoundDevice(Audio* audio)
: Audio::Device(audio)
, lpDS(0), lpdsb(0), lpdsbPrimary(0), lpdsNotify(0)
, mMixingBuffer(0), mMixingSize(0), mInternalSize(0)
, mPlaying(false), mInitialized(false)
, mIsThereAnException(false), mException("No Exception")
{
}
DSoundDevice::~DSoundDevice()
{
assert( mInitialized == false );
}
void DSoundDevice::Update()
{
if(mIsThereAnException) throw mException;
}
void DSoundDevice::Init()
{
ScreenWindows* screen = static_cast<ScreenWindows*>(pAudio->Get_Screen());
mInternalFormat.Set_SampleFormat( Audio::Format::SIGNED_16_STEREO );
mInternalFormat.Set_Rate( 44100 );
mMixingFormat.Set_SampleFormat( Audio::Format::SIGNED_FIXED_8_24_STEREO );
// mMixingFormat.Set_SampleFormat( Audio::Format::SIGNED_16_STEREO );
mMixingFormat.Set_Rate( 44100 );
DSBUFFERDESC dsbdesc;
// DSound Init
if(DirectSoundCreate(0,&lpDS,0)!=DS_OK)
{
lpDS=0;
THROW_EXCEPTION( "Init() -> DirectSoundCreate" );
}
if(lpDS->SetCooperativeLevel(screen->Get_hWnd(),DSSCL_PRIORITY)!=DS_OK)
{
Destroy();
THROW_EXCEPTION( "Init() -> SetCooperativeLevel" );
}
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_PRIMARYBUFFER;
dsbdesc.dwBufferBytes = 0;
dsbdesc.lpwfxFormat = NULL;
if(FAILED(lpDS->CreateSoundBuffer(&dsbdesc, &lpdsbPrimary, NULL)))
{
Destroy();
THROW_EXCEPTION( "Init() -> primary sound buffer CreateSoundBuffer()" );
}
// Setup primary buffer
WAVEFORMATEX wfx;
memset(&wfx, 0, sizeof(WAVEFORMATEX));
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = mInternalFormat.Get_Channels();
wfx.wBitsPerSample = mInternalFormat.Get_Bits();
wfx.nSamplesPerSec = mInternalFormat.Get_Rate();
wfx.nBlockAlign = mInternalFormat.Get_BlockAlign();
wfx.nAvgBytesPerSec = mInternalFormat.Get_AverageBytesPerSecond();
if(FAILED (lpdsbPrimary->SetFormat(&wfx)))
{
Destroy();
THROW_EXCEPTION( "Init() -> primary sound buffer SetFormat()" );
}
// get a buffer size which needs to be updated 40 times per second.
// and round it to a power of 2 (4096 bytes with 44100hz 16bits stereo)
mInternalSize = 1<<ilog2( wfx.nAvgBytesPerSec / BUFFER_UPDATES_PER_SECOND );
// Setup secondary buffer
memset(&dsbdesc, 0, sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_CTRLPOSITIONNOTIFY | DSBCAPS_LOCSOFTWARE;
dsbdesc.dwBufferBytes = mInternalSize * 2; // two buffers
dsbdesc.lpwfxFormat = &wfx;
memset(&wfx, 0, sizeof(WAVEFORMATEX));
wfx.wFormatTag = WAVE_FORMAT_PCM;
// the same format
wfx.nChannels = mInternalFormat.Get_Channels();
wfx.wBitsPerSample = mInternalFormat.Get_Bits();
wfx.nSamplesPerSec = mInternalFormat.Get_Rate();
wfx.nBlockAlign = mInternalFormat.Get_BlockAlign();
wfx.nAvgBytesPerSec = mInternalFormat.Get_AverageBytesPerSecond();
if(FAILED(lpDS->CreateSoundBuffer(&dsbdesc, &lpdsb, NULL)))
{
Destroy();
THROW_EXCEPTION( "Init() -> secondary sound buffer CreateSoundBuffer()" );
}
// Setup notifications
for (int i = 0; i < NUMEVENTS; i++)
{
rghEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
if (NULL == rghEvent[i])
{
Destroy();
THROW_EXCEPTION( "Init() -> CreateEvent()" );
}
}
rgdsbpn[0].dwOffset = 0;
rgdsbpn[0].hEventNotify = rghEvent[0];
rgdsbpn[1].dwOffset = (dsbdesc.dwBufferBytes/2);
rgdsbpn[1].hEventNotify = rghEvent[1];
if(FAILED(lpdsb->QueryInterface(IID_IDirectSoundNotify, (VOID **)&lpdsNotify)))
{
Destroy();
THROW_EXCEPTION( "Init() -> QueryInterface()" );
}
if(FAILED(lpdsNotify->SetNotificationPositions(NUMEVENTS, rgdsbpn)))
{
IDirectSoundNotify_Release(lpdsNotify);
Destroy();
THROW_EXCEPTION( "Init() -> SetNotificationPositions()" );
}
lpdsb->Play(0, 0, DSBPLAY_LOOPING);
mPlaying = true;
mMixingSize = (mInternalSize * mMixingFormat.Get_BlockAlign()) / mInternalFormat.Get_BlockAlign();
mMixingBuffer = new char[mMixingSize];
// fill the buffer for the first time
Mix(mMixingBuffer, mMixingSize, mMixingFormat);
mInitialized = true;
Start();
}
void DSoundDevice::Destroy()
{
Stop();
// DSound destroy
mInitialized = false;
if(lpdsNotify)
{
lpdsNotify->Release();
lpdsNotify = 0;
}
if(lpdsb)
{
lpdsb->Release();
lpdsb = 0;
}
if(lpdsbPrimary)
{
lpdsbPrimary->Release();
lpdsbPrimary = 0;
}
if(lpDS)
{
lpDS->Release();
lpDS = 0;
}
if(mMixingBuffer)
{
delete mMixingBuffer;
mMixingBuffer = 0;
}
}
void DSoundDevice::Run()
{
try {
if(!mInitialized) THROW_EXCEPTION( "DSound device not initialized" );
bool done = false;
// let's auto set the priority to max!
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST);
// Let's assure the granularity
TIMECAPS timecaps;
timeGetDevCaps(&timecaps, sizeof(timecaps) );
timeBeginPeriod( timecaps.wPeriodMax );
while(!Get_StopRequest() && !done)
{
DWORD dwEvt = MsgWaitForMultipleObjects(
NUMEVENTS, // How many possible events
rghEvent, // Location of handles
FALSE, // Wait for all? NO!
5, // How long to wait
QS_ALLINPUT); // Any message is an event
// To pause the mixer when the focus is lost.
if(!pAudio->Get_Screen()->Is_Active())
{
if(mPlaying)
{
mPlaying = false;
lpdsb->Stop();
}
}
else
{
if(!mPlaying)
{
mPlaying = true;
lpdsb->Play(0, 0, DSBPLAY_LOOPING);
}
}
if(dwEvt == WAIT_TIMEOUT) continue;
// WAIT_OBJECT_0 == 0 but is properly treated as an arbitrary
// index value assigned to the first event, therefore we subtract
// it from dwEvt to get the zero-based index of the event.
dwEvt -= WAIT_OBJECT_0;
// If the event was set by the buffer, there's input
// to process.
if (dwEvt < NUMEVENTS)
{
int dwStartOfs;
if (dwEvt == 0) dwStartOfs = rgdsbpn[1].dwOffset;
else dwStartOfs = rgdsbpn[0].dwOffset;
void* lpvPtr;
unsigned long dwBytes;
if( lpdsb->Lock(dwStartOfs, mInternalSize, &lpvPtr, &dwBytes, 0, 0, 0) == DSERR_BUFFERLOST)
{
lpdsb->Restore();
if( lpdsb->Lock(dwStartOfs, mInternalSize, &lpvPtr, &dwBytes, 0, 0, 0) == DSERR_BUFFERLOST)
THROW_EXCEPTION("DSound Buffer lost when locking");
}
// fill the DSound internal buffer.
mInternalFormat.Convert(lpvPtr, mMixingBuffer, mInternalSize, mMixingFormat);
if( FAILED(lpdsb->Unlock(lpvPtr, dwBytes, 0, 0)) )
THROW_EXCEPTION("DSound Buffer lost when unlocking");
assert( dwBytes == mInternalSize );
// fill the buffer for the next regeneration
Mix(mMixingBuffer, mMixingSize, mMixingFormat);
}
// If it's the last event, it's a message
// if this really needed?
else if (dwEvt == NUMEVENTS)
{
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT) done = true;
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
} // end message processing
}
timeEndPeriod( timecaps.wPeriodMax );
}
catch( const Exception& e )
{
mException = e;
mIsThereAnException = true;
}
}
Desde ya infinitas gracias!