Show: Today's Messages :: Show Polls :: Message Navigator |
Networked audio for mednafen. RSound. [message #1735] | Mon, 24 May 2010 06:38 | |||
| ||||
Hi, I've been developing a networked audio system for some time now. It's very simplistic. Simpler than ESounD I'd say, but it implements "proper" latency handling. RSound It's focused on audio and video players mostly which do not have any latency requirement. I've tried however to see if it's possible to implement decent latency (read: good enough) using my simple architecture. Emulators are a quite good benchmark for this. Turned out that it was possible, assuming the audio driver on the server side has accurate enough latency measurement. I also found that the approach might actually solve several problems related to audio/video syncing often found in emulators. Emulators usually attempt to set the buffer sizes of the sound card really low, and use blocking audio to sync. This has several problems. Mednafen relies on writing small chunks to the audio drivers at all times in a blocking fashion. If it chooses to write e.g. 1kB, it would expect the audio backend to allow this write in about 6-7ms (assuming ~170kB/s audio data rate). This rarely happens using conventional audio drivers and setups. Assuming 60 fps from the video, a write to the sound card can never take more than 16ms, else, frames will drop, and chopping occurs. This is what occurs when e.g. the ALSA driver of mednafen attempts to use the dmix module, which by default has a very high fragment size. I usually get very nasty video chopping since the default fragment size is something like 30-40ms. It's possible with a raw hw:0 to get a very low fragment size, which sort of solves the problem. But the whole thing does indeed feel flaky. I've attempted to solve this by using a timer based scheme in RSound which is also somewhat exposed to the programmer. Assuming that we are able to calculate the latency of the stream quite accurately, we can easily sleep for some time during each write to the sound card. Using this approach, we never sleep for too long. There is no conception of "fragment size" involved here at all. However, now, we rely on accurate latency measurement, which might be just as flaky I'm not quite sure if this approach can be translated over to PulseAudio. I currently have something like this: #include "../sexyal.h" #include <stdlib.h> #include <stdio.h> #include <rsound.h> #include <string.h> typedef struct { rsound_t *rd; int max_avail; int bps; } RSDWrap; static int RawWrite(SexyAL_device *device, const void *data, uint32_t len) { RSDWrap *rsd = (RSDWrap *)device->private_data; //fprintf(stderr, "RawWrite: %u\n", len); // We simulate some short blocking for each write to keep the latency decent. rsd_delay_wait(rsd->rd); if ( rsd_write(rsd->rd, data, len) == 0 ) return 0; return 1; } static int RawCanWrite(SexyAL_device *device, uint32_t *can_write) { RSDWrap *rsd = (RSDWrap *)device->private_data; int latency = rsd_delay(rsd->rd); int writable = rsd->max_avail - latency; if ( writable < 0 ) writable = 0; *can_write = writable; //fprintf(stderr, "Writable: %u\n", writable); //fprintf(stderr, "RSD Latency: %d\n", (int)rsd_delay_ms(rsd->rd)); return 1; } static int Pause(SexyAL_device *device, int state) { RSDWrap *rsd = (RSDWrap *)device->private_data; if ( rsd_pause(rsd->rd, state) == 0 ) return 1; else return 0; } static int Clear(SexyAL_device *device) { RSDWrap *rsd = (RSDWrap *)device->private_data; int err = 0; err += rsd_stop(rsd->rd); err += rsd_start(rsd->rd); if ( err < 0 ) return 0; return 1; } static int RawClose(SexyAL_device *device) { if(device) { if(device->private_data) { RSDWrap *rsd = (RSDWrap *)device->private_data; rsd_stop(rsd->rd); rsd_free(rsd->rd); free(device->private_data); } free(device); return(1); } return(0); } SexyAL_device *SexyALI_RSOUND_Open(const char *id, SexyAL_format *format, SexyAL_buffering *buffering) { SexyAL_device *device = NULL; RSDWrap *rsd = NULL; int rate; int channels; int fmt; int bufsize; int desired_buffertime = 64; // 64 milliseconds int latency; int min_latency; rsound_t *rd; if ( rsd_init(&rd) < 0 ) { puts("Couldn't initialize RSound\n"); return 0; } channels = format->channels; rate = format->rate; switch(format->sampformat) { case SEXYAL_FMT_PCMU8: fmt = RSD_U8; break; case SEXYAL_FMT_PCMS8: fmt = RSD_S8; break; case SEXYAL_FMT_PCMS16: fmt = RSD_S16_NE; break; case SEXYAL_FMT_PCMU16: fmt = RSD_U16_NE; break; default: fmt = RSD_S16_NE; format->sampformat = SEXYAL_FMT_PCMS16; } rsd_set_param(rd, RSD_SAMPLERATE, (void*)&rate); rsd_set_param(rd, RSD_CHANNELS, (void*)&channels); rsd_set_param(rd, RSD_FORMAT, (void*)&fmt); bufsize = (1 << 14); // A large enough buffer size here means that we will never block on rsd_write(). rsd_set_param(rd, RSD_BUFSIZE, (void*)&bufsize); device = (SexyAL_device *)malloc(sizeof(SexyAL_device)); if(!(rsd = (RSDWrap *)calloc(1, sizeof(RSDWrap)))) { goto error; } if ( rsd_start(rd) < 0 ) { fprintf(stderr, "Could not connect to server.\n"); goto error; } // We check the minimum latency that we can reliably get min_latency = (rd->backend_info.latency * 1000) / (rate * rsd_samplesize(rd) * channels); latency = (min_latency > desired_buffertime) ? (min_latency * 3 / 2) : desired_buffertime; // Multiply min latency by 1.5 to be more safe. //fprintf(stderr, "Minimum latency: %d ms\n", min_latency); rsd_set_param(rd, RSD_LATENCY, (void*)&latency); rsd->rd = rd; device->private_data = rsd; device->RawCanWrite = RawCanWrite; device->RawWrite = RawWrite; device->RawClose = RawClose; device->Clear = Clear; device->Pause = Pause; buffering->latency = (latency * rate)/1000; buffering->buffer_size = buffering->latency; buffering->period_size = 16; // We just set some low fragsize, although it doesn't mean much ;) rsd->bps = rate * rsd_samplesize(rd) * channels; rsd->max_avail = buffering->latency * channels * rsd_samplesize(rd); memcpy(&device->buffering, buffering, sizeof(SexyAL_buffering)); memcpy(&device->format, format, sizeof(SexyAL_format)); return device; error: if ( rsd ) free(rsd); if ( rd ) rsd_free(rd); return NULL; } [Updated on: Mon, 24 May 2010 06:43] | ||||
|
Previous Topic: | [Help] Mednafen 0.8.+ MingW/MSYS Compile for Win32 |
Next Topic: | [help - MinGW / Cygwin] MinGW doesn't recognize the '/cygdrive/' path |