Rewrite the queue management a bit so that blocks are not completed until

they have finished playing. This also makes the queue system mandatory for
all backends.


git-svn-id: svn://svn.code.sf.net/p/rdesktop/code/trunk/rdesktop@1301 423420c4-83ab-492f-b58f-81f9feb106b5
This commit is contained in:
Pierre Ossman 2006-10-26 09:47:17 +00:00
parent 227fad6d96
commit 22d88645ff
9 changed files with 216 additions and 86 deletions

View File

@ -163,15 +163,14 @@ struct async_iorequest *rdpdr_remove_iorequest(struct async_iorequest *prev,
void rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out); void rdpdr_check_fds(fd_set * rfds, fd_set * wfds, BOOL timed_out);
BOOL rdpdr_abort_io(uint32 fd, uint32 major, NTSTATUS status); BOOL rdpdr_abort_io(uint32 fd, uint32 major, NTSTATUS status);
/* rdpsnd.c */ /* rdpsnd.c */
void rdpsnd_send_completion(uint16 tick, uint8 packet_index);
BOOL rdpsnd_init(char *optarg); BOOL rdpsnd_init(char *optarg);
void rdpsnd_show_help(void); void rdpsnd_show_help(void);
void rdpsnd_play(void); void rdpsnd_play(void);
void rdpsnd_queue_write(STREAM s, uint16 tick, uint8 index); void rdpsnd_add_fds(int *n, fd_set * rfds, fd_set * wfds, struct timeval *tv);
void rdpsnd_check_fds(fd_set * rfds, fd_set * wfds);
struct audio_packet *rdpsnd_queue_current_packet(void); struct audio_packet *rdpsnd_queue_current_packet(void);
BOOL rdpsnd_queue_empty(void); BOOL rdpsnd_queue_empty(void);
void rdpsnd_queue_init(void); void rdpsnd_queue_next(unsigned long completed_in_us);
void rdpsnd_queue_next(void);
int rdpsnd_queue_next_tick(void); int rdpsnd_queue_next_tick(void);
/* secure.c */ /* secure.c */
void sec_hash_48(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2, uint8 salt); void sec_hash_48(uint8 * out, uint8 * in, uint8 * salt1, uint8 * salt2, uint8 salt);

131
rdpsnd.c
View File

