Stratos: Punto de Encuentro de Desarrolladores

¡Bienvenido a Stratos!

Acceder

Foros





DSound Mixer toma 2

Iniciado por Pogacha, 09 de Febrero de 2008, 05:51:08 PM

« anterior - próximo »

Pogacha

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!

Zaelsius

El uso de MsgWaitForMultipleObjects me hace sospechar. Check:

- ¿Por qué tomas dwMilliseconds como 5ms (¿valor arbitrario?) y no cero? Usando cero, la función retorna inmediatamente con WAIT_TIMEOUT si no hay mensajes.

- ¿Seguro que necesitas atender todos los eventos incluidos en QS_ALLINPUT? Esa constante incluye: QS_INPUT, QS_POSTMESSAGE, QS_TIMER, QS_PAINT, QS_HOTKEY, and QS_SENDMESSAGE. Me parece que para el thread del mixer solo te interesa 1 o 2 de esos, no sé.

Buscando en Google, he leído que el uso de MsgWaitForMultipleObjects() que hace algunos ejemplos de DirectSound del DXSDK no son del todo correctos/seguros, y mencionaban no usar los mismos parámetros. También he leído que es mejor usar WaitForMultipleObjects() o WaitForSingleObject(), aunque no explicaban por qué. Espero haberte dado algunas ideas :)

Pogacha

Cita de: "ZaelSiuS"El uso de MsgWaitForMultipleObjects me hace sospechar. Check:
OK, investigue y MsgWaitForMultipleObjects es para cuando en el thread se crean ventanas y deben ser atendidos mensajes de ventanas (de ahí mi pregunta en el codigo de arriba), WaitForMultipleObjects es lo recomendado para usar cuando no hay nada mas que sincronización con dsound.
Citar- ¿Por qué tomas dwMilliseconds como 5ms (¿valor arbitrario?) y no cero? Usando cero, la función retorna inmediatamente con WAIT_TIMEOUT si no hay mensajes.
Pues mi intención es que el thread duerma todo lo que pueda. Se supone que el time-out es un limite de espera, que si en 5 milisegundos la señal no se presenta sale de todos modos y de esta manera checkeo que si la ventana perdio o no el foco y entonces pauso o reanudo su marcha en caso de un cambio de estado.

5 ms es arbitrario, el buffer debe ser actualizado cada 25ms (40 veces por segundo) por ende esta salida para testear el estado de la ventana no deberia afectar al rendimiento.

OK, voy a probar con esto. Muchisimas gracias.

Si alguien tiene alguna otra idea le agradeceré, especialmente en la forma de interactuar con DSound.

Saludos!

AK47

Yo solo entro para comentar que implemente el asunto de reproducir ficheros OGG mediante la biblioteca que proporciona Vorbis, usando para ello un thread y hasta donde yo se no he tenido ningun problema.

Uso DSBCAPS_CTRLPOSITIONNOTIFY para crear el buffer de DSound, y le digo que me notifique con eventos con el siguiente codigo:
if(SUCCEEDED(m_SoundBuffer->QueryInterface(IID_IDirectSoundNotify8, reinterpret_cast<LPVOID*>(&dsNotify))))
{
CKernel::outputStream() << "Setting events" << std::endl;
std::vector<DSBPOSITIONNOTIFY> notifyData(2);
notifyData[0].dwOffset = m_BufferSize / 4; // A quarter length of buffer;
notifyData[0].hEventNotify = bufferQuarter; // Event HANDLE
notifyData[1].dwOffset = (m_BufferSize / 4) * 3;
notifyData[1].hEventNotify = buffer3Quater; // Event HANDLE
dsNotify->SetNotificationPositions(notifyData.size(), &notifyData[0]);
dsNotify->Release();
}
else
{
Error("Cannot set notification events");
}


Es decir, le digo que me notifique cuando llegue a un cuarto o a 3 cuartos del buffer. Asi, en el primer caso relleno a partir de 3 cuartos hasta el resto del buffer, dando la "vuelta" (es un buffer circular). Cuando me notifica que el play cursor llega a los 3 cuartos, relleno del primer cuarto a esos 3 cuartos. Resumiendo, voy rellenando lo que el play cursor ha dejado "atras" en el buffer, es decir, que voy rellenandolo con cierta antelacion. El buffer es de 3 segundos a 44100 Hz y 16 bits.

Para esperar a los eventos uso la funcion WaitForMultipleObjects.

Espero haberte ayudado un poco :)

Pogacha

Gracias por el aporte.

En que se basa la idea de notificar a 1/4 y a 3/4 del buffer?
Lo he visto un par de veces y no entiendo la diferencia siendo que esto trae una minima complicación extra y aparentemente ninguna ganancia.

