X-Git-Url: https://code.delx.au/pulseaudio/blobdiff_plain/cc1e2654d30ee7547aebd3c1d5b26802e8bea5ff..c2002dcd1f769e0a54976ad2d586a29c9316ffce:/src/modules/module-alsa-sink.c diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c index 513350fc..f9fb9bd5 100644 --- a/src/modules/module-alsa-sink.c +++ b/src/modules/module-alsa-sink.c @@ -1,9 +1,7 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. - Copyright 2004-2006 Lennart Poettering + Copyright 2004-2008 Lennart Poettering Copyright 2006 Pierre Ossman for Cendio AB PulseAudio is free software; you can redistribute it and/or modify @@ -30,6 +28,10 @@ #include +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + #include #include #include @@ -60,7 +62,7 @@ PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name= " "device= " - "device_id= " + "device_id= " "format= " "rate= " "channels= " @@ -72,9 +74,28 @@ PA_MODULE_USAGE( "tsched_buffer_size= " "tsched_buffer_watermark="); +static const char* const valid_modargs[] = { + "sink_name", + "device", + "device_id", + "format", + "rate", + "channels", + "channel_map", + "fragments", + "fragment_size", + "mmap", + "tsched", + "tsched_buffer_size", + "tsched_buffer_watermark", + NULL +}; + #define DEFAULT_DEVICE "default" -#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) -#define DEFAULT_TSCHED_WATERMARK_USEC (10*PA_USEC_PER_MSEC) +#define DEFAULT_TSCHED_BUFFER_USEC (2*PA_USEC_PER_SEC) /* 2s */ +#define DEFAULT_TSCHED_WATERMARK_USEC (20*PA_USEC_PER_MSEC) /* 20ms */ +#define TSCHED_MIN_SLEEP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ +#define TSCHED_MIN_WAKEUP_USEC (3*PA_USEC_PER_MSEC) /* 3ms */ struct userdata { pa_core *core; @@ -93,6 +114,8 @@ struct userdata { long hw_volume_max, hw_volume_min; long hw_dB_max, hw_dB_min; pa_bool_t hw_dB_supported; + pa_bool_t mixer_seperate_channels; + pa_cvolume hardware_volume; size_t frame_size, fragment_size, hwbuf_size, tsched_watermark; unsigned nfragments; @@ -102,7 +125,7 @@ struct userdata { pa_bool_t use_mmap, use_tsched; - pa_bool_t first; + pa_bool_t first, after_rewind; pa_rtpoll_item *alsa_rtpoll_item; @@ -110,232 +133,345 @@ struct userdata { pa_smoother *smoother; int64_t frame_index; + uint64_t since_start; snd_pcm_sframes_t hwbuf_unused_frames; }; -static const char* const valid_modargs[] = { - "sink_name", - "device", - "device_id", - "format", - "rate", - "channels", - "channel_map", - "fragments", - "fragment_size", - "mmap", - "tsched", - "tsched_buffer_size", - "tsched_buffer_watermark", - NULL -}; +static void fix_tsched_watermark(struct userdata *u) { + size_t max_use; + size_t min_sleep, min_wakeup; + pa_assert(u); + + max_use = u->hwbuf_size - (size_t) u->hwbuf_unused_frames * u->frame_size; + + min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->sink->sample_spec); + min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->sink->sample_spec); + + if (min_sleep > max_use/2) + min_sleep = pa_frame_align(max_use/2, &u->sink->sample_spec); + if (min_sleep < u->frame_size) + min_sleep = u->frame_size; + + if (min_wakeup > max_use/2) + min_wakeup = pa_frame_align(max_use/2, &u->sink->sample_spec); + if (min_wakeup < u->frame_size) + min_wakeup = u->frame_size; + + if (u->tsched_watermark > max_use-min_sleep) + u->tsched_watermark = max_use-min_sleep; + + if (u->tsched_watermark < min_wakeup) + u->tsched_watermark = min_wakeup; +} + +static void hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) { + pa_usec_t usec, wm; + + pa_assert(sleep_usec); + pa_assert(process_usec); + + pa_assert(u); + + usec = pa_sink_get_requested_latency_within_thread(u->sink); + + if (usec == (pa_usec_t) -1) + usec = pa_bytes_to_usec(u->hwbuf_size, &u->sink->sample_spec); + +/* pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); */ + + wm = pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec); + + if (usec >= wm) { + *sleep_usec = usec - wm; + *process_usec = wm; + } else + *process_usec = *sleep_usec = usec / 2; + +/* pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */ +} + +static int try_recover(struct userdata *u, const char *call, int err) { + pa_assert(u); + pa_assert(call); + pa_assert(err < 0); + + pa_log_debug("%s: %s", call, snd_strerror(err)); + + pa_assert(err != -EAGAIN); + + if (err == -EPIPE) + pa_log_debug("%s: Buffer underrun!", call); + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) { + u->first = TRUE; + u->since_start = 0; + return 0; + } + + pa_log("%s: %s", call, snd_strerror(err)); + return -1; +} + +static size_t check_left_to_play(struct userdata *u, snd_pcm_sframes_t n) { + size_t left_to_play; -static int mmap_write(struct userdata *u) { + if ((size_t) n*u->frame_size < u->hwbuf_size) + left_to_play = u->hwbuf_size - ((size_t) n*u->frame_size); + else + left_to_play = 0; + + if (left_to_play > 0) { +/* pa_log_debug("%0.2f ms left to play", (double) pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) / PA_USEC_PER_MSEC); */ + } else if (!u->first && !u->after_rewind) { + pa_log_info("Underrun!"); + + if (u->use_tsched) { + size_t old_watermark = u->tsched_watermark; + + u->tsched_watermark *= 2; + fix_tsched_watermark(u); + + if (old_watermark != u->tsched_watermark) + pa_log_notice("Increasing wakeup watermark to %0.2f ms", + (double) pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec) / PA_USEC_PER_MSEC); + } + } + + return left_to_play; +} + +static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { int work_done = 0; + pa_usec_t max_sleep_usec = 0, process_usec = 0; + size_t left_to_play; pa_assert(u); pa_sink_assert_ref(u->sink); + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); + for (;;) { - pa_memchunk chunk; - void *p; snd_pcm_sframes_t n; - int err; - const snd_pcm_channel_area_t *areas; - snd_pcm_uframes_t offset, frames; + int r; snd_pcm_hwsync(u->pcm_handle); /* First we determine how many samples are missing to fill the * buffer up to 100% */ - if (PA_UNLIKELY((n = snd_pcm_avail_update(u->pcm_handle)) < 0)) { + if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { - pa_log_debug("snd_pcm_avail_update: %s", snd_strerror(n)); + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) + continue; - if (err == -EAGAIN) { - pa_log_debug("EAGAIN"); - return work_done; - } + return r; + } - if (n == -EPIPE) - pa_log_debug("snd_pcm_avail_update: Buffer underrun!"); + left_to_play = check_left_to_play(u, n); - if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0) { - u->first = TRUE; - continue; - } + if (u->use_tsched) - pa_log("snd_pcm_recover: %s", snd_strerror(err)); - return -1; - } + /* We won't fill up the playback buffer before at least + * half the sleep time is over because otherwise we might + * ask for more data from the clients then they expect. We + * need to guarantee that clients only have to keep around + * a single hw buffer length. */ - /* We only use part of the buffer that matches our - * dynamically requested latency */ + if (!polled && + pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + break; - if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) - return work_done; + if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) { - frames = n = n - u->hwbuf_unused_frames; + if (polled) + pa_log("ALSA woke us up to write new data to the device, but there was actually nothing to write! " + "Most likely this is an ALSA driver bug. Please report this issue to the PulseAudio developers."); - if (PA_UNLIKELY((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0)) { + break; + } - pa_log_debug("snd_pcm_mmap_begin: %s", snd_strerror(err)); + n -= u->hwbuf_unused_frames; - if (err == -EAGAIN) { - pa_log_debug("EAGAIN"); - return work_done; - } + polled = FALSE; - if (err == -EPIPE) - pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!"); +/* pa_log_debug("Filling up"); */ - if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) { - u->first = TRUE; - continue; - } + for (;;) { + pa_memchunk chunk; + void *p; + int err; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n; + snd_pcm_sframes_t sframes; - pa_log("Failed to write data to DSP: %s", snd_strerror(err)); - return -1; - } +/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ - /* Check these are multiples of 8 bit */ - pa_assert((areas[0].first & 7) == 0); - pa_assert((areas[0].step & 7)== 0); + if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { - /* We assume a single interleaved memory buffer */ - pa_assert((areas[0].first >> 3) == 0); - pa_assert((areas[0].step >> 3) == u->frame_size); + if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) + continue; - p = (uint8_t*) areas[0].addr + (offset * u->frame_size); + return r; + } - chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1); - chunk.length = pa_memblock_get_length(chunk.memblock); - chunk.index = 0; + /* Make sure that if these memblocks need to be copied they will fit into one slot */ + if (frames > pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size) + frames = pa_mempool_block_size_max(u->sink->core->mempool)/u->frame_size; - pa_sink_render_into_full(u->sink, &chunk); + /* Check these are multiples of 8 bit */ + pa_assert((areas[0].first & 7) == 0); + pa_assert((areas[0].step & 7)== 0); - /* FIXME: Maybe we can do something to keep this memory block - * a little bit longer around? */ - pa_memblock_unref_fixed(chunk.memblock); + /* We assume a single interleaved memory buffer */ + pa_assert((areas[0].first >> 3) == 0); + pa_assert((areas[0].step >> 3) == u->frame_size); - if (PA_UNLIKELY((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + p = (uint8_t*) areas[0].addr + (offset * u->frame_size); - pa_log_debug("snd_pcm_mmap_commit: %s", snd_strerror(err)); + chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, TRUE); + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; - if (err == -EAGAIN) { - pa_log_debug("EAGAIN"); - return work_done; - } + pa_sink_render_into_full(u->sink, &chunk); - if (err == -EPIPE) - pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!"); + /* FIXME: Maybe we can do something to keep this memory block + * a little bit longer around? */ + pa_memblock_unref_fixed(chunk.memblock); - if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) { - u->first = TRUE; - continue; + if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0) + continue; + + return r; } - pa_log("Failed to write data to DSP: %s", snd_strerror(err)); - return -1; - } + work_done = 1; - work_done = 1; + u->frame_index += (int64_t) frames; + u->since_start += frames * u->frame_size; - u->frame_index += frames; +/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ - pa_log_debug("wrote %llu frames", (unsigned long long) frames); + if (frames >= (snd_pcm_uframes_t) n) + break; - if (PA_LIKELY(frames >= (snd_pcm_uframes_t) n)) - return work_done; + n -= (snd_pcm_sframes_t) frames; + } } + + *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; + return work_done; } -static int unix_write(struct userdata *u) { - snd_pcm_status_t *status; +static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { int work_done = 0; - - snd_pcm_status_alloca(&status); + pa_usec_t max_sleep_usec = 0, process_usec = 0; + size_t left_to_play; pa_assert(u); pa_sink_assert_ref(u->sink); + if (u->use_tsched) + hw_sleep_time(u, &max_sleep_usec, &process_usec); + for (;;) { - void *p; - snd_pcm_sframes_t n, frames; - int err; + snd_pcm_sframes_t n; + int r; snd_pcm_hwsync(u->pcm_handle); - if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0)) { - pa_log("Failed to query DSP status data: %s", snd_strerror(err)); - return -1; + if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { + + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) + continue; + + return r; } - if (PA_UNLIKELY(snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size)) - pa_log_debug("Buffer underrun!"); + left_to_play = check_left_to_play(u, n); + + if (u->use_tsched) - n = snd_pcm_status_get_avail(status); + /* We won't fill up the playback buffer before at least + * half the sleep time is over because otherwise we might + * ask for more data from the clients then they expect. We + * need to guarantee that clients only have to keep around + * a single hw buffer length. */ - /* We only use part of the buffer that matches our - * dynamically requested latency */ + if (!polled && + pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + break; - if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) - return work_done; + if (PA_UNLIKELY(n <= u->hwbuf_unused_frames)) { + + if (polled) + pa_log("ALSA woke us up to write new data to the device, but there was actually nothing to write! " + "Most likely this is an ALSA driver bug. Please report this issue to the PulseAudio developers."); + + break; + } n -= u->hwbuf_unused_frames; - if (u->memchunk.length <= 0) - pa_sink_render(u->sink, n * u->frame_size, &u->memchunk); + polled = FALSE; - pa_assert(u->memchunk.length > 0); + for (;;) { + snd_pcm_sframes_t frames; + void *p; - frames = u->memchunk.length / u->frame_size; +/* pa_log_debug("%lu frames to write", (unsigned long) frames); */ - if (frames > n) - frames = n; + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, (size_t) n * u->frame_size, &u->memchunk); - p = pa_memblock_acquire(u->memchunk.memblock); - frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, frames); - pa_memblock_release(u->memchunk.memblock); + pa_assert(u->memchunk.length > 0); - pa_assert(frames != 0); + frames = (snd_pcm_sframes_t) (u->memchunk.length / u->frame_size); - if (PA_UNLIKELY(frames < 0)) { + if (frames > n) + frames = n; - if (frames == -EAGAIN) { - pa_log_debug("EAGAIN"); - return work_done; - } + p = pa_memblock_acquire(u->memchunk.memblock); + frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, (snd_pcm_uframes_t) frames); + pa_memblock_release(u->memchunk.memblock); - if (frames == -EPIPE) - pa_log_debug("snd_pcm_avail_update: Buffer underrun!"); + pa_assert(frames != 0); - if ((frames = snd_pcm_recover(u->pcm_handle, frames, 1)) == 0) { - u->first = TRUE; - continue; + if (PA_UNLIKELY(frames < 0)) { + + if ((r = try_recover(u, "snd_pcm_writei", (int) frames)) == 0) + continue; + + return r; } - pa_log("Failed to write data to DSP: %s", snd_strerror(frames)); - return -1; - } + u->memchunk.index += (size_t) frames * u->frame_size; + u->memchunk.length -= (size_t) frames * u->frame_size; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } - u->memchunk.index += frames * u->frame_size; - u->memchunk.length -= frames * u->frame_size; + work_done = 1; - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - pa_memchunk_reset(&u->memchunk); - } + u->frame_index += frames; + u->since_start += (size_t) frames * u->frame_size; - work_done = 1; +/* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ - u->frame_index += frames; + if (frames >= n) + break; - if (PA_LIKELY(frames >= n)) - return work_done; + n -= frames; + } } + + *sleep_usec = pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) - process_usec; + return work_done; } static void update_smoother(struct userdata *u) { @@ -343,6 +479,10 @@ static void update_smoother(struct userdata *u) { int64_t frames; int err; pa_usec_t now1, now2; +/* struct timeval timestamp; */ + snd_pcm_status_t *status; + + snd_pcm_status_alloca(&status); pa_assert(u); pa_assert(u->pcm_handle); @@ -352,30 +492,45 @@ static void update_smoother(struct userdata *u) { snd_pcm_hwsync(u->pcm_handle); snd_pcm_avail_update(u->pcm_handle); +/* if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0)) { */ +/* pa_log("Failed to query DSP status data: %s", snd_strerror(err)); */ +/* return; */ +/* } */ + +/* delay = snd_pcm_status_get_delay(status); */ + if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) { - pa_log_warn("Failed to get delay: %s", snd_strerror(err)); + pa_log("Failed to query DSP status data: %s", snd_strerror(err)); return; } frames = u->frame_index - delay; - pa_log_debug("frame_index = %llu, delay = %llu, p = %llu", (unsigned long long) u->frame_index, (unsigned long long) delay, (unsigned long long) frames); +/* pa_log_debug("frame_index = %llu, delay = %llu, p = %llu", (unsigned long long) u->frame_index, (unsigned long long) delay, (unsigned long long) frames); */ + +/* snd_pcm_status_get_tstamp(status, ×tamp); */ +/* pa_rtclock_from_wallclock(×tamp); */ +/* now1 = pa_timeval_load(×tamp); */ now1 = pa_rtclock_usec(); - now2 = pa_bytes_to_usec(frames * u->frame_size, &u->sink->sample_spec); + now2 = pa_bytes_to_usec((uint64_t) frames * u->frame_size, &u->sink->sample_spec); pa_smoother_put(u->smoother, now1, now2); } static pa_usec_t sink_get_latency(struct userdata *u) { pa_usec_t r = 0; int64_t delay; + pa_usec_t now1, now2; pa_assert(u); - delay = u->frame_index - pa_smoother_get(u->smoother, pa_rtclock_usec()); + now1 = pa_rtclock_usec(); + now2 = pa_smoother_get(u->smoother, now1); + + delay = (int64_t) pa_bytes_to_usec((uint64_t) u->frame_index * u->frame_size, &u->sink->sample_spec) - (int64_t) now2; if (delay > 0) - r = pa_bytes_to_usec(delay * u->frame_size, &u->sink->sample_spec); + r = (pa_usec_t) delay; if (u->memchunk.memblock) r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); @@ -384,28 +539,14 @@ static pa_usec_t sink_get_latency(struct userdata *u) { } static int build_pollfd(struct userdata *u) { - int err; - struct pollfd *pollfd; - int n; - pa_assert(u); pa_assert(u->pcm_handle); - if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) { - pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n)); - return -1; - } - if (u->alsa_rtpoll_item) pa_rtpoll_item_free(u->alsa_rtpoll_item); - u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n); - pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL); - - if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) { - pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); + if (!(u->alsa_rtpoll_item = pa_alsa_build_pollfd(u->pcm_handle, u->rtpoll))) return -1; - } return 0; } @@ -416,8 +557,8 @@ static int suspend(struct userdata *u) { pa_smoother_pause(u->smoother, pa_rtclock_usec()); - /* Let's suspend */ - snd_pcm_drain(u->pcm_handle); + /* Let's suspend -- we don't call snd_pcm_drain() here since that might + * take awfully long with our long buffer sizes today. */ snd_pcm_close(u->pcm_handle); u->pcm_handle = NULL; @@ -431,84 +572,58 @@ static int suspend(struct userdata *u) { return 0; } -static pa_usec_t hw_sleep_time(struct userdata *u) { - pa_usec_t usec, wm; +static int update_sw_params(struct userdata *u) { + snd_pcm_uframes_t avail_min; + int err; pa_assert(u); - usec = pa_sink_get_requested_latency_within_thread(u->sink); - - if (usec <= 0) - usec = pa_bytes_to_usec(u->hwbuf_size, &u->sink->sample_spec); + /* Use the full buffer if noone asked us for anything specific */ + u->hwbuf_unused_frames = 0; - pa_log_debug("hw buffer time: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); + if (u->use_tsched) { + pa_usec_t latency; - wm = pa_bytes_to_usec(u->tsched_watermark, &u->sink->sample_spec); + if ((latency = pa_sink_get_requested_latency_within_thread(u->sink)) != (pa_usec_t) -1) { + size_t b; - if (usec >= wm) - usec -= wm; - else - usec /= 2; + pa_log_debug("latency set to %0.2fms", (double) latency / PA_USEC_PER_MSEC); - pa_log_debug("after watermark: %u ms", (unsigned) (usec / PA_USEC_PER_MSEC)); + b = pa_usec_to_bytes(latency, &u->sink->sample_spec); - return usec; -} + /* We need at least one sample in our buffer */ -static void update_hwbuf_unused_frames(struct userdata *u) { - pa_usec_t usec; - size_t b; + if (PA_UNLIKELY(b < u->frame_size)) + b = u->frame_size; - pa_assert(u); + u->hwbuf_unused_frames = (snd_pcm_sframes_t) + (PA_LIKELY(b < u->hwbuf_size) ? + ((u->hwbuf_size - b) / u->frame_size) : 0); + } - if ((usec = pa_sink_get_requested_latency_within_thread(u->sink)) <= 0) { - /* Use the full buffer if noone asked us for anything - * specific */ - u->hwbuf_unused_frames = 0; - return; + fix_tsched_watermark(u); } - b = pa_usec_to_bytes(usec, &u->sink->sample_spec); - - /* We need at least one sample in our buffer */ - - if (PA_UNLIKELY(b < u->frame_size)) - b = u->frame_size; - - u->hwbuf_unused_frames = - PA_LIKELY(b < u->hwbuf_size) ? - ((u->hwbuf_size - b) / u->frame_size) : 0; -} - -static int update_sw_params(struct userdata *u) { - snd_pcm_uframes_t avail_min; - int err; + pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames); - pa_assert(u); + /* We need at last one frame in the used part of the buffer */ + avail_min = (snd_pcm_uframes_t) u->hwbuf_unused_frames + 1; if (u->use_tsched) { - pa_usec_t usec; + pa_usec_t sleep_usec, process_usec; - usec = hw_sleep_time(u); - - avail_min = pa_usec_to_bytes(usec, &u->sink->sample_spec) / u->frame_size; - - if (avail_min <= 0) - avail_min = 1; - - } else - avail_min = 1; + hw_sleep_time(u, &sleep_usec, &process_usec); + avail_min += pa_usec_to_bytes(sleep_usec, &u->sink->sample_spec); + } - pa_log("setting avail_min=%lu", (unsigned long) avail_min); + pa_log_debug("setting avail_min=%lu", (unsigned long) avail_min); if ((err = pa_alsa_set_sw_params(u->pcm_handle, avail_min)) < 0) { pa_log("Failed to set software parameters: %s", snd_strerror(err)); return err; } - update_hwbuf_unused_frames(u); - - pa_log("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames); + pa_sink_set_max_request(u->sink, u->hwbuf_size - (size_t) u->hwbuf_unused_frames * u->frame_size); return 0; } @@ -526,7 +641,11 @@ static int unsuspend(struct userdata *u) { pa_log_info("Trying resume..."); snd_config_update_free_global(); - if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK, + /*SND_PCM_NONBLOCK|*/ + SND_PCM_NO_AUTO_RESAMPLE| + SND_PCM_NO_AUTO_CHANNELS| + SND_PCM_NO_AUTO_FORMAT)) < 0) { pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); goto fail; } @@ -553,7 +672,9 @@ static int unsuspend(struct userdata *u) { } if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { - pa_log_warn("Resume failed, couldn't restore original fragment settings."); + pa_log_warn("Resume failed, couldn't restore original fragment settings. (Old: %lu*%lu, New %lu*%lu)", + (unsigned long) u->nfragments, (unsigned long) u->fragment_size, + (unsigned long) nfrags, period_size * u->frame_size); goto fail; } @@ -566,6 +687,7 @@ static int unsuspend(struct userdata *u) { /* FIXME: We need to reload the volume somehow */ u->first = TRUE; + u->since_start = 0; pa_log_info("Resumed successfully..."); @@ -601,7 +723,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); if (suspend(u) < 0) return -1; @@ -629,14 +751,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse } break; - -/* case PA_SINK_MESSAGE_ADD_INPUT: */ -/* case PA_SINK_MESSAGE_REMOVE_INPUT: */ -/* case PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER: { */ -/* int r = pa_sink_process_msg(o, code, data, offset, chunk); */ -/* update_hwbuf_unused_frames(u); */ -/* return r; */ -/* } */ } return pa_sink_process_msg(o, code, data, offset, chunk); @@ -652,40 +766,101 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { return 0; if (mask & SND_CTL_EVENT_MASK_VALUE) { - pa_sink_get_volume(u->sink); - pa_sink_get_mute(u->sink); + pa_sink_get_volume(u->sink, TRUE); + pa_sink_get_mute(u->sink, TRUE); } return 0; } +static pa_volume_t from_alsa_volume(struct userdata *u, long alsa_vol) { + + return (pa_volume_t) round(((double) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / + (double) (u->hw_volume_max - u->hw_volume_min)); +} + +static long to_alsa_volume(struct userdata *u, pa_volume_t vol) { + long alsa_vol; + + alsa_vol = (long) round(((double) vol * (double) (u->hw_volume_max - u->hw_volume_min)) + / PA_VOLUME_NORM) + u->hw_volume_min; + + return PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); +} + static int sink_get_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; int err; - int i; + unsigned i; + pa_cvolume r; + char t[PA_CVOLUME_SNPRINT_MAX]; pa_assert(u); pa_assert(u->mixer_elem); - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; + if (u->mixer_seperate_channels) { - pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); + r.channels = s->sample_spec.channels; - if (u->hw_dB_supported) { + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; - if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) >= 0) { - s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); - continue; + if (u->hw_dB_supported) { + + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); +#endif + + r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); + } else { + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + r.values[i] = from_alsa_volume(u, alsa_vol); } + } + + } else { + long alsa_vol; + + if (u->hw_dB_supported) { + + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol)); +#endif + + pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); + + } else { - u->hw_dB_supported = FALSE; + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + + pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); } + } - if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) - goto fail; + pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); + + if (!pa_cvolume_equal(&u->hardware_volume, &r)) { + + u->hardware_volume = s->volume = r; + + if (u->hw_dB_supported) { + pa_cvolume reset; + + /* Hmm, so the hardware volume changed, let's reset our software volume */ - s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); + pa_cvolume_reset(&reset, s->sample_spec.channels); + pa_sink_set_soft_volume(s, &reset); + } } return 0; @@ -699,44 +874,101 @@ fail: static int sink_set_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; int err; - int i; + unsigned i; + pa_cvolume r; pa_assert(u); pa_assert(u->mixer_elem); - for (i = 0; i < s->sample_spec.channels; i++) { - long alsa_vol; - pa_volume_t vol; + if (u->mixer_seperate_channels) { + + r.channels = s->sample_spec.channels; + + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; + pa_volume_t vol; + + vol = s->volume.values[i]; + + if (u->hw_dB_supported) { + + alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + alsa_vol += u->hw_dB_max; + alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); + + if ((err = snd_mixer_selem_set_playback_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; - pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); + r.values[i] = pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0); - vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM); + } else { + alsa_vol = to_alsa_volume(u, vol); + + if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol)) < 0) + goto fail; + + r.values[i] = from_alsa_volume(u, alsa_vol); + } + } + + } else { + pa_volume_t vol; + long alsa_vol; + + vol = pa_cvolume_max(&s->volume); if (u->hw_dB_supported) { alsa_vol = (long) (pa_sw_volume_to_dB(vol) * 100); + alsa_vol += u->hw_dB_max; alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_dB_min, u->hw_dB_max); - if ((err = snd_mixer_selem_set_playback_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, -1)) >= 0) { + if ((err = snd_mixer_selem_set_playback_dB_all(u->mixer_elem, alsa_vol, 1)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; - if (snd_mixer_selem_get_playback_dB(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) - s->volume.values[i] = pa_sw_volume_from_dB(alsa_vol / 100.0); + pa_cvolume_set(&r, s->volume.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0)); - continue; - } + } else { + alsa_vol = to_alsa_volume(u, vol); - u->hw_dB_supported = FALSE; + if ((err = snd_mixer_selem_set_playback_volume_all(u->mixer_elem, alsa_vol)) < 0) + goto fail; + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; + pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol)); } + } - alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; - alsa_vol = PA_CLAMP_UNLIKELY(alsa_vol, u->hw_volume_min, u->hw_volume_max); + u->hardware_volume = r; - if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) - goto fail; + if (u->hw_dB_supported) { + char t[PA_CVOLUME_SNPRINT_MAX]; - if (snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &alsa_vol) >= 0) - s->volume.values[i] = (pa_volume_t) roundf(((float) (alsa_vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); - } + /* Match exactly what the user requested by software */ + + pa_sw_cvolume_divide(&r, &s->volume, &r); + pa_sink_set_soft_volume(s, &r); + + pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->volume)); + pa_log_debug("Got hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &u->hardware_volume)); + pa_log_debug("Calculated software volume: %s", pa_cvolume_snprint(t, sizeof(t), &r)); + + } else + + /* We can't match exactly what the user requested, hence let's + * at least tell the user about it */ + + s->volume = r; return 0; @@ -780,14 +1012,97 @@ static int sink_set_mute_cb(pa_sink *s) { static void sink_update_requested_latency_cb(pa_sink *s) { struct userdata *u = s->userdata; - + snd_pcm_sframes_t before; pa_assert(u); + if (!u->pcm_handle) + return; + + before = u->hwbuf_unused_frames; update_sw_params(u); + + /* Let's check whether we now use only a smaller part of the + buffer then before. If so, we need to make sure that subsequent + rewinds are relative to the new maxium fill level and not to the + current fill level. Thus, let's do a full rewind once, to clear + things up. */ + + if (u->hwbuf_unused_frames > before) { + pa_log_debug("Requesting rewind due to latency change."); + pa_sink_request_rewind(s, (size_t) -1); + } +} + +static int process_rewind(struct userdata *u) { + snd_pcm_sframes_t unused; + size_t rewind_nbytes, unused_nbytes, limit_nbytes; + pa_assert(u); + + /* Figure out how much we shall rewind and reset the counter */ + rewind_nbytes = u->sink->thread_info.rewind_nbytes; + u->sink->thread_info.rewind_nbytes = 0; + + if (rewind_nbytes <= 0) + goto finish; + + pa_assert(rewind_nbytes > 0); + pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); + + snd_pcm_hwsync(u->pcm_handle); + if ((unused = snd_pcm_avail_update(u->pcm_handle)) < 0) { + pa_log("snd_pcm_avail_update() failed: %s", snd_strerror((int) unused)); + return -1; + } + + unused_nbytes = u->tsched_watermark + (size_t) unused * u->frame_size; + + if (u->hwbuf_size > unused_nbytes) + limit_nbytes = u->hwbuf_size - unused_nbytes; + else + limit_nbytes = 0; + + if (rewind_nbytes > limit_nbytes) + rewind_nbytes = limit_nbytes; + + if (rewind_nbytes > 0) { + snd_pcm_sframes_t in_frames, out_frames; + + pa_log_debug("Limited to %lu bytes.", (unsigned long) rewind_nbytes); + + in_frames = (snd_pcm_sframes_t) (rewind_nbytes / u->frame_size); + pa_log_debug("before: %lu", (unsigned long) in_frames); + if ((out_frames = snd_pcm_rewind(u->pcm_handle, (snd_pcm_uframes_t) in_frames)) < 0) { + pa_log("snd_pcm_rewind() failed: %s", snd_strerror((int) out_frames)); + return -1; + } + pa_log_debug("after: %lu", (unsigned long) out_frames); + + rewind_nbytes = (size_t) out_frames * u->frame_size; + + if (rewind_nbytes <= 0) + pa_log_info("Tried rewind, but was apparently not possible."); + else { + u->frame_index -= out_frames; + pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); + pa_sink_process_rewind(u->sink, rewind_nbytes); + + u->after_rewind = TRUE; + return 0; + } + } else + pa_log_debug("Mhmm, actually there is nothing to rewind."); + +finish: + + pa_sink_process_rewind(u->sink, 0); + + return 0; + } static void thread_func(void *userdata) { struct userdata *u = userdata; + unsigned short revents = 0; pa_assert(u); @@ -799,74 +1114,35 @@ static void thread_func(void *userdata) { pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); -/* update_hwbuf_unused_frames(u); */ - for (;;) { int ret; - pa_log_debug("loop"); +/* pa_log_debug("loop"); */ /* Render some data and write it to the dsp */ - if (PA_SINK_OPENED(u->sink->thread_info.state)) { - int work_done = 0; - - if (u->sink->thread_info.rewind_nbytes > 0 && u->use_tsched) { - snd_pcm_sframes_t frames, limit, unused; - - pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) u->sink->thread_info.rewind_nbytes); - - frames = u->sink->thread_info.rewind_nbytes / u->frame_size; + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + int work_done; + pa_usec_t sleep_usec = 0; - snd_pcm_hwsync(u->pcm_handle); - if ((unused = snd_pcm_avail_update(u->pcm_handle)) < 0) { - pa_log("snd_pcm_avail_update() failed: %s", snd_strerror(unused)); - goto fail; - } - - limit = (u->hwbuf_size / u->frame_size) - unused; - - if (frames > limit) - frames = limit; - - frames = 0; - - if (frames > 0) { - - pa_log_debug("Limited to %lu bytes.", (unsigned long) frames * u->frame_size); - - if ((frames = snd_pcm_rewind(u->pcm_handle, frames)) < 0) { - pa_log("snd_pcm_rewind() failed: %s", snd_strerror(frames)); + if (u->sink->thread_info.rewind_requested) + if (process_rewind(u) < 0) goto fail; - } - if ((u->sink->thread_info.rewind_nbytes = frames * u->frame_size) <= 0) - pa_log_info("Tried rewind, but was apparently not possible."); - else { - u->frame_index -= frames; - pa_log_debug("Rewound %lu bytes.", (unsigned long) u->sink->thread_info.rewind_nbytes); - pa_sink_process_rewind(u->sink); - } - } else { - pa_log_debug("Mhmm, actually there is nothing to rewind."); - } - } + if (u->use_mmap) + work_done = mmap_write(u, &sleep_usec, revents & POLLOUT); + else + work_done = unix_write(u, &sleep_usec, revents & POLLOUT); - if (u->use_mmap) { - if ((work_done = mmap_write(u)) < 0) - goto fail; - } else { - if ((work_done = unix_write(u)) < 0) - goto fail; - } + if (work_done < 0) + goto fail; - pa_log_debug("work_done = %i", work_done); +/* pa_log_debug("work_done = %i", work_done); */ if (work_done) { if (u->first) { pa_log_info("Starting playback."); snd_pcm_start(u->pcm_handle); - u->first = FALSE; pa_smoother_resume(u->smoother, pa_rtclock_usec()); } @@ -875,41 +1151,54 @@ static void thread_func(void *userdata) { } if (u->use_tsched) { - pa_usec_t usec, cusec; + pa_usec_t cusec; - /* OK, the playback buffer is now full, let's - * calculate when to wake up next */ + if (u->since_start <= u->hwbuf_size) { + + /* USB devices on ALSA seem to hit a buffer + * underrun during the first iterations much + * quicker then we calculate here, probably due to + * the transport latency. To accomodate for that + * we artificially decrease the sleep time until + * we have filled the buffer at least once + * completely.*/ - usec = hw_sleep_time(u); + /*pa_log_debug("Cutting sleep time for the initial iterations by half.");*/ + sleep_usec /= 2; + } - pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) usec / PA_USEC_PER_MSEC); + /* OK, the playback buffer is now full, let's + * calculate when to wake up next */ +/* pa_log_debug("Waking up in %0.2fms (sound card clock).", (double) sleep_usec / PA_USEC_PER_MSEC); */ /* Convert from the sound card time domain to the * system time domain */ - cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), usec); + cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec); - pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); +/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */ /* We don't trust the conversion, so we wake up whatever comes first */ - pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(usec, cusec)); + pa_rtpoll_set_timer_relative(u->rtpoll, PA_MIN(sleep_usec, cusec)); } + u->first = FALSE; + u->after_rewind = FALSE; + } else if (u->use_tsched) /* OK, we're in an invalid state, let's disable our timers */ pa_rtpoll_set_timer_disabled(u->rtpoll); /* Hmm, nothing to do. Let's sleep */ - if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) goto fail; if (ret == 0) goto finish; /* Tell ALSA about this and process its response */ - if (PA_SINK_OPENED(u->sink->thread_info.state)) { + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { struct pollfd *pollfd; - unsigned short revents = 0; int err; unsigned n; @@ -920,53 +1209,18 @@ static void thread_func(void *userdata) { goto fail; } - if (revents & (POLLERR|POLLNVAL|POLLHUP)) { - snd_pcm_state_t state; - - if (revents & POLLERR) - pa_log_warn("Got POLLERR from ALSA"); - if (revents & POLLNVAL) - pa_log_warn("Got POLLNVAL from ALSA"); - if (revents & POLLHUP) - pa_log_warn("Got POLLHUP from ALSA"); - - state = snd_pcm_state(u->pcm_handle); - pa_log_warn("PCM state is %s", snd_pcm_state_name(state)); - - /* Try to recover from this error */ - - switch (state) { - - case SND_PCM_STATE_XRUN: - if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) { - pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err)); - goto fail; - } - break; - - case SND_PCM_STATE_SUSPENDED: - if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) { - pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err)); - goto fail; - } - break; - - default: - - snd_pcm_drop(u->pcm_handle); - - if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) { - pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err)); - goto fail; - } - break; - } + if (revents & (POLLIN|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) { + if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) + goto fail; u->first = TRUE; + u->since_start = 0; } - pa_log_debug("alsa revents = %i", revents); - } + if (revents && u->use_tsched) + pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : ""); + } else + revents = 0; } fail: @@ -997,17 +1251,13 @@ int pa__init(pa_module*m) { pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d; pa_usec_t usec; pa_sink_new_data data; - static const char * const class_table[SND_PCM_CLASS_LAST+1] = { - [SND_PCM_CLASS_GENERIC] = "sound", - [SND_PCM_CLASS_MULTI] = NULL, - [SND_PCM_CLASS_MODEM] = "modem", - [SND_PCM_CLASS_DIGITIZER] = NULL - }; snd_pcm_info_alloca(&pcm_info); pa_assert(m); + pa_alsa_redirect_errors_inc(); + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { pa_log("Failed to parse module arguments"); goto fail; @@ -1022,11 +1272,11 @@ int pa__init(pa_module*m) { frame_size = pa_frame_size(&ss); nfrags = m->core->default_n_fragments; - frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); + frag_size = (uint32_t) pa_usec_to_bytes(m->core->default_fragment_size_msec*PA_USEC_PER_MSEC, &ss); if (frag_size <= 0) - frag_size = frame_size; - tsched_size = pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); - tsched_watermark = pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss); + frag_size = (uint32_t) frame_size; + tsched_size = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_BUFFER_USEC, &ss); + tsched_watermark = (uint32_t) pa_usec_to_bytes(DEFAULT_TSCHED_WATERMARK_USEC, &ss); if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0 || @@ -1046,12 +1296,12 @@ int pa__init(pa_module*m) { } if (pa_modargs_get_value_boolean(ma, "tsched", &use_tsched) < 0) { - pa_log("Failed to parse timer_scheduling argument."); + pa_log("Failed to parse tsched argument."); goto fail; } if (use_tsched && !pa_rtclock_hrtimer()) { - pa_log("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); + pa_log_notice("Disabling timer-based scheduling because high-resolution timers are not available from the kernel."); use_tsched = FALSE; } @@ -1062,12 +1312,13 @@ int pa__init(pa_module*m) { u->use_mmap = use_mmap; u->use_tsched = use_tsched; u->first = TRUE; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->since_start = 0; + u->after_rewind = FALSE; u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); u->alsa_rtpoll_item = NULL; - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); - u->smoother = pa_smoother_new(DEFAULT_TSCHED_BUFFER_USEC*2, DEFAULT_TSCHED_BUFFER_USEC*2, TRUE); + u->smoother = pa_smoother_new(DEFAULT_TSCHED_BUFFER_USEC*2, DEFAULT_TSCHED_BUFFER_USEC*2, TRUE, 5); usec = pa_rtclock_usec(); pa_smoother_set_time_offset(u->smoother, usec); pa_smoother_pause(u->smoother, usec); @@ -1158,7 +1409,7 @@ int pa__init(pa_module*m) { } if (found) - if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM"))) + if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM", TRUE))) found = FALSE; if (!found) { @@ -1182,14 +1433,11 @@ int pa__init(pa_module*m) { pa_sink_new_data_set_sample_spec(&data, &ss); pa_sink_new_data_set_channel_map(&data, &map); + pa_alsa_init_proplist(data.proplist, pcm_info); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->device_name); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_API, "alsa"); - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, snd_pcm_info_get_name(pcm_info)); - - if (class_table[snd_pcm_info_get_class(pcm_info)]) - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, class_table[snd_pcm_info_get_class(pcm_info)]); - - pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap_rewrite" : (u->use_mmap ? "mmap" : "serial")); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_BUFFER_SIZE, "%lu", (unsigned long) (period_frames * frame_size * nfrags)); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_BUFFERING_FRAGMENT_SIZE, "%lu", (unsigned long) (period_frames * frame_size)); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_ACCESS_MODE, u->use_tsched ? "mmap+timer" : (u->use_mmap ? "mmap" : "serial")); u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); pa_sink_new_data_done(&data); @@ -1208,7 +1456,7 @@ int pa__init(pa_module*m) { pa_sink_set_rtpoll(u->sink, u->rtpoll); u->frame_size = frame_size; - u->fragment_size = frag_size = period_frames * frame_size; + u->fragment_size = frag_size = (uint32_t) (period_frames * frame_size); u->nfragments = nfrags; u->hwbuf_size = u->fragment_size * nfrags; u->hwbuf_unused_frames = 0; @@ -1217,13 +1465,18 @@ int pa__init(pa_module*m) { u->hw_dB_supported = FALSE; u->hw_dB_min = u->hw_dB_max = 0; u->hw_volume_min = u->hw_volume_max = 0; + u->mixer_seperate_channels = FALSE; + pa_cvolume_mute(&u->hardware_volume, u->sink->sample_spec.channels); - u->sink->thread_info.max_rewind = use_tsched ? u->hwbuf_size : 0; + if (use_tsched) + fix_tsched_watermark(u); - u->sink->max_latency = pa_bytes_to_usec(u->hwbuf_size, &ss); + u->sink->thread_info.max_rewind = use_tsched ? u->hwbuf_size : 0; + u->sink->thread_info.max_request = u->hwbuf_size; - if (!use_tsched) - u->sink->min_latency = u->sink->max_latency; + pa_sink_set_latency_range(u->sink, + !use_tsched ? pa_bytes_to_usec(u->hwbuf_size, &ss) : (pa_usec_t) -1, + pa_bytes_to_usec(u->hwbuf_size, &ss)); pa_log_info("Using %u fragments of size %lu bytes, buffer time is %0.2fms", nfrags, (long unsigned) u->fragment_size, @@ -1241,73 +1494,67 @@ int pa__init(pa_module*m) { if (u->mixer_handle) { pa_assert(u->mixer_elem); - if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) - - if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0 && - snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) >= 0) { - - pa_bool_t suitable = TRUE; + if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) { + pa_bool_t suitable = FALSE; + if (snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) < 0) + pa_log_info("Failed to get volume range. Falling back to software volume control."); + else if (u->hw_volume_min >= u->hw_volume_max) + pa_log_warn("Your kernel driver is broken: it reports a volume range from %li to %li which makes no sense.", u->hw_volume_min, u->hw_volume_max); + else { pa_log_info("Volume ranges from %li to %li.", u->hw_volume_min, u->hw_volume_max); + suitable = TRUE; + } - if (u->hw_volume_min > u->hw_volume_max) { - - pa_log_info("Minimal volume %li larger than maximum volume %li. Strange stuff Falling back to software volume control.", u->hw_volume_min, u->hw_volume_max); - suitable = FALSE; - - } else if (u->hw_volume_max - u->hw_volume_min < 3) { - - pa_log_info("Device has less than 4 volume levels. Falling back to software volume control."); - suitable = FALSE; - - } else if (snd_mixer_selem_get_playback_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) >= 0) { - - pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", u->hw_dB_min/100.0, u->hw_dB_max/100.0); - - /* Let's see if this thing actually is useful for muting */ - if (u->hw_dB_min > -6000) { - pa_log_info("Device cannot attenuate for more than -60 dB (only %0.2f dB supported), falling back to software volume control.", ((double) u->hw_dB_min) / 100); - - suitable = FALSE; - } else if (u->hw_dB_max < 0) { - - pa_log_info("Device is still attenuated at maximum volume setting (%0.2f dB is maximum). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_max) / 100); - suitable = FALSE; - - } else if (u->hw_dB_min >= u->hw_dB_max) { - - pa_log_info("Minimal dB (%0.2f) larger or equal to maximum dB (%0.2f). Strange stuff. Falling back to software volume control.", ((double) u->hw_dB_min) / 100, ((double) u->hw_dB_max) / 100); - suitable = FALSE; + if (suitable) { + if (snd_mixer_selem_get_playback_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0) + pa_log_info("Mixer doesn't support dB information."); + else { +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_min, sizeof(u->hw_dB_min)); + VALGRIND_MAKE_MEM_DEFINED(&u->hw_dB_max, sizeof(u->hw_dB_max)); +#endif - } else { + if (u->hw_dB_min >= u->hw_dB_max) + pa_log_warn("Your kernel driver is broken: it reports a volume range from %0.2f dB to %0.2f dB which makes no sense.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); + else { + pa_log_info("Volume ranges from %0.2f dB to %0.2f dB.", (double) u->hw_dB_min/100.0, (double) u->hw_dB_max/100.0); + u->hw_dB_supported = TRUE; if (u->hw_dB_max > 0) { - /* dB > 0 means overamplification, and clipping, we don't want that here */ - pa_log_info("Device can do overamplification for %0.2f dB. Limiting to 0 db", ((double) u->hw_dB_max) / 100); - u->hw_dB_max = 0; - } - - u->hw_dB_supported = TRUE; + u->sink->base_volume = pa_sw_volume_from_dB(- (double) u->hw_dB_max/100.0); + pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->sink->base_volume)); + } else + pa_log_info("No particular base volume set, fixing to 0 dB"); } } - if (suitable) { - u->sink->get_volume = sink_get_volume_cb; - u->sink->set_volume = sink_set_volume_cb; - u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SINK_DECIBEL_VOLUME : 0); - pa_log_info("Using hardware volume control. %s dB scale.", u->hw_dB_supported ? "Using" : "Not using"); + if (!u->hw_dB_supported && + u->hw_volume_max - u->hw_volume_min < 3) { - } else { - pa_log_info("Using software volume control. Trying to reset sound card to 0 dB."); - pa_alsa_0dB_playback(u->mixer_elem); + pa_log_info("Device doesn't do dB volume and has less than 4 volume levels. Falling back to software volume control."); + suitable = FALSE; } } + if (suitable) { + u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0; + + u->sink->get_volume = sink_get_volume_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->flags |= PA_SINK_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SINK_DECIBEL_VOLUME : 0); + pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); + + } else + pa_log_info("Using software volume control."); + } + if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) { u->sink->get_mute = sink_get_mute_cb; u->sink->set_mute = sink_set_mute_cb; u->sink->flags |= PA_SINK_HW_MUTE_CTRL; - } + } else + pa_log_info("Using software mute control."); u->mixer_fdl = pa_alsa_fdlist_new(); @@ -1366,8 +1613,10 @@ void pa__done(pa_module*m) { pa_assert(m); - if (!(u = m->userdata)) + if (!(u = m->userdata)) { + pa_alsa_redirect_errors_dec(); return; + } if (u->sink) pa_sink_unlink(u->sink); @@ -1409,4 +1658,6 @@ void pa__done(pa_module*m) { pa_xfree(u); snd_config_update_free_global(); + + pa_alsa_redirect_errors_dec(); }