Mednafen Members Members   Search Search   Help Help   Register Register   Login Login   Home Home
Home » Mednafen » Development » Networked audio for mednafen. RSound.
Show: Today's Messages  :: Show Polls :: Message Navigator
Switch to threaded view of this topic Create a new topic Submit Reply
Networked audio for mednafen. RSound. [message #1735] Mon, 24 May 2010 06:38
Themaister  [PM]
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 Wink 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]

  Switch to threaded view of this topic Create a new topic Submit Reply
Previous Topic:[Help] Mednafen 0.8.+ MingW/MSYS Compile for Win32
Next Topic:[help - MinGW / Cygwin] MinGW doesn't recognize the '/cygdrive/' path
Goto Forum:
  

-=] Back to Top [=-
[ Syndicate this forum (XML) ] [ ]

Current Time: Sat May 18 10:50:43 CDT 2024
.:: Contact :: Home ::.

Powered by FUDforum.
Copyright © FUDforum Bulletin Board Software