Otra cosa, según el SDK de DX, el DSBCAPS_CTRLPOSITIONNOTIFY puede devolver mal la posición del buffer ...

CitarDSBCAPS_GETCURRENTPOSITION2
Indicates that IDirectSoundBuffer::GetCurrentPosition should use the new behavior of the play cursor. In DirectSound in DirectX 1, the play cursor was significantly ahead of the actual playing sound on emulated sound cards; it was directly behind the write cursor. Now, if the DSBCAPS_GETCURRENTPOSITION2 flag is specified, the application can get a more accurate play position. If this flag is not specified, the old behavior is preserved for compatibility. Note that this flag affects only emulated sound cards; if a DirectSound driver is present, the play cursor is accurate for DirectSound in all versions of DirectX.

synchrnzr

Sólo le he podido echar un vistazo rápido para encima y creo que te complicas mucho. También he visto varias cosas un poco raras (¿empiezas a reproducir antes de llenar los buffers? :shock:) A mi tampoco me gusta lo de esperar 5ms en el WaitForMultipleObjects(), por cierto. No te podría garantizar que sea un problema, pero yo lo evitaría.

Lo de notificar a principio y mitad del buffer o a 1/4 y 3/4 es indiferente. Haciéndolo al principio y a la mitad funciona perfectamente y es algo más simple (vamos, a mi no me ha dado problemas nunca...) :)

Y respecto a lo del DSBCAPS_CTRLPOSITIONNOTIFY, utiliza DSBCAPS_CTRLPOSITIONNOTIFY2, el primero no es nada fiable. La documentación del SDK está lleno de perlas de estas, además escritas como remarks en letra pequeñita, así que cae to kiski. Por ejemplo, he tenido alumnos que intentaban usar los FX de DirectSound pero no se habían fijado en que si el buffer sobre el que aplicas el efecto no tiene una longitud mínima, no se aplican. Ándate con ojo ;)

De todas formas, hay un ejemplo en el SDK de streaming bastante bueno; úsalo de referencia y encontrarás el problema rápidamente. Con DirectSound siempre me he fijado más en los ejemplos que en la documentación: hay comentarios entre el código de cosas que deberían estar en la documentación; si no haces ciertas cosas de cierta forma, no van :x

sync

AK47

No me acuerdo de todos los pasos y decisiones que tome para hacer lo que hice. Lo de notificar en 1/4 y 3/4 del buffer lo hago, creo, para no depender de la inexactitud de GetCurrentPosition(): si me dice que anda a menos de la mitad del buffer (lo miro cuando salta la notificacion), relleno la otra mitad del buffer, sino relleno la primera mitad. Asi tengo buffer de sobra para reproducir. Lo que he comentado en el anterior post no es del todo correcto, ahora que he mirado mas el codigo :oops:

Lo de DSBCAPS_GETCURRENTPOSITION2 no lo sabia, lo he probado y sigue yendo bien despues de usarlo. Gracias ;)

Pogacha

Cita de: "synchrnzr"También he visto varias cosas un poco raras (¿empiezas a reproducir antes de llenar los buffers? :shock:)
Si  :oops:  ... ya lo arreglo.

CitarA mi tampoco me gusta lo de esperar 5ms en el WaitForMultipleObjects(), por cierto. No te podría garantizar que sea un problema, pero yo lo evitaría.
Se puede sacar ... probaré entonces.

CitarLo de notificar a principio y mitad del buffer o a 1/4 y 3/4 es indiferente. Haciéndolo al principio y a la mitad funciona perfectamente y es algo más simple (vamos, a mi no me ha dado problemas nunca...) :)
Eso supongo yo ... pero lo he visto dos o 3 veces (contando esta) y no se cual es la idea de esto.

CitarDe todas formas, hay un ejemplo en el SDK de streaming bastante bueno; úsalo de referencia y encontrarás el problema rápidamente. Con DirectSound siempre me he fijado más en los ejemplos que en la documentación: hay comentarios entre el código de cosas que deberían estar en la documentación; si no haces ciertas cosas de cierta forma, no van :x
Esto es una adaptación del ejemplo del SDK :P .

Voy a probar con ese par de cambios a ver que pasa.
Muchas gracias.

AK47

Lo de 1/4 3/4 creo que lo saque del SDK, aunque no estoy seguro :-|






Stratos es un servicio gratuito, cuyos costes se cubren en parte con la publicidad.
Por favor, desactiva el bloqueador de anuncios en esta web para ayudar a que siga adelante.
Muchísimas gracias.