/* * EsounD audio output driver for MPlayer * * copyright (c) 2002 Juergen Keil * * This file is part of MPlayer. * * MPlayer is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * MPlayer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with MPlayer; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /* * TODO / known problems: * - does not work well when the esd daemon has autostandby disabled * (workaround: run esd with option "-as 2" - fortunatelly this is * the default) * - plays noise on a linux 2.4.4 kernel with a SB16PCI card, when using * a local tcp connection to the esd daemon; there is no noise when using * a unix domain socket connection. * (there are EIO errors reported by the sound card driver, so this is * most likely a linux sound card driver problem) */ #include #include #include #include #include #include #include #include #include #ifdef __svr4__ #include #endif #include #include "config.h" #include "audio_out.h" #include "audio_out_internal.h" #include "libaf/af_format.h" #include "mp_msg.h" #define ESD_RESAMPLES 0 #define ESD_DEBUG 0 #if ESD_DEBUG #define dprintf(...) printf(__VA_ARGS__) #else #define dprintf(...) /**/ #endif #define ESD_CLIENT_NAME "MPlayer" #define ESD_MAX_DELAY (1.0f) /* max amount of data buffered in esd (#sec) */ static const ao_info_t info = { "EsounD audio output", "esd", "Juergen Keil ", "" }; LIBAO_EXTERN(esd) static int esd_fd = -1; static int esd_play_fd = -1; static esd_server_info_t *esd_svinfo; static int esd_latency; static int esd_bytes_per_sample; static unsigned long esd_samples_written; static struct timeval esd_play_start; extern float audio_delay; /* * to set/get/query special features/parameters */ static int control(int cmd, void *arg) { esd_player_info_t *esd_pi; esd_info_t *esd_i; time_t now; static time_t vol_cache_time; static ao_control_vol_t vol_cache; switch (cmd) { case AOCONTROL_GET_VOLUME: time(&now); if (now == vol_cache_time) { *(ao_control_vol_t *)arg = vol_cache; return CONTROL_OK; } dprintf("esd: get vol\n"); if ((esd_i = esd_get_all_info(esd_fd)) == NULL) return CONTROL_ERROR; for (esd_pi = esd_i->player_list; esd_pi != NULL; esd_pi = esd_pi->next) if (strcmp(esd_pi->name, ESD_CLIENT_NAME) == 0) break; if (esd_pi != NULL) { ao_control_vol_t *vol = (ao_control_vol_t *)arg; vol->left = esd_pi->left_vol_scale * 100 / ESD_VOLUME_BASE; vol->right = esd_pi->right_vol_scale * 100 / ESD_VOLUME_BASE; vol_cache = *vol; vol_cache_time = now; } esd_free_all_info(esd_i); return CONTROL_OK; case AOCONTROL_SET_VOLUME: dprintf("esd: set vol\n"); if ((esd_i = esd_get_all_info(esd_fd)) == NULL) return CONTROL_ERROR; for (esd_pi = esd_i->player_list; esd_pi != NULL; esd_pi = esd_pi->next) if (strcmp(esd_pi->name, ESD_CLIENT_NAME) == 0) break; if (esd_pi != NULL) { ao_control_vol_t *vol = (ao_control_vol_t *)arg; esd_set_stream_pan(esd_fd, esd_pi->source_id, vol->left * ESD_VOLUME_BASE / 100, vol->right * ESD_VOLUME_BASE / 100); vol_cache = *vol; time(&vol_cache_time); } esd_free_all_info(esd_i); return CONTROL_OK; default: return CONTROL_UNKNOWN; } } /* * open & setup audio device * return: 1=success 0=fail */ static int init(int rate_hz, int channels, int format, int flags) { esd_format_t esd_fmt; int bytes_per_sample; int fl; char *server = ao_subdevice; /* NULL for localhost */ float lag_seconds, lag_net, lag_serv; struct timeval proto_start, proto_end; if (esd_fd < 0) { esd_fd = esd_open_sound(server); if (esd_fd < 0) { mp_tmsg(MSGT_AO, MSGL_ERR, "[AO ESD] esd_open_sound failed: %s\n", strerror(errno)); return 0; } /* get server info, and measure network latency */ gettimeofday(&proto_start, NULL); esd_svinfo = esd_get_server_info(esd_fd); if(server) { gettimeofday(&proto_end, NULL); lag_net = (proto_end.tv_sec - proto_start.tv_sec) + (proto_end.tv_usec - proto_start.tv_usec) / 1000000.0; lag_net /= 2.0; /* round trip -> one way */ } else lag_net = 0.0; /* no network lag */ /* if (esd_svinfo) { mp_msg(MSGT_AO, MSGL_INFO, "AO: [esd] server info:\n"); esd_print_server_info(esd_svinfo); } */ } esd_fmt = ESD_STREAM | ESD_PLAY; #if ESD_RESAMPLES /* let the esd daemon convert sample rate */ #else /* let mplayer's audio filter convert the sample rate */ if (esd_svinfo != NULL) rate_hz = esd_svinfo->rate; #endif ao_data.samplerate = rate_hz; /* EsounD can play mono or stereo */ switch (channels) { case 1: esd_fmt |= ESD_MONO; ao_data.channels = bytes_per_sample = 1; break; default: esd_fmt |= ESD_STEREO; ao_data.channels = bytes_per_sample = 2; break; } /* EsounD can play 8bit unsigned and 16bit signed native */ switch (format) { case AF_FORMAT_S8: case AF_FORMAT_U8: esd_fmt |= ESD_BITS8; ao_data.format = AF_FORMAT_U8; break; default: esd_fmt |= ESD_BITS16; ao_data.format = AF_FORMAT_S16_NE; bytes_per_sample *= 2; break; } /* modify audio_delay depending on esd_latency * latency is number of samples @ 44.1khz stereo 16 bit * adjust according to rate_hz & bytes_per_sample */ #ifdef CONFIG_ESD_LATENCY esd_latency = esd_get_latency(esd_fd); #else esd_latency = ((channels == 1 ? 2 : 1) * ESD_DEFAULT_RATE * (ESD_BUF_SIZE + 64 * (4.0f / bytes_per_sample)) ) / rate_hz; esd_latency += ESD_BUF_SIZE * 2; #endif if(esd_latency > 0) { lag_serv = (esd_latency * 4.0f) / (bytes_per_sample * rate_hz); lag_seconds = lag_net + lag_serv; audio_delay += lag_seconds; mp_tmsg(MSGT_AO, MSGL_INFO,"[AO ESD] latency: [server: %0.2fs, net: %0.2fs] (adjust %0.2fs)\n", lag_serv, lag_net, lag_seconds); } esd_play_fd = esd_play_stream_fallback(esd_fmt, rate_hz, server, ESD_CLIENT_NAME); if (esd_play_fd < 0) { mp_tmsg(MSGT_AO, MSGL_ERR, "[AO ESD] failed to open ESD playback stream: %s\n", strerror(errno)); return 0; } /* enable non-blocking i/o on the socket connection to the esd server */ if ((fl = fcntl(esd_play_fd, F_GETFL)) >= 0) fcntl(esd_play_fd, F_SETFL, O_NDELAY|fl); #if ESD_DEBUG { int sbuf, rbuf, len; len = sizeof(sbuf); getsockopt(esd_play_fd, SOL_SOCKET, SO_SNDBUF, &sbuf, &len); len = sizeof(rbuf); getsockopt(esd_play_fd, SOL_SOCKET, SO_RCVBUF, &rbuf, &len); dprintf("esd: send/receive socket buffer space %d/%d bytes\n", sbuf, rbuf); } #endif ao_data.bps = bytes_per_sample * rate_hz; ao_data.outburst = ao_data.bps > 100000 ? 4*ESD_BUF_SIZE : 2*ESD_BUF_SIZE; esd_play_start.tv_sec = 0; esd_samples_written = 0; esd_bytes_per_sample = bytes_per_sample; return 1; } /* * close audio device */ static void uninit(int immed) { if (esd_play_fd >= 0) { esd_close(esd_play_fd); esd_play_fd = -1; } if (esd_svinfo) { esd_free_server_info(esd_svinfo); esd_svinfo = NULL; } if (esd_fd >= 0) { esd_close(esd_fd); esd_fd = -1; } } /* * plays 'len' bytes of 'data' * it should round it down to outburst*n * return: number of bytes played */ static int play(void* data, int len, int flags) { int offs; int nwritten; int nsamples; int n; /* round down buffersize to a multiple of ESD_BUF_SIZE bytes */ len = len / ESD_BUF_SIZE * ESD_BUF_SIZE; if (len <= 0) return 0; #define SINGLE_WRITE 0 #if SINGLE_WRITE nwritten = write(esd_play_fd, data, len); #else for (offs = 0, nwritten=0; offs + ESD_BUF_SIZE <= len; offs += ESD_BUF_SIZE) { /* * note: we're writing to a non-blocking socket here. * A partial write means, that the socket buffer is full. */ n = write(esd_play_fd, (char*)data + offs, ESD_BUF_SIZE); if ( n < 0 ) { if ( errno != EAGAIN ) dprintf("esd play: write failed: %s\n", strerror(errno)); break; } else if ( n != ESD_BUF_SIZE ) { nwritten += n; break; } else nwritten += n; } #endif 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); } return nwritten; } /* * stop playing, keep buffers (for pause) */ static void audio_pause(void) { /* * not possible with esd. the esd daemom will continue playing * buffered data (not more than ESD_MAX_DELAY seconds of samples) */ } /* * resume playing, after audio_pause() */ static void audio_resume(void) { /* * not possible with esd. * * Let's hope the pause was long enough that the esd ran out of * buffered data; we restart our time based delay computation * for an audio resume. */ esd_play_start.tv_sec = 0; esd_samples_written = 0; } /* * stop playing and empty buffers (for seeking/pause) */ static void reset(void) { #ifdef __svr4__ /* throw away data buffered in the esd connection */ if (ioctl(esd_play_fd, I_FLUSH, FLUSHW)) perror("I_FLUSH"); #endif } /* * return: how many bytes can be played without blocking */ static int get_space(void) { 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_delay()) >= ESD_MAX_DELAY) { dprintf("esd get_space: too much data buffered\n"); return 0; } FD_ZERO(&wfds); FD_SET(esd_play_fd, &wfds); tmout.tv_sec = 0; tmout.tv_usec = 0; if (select(esd_play_fd + 1, NULL, &wfds, NULL, &tmout) != 1) return 0; if (!FD_ISSET(esd_play_fd, &wfds)) return 0; /* try to fill 50% of the remaining "free" buffer space */ space = (ESD_MAX_DELAY - current_delay) * ao_data.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; } /* * return: delay in seconds between first and last sample in buffer */ static float get_delay(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 / ao_data.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; }