--- esddsp.c.old 2006-09-23 21:12:29.000000000 +0200 +++ esddsp.c 2006-09-25 18:54:31.000000000 +0200 @@ -43,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -81,10 +82,23 @@ static char *mixer = NULL; static int use_mixer = 0; +/* Delay and OSPACE handling */ + +static int esd_nonblock = 0; /* By default we are blocking */ + +static int esd_bytes_per_sample = 0; +static long esd_bps = 0; +static int esd_samplerate = 44100; +static unsigned long esd_samples_written = 0; /* long long ? */ +static struct timeval esd_play_start; + +#define ESD_MAX_DELAY (1.0f) /* max amount of data buffered in esd (#sec) */ + /* * memory mapping emulation */ static int mmapemu = 0; +static int mmap_active = 0; static count_info mmapemu_ocount; static char *mmapemu_obuffer = NULL; static int mmapemu_osize; @@ -197,6 +211,7 @@ long long usec_since_last_flush; long long bytes_to_flush; int frags_to_flush; + int nsamples; gettimeofday(¤t_time, NULL); usec_since_last_flush = 1000000 * (current_time.tv_sec - mmapemu_last_flush.tv_sec) + (current_time.tv_usec - mmapemu_last_flush.tv_usec); @@ -217,6 +232,13 @@ mmapemu_ocount.ptr %= mmapemu_osize; mmapemu_ocount.blocks++; mmapemu_ocount.bytes += MMAPEMU_FRAGSIZE; + + /* ODELAY handling */ + if (!esd_play_start.tv_sec) + gettimeofday(&esd_play_start, NULL); + + nsamples = MMAPEMU_FRAGSIZE / esd_bytes_per_sample; + esd_samples_written += nsamples; } } @@ -255,6 +277,10 @@ DPRINTF ("hijacking /dev/dsp %s, and taking it to esd...\n", symname); settings = done = 0; + + if (flags & O_NONBLOCK) + esd_nonblock=1; + return (sndfd = esd_open_sound (NULL)); } else if (use_mixer && !strcmp (pathname, "/dev/mixer")) @@ -266,6 +292,76 @@ return (**func) (pathname, flags, mode); } + + +static float get_odelay(void) +{ + struct timeval now; + double buffered_samples_time; + double play_time; + + if (!esd_play_start.tv_sec) + return 0; + + buffered_samples_time = (float)esd_samples_written / esd_samplerate; + gettimeofday(&now, NULL); + play_time = now.tv_sec - esd_play_start.tv_sec; + play_time += (now.tv_usec - esd_play_start.tv_usec) / 1000000.; + + /* DPRINTF("esd delay: %f %f\n", play_time, buffered_samples_time); */ + + if (play_time > buffered_samples_time) { + DPRINTF("esd: underflow\n"); + esd_play_start.tv_sec = 0; + esd_samples_written = 0; + return 0; + } + + DPRINTF("esd: get_delay %f\n", buffered_samples_time - play_time); + return buffered_samples_time - play_time; +} + +/* How many bytes can be played without blocking */ +static int get_ospace(int fd) +{ + struct timeval tmout; + fd_set wfds; + float current_delay; + int space; + + /* + * Don't buffer too much data in the esd daemon. + * + * If we send too much, esd will block in write()s to the sound + * device, and the consequence is a huge slow down for things like + * esd_get_all_info(). + */ + if ((current_delay = get_odelay()) >= ESD_MAX_DELAY) { + DPRINTF("esd get_space: too much data buffered\n"); + return 0; + } + + FD_ZERO(&wfds); + FD_SET(fd, &wfds); + tmout.tv_sec = 0; + tmout.tv_usec = 0; + + if (select(fd + 1, NULL, &wfds, NULL, &tmout) != 1) + return 0; + + if (!FD_ISSET(fd, &wfds)) + return 0; + + /* try to fill 50% of the remaining "free" buffer space */ + space = (ESD_MAX_DELAY - current_delay) * esd_bps * 0.5f; + + /* round up to next multiple of ESD_BUF_SIZE */ + space = (space + ESD_BUF_SIZE-1) / ESD_BUF_SIZE * ESD_BUF_SIZE; + + DPRINTF("esd get_space: %d\n", space); + return space; +} + int open (const char *pathname, int flags, ...) { @@ -300,6 +396,69 @@ } #endif +/* FIXME: We need to check for O_NONBLOCK here ... */ + +/* +int fcntl(int fd, int cmd, ...) +{ + static int (*func) (int, int, long) = NULL; + + if (!func) + func = (int (*) (int, int, long)) dlsym (REAL_LIBC, "fcntl"); + + if (fd == sndfd && cmd == F_GETFL) + esd_nonblock = ((arg & O_NDELAY) == O_NDELAY); + return (*func)(fd, cmd, arg); +} +*/ + +int +write (int fd, const void* data, size_t len) +{ + static int (*func) (int, const void*, size_t) = NULL; + int nwritten; + int nsamples; + + if (!func) + func = (int (*) (int, const void*, size_t)) dlsym (REAL_LIBC, "write"); + + if(fd != sndfd || sndfd == -1 || done == 2) + return (*func)(fd, data, len); + + if (!done) + return -EIO; + + if (mmap_active) + return -ENXIO; + + nwritten = (*func)(fd, data, len); + + if (nwritten > 0) { + if (!esd_play_start.tv_sec) + gettimeofday(&esd_play_start, NULL); + nsamples = nwritten / esd_bytes_per_sample; + esd_samples_written += nsamples; + + DPRINTF("esd play: %d %lu\n", nsamples, esd_samples_written); + } else { + DPRINTF("esd play: blocked / %lu\n", esd_samples_written); + } + + /* Try to adjust to lag ... */ + + if (!esd_nonblock) { + float odelay = get_odelay(); + + /* Hopefully we won't run into a buffer underrun, so keep 2*ESD_BUF_SIZE as buffer */ + odelay -= (float)(2.0*ESD_BUF_SIZE) / (float)esd_bps; + DPRINTF("adj. usleep(%.2f)\n", odelay); + if (odelay > 0) + usleep(odelay*1000*1000); + } + + return nwritten; +} + static int dspctl (int fd, request_t request, void *argp) { @@ -322,6 +481,18 @@ settings |= 1; break; + case SNDCTL_DSP_CHANNELS: /* _SIOWR('P', 6, int) */ + fmt &= ~ESD_MONO; + fmt &= ~ESD_STEREO; + + if (*arg == 1) + fmt |= ESD_MONO; + + if (*arg == 2) + fmt |= ESD_STEREO; + + break; + case SNDCTL_DSP_SPEED: speed = *arg; settings |= 2; @@ -341,6 +512,7 @@ break; case SNDCTL_DSP_GETFMTS: + /* *arg = AFMT_S16_LE | AFMT_S8 | AFMT_U8;*/ *arg = 0x38; break; @@ -357,7 +529,7 @@ case SNDCTL_DSP_GETISPACE: { audio_buf_info *bufinfo = (audio_buf_info *) argp; - if (mmapemu) + if (mmap_active) { /* Number of fragments allocated for buffering */ bufinfo->fragstotal = MMAPEMU_FRAGSTOTAL; @@ -375,15 +547,18 @@ /* Size of a fragment in bytes (same as SNDCTL_DSP_GETBLKSIZE) */ bufinfo->fragsize = ESD_BUF_SIZE; /* Number of bytes that can be read or written immediately without blocking */ - bufinfo->bytes = ESD_BUF_SIZE; + bufinfo->bytes = get_ospace(fd); /* Number of full fragments that can be read or written without blocking */ - bufinfo->fragments = 1; + bufinfo->fragments = (bufinfo->bytes / ESD_BUF_SIZE); + + if (bufinfo->fragments > bufinfo->fragstotal) + bufinfo->fragstotal = bufinfo->fragments; } } break; case SNDCTL_DSP_GETOPTR: - if (mmapemu) + if (mmap_active) { DPRINTF("esddsp: mmapemu getoptr\n"); mmapemu_flush(); @@ -393,19 +568,28 @@ else { DPRINTF ("esddsp: SNDCTL_DSP_GETOPTR unsupported\n"); + return -ENOTTY; } break; + case SNDCTL_DSP_GETODELAY: + if (mmap_active) + *(int *)arg = get_odelay() * mmapemu_bytes_per_sec; + else + *(int *)arg = get_odelay() * esd_bps; + + break; default: DPRINTF ("unhandled /dev/dsp ioctl (%x - %p)\n", request, argp); break; + /*return -ENOTTY;*/ } if (settings == 3 && !done) { int proto = ESD_PROTO_STREAM_PLAY; - done = 1; + done = 2; /* We are ready, but we are not yet playing samples */ if (write (fd, &proto, sizeof (proto)) != sizeof (proto)) return -1; @@ -416,13 +600,21 @@ if (write (fd, ident, ESD_NAME_MAX) != ESD_NAME_MAX) return -1; + done = 1; + if (mmapemu) { mmapemu_ocount.ptr=mmapemu_ocount.blocks=mmapemu_ocount.bytes=0; mmapemu_bytes_per_sec = speed * ((fmt & ESD_BITS16) ? 2 : 1) * ((fmt & ESD_STEREO) ? 2 : 1); - gettimeofday(&mmapemu_last_flush, NULL); } + /* OSPACE and delay handling */ + esd_play_start.tv_sec = 0; + esd_samples_written = 0; + esd_bytes_per_sample = ((fmt & ESD_BITS16) ? 2 : 1) * ((fmt & ESD_STEREO) ? 2 : 1); + esd_bps = esd_bytes_per_sample * speed; + esd_samplerate = speed; + fmt = ESD_STREAM | ESD_PLAY | ESD_MONO; speed = 0; @@ -571,14 +763,18 @@ return (**func)(start,length,prot,flags,fd,offset); else { - DPRINTF ("esddsp: %s - start = %x, length = %d, prot = %d\n", + DPRINTF ("esddsp: %s - start = %p, length = %u, prot = %d\n", symname, start, length, prot); - DPRINTF (" flags = %d, fd = %d, offset = %d\n",flags, fd,offset); + DPRINTF (" flags = %d, fd = %d, offset = %d\n",flags, fd, (int)offset); if(mmapemu) { mmapemu_osize = length; mmapemu_obuffer = malloc(length); mmapemu_ocount.ptr = mmapemu_ocount.blocks = mmapemu_ocount.bytes = 0; + mmap_active = 1; + gettimeofday(&mmapemu_last_flush, NULL); + esd_play_start.tv_sec = 0; + return mmapemu_obuffer; } else DPRINTF ("esddsp: /dev/dsp %s (unsupported, try -m option)...\n", symname); @@ -615,6 +811,8 @@ DPRINTF ("esddsp: /dev/dsp munmap...\n"); mmapemu_obuffer = NULL; + mmap_active = 0; + esd_play_start.tv_sec = 0; free(start); return 0;