@ -47,11 +47,16 @@ static BOOL device_open;
static WAVEFORMATEX formats[MAX_FORMATS]; static WAVEFORMATEX formats[MAX_FORMATS];
static unsigned int format_count; static unsigned int format_count;
static unsigned int current_format; static unsigned int current_format;
unsigned int queue_hi, queue_lo; unsigned int queue_hi, queue_lo, queue_pending;
struct audio_packet packet_queue[MAX_QUEUE]; struct audio_packet packet_queue[MAX_QUEUE];
void (*wave_out_play) (void); void (*wave_out_play) (void);
static void rdpsnd_queue_write(STREAM s, uint16 tick, uint8 index);
static void rdpsnd_queue_init(void);
static void rdpsnd_queue_complete_pending(void);
static long rdpsnd_queue_next_completion(void);
static STREAM static STREAM
rdpsnd_init_packet(uint16 type, uint16 size) rdpsnd_init_packet(uint16 type, uint16 size)
{ {
@ -74,7 +79,7 @@ rdpsnd_send(STREAM s)
channel_send(s, rdpsnd_channel); channel_send(s, rdpsnd_channel);
} }
void static void
rdpsnd_send_completion(uint16 tick, uint8 packet_index) rdpsnd_send_completion(uint16 tick, uint8 packet_index)
{ {
STREAM s; STREAM s;
@ -232,10 +237,9 @@ rdpsnd_process(STREAM s)
/* Insert the 4 missing bytes retrieved from last RDPSND_WRITE */ /* Insert the 4 missing bytes retrieved from last RDPSND_WRITE */
memcpy(s->data, missing_bytes, 4); memcpy(s->data, missing_bytes, 4);
current_driver-> rdpsnd_queue_write(rdpsnd_dsp_process
wave_out_write(rdpsnd_dsp_process (s, current_driver, &formats[current_format]), tick,
(s, current_driver, &formats[current_format]), tick, packet_index);
packet_index);
awaiting_data_packet = False; awaiting_data_packet = False;
return; return;
} }
@ -338,7 +342,7 @@ rdpsnd_register_drivers(char *options)
#endif #endif
#if defined(RDPSND_LIBAO) #if defined(RDPSND_LIBAO)
*reg = libao_register(options); *reg = libao_register(options);
assert(*reg); assert(*reg);
reg = &((*reg)->next); reg = &((*reg)->next);
#endif #endif
} }
@ -362,6 +366,8 @@ rdpsnd_init(char *optarg)
return False; return False;
} }
rdpsnd_queue_init();
if (optarg != NULL && strlen(optarg) > 0) if (optarg != NULL && strlen(optarg) > 0)
{ {
driver = options = optarg; driver = options = optarg;
@ -424,12 +430,46 @@ rdpsnd_play(void)
} }
void void
rdpsnd_add_fds(int *n, fd_set * rfds, fd_set * wfds, struct timeval *tv)
{
long next_pending;
if (g_dsp_busy)
{
FD_SET(g_dsp_fd, wfds);
*n = (g_dsp_fd > *n) ? g_dsp_fd : *n;
}
next_pending = rdpsnd_queue_next_completion();
if (next_pending >= 0)
{
long cur_timeout;
cur_timeout = tv->tv_sec * 1000000 + tv->tv_usec;
if (cur_timeout > next_pending)
{
tv->tv_sec = next_pending / 1000000;
tv->tv_usec = next_pending % 1000000;
}
}
}
void
rdpsnd_check_fds(fd_set * rfds, fd_set * wfds)
{
rdpsnd_queue_complete_pending();
if (g_dsp_busy && FD_ISSET(g_dsp_fd, wfds))
rdpsnd_play();
}
static void
rdpsnd_queue_write(STREAM s, uint16 tick, uint8 index) rdpsnd_queue_write(STREAM s, uint16 tick, uint8 index)
{ {
struct audio_packet *packet = &packet_queue[queue_hi]; struct audio_packet *packet = &packet_queue[queue_hi];
unsigned int next_hi = (queue_hi + 1) % MAX_QUEUE; unsigned int next_hi = (queue_hi + 1) % MAX_QUEUE;
if (next_hi == queue_lo) if (next_hi == queue_pending)
{ {
error("No space to queue audio packet\n"); error("No space to queue audio packet\n");
return; return;
@ -441,6 +481,8 @@ rdpsnd_queue_write(STREAM s, uint16 tick, uint8 index)
packet->tick = tick; packet->tick = tick;
packet->index = index; packet->index = index;
gettimeofday(&packet->arrive_tv, NULL);
if (!g_dsp_busy) if (!g_dsp_busy)
current_driver->wave_out_play(); current_driver->wave_out_play();
} }
@ -457,17 +499,30 @@ rdpsnd_queue_empty(void)
return (queue_lo == queue_hi); return (queue_lo == queue_hi);
} }
void static void
rdpsnd_queue_init(void) rdpsnd_queue_init(void)
{ {
queue_lo = queue_hi = 0; queue_pending = queue_lo = queue_hi = 0;
} }
void void
rdpsnd_queue_next(void) rdpsnd_queue_next(unsigned long completed_in_us)
{ {
xfree(packet_queue[queue_lo].s.data); struct audio_packet *packet;
assert(!rdpsnd_queue_empty());
packet = &packet_queue[queue_lo];
gettimeofday(&packet->completion_tv, NULL);
packet->completion_tv.tv_usec += completed_in_us;
packet->completion_tv.tv_sec += packet->completion_tv.tv_usec / 1000000;
packet->completion_tv.tv_usec %= 1000000;
queue_lo = (queue_lo + 1) % MAX_QUEUE; queue_lo = (queue_lo + 1) % MAX_QUEUE;
rdpsnd_queue_complete_pending();
} }
int int
@ -482,3 +537,55 @@ rdpsnd_queue_next_tick(void)
return (packet_queue[queue_lo].tick + 65535) % 65536; return (packet_queue[queue_lo].tick + 65535) % 65536;
} }
} }
static void
rdpsnd_queue_complete_pending(void)
{
struct timeval now;
long elapsed;
struct audio_packet *packet;
gettimeofday(&now, NULL);
while (queue_pending != queue_lo)
{
packet = &packet_queue[queue_pending];
if (now.tv_sec < packet->completion_tv.tv_sec)
break;
if ((now.tv_sec == packet->completion_tv.tv_sec) &&
(now.tv_usec < packet->completion_tv.tv_usec))
break;
elapsed = (packet->completion_tv.tv_sec - packet->arrive_tv.tv_sec) * 1000000 +
(packet->completion_tv.tv_usec - packet->arrive_tv.tv_usec);
xfree(packet->s.data);
rdpsnd_send_completion((packet->tick + elapsed) % 65536, packet->index);
queue_pending = (queue_pending + 1) % MAX_QUEUE;
}
}
static long
rdpsnd_queue_next_completion(void)
{
struct audio_packet *packet;
long remaining;
struct timeval now;
if (queue_pending == queue_lo)
return -1;
gettimeofday(&now, NULL);
packet = &packet_queue[queue_pending];
remaining = (packet->completion_tv.tv_sec - now.tv_sec) * 1000000 +
(packet->completion_tv.tv_usec - now.tv_usec);
if (remaining < 0)
return 0;
return remaining;
}

