Gå til innhold

OpenAL - C. Gjør jeg ting korrekt?


Anbefalte innlegg

Har prøvd å fikse en OpenAL backend til lydserveren min, og har implementert en callback. Slet lenge med å få til openal slik jeg ville (var ikke lett å finne gode tutorials :\), men ting fungerer nå virker det som. Har ikke verdens beste latency akkurat, men funker bedre enn PortAudio med winmm backend i hvertfall (verste latency jeg har vært borti).

 

Vel, er spesielt write()-callbacken jeg ikke er sikker på. Har tilpassa koden til Win32 sånn halvveis for å slippe cygwin.

static size_t al_write(void *data, const void* buf, size_t size)
{

al_t *al = data;

// Fills up the buffer before we start playing. Should give deterministic latency.
if ( al->queue < al->num_buffers )
{
	alBufferData(al->buffers[al->queue++], al->format, buf, size, al->rate);
	if ( alGetError() != AL_NO_ERROR )
	{
		return 0;
	}

	if ( al->queue == al->num_buffers )
	{
		alSourceQueueBuffers(al->source, al->num_buffers, al->buffers);
		alSourcePlay(al->source);
		if ( alGetError() != AL_NO_ERROR )
		{
			return 0;
		}
	}

	return size;
}

ALuint buffer;
ALint val;

// Waits until we have a buffer we can unqueue.
// Let's unqueue. Stupid busywait with Sleep() >_> (Is there no polling for processed buffer? :\)
for(;
{
	alGetSourcei(al->source, AL_BUFFERS_PROCESSED, &val);
	if ( val > 0 )
		break;
	Sleep(1);  
}

alSourceUnqueueBuffers(al->source, 1, &buffer);

// Buffers up the data
alBufferData(buffer, al->format, buf, size, al->rate);
alSourceQueueBuffers(al->source, 1, &buffer);

if ( alGetError() != AL_NO_ERROR )
	return 0;

// Checks if we're playing
alGetSourcei(al->source, AL_SOURCE_STATE, &val);
if ( val != AL_PLAYING )
	alSourcePlay(al->source);

if ( alGetError() != AL_NO_ERROR )
	return 0;

return size;
}

 

Er spesielt denne kodesnutten jeg lurer på

for(;
{
alGetSourcei(al->source, AL_BUFFERS_PROCESSED, &val);
if ( val > 0 )
	break;
Sleep(1); // Linux -> nanosleep() 
}

Det må da være en bedre måte å vente på at en buffer har blitt prosessert noe ala select() og i den duren? Meningsløs looping med Sleep() får meg til å spy :c Har ikke lyst til å hamre CPU-en med 100% akkurat.

 

I tillegg kræsjer shutdown-funksjonen min (kalles via atexit()). Sikkert segfault men ikke greit å vite :D)

static void al_shutdown(void)
{

  // For some reason, this crashes the program. Works in Linux though :'<

  alcMakeContextCurrent(NULL);
  alcDestroyContext(global_context);
  alcCloseDevice(global_handle);
}

Er det noe spesielt man må gjøre før man kaller på disse funksjonene? Serveren er multi-threaded og spiller av flere streams på en gang.

 

Angående buffer-størrelser og antall. Hva er anbefalt? Vil ha så lav latency som mulig uten at det blir et problem. Helst noe rundt 50ms. Så langt bruker jeg 2 KiB buffer og 4 buffere, men får noen dropouts her og der.

Endret av TheMaister
Lenke til kommentar
Videoannonse
Annonse

<snip>

 

Er spesielt denne kodesnutten jeg lurer på

for(;
{
alGetSourcei(al->source, AL_BUFFERS_PROCESSED, &val);
if ( val > 0 )
	break;
Sleep(1); // Linux -> nanosleep() 
}

Det må da være en bedre måte å vente på at en buffer har blitt prosessert noe ala select() og i den duren? Meningsløs looping med Sleep() får meg til å spy :c Har ikke lyst til å hamre CPU-en med 100% akkurat.

Så vidt jeg vet så er det ikke det, men du gjør ting litt bakvendt i programmet. Du burde skjekke om den er ferdig med å spille av en buffer før du kaller på al_write... evt. bare legge til en ekstra buffer i køen.

 

Angående buffer-størrelser og antall. Hva er anbefalt? Vil ha så lav latency som mulig uten at det blir et problem. Helst noe rundt 50ms. Så langt bruker jeg 2 KiB buffer og 4 buffere, men får noen dropouts her og der.

 

Jeg har ikke testa med 2k men 4k fungerer utmerket i mitt program når jeg spiller av fra disk (16bx2 44Khz) hvor stor buffer du trenger er vanskelig/umlig å si uten å vite mer om programmet/systemet ditt.

 

Jeg må kikke med på koden før jeg kan si noe mer, men det har jeg ikke tid til nå :(

Lenke til kommentar

Så vidt jeg vet så er det ikke det, men du gjør ting litt bakvendt i programmet. Du burde skjekke om den er ferdig med å spille av en buffer før du kaller på al_write... evt. bare legge til en ekstra buffer i køen.

 

Er ikke helt sikker på om det hjelper stort på problemet. I uansett tilfelle blir jeg nødt til å vente på at en buffer blir prosessert. Om jeg gjør det i callbacken eller server-threaden blir egentlig det samme. Serveren støtter nå 5 forskjellige backends, og i alle andre backends trenger jeg bare:

 

write() - OSS

snd_pcm_writei() - ALSA

ao_play() - libao

Pa_WriteStream() - Portaudio

 

Alle disse funksjonene er blocking som default og funker svært godt til applikasjonen. Det virker som at jeg blir nødt til å simulere denne oppførselen da i OpenAL for å få til blocking I/O.

 

Å sende ekstra buffere helt blindt til lydkortet tror jeg funker ganske dårlig. Følger jeg den tankegangen blir jeg sittende med en buffer som er like stor som lydstreamen jeg skal spille av, og i tillegg blir latencyen helt grusom. :\

 

Her er f.eks tilsvarende kode i libAO

static size_t ao_rsd_write(void *data, const void* buf, size_t size)
{
  ao_t *sound = data;
  if ( ao_play(sound->device, (void*)buf, size) == 0 )
     return -1;
  return size;
}

Lenke til kommentar

Er fullt mulig det. I eksempler rundt på nettet som brukte et blocking streaming design ble QueueBuffers brukt, så vet ikke. Men finnes det da en mekanisme som automatisk blokker når bufferen (på kanskje 50-60ms) blir "full" i OpenAL? Eller kan jeg bare QueueBuffer så mye jeg vil, også sørger OpenAL for å blokke når det trengs? Alle eksemplene jeg har sett på har ventet på å unqueue bufre før de queuer opp flere buffere, slik at de holder seg innenfor et visst latencynivå.

 

Den eneste faktoren som forhindrer lydstreamen fra klienten til å spilles ferdig med en gang er blokkingen i lyd-backenden. Jeg simulerer et lydkort på klientsiden, og det gir ingen mening om jeg skal kunne bufre opp en hel stream på en gang.

Endret av TheMaister
Lenke til kommentar

Er fullt mulig det. I eksempler rundt på nettet som brukte et blocking streaming design ble QueueBuffers brukt, så vet ikke. Men finnes det da en mekanisme som automatisk blokker når bufferen (på kanskje 50-60ms) blir "full" i OpenAL? Eller kan jeg bare QueueBuffer så mye jeg vil, også sørger OpenAL for å blokke når det trengs? Alle eksemplene jeg har sett på har ventet på å unqueue bufre før de queuer opp flere buffere, slik at de holder seg innenfor et visst latencynivå.

Bufferet til OpenAL blir ikke fullt, men du kan slippe opp for minne :), det beste tipse jeg kan gi deg er å legge til å sove i så lang tid det tar 1. stk buffer å spille av... det burde være enkelt å regne ut, på den måten slipper du å skjekke hele tiden.

 

Den eneste faktoren som forhindrer lydstreamen fra klienten til å spilles ferdig med en gang er blokkingen i lyd-backenden. Jeg simulerer et lydkort på klientsiden, og det gir ingen mening om jeg skal kunne bufre opp en hel stream på en gang.

 

Nå er jeg helt forvirret over hvordan systemet ditt fungerer.

Slik jeg har forstått det så streamer du lyd så derfor er er latency viktig for deg, men så sier du at det er klienten som holder igjen system slik at ikke serveren sender "alt" på en gang... så da forstår jeg det slik at lyden som skal spilles av egentlig ligger helt klar og bare venter på å bli sendt?

Lenke til kommentar

Nei, all lyd-data ligger ikke klar :p Brukeren av klient-APIet må kalle på rsd_write() for å fylle opp data.

 

 

Vel, kan legge den til å sove litt lengre, men etter min erfaring var Windows sin sleep-mekanisme så lite nøyaktig, at da jeg prøvde det fikk jeg sporadiske dropouts pga at Sleep() returnerte altfor seint i enkelte tilfeller. :\ Men 1ms går vel egentlig greit. OpenAL sitt fokusområde er jo spill, så kan skjønne måten de har lagt opp API-et sitt på. Så lenge CPU bruk vises på 0% :p Skal legge til at jeg er veldig fornøyd med lydkvaliteten og til dels latencyen (til Windows å være).

 

 

Bør kanskje forklare nærmere hvordan strukturen er, siden jeg har nok ikke forklart hele greia :p

 

Klienten ser et blocking lyd-API, noe liknende OSS/libao. ( open, set_params, write, etc )

Klienten kan kalle på rsd_write(), som skriver til den interne FIFO-bufferen i biblioteket. Denne FIFO-bufferen sørger for å blocke (implementert med pthread_cond_signal() og pthread_cond_wait()) for å unngå for mye data i den interne bufferen. Dette simulerer et lydkort nokså greit.

 

I en separat tråd blir denne bufferen streamet over nettet til serveren så lenge det er data å ta i bufferen. Finnes sikkert fiks ferdige klasser for sånt i nymotens språk, osv :p

 

Serveren mottar dataene så fort de kommer over nettet og sender lyddataene til lydkortet.

 

Hvis det ikke er minst én blocking-mekanisme i både klient og server, vil jeg ideelt sett kunne spille av en låt på et par sekunder, som blir helt feil. Siden de fleste musikkspillere jeg har vært borti spiller av lyd så fort den klarer å dekode lyden, og setter sin lit til at lyd-API-et som brukes blocker for at ikke morsomme ting skal skje.

 

Uten å gjøre noe spesielt for å senke latencyen får jeg dog ganske stor latency allikevel. Det er flere buffere som må bli fulle før blocking faktisk oppstår.

 

- Intern klient-buffer er full. Hvis den ikke har noen begrensninger oppstår aldri blocking.

- Klienten sin nettverksbuffer er full

- Serveren sin nettverksbuffer er full

- Lyd-APIet sin buffer --->er full<---. Må også ha noen begrensninger på buffer-størrelse.

 

 

Dette utgjør omlag 1-1.5s med latency når disse er fulle. Det går an på klientsiden å sørge for at bufferen holder seg ganske liten med å bruke timere, etc. (Meningsløst for musikk- og videospillere så klart, men måtte implementere det for å bruke BSNES, f.eks) Litt dirty sånn sett, men funker helt greit.

 

Vet ikke om det var godt nok forklart, men.

 

Kan ta et eksempel på en implementasjon i MPD for å illustrere problemet.

static size_t
rsound_play(void *data, const void *chunk, size_t size, GError **error)
{
  int rc;
  rsound_t *rd = data;

  rc = rsd_write(rd, chunk, size);
  if ( rc <= 0 )
  {
     g_set_error(error, rsound_output_quark(), 0, "Failed to play data");
     return 0;
  }

  return rc;
}

 

MPD kaller på play()-callbacken hele tiden. Uten blocking noen steder vil man kunne starte en sang, og whops, på 2 sekunder ble den ferdig, og neste sang begynner. :D

Endret av TheMaister
Lenke til kommentar

Opprett en konto eller logg inn for å kommentere

Du må være et medlem for å kunne skrive en kommentar

Opprett konto

Det er enkelt å melde seg inn for å starte en ny konto!

Start en konto

Logg inn

Har du allerede en konto? Logg inn her.

Logg inn nå
  • Hvem er aktive   0 medlemmer

    • Ingen innloggede medlemmer aktive
×
×
  • Opprett ny...