diff options
-rw-r--r-- | src/audio_out/audio_esd_out.c | 157 |
1 files changed, 133 insertions, 24 deletions
diff --git a/src/audio_out/audio_esd_out.c b/src/audio_out/audio_esd_out.c index b0ddaaf9b..0329ba5c9 100644 --- a/src/audio_out/audio_esd_out.c +++ b/src/audio_out/audio_esd_out.c @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * - * $Id: audio_esd_out.c,v 1.24 2002/12/21 12:56:46 miguelfreitas Exp $ + * $Id: audio_esd_out.c,v 1.25 2002/12/27 17:49:17 jkeil Exp $ */ #ifdef HAVE_CONFIG_H @@ -32,6 +32,7 @@ #include <esd.h> #include <signal.h> #include <sys/time.h> +#include <sys/uio.h> #include <inttypes.h> #include "xine_internal.h" @@ -41,6 +42,7 @@ #define AO_OUT_ESD_IFACE_VERSION 7 +#define REBLOCK 1 /* reblock output to ESD_BUF_SIZE blks */ #define GAP_TOLERANCE 5000 typedef struct esd_driver_s { @@ -61,6 +63,7 @@ typedef struct esd_driver_s { uint32_t bytes_in_buffer; /* number of bytes writen to esd */ int gap_tolerance, latency; + int server_sample_rate; struct timeval start_time; @@ -70,6 +73,23 @@ typedef struct esd_driver_s { int mute; } mixer; +#if REBLOCK + /* + * Temporary sample buffer used to reblock the sample output stream + * to writes using buffer sizes of n*ESD_BUF_SIZE bytes. + * + * The reblocking avoids a bug with esd 0.2.18 servers and reduces + * cpu load with newer versions of the esd server. + * + * The esd 0.2.18 version zero fills "partial"/"incomplete" blocks. + * esd 0.2.28+ has fixed this problem, by performing a busy polling + * loop reading from a nonblocking socket to get the remainder of + * the partial block. This is wasting a lot of cpu cycles. + */ + char reblock_buf[ESD_BUF_SIZE]; + int reblock_rem; +#endif + } esd_driver_t; typedef struct { @@ -98,16 +118,17 @@ static int ao_esd_open(ao_driver_t *this_gen, if (this->audio_fd>=0) { - if ( (mode == this->mode) && (rate == this->input_sample_rate) ) + if ( (mode == this->mode) && (rate == this->input_sample_rate) ) return this->output_sample_rate; - close (this->audio_fd); + esd_close (this->audio_fd); } this->mode = mode; this->input_sample_rate = rate; this->output_sample_rate = rate; this->bytes_in_buffer = 0; + this->start_time.tv_sec = 0; /* * open stream to ESD server @@ -128,20 +149,24 @@ static int ao_esd_open(ao_driver_t *this_gen, this->bytes_per_frame=(bits*this->num_channels)/8; - if (this->output_sample_rate > 44100) - this->output_sample_rate = 44100; - - this->output_sample_k_rate = this->output_sample_rate / 1000; +#if ESD_RESAMPLE + /* esd resamples (only for sample rates < the esd server's sample rate) */ + if (this->output_sample_rate > this->server_sample_rate) + this->output_sample_rate = this->server_sample_rate; +#else + /* use xine's resample code */ + this->output_sample_rate = this->server_sample_rate; +#endif + this->output_sample_k_rate = this->output_sample_rate / 1000; this->audio_fd = esd_play_stream(format, this->output_sample_rate, NULL, this->pname); if (this->audio_fd < 0) { + char *server = getenv("ESPEAKER"); printf("audio_esd_out: connecting to ESD server %s: %s\n", - getenv("ESPEAKER"), strerror(errno)); + server ? server : "<default>", strerror(errno)); return 0; } - gettimeofday(&this->start_time, NULL); - return this->output_sample_rate; } @@ -165,14 +190,19 @@ static int ao_esd_delay(ao_driver_t *this_gen) int frames; struct timeval tv; + if (this->start_time.tv_sec == 0) + return 0; + gettimeofday(&tv, NULL); - frames = (tv.tv_usec + 1000000 - this->start_time.tv_usec) + frames = (tv.tv_usec - this->start_time.tv_usec) * this->output_sample_k_rate / 1000; frames += (tv.tv_sec - this->start_time.tv_sec) * this->output_sample_rate; frames -= this->latency; + if (frames < 0) + frames = 0; /* calc delay */ @@ -180,7 +210,6 @@ static int ao_esd_delay(ao_driver_t *this_gen) if (bytes_left<=0) /* buffer ran dry */ bytes_left = 0; - return bytes_left / this->bytes_per_frame; } @@ -195,28 +224,96 @@ static int ao_esd_write(ao_driver_t *this_gen, if (this->audio_fd<0) return 1; + if (this->start_time.tv_sec == 0) + gettimeofday(&this->start_time, NULL); + /* check if simulated buffer ran dry */ gettimeofday(&tv, NULL); - frames = (tv.tv_usec + 1000000 - this->start_time.tv_usec) + frames = (tv.tv_usec - this->start_time.tv_usec) * this->output_sample_k_rate / 1000; frames += (tv.tv_sec - this->start_time.tv_sec) * this->output_sample_rate; frames -= this->latency; + if (frames < 0) + frames = 0; /* calc delay */ simulated_bytes_in_buffer = frames * this->bytes_per_frame; - + if (this->bytes_in_buffer < simulated_bytes_in_buffer) this->bytes_in_buffer = simulated_bytes_in_buffer; +#if REBLOCK + { + struct iovec iov[2]; + int iovcnt; + int num_bytes; + int nwritten; + int rem; + + if (this->reblock_rem + num_frames*this->bytes_per_frame < ESD_BUF_SIZE) { + /* + * the stuff in the temporary reblocking buffer plus the new + * samples still do not give a complete ESD_BUF_SIZE block. + * just save the new samples in the reblocking buffer for later. + */ + memcpy(this->reblock_buf + this->reblock_rem, + frame_buffer, + num_frames * this->bytes_per_frame); + this->reblock_rem += num_frames * this->bytes_per_frame; + return 1; + } + + /* OK, we have at least one complete ESD_BUF_SIZE block */ + + iovcnt = 0; + num_bytes = 0; + if (this->reblock_rem > 0) { + /* send any saved samples from the reblocking buffer first */ + iov[iovcnt].iov_base = this->reblock_buf; + iov[iovcnt].iov_len = this->reblock_rem; + iovcnt++; + num_bytes += this->reblock_rem; + this->reblock_rem = 0; + } + rem = (num_bytes + num_frames * this->bytes_per_frame) % ESD_BUF_SIZE; + if (num_frames * this->bytes_per_frame > rem) { + /* + * add samples from caller, so that the total number of bytes is + * a multiple of ESD_BUF_SIZE + */ + iov[iovcnt].iov_base = frame_buffer; + iov[iovcnt].iov_len = num_frames * this->bytes_per_frame - rem; + num_bytes += num_frames * this->bytes_per_frame - rem; + iovcnt++; + } + + nwritten = writev(this->audio_fd, iov, iovcnt); + if (nwritten != num_bytes) { + if (nwritten < 0) + printf("audio_esd_out: writev failed: %s\n", strerror(errno)); + else + printf("audio_esd_out: warning, incomplete write: %d\n", nwritten); + } + if (nwritten > 0) + this->bytes_in_buffer += nwritten; + + if (rem > 0) { + /* save the remaining bytes for the next ao_esd_write() */ + memcpy(this->reblock_buf, + (char*)frame_buffer + iov[iovcnt-1].iov_len, rem); + this->reblock_rem = rem; + } + } +#else this->bytes_in_buffer += num_frames * this->bytes_per_frame; write(this->audio_fd, frame_buffer, num_frames * this->bytes_per_frame); - +#endif return 1; } @@ -371,11 +468,14 @@ static int ao_esd_ctrl(ao_driver_t *this_gen, int cmd, ...) { static ao_driver_t *open_plugin (audio_driver_class_t *class_gen, const void *data) { - esd_class_t *class = (esd_class_t *) class_gen; - config_values_t *config = class->config; - esd_driver_t *this; - int audio_fd; - sigset_t vo_mask, vo_mask_orig; + esd_class_t *class = (esd_class_t *) class_gen; + config_values_t *config = class->config; + esd_driver_t *this; + int audio_fd; + int err; + esd_server_info_t *esd_svinfo; + int server_sample_rate; + sigset_t vo_mask, vo_mask_orig; /* * open stream to ESD server @@ -383,7 +483,7 @@ static ao_driver_t *open_plugin (audio_driver_class_t *class_gen, * esd_open_sound needs a working SIGALRM for detecting a failed * attempt to autostart the esd daemon; esd notifies the process that * attempts the esd daemon autostart with a SIGALRM (SIGUSR1) signal - * about a failure to open the audio device (successful daemin startup). + * about a failure to open the audio device (successful daemon startup). * * Temporarily release the blocked SIGALRM, while esd_open_sound is active. * (Otherwise xine hangs in esd_open_sound on a machine without sound) @@ -396,7 +496,8 @@ static ao_driver_t *open_plugin (audio_driver_class_t *class_gen, printf("audio_esd_out: connecting to esd server...\n"); audio_fd = esd_open_sound(NULL); - + err = errno; + if (sigprocmask(SIG_SETMASK, &vo_mask_orig, NULL)) printf("audio_esd_out: cannot block SIGALRM: %s\n", strerror(errno)); @@ -405,20 +506,28 @@ static ao_driver_t *open_plugin (audio_driver_class_t *class_gen, /* print a message so the user knows why ESD failed */ printf("audio_esd_out: can't connect to %s ESD server: %s\n", - server ? server : "local", strerror(errno)); + server ? server : "<default>", strerror(err)); return NULL; } + esd_svinfo = esd_get_server_info(audio_fd); + if (esd_svinfo) { + server_sample_rate = esd_svinfo->rate; + esd_free_server_info(esd_svinfo); + } else + server_sample_rate = 44100; + esd_close(audio_fd); this = (esd_driver_t *) xine_xmalloc (sizeof (esd_driver_t)); this->pname = strdup("xine esd audio output plugin"); this->output_sample_rate = 0; + this->server_sample_rate = server_sample_rate; this->audio_fd = -1; this->capabilities = AO_CAP_MODE_MONO | AO_CAP_MODE_STEREO | AO_CAP_MIXER_VOL | AO_CAP_MUTE_VOL; - this->latency = config->register_range (config, "audio.esd_latency", 30000, + this->latency = config->register_range (config, "audio.esd_latency", 0, -30000, 90000, _("esd audio output latency (adjust a/v sync)"), NULL, 0, NULL, NULL); |