View File

@ -23,12 +23,14 @@ struct audio_packet
struct stream s; struct stream s;
uint16 tick; uint16 tick;
uint8 index; uint8 index;
struct timeval arrive_tv;
struct timeval completion_tv;
}; };
struct audio_driver struct audio_driver
{ {
void (*wave_out_write) (STREAM s, uint16 tick, uint8 index); BOOL(*wave_out_open) (void);
BOOL(*wave_out_open) (void);
void (*wave_out_close) (void); void (*wave_out_close) (void);
BOOL(*wave_out_format_supported) (WAVEFORMATEX * pwfx); BOOL(*wave_out_format_supported) (WAVEFORMATEX * pwfx);
BOOL(*wave_out_set_format) (WAVEFORMATEX * pwfx); BOOL(*wave_out_set_format) (WAVEFORMATEX * pwfx);

View File

@ -37,6 +37,7 @@ static snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK;
static BOOL reopened; static BOOL reopened;
static short samplewidth; static short samplewidth;
static int audiochannels; static int audiochannels;
static unsigned int rate;
static char *pcm_name; static char *pcm_name;
BOOL BOOL
@ -51,7 +52,6 @@ alsa_open(void)
} }
g_dsp_fd = 0; g_dsp_fd = 0;
rdpsnd_queue_init();
reopened = True; reopened = True;
@ -63,11 +63,7 @@ alsa_close(void)
{ {
/* Ack all remaining packets */ /* Ack all remaining packets */
while (!rdpsnd_queue_empty()) while (!rdpsnd_queue_empty())
{ rdpsnd_queue_next(0);
rdpsnd_send_completion(rdpsnd_queue_current_packet()->tick,
rdpsnd_queue_current_packet()->index);
rdpsnd_queue_next();
}
if (pcm_handle) if (pcm_handle)
{ {
@ -113,7 +109,6 @@ BOOL
alsa_set_format(WAVEFORMATEX * pwfx) alsa_set_format(WAVEFORMATEX * pwfx)
{ {
snd_pcm_hw_params_t *hwparams = NULL; snd_pcm_hw_params_t *hwparams = NULL;
unsigned int rate, exact_rate;
int err; int err;
unsigned int buffertime; unsigned int buffertime;
@ -165,8 +160,8 @@ alsa_set_format(WAVEFORMATEX * pwfx)
} }
#endif #endif
exact_rate = rate = pwfx->nSamplesPerSec; rate = pwfx->nSamplesPerSec;
if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &exact_rate, 0)) < 0) if ((err = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &rate, 0)) < 0)
{ {
error("snd_pcm_hw_params_set_rate_near: %s\n", snd_strerror(err)); error("snd_pcm_hw_params_set_rate_near: %s\n", snd_strerror(err));
return False; return False;
@ -254,6 +249,9 @@ alsa_play(void)
if ((out->p == out->end) || duration > next_tick - packet->tick + 500) if ((out->p == out->end) || duration > next_tick - packet->tick + 500)
{ {
snd_pcm_sframes_t delay_frames;
unsigned long delay_us;
prev_s = tv.tv_sec; prev_s = tv.tv_sec;
prev_us = tv.tv_usec; prev_us = tv.tv_usec;
@ -264,8 +262,14 @@ alsa_play(void)
(packet->tick + duration) % 65536, next_tick % 65536)); (packet->tick + duration) % 65536, next_tick % 65536));
} }
rdpsnd_send_completion(((packet->tick + duration) % 65536), packet->index); if (snd_pcm_delay(pcm_handle, &delay_frames) < 0)
rdpsnd_queue_next(); delay_frames = out->size / (samplewidth * audiochannels);
if (delay_frames < 0)
delay_frames = 0;
delay_us = delay_frames * (1000000 / rate);
rdpsnd_queue_next(delay_us);
} }
g_dsp_busy = 1; g_dsp_busy = 1;
@ -277,7 +281,6 @@ alsa_register(char *options)
{ {
static struct audio_driver alsa_driver; static struct audio_driver alsa_driver;
alsa_driver.wave_out_write = rdpsnd_queue_write;
alsa_driver.wave_out_open = alsa_open; alsa_driver.wave_out_open = alsa_open;
alsa_driver.wave_out_close = alsa_close; alsa_driver.wave_out_close = alsa_close;
alsa_driver.wave_out_format_supported = alsa_format_supported; alsa_driver.wave_out_format_supported = alsa_format_supported;

View File

@ -65,7 +65,6 @@ libao_open(void)
} }
g_dsp_fd = 0; g_dsp_fd = 0;
rdpsnd_queue_init();
reopened = True; reopened = True;
@ -78,9 +77,7 @@ libao_close(void)
/* Ack all remaining packets */ /* Ack all remaining packets */
while (!rdpsnd_queue_empty()) while (!rdpsnd_queue_empty())
{ {
rdpsnd_send_completion(rdpsnd_queue_current_packet()->tick, rdpsnd_queue_next(0);
rdpsnd_queue_current_packet()->index);
rdpsnd_queue_next();
} }
if (o_device != NULL) if (o_device != NULL)
@ -171,8 +168,7 @@ libao_play(void)
(packet->tick + duration) % 65536, next_tick % 65536)); (packet->tick + duration) % 65536, next_tick % 65536));
} }
rdpsnd_send_completion(((packet->tick + duration) % 65536), packet->index); rdpsnd_queue_next(duration);
rdpsnd_queue_next();
} }
g_dsp_busy = 1; g_dsp_busy = 1;
@ -186,7 +182,6 @@ libao_register(char *options)
struct ao_info *libao_info; struct ao_info *libao_info;
static char description[101]; static char description[101];
libao_driver.wave_out_write = rdpsnd_queue_write;
libao_driver.wave_out_open = libao_open; libao_driver.wave_out_open = libao_open;
libao_driver.wave_out_close = libao_close; libao_driver.wave_out_close = libao_close;
libao_driver.wave_out_format_supported = rdpsnd_dsp_resample_supported; libao_driver.wave_out_format_supported = rdpsnd_dsp_resample_supported;

View File

@ -33,9 +33,12 @@
#include <unistd.h> #include <unistd.h>
#include <fcntl.h> #include <fcntl.h>
#include <errno.h> #include <errno.h>
#include <unistd.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <sys/soundcard.h> #include <sys/soundcard.h>
#include <sys/types.h>
#include <sys/stat.h>
#define DEFAULTDEVICE "/dev/dsp" #define DEFAULTDEVICE "/dev/dsp"
#define MAX_LEN 512 #define MAX_LEN 512
@ -44,6 +47,29 @@ static int snd_rate;
static short samplewidth; static short samplewidth;
static char *dsp_dev; static char *dsp_dev;
static struct audio_driver oss_driver; static struct audio_driver oss_driver;
static BOOL in_esddsp;
static BOOL
detect_esddsp(void)
{
struct stat s;
char *preload;
if (fstat(g_dsp_fd, &s) == -1)
return False;
if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode))
return False;
preload = getenv("LD_PRELOAD");
if (preload == NULL)
return False;
if (strstr(preload, "esddsp") == NULL)
return False;
return True;
}
BOOL BOOL
oss_open(void) oss_open(void)
@ -54,6 +80,8 @@ oss_open(void)
return False; return False;
} }
in_esddsp = detect_esddsp();
return True; return True;
} }
@ -202,10 +230,6 @@ oss_play(void)
struct audio_packet *packet; struct audio_packet *packet;
ssize_t len; ssize_t len;
STREAM out; STREAM out;
static long startedat_us;
static long startedat_s;
static BOOL started = False;
struct timeval tv;
if (rdpsnd_queue_empty()) if (rdpsnd_queue_empty())
{ {
@ -216,14 +240,6 @@ oss_play(void)
packet = rdpsnd_queue_current_packet(); packet = rdpsnd_queue_current_packet();
out = &packet->s; out = &packet->s;
if (!started)
{
gettimeofday(&tv, NULL);
startedat_us = tv.tv_usec;
startedat_s = tv.tv_sec;
started = True;
}
len = out->end - out->p; len = out->end - out->p;
len = write(g_dsp_fd, out->p, (len > MAX_LEN) ? MAX_LEN : len); len = write(g_dsp_fd, out->p, (len > MAX_LEN) ? MAX_LEN : len);
@ -236,37 +252,52 @@ oss_play(void)
} }
out->p += len; out->p += len;
if (out->p == out->end) if (out->p == out->end)
{ {
long long duration; int delay_bytes;
long elapsed; unsigned long delay_us;
audio_buf_info info;
gettimeofday(&tv, NULL); if (in_esddsp)
duration = (out->size * (1000000 / (samplewidth * snd_rate)));
elapsed = (tv.tv_sec - startedat_s) * 1000000 + (tv.tv_usec - startedat_us);
if (elapsed >= (duration * 85) / 100)
{ {
/* We need to add 50 to tell windows that time has passed while /* EsounD has no way of querying buffer status, so we have to
* playing this packet */ * go with a fixed size. */
rdpsnd_send_completion(packet->tick + 50, packet->index); delay_bytes = out->size;
rdpsnd_queue_next();
started = False;
} }
else else
{ {
g_dsp_busy = 1; #ifdef SNDCTL_DSP_GETODELAY
return; delay_bytes = 0;
if (ioctl(g_dsp_fd, SNDCTL_DSP_GETODELAY, &delay_bytes) == -1)
delay_bytes = -1;
#else
delay_bytes = -1;
#endif
if (delay_bytes == -1)
{
if (ioctl(g_dsp_fd, SNDCTL_DSP_GETOSPACE, &info) != -1)
delay_bytes = info.fragstotal * info.fragsize - info.bytes;
else
delay_bytes = out->size;
}
} }
delay_us = delay_bytes * (1000000 / (samplewidth * snd_rate));
rdpsnd_queue_next(delay_us);
} }
g_dsp_busy = 1; else
{
g_dsp_busy = 1;
}
return; return;
} }
struct audio_driver * struct audio_driver *
oss_register(char *options) oss_register(char *options)
{ {
oss_driver.wave_out_write = rdpsnd_queue_write;
oss_driver.wave_out_open = oss_open; oss_driver.wave_out_open = oss_open;
oss_driver.wave_out_close = oss_close; oss_driver.wave_out_close = oss_close;
oss_driver.wave_out_format_supported = oss_format_supported; oss_driver.wave_out_format_supported = oss_format_supported;

View File

@ -68,8 +68,6 @@ sgi_open(void)
min_volume, max_volume, volume_range); min_volume, max_volume, volume_range);
#endif #endif
rdpsnd_queue_init();
audioconfig = alNewConfig(); audioconfig = alNewConfig();
if (audioconfig == (ALconfig) 0) if (audioconfig == (ALconfig) 0)
{ {
@ -266,8 +264,7 @@ sgi_play(void)
gf = alGetFilled(output_port); gf = alGetFilled(output_port);
if (gf < (4 * maxFillable / 10)) if (gf < (4 * maxFillable / 10))
{ {
rdpsnd_send_completion(packet->tick, packet->index); rdpsnd_queue_next(0);
rdpsnd_queue_next();
} }
else else
{ {
@ -287,7 +284,6 @@ sgi_register(char *options)
{ {
static struct audio_driver sgi_driver; static struct audio_driver sgi_driver;
sgi_driver.wave_out_write = rdpsnd_queue_write;
sgi_driver.wave_out_open = sgi_open; sgi_driver.wave_out_open = sgi_open;
sgi_driver.wave_out_close = sgi_close; sgi_driver.wave_out_close = sgi_close;
sgi_driver.wave_out_format_supported = sgi_format_supported; sgi_driver.wave_out_format_supported = sgi_format_supported;

View File

@ -50,7 +50,6 @@ sun_open(void)
/* Non-blocking so that user interface is responsive */ /* Non-blocking so that user interface is responsive */
fcntl(g_dsp_fd, F_SETFL, fcntl(g_dsp_fd, F_GETFL) | O_NONBLOCK); fcntl(g_dsp_fd, F_SETFL, fcntl(g_dsp_fd, F_GETFL) | O_NONBLOCK);
rdpsnd_queue_init();
g_reopened = True; g_reopened = True;
return True; return True;
@ -235,8 +234,7 @@ sun_play(void)
samplecnt += numsamples; samplecnt += numsamples;
/* We need to add 50 to tell windows that time has passed while /* We need to add 50 to tell windows that time has passed while
* playing this packet */ * playing this packet */
rdpsnd_send_completion(packet->tick + 50, packet->index); rdpsnd_queue_next(50);
rdpsnd_queue_next();
sentcompletion = True; sentcompletion = True;
} }
else else
@ -253,7 +251,6 @@ sun_register(char *options)
{ {
static struct audio_driver sun_driver; static struct audio_driver sun_driver;
sun_driver.wave_out_write = rdpsnd_queue_write;
sun_driver.wave_out_open = sun_open; sun_driver.wave_out_open = sun_open;
sun_driver.wave_out_close = sun_close; sun_driver.wave_out_close = sun_close;
sun_driver.wave_out_format_supported = sun_format_supported; sun_driver.wave_out_format_supported = sun_format_supported;

24
xwin.c
View File

@ -2268,18 +2268,14 @@ ui_select(int rdp_socket)
FD_SET(rdp_socket, &rfds); FD_SET(rdp_socket, &rfds);
FD_SET(g_x_socket, &rfds); FD_SET(g_x_socket, &rfds);
#ifdef WITH_RDPSND
/* FIXME: there should be an API for registering fds */
if (g_dsp_busy)
{
FD_SET(g_dsp_fd, &wfds);
n = (g_dsp_fd > n) ? g_dsp_fd : n;
}
#endif
/* default timeout */ /* default timeout */
tv.tv_sec = 60; tv.tv_sec = 60;
tv.tv_usec = 0; tv.tv_usec = 0;
#ifdef WITH_RDPSND
rdpsnd_add_fds(&n, &rfds, &wfds, &tv);
#endif
/* add redirection handles */ /* add redirection handles */
rdpdr_add_fds(&n, &rfds, &wfds, &tv, &s_timeout); rdpdr_add_fds(&n, &rfds, &wfds, &tv, &s_timeout);
seamless_select_timeout(&tv); seamless_select_timeout(&tv);
@ -2292,21 +2288,25 @@ ui_select(int rdp_socket)
error("select: %s\n", strerror(errno)); error("select: %s\n", strerror(errno));
case 0: case 0:
#ifdef WITH_RDPSND
rdpsnd_check_fds(&rfds, &wfds);
#endif
/* Abort serial read calls */ /* Abort serial read calls */
if (s_timeout) if (s_timeout)
rdpdr_check_fds(&rfds, &wfds, (BOOL) True); rdpdr_check_fds(&rfds, &wfds, (BOOL) True);
continue; continue;
} }
#ifdef WITH_RDPSND
rdpsnd_check_fds(&rfds, &wfds);
#endif
rdpdr_check_fds(&rfds, &wfds, (BOOL) False); rdpdr_check_fds(&rfds, &wfds, (BOOL) False);
if (FD_ISSET(rdp_socket, &rfds)) if (FD_ISSET(rdp_socket, &rfds))
return 1; return 1;
#ifdef WITH_RDPSND
if (g_dsp_busy && FD_ISSET(g_dsp_fd, &wfds))
rdpsnd_play();
#endif
} }
} }