PulseAudio is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
- by the Free Software Foundation; either version 2 of the License,
+ by the Free Software Foundation; either version 2.1 of the License,
or (at your option) any later version.
PulseAudio is distributed in the hope that it will be useful, but
#include <asoundlib.h>
-#ifdef HAVE_VALGRIND_MEMCHECK_H
-#include <valgrind/memcheck.h>
-#endif
-
-#include <pulse/xmalloc.h>
-#include <pulse/util.h>
-#include <pulse/timeval.h>
#include <pulse/i18n.h>
+#include <pulse/rtclock.h>
+#include <pulse/timeval.h>
+#include <pulse/util.h>
+#include <pulse/xmalloc.h>
#include <pulsecore/core-error.h>
#include <pulsecore/core.h>
#include <pulsecore/memchunk.h>
#include <pulsecore/sink.h>
#include <pulsecore/modargs.h>
+#include <pulsecore/core-rtclock.h>
#include <pulsecore/core-util.h>
#include <pulsecore/sample-util.h>
#include <pulsecore/log.h>
#include <pulsecore/thread-mq.h>
#include <pulsecore/rtpoll.h>
#include <pulsecore/time-smoother.h>
-#include <pulsecore/rtclock.h>
+
+#include <modules/reserve-wrap.h>
#include "alsa-util.h"
#include "alsa-source.h"
+/* #define DEBUG_TIMING */
+
#define DEFAULT_DEVICE "default"
#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 */
+#define TSCHED_WATERMARK_STEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+#define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC) /* 10ms */
+#define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC) /* 4ms */
struct userdata {
pa_core *core;
pa_alsa_fdlist *mixer_fdl;
snd_mixer_t *mixer_handle;
- snd_mixer_elem_t *mixer_elem;
- 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_alsa_path_set *mixer_path_set;
+ pa_alsa_path *mixer_path;
pa_cvolume hardware_volume;
- size_t frame_size, fragment_size, hwbuf_size, tsched_watermark;
+ size_t
+ frame_size,
+ fragment_size,
+ hwbuf_size,
+ tsched_watermark,
+ hwbuf_unused,
+ min_sleep,
+ min_wakeup,
+ watermark_step;
+
unsigned nfragments;
char *device_name;
+ char *control_device;
- pa_bool_t use_mmap, use_tsched;
+ pa_bool_t use_mmap:1, use_tsched:1;
pa_rtpoll_item *alsa_rtpoll_item;
snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST];
pa_smoother *smoother;
- int64_t frame_index;
+ uint64_t read_count;
- snd_pcm_sframes_t hwbuf_unused_frames;
+ pa_reserve_wrapper *reserve;
+ pa_hook_slot *reserve_slot;
+ pa_reserve_monitor_wrapper *monitor;
+ pa_hook_slot *monitor_slot;
};
static void userdata_free(struct userdata *u);
+static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct userdata *u) {
+ pa_assert(r);
+ pa_assert(u);
+
+ if (pa_source_suspend(u->source, TRUE, PA_SUSPEND_APPLICATION) < 0)
+ return PA_HOOK_CANCEL;
+
+ return PA_HOOK_OK;
+}
+
+static void reserve_done(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->reserve_slot) {
+ pa_hook_slot_free(u->reserve_slot);
+ u->reserve_slot = NULL;
+ }
+
+ if (u->reserve) {
+ pa_reserve_wrapper_unref(u->reserve);
+ u->reserve = NULL;
+ }
+}
+
+static void reserve_update(struct userdata *u) {
+ const char *description;
+ pa_assert(u);
+
+ if (!u->source || !u->reserve)
+ return;
+
+ if ((description = pa_proplist_gets(u->source->proplist, PA_PROP_DEVICE_DESCRIPTION)))
+ pa_reserve_wrapper_set_application_device_name(u->reserve, description);
+}
+
+static int reserve_init(struct userdata *u, const char *dname) {
+ char *rname;
+
+ pa_assert(u);
+ pa_assert(dname);
+
+ if (u->reserve)
+ return 0;
+
+ if (pa_in_system_mode())
+ return 0;
+
+ /* We are resuming, try to lock the device */
+ if (!(rname = pa_alsa_get_reserve_name(dname)))
+ return 0;
+
+ u->reserve = pa_reserve_wrapper_get(u->core, rname);
+ pa_xfree(rname);
+
+ if (!(u->reserve))
+ return -1;
+
+ reserve_update(u);
+
+ pa_assert(!u->reserve_slot);
+ u->reserve_slot = pa_hook_connect(pa_reserve_wrapper_hook(u->reserve), PA_HOOK_NORMAL, (pa_hook_cb_t) reserve_cb, u);
+
+ return 0;
+}
+
+static pa_hook_result_t monitor_cb(pa_reserve_monitor_wrapper *w, void* busy, struct userdata *u) {
+ pa_bool_t b;
+
+ pa_assert(w);
+ pa_assert(u);
+
+ b = PA_PTR_TO_UINT(busy) && !u->reserve;
+
+ pa_source_suspend(u->source, b, PA_SUSPEND_APPLICATION);
+ return PA_HOOK_OK;
+}
+
+static void monitor_done(struct userdata *u) {
+ pa_assert(u);
+
+ if (u->monitor_slot) {
+ pa_hook_slot_free(u->monitor_slot);
+ u->monitor_slot = NULL;
+ }
+
+ if (u->monitor) {
+ pa_reserve_monitor_wrapper_unref(u->monitor);
+ u->monitor = NULL;
+ }
+}
+
+static int reserve_monitor_init(struct userdata *u, const char *dname) {
+ char *rname;
+
+ pa_assert(u);
+ pa_assert(dname);
+
+ if (pa_in_system_mode())
+ return 0;
+
+ /* We are resuming, try to lock the device */
+ if (!(rname = pa_alsa_get_reserve_name(dname)))
+ return 0;
+
+ u->monitor = pa_reserve_monitor_wrapper_get(u->core, rname);
+ pa_xfree(rname);
+
+ if (!(u->monitor))
+ return -1;
+
+ pa_assert(!u->monitor_slot);
+ u->monitor_slot = pa_hook_connect(pa_reserve_monitor_wrapper_hook(u->monitor), PA_HOOK_NORMAL, (pa_hook_cb_t) monitor_cb, u);
+
+ return 0;
+}
+
+static void fix_min_sleep_wakeup(struct userdata *u) {
+ size_t max_use, max_use_2;
+ pa_assert(u);
+
+ max_use = u->hwbuf_size - u->hwbuf_unused;
+ max_use_2 = pa_frame_align(max_use/2, &u->source->sample_spec);
+
+ u->min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->source->sample_spec);
+ u->min_sleep = PA_CLAMP(u->min_sleep, u->frame_size, max_use_2);
+
+ u->min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->source->sample_spec);
+ u->min_wakeup = PA_CLAMP(u->min_wakeup, u->frame_size, max_use_2);
+}
+
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;
+ max_use = u->hwbuf_size - u->hwbuf_unused;
+
+ if (u->tsched_watermark > max_use - u->min_sleep)
+ u->tsched_watermark = max_use - u->min_sleep;
- min_sleep = pa_usec_to_bytes(TSCHED_MIN_SLEEP_USEC, &u->source->sample_spec);
- min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->source->sample_spec);
+ if (u->tsched_watermark < u->min_wakeup)
+ u->tsched_watermark = u->min_wakeup;
+}
+
+static void adjust_after_overrun(struct userdata *u) {
+ size_t old_watermark;
+ pa_usec_t old_min_latency, new_min_latency;
- if (min_sleep > max_use/2)
- min_sleep = pa_frame_align(max_use/2, &u->source->sample_spec);
- if (min_sleep < u->frame_size)
- min_sleep = u->frame_size;
+ pa_assert(u);
+ pa_assert(u->use_tsched);
- if (min_wakeup > max_use/2)
- min_wakeup = pa_frame_align(max_use/2, &u->source->sample_spec);
- if (min_wakeup < u->frame_size)
- min_wakeup = u->frame_size;
+ /* First, just try to increase the watermark */
+ old_watermark = u->tsched_watermark;
+ u->tsched_watermark = PA_MIN(u->tsched_watermark * 2, u->tsched_watermark + u->watermark_step);
- if (u->tsched_watermark > max_use-min_sleep)
- u->tsched_watermark = max_use-min_sleep;
+ fix_tsched_watermark(u);
- if (u->tsched_watermark < min_wakeup)
- u->tsched_watermark = min_wakeup;
+ 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->source->sample_spec) / PA_USEC_PER_MSEC);
+ return;
+ }
+
+ /* Hmm, we cannot increase the watermark any further, hence let's raise the latency */
+ old_min_latency = u->source->thread_info.min_latency;
+ new_min_latency = PA_MIN(old_min_latency * 2, old_min_latency + TSCHED_WATERMARK_STEP_USEC);
+ new_min_latency = PA_MIN(new_min_latency, u->source->thread_info.max_latency);
+
+ if (old_min_latency != new_min_latency) {
+ pa_log_notice("Increasing minimal latency to %0.2f ms",
+ (double) new_min_latency / PA_USEC_PER_MSEC);
+
+ pa_source_set_latency_range_within_thread(u->source, new_min_latency, u->source->thread_info.max_latency);
+ return;
+ }
+
+ /* When we reach this we're officialy fucked! */
}
static pa_usec_t hw_sleep_time(struct userdata *u, pa_usec_t *sleep_usec, pa_usec_t*process_usec) {
if (usec == (pa_usec_t) -1)
usec = pa_bytes_to_usec(u->hwbuf_size, &u->source->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->source->sample_spec);
- if (usec >= wm) {
- *sleep_usec = usec - wm;
- *process_usec = wm;
- } else
- *process_usec = *sleep_usec = usec /= 2;
+ if (wm > usec)
+ wm = usec/2;
+
+ *sleep_usec = usec - wm;
+ *process_usec = wm;
-/* pa_log_debug("after watermark: %u ms", (unsigned) (*sleep_usec / PA_USEC_PER_MSEC)); */
+#ifdef DEBUG_TIMING
+ pa_log_debug("Buffer time: %lu ms; Sleep time: %lu ms; Process time: %lu ms",
+ (unsigned long) (usec / PA_USEC_PER_MSEC),
+ (unsigned long) (*sleep_usec / PA_USEC_PER_MSEC),
+ (unsigned long) (*process_usec / PA_USEC_PER_MSEC));
+#endif
return usec;
}
pa_assert(call);
pa_assert(err < 0);
- pa_log_debug("%s: %s", call, snd_strerror(err));
+ pa_log_debug("%s: %s", call, pa_alsa_strerror(err));
pa_assert(err != -EAGAIN);
if (err == -EPIPE)
pa_log_debug("%s: Buffer overrun!", call);
- if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) {
- snd_pcm_start(u->pcm_handle);
- return 0;
+ if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) < 0) {
+ pa_log("%s: %s", call, pa_alsa_strerror(err));
+ return -1;
}
- pa_log("%s: %s", call, snd_strerror(err));
- return -1;
+ snd_pcm_start(u->pcm_handle);
+ return 0;
}
-static size_t check_left_to_record(struct userdata *u, snd_pcm_sframes_t n) {
+static size_t check_left_to_record(struct userdata *u, size_t n_bytes) {
size_t left_to_record;
- size_t rec_space = u->hwbuf_size - (size_t) u->hwbuf_unused_frames*u->frame_size;
+ size_t rec_space = u->hwbuf_size - u->hwbuf_unused;
- if ((size_t) n*u->frame_size < rec_space)
- left_to_record = rec_space - ((size_t) n*u->frame_size);
- else
- left_to_record = 0;
+ /* We use <= instead of < for this check here because an overrun
+ * only happens after the last sample was processed, not already when
+ * it is removed from the buffer. This is particularly important
+ * when block transfer is used. */
+
+ if (n_bytes <= rec_space) {
+ left_to_record = rec_space - n_bytes;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC);
+#endif
- if (left_to_record > 0) {
-/* pa_log_debug("%0.2f ms left to record", (double) pa_bytes_to_usec(left_to_record, &u->source->sample_spec) / PA_USEC_PER_MSEC); */
} else {
- if (pa_log_ratelimit())
- pa_log_info("Overrun!");
+ left_to_record = 0;
- if (u->use_tsched) {
- size_t old_watermark = u->tsched_watermark;
+#ifdef DEBUG_TIMING
+ PA_DEBUG_TRAP;
+#endif
- u->tsched_watermark *= 2;
- fix_tsched_watermark(u);
+ if (pa_log_ratelimit())
+ pa_log_info("Overrun!");
- 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->source->sample_spec) / PA_USEC_PER_MSEC);
- }
+ if (u->use_tsched)
+ adjust_after_overrun(u);
}
return left_to_record;
}
static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
- int work_done = 0;
+ pa_bool_t work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
+ unsigned j = 0;
pa_assert(u);
pa_source_assert_ref(u->source);
for (;;) {
snd_pcm_sframes_t n;
+ size_t n_bytes;
int r;
- snd_pcm_hwsync(u->pcm_handle);
-
- if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
+ if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
- if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0)
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
continue;
return r;
}
- left_to_record = check_left_to_record(u, n);
+ n_bytes = (size_t) n * u->frame_size;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("avail: %lu", (unsigned long) n_bytes);
+#endif
+
+ left_to_record = check_left_to_record(u, n_bytes);
if (u->use_tsched)
if (!polled &&
- pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2)
+ pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) {
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not reading, because too early.");
+#endif
break;
+ }
+
+ if (PA_UNLIKELY(n_bytes <= 0)) {
- if (PA_UNLIKELY(n <= 0)) {
+ if (polled)
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(u->pcm_handle);
+ pa_log(_("ALSA woke us up to read new data from the device, but there was actually nothing to read!\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.\n"
+ "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail() returned 0 or another value < min_avail."),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ } PA_ONCE_END;
+
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not reading, because not necessary.");
+#endif
+ break;
+ }
- if (polled && pa_log_ratelimit())
- pa_log(_("ALSA woke us up to read new data from the device, but there was actually nothing to read! "
- "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. "
- "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail_update() returned 0."));
+ if (++j > 10) {
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not filling up, because already too many iterations.");
+#endif
break;
}
polled = FALSE;
+#ifdef DEBUG_TIMING
+ pa_log_debug("Reading");
+#endif
+
for (;;) {
int err;
const snd_pcm_channel_area_t *areas;
- snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n;
+ snd_pcm_uframes_t offset, frames;
pa_memchunk chunk;
void *p;
snd_pcm_sframes_t sframes;
+ frames = (snd_pcm_uframes_t) (n_bytes / u->frame_size);
+
/* pa_log_debug("%lu frames to read", (unsigned long) frames); */
if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
return r;
}
- work_done = 1;
+ work_done = TRUE;
- u->frame_index += (int64_t) frames;
+ u->read_count += frames * u->frame_size;
-/* pa_log_debug("read %lu frames", (unsigned long) frames); */
+#ifdef DEBUG_TIMING
+ pa_log_debug("Read %lu bytes (of possible %lu bytes)", (unsigned long) (frames * u->frame_size), (unsigned long) n_bytes);
+#endif
- if (frames >= (snd_pcm_uframes_t) n)
+ if ((size_t) frames * u->frame_size >= n_bytes)
break;
- n -= (snd_pcm_sframes_t) frames;
+ n_bytes -= (size_t) frames * u->frame_size;
}
}
- *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec;
- return work_done;
+ *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec);
+
+ if (*sleep_usec > process_usec)
+ *sleep_usec -= process_usec;
+ else
+ *sleep_usec = 0;
+
+ return work_done ? 1 : 0;
}
static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) {
- int work_done = 0;
+ int work_done = FALSE;
pa_usec_t max_sleep_usec = 0, process_usec = 0;
size_t left_to_record;
+ unsigned j = 0;
pa_assert(u);
pa_source_assert_ref(u->source);
for (;;) {
snd_pcm_sframes_t n;
+ size_t n_bytes;
int r;
- snd_pcm_hwsync(u->pcm_handle);
-
- if (PA_UNLIKELY((n = pa_alsa_safe_avail_update(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
+ if (PA_UNLIKELY((n = pa_alsa_safe_avail(u->pcm_handle, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
- if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0)
+ if ((r = try_recover(u, "snd_pcm_avail", (int) n)) == 0)
continue;
return r;
}
- left_to_record = check_left_to_record(u, n);
+ n_bytes = (size_t) n * u->frame_size;
+ left_to_record = check_left_to_record(u, n_bytes);
if (u->use_tsched)
if (!polled &&
pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2)
break;
- if (PA_UNLIKELY(n <= 0)) {
+ if (PA_UNLIKELY(n_bytes <= 0)) {
- if (polled && pa_log_ratelimit())
- pa_log(_("ALSA woke us up to read new data from the device, but there was actually nothing to read! "
- "Most likely this is an ALSA driver bug. Please report this issue to the ALSA developers. "
- "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail_update() returned 0."));
+ if (polled)
+ PA_ONCE_BEGIN {
+ char *dn = pa_alsa_get_driver_name_by_pcm(u->pcm_handle);
+ pa_log(_("ALSA woke us up to read new data from the device, but there was actually nothing to read!\n"
+ "Most likely this is a bug in the ALSA driver '%s'. Please report this issue to the ALSA developers.\n"
+ "We were woken up with POLLIN set -- however a subsequent snd_pcm_avail() returned 0 or another value < min_avail."),
+ pa_strnull(dn));
+ pa_xfree(dn);
+ } PA_ONCE_END;
- return work_done;
+ break;
+ }
+
+ if (++j > 10) {
+#ifdef DEBUG_TIMING
+ pa_log_debug("Not filling up, because already too many iterations.");
+#endif
+
+ break;
}
polled = FALSE;
frames = (snd_pcm_sframes_t) (pa_memblock_get_length(chunk.memblock) / u->frame_size);
- if (frames > n)
- frames = n;
+ if (frames > (snd_pcm_sframes_t) (n_bytes/u->frame_size))
+ frames = (snd_pcm_sframes_t) (n_bytes/u->frame_size);
/* pa_log_debug("%lu frames to read", (unsigned long) n); */
pa_source_post(u->source, &chunk);
pa_memblock_unref(chunk.memblock);
- work_done = 1;
+ work_done = TRUE;
- u->frame_index += frames;
+ u->read_count += frames * u->frame_size;
/* pa_log_debug("read %lu frames", (unsigned long) frames); */
- if (frames >= n)
+ if ((size_t) frames * u->frame_size >= n_bytes)
break;
- n -= frames;
+ n_bytes -= (size_t) frames * u->frame_size;
}
}
- *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec) - process_usec;
- return work_done;
+ *sleep_usec = pa_bytes_to_usec(left_to_record, &u->source->sample_spec);
+
+ if (*sleep_usec > process_usec)
+ *sleep_usec -= process_usec;
+ else
+ *sleep_usec = 0;
+
+ return work_done ? 1 : 0;
}
static void update_smoother(struct userdata *u) {
snd_pcm_sframes_t delay = 0;
- int64_t frames;
+ uint64_t position;
int err;
- pa_usec_t now1, now2;
+ pa_usec_t now1 = 0, now2;
+ snd_pcm_status_t *status;
+
+ snd_pcm_status_alloca(&status);
pa_assert(u);
pa_assert(u->pcm_handle);
/* Let's update the time smoother */
- snd_pcm_hwsync(u->pcm_handle);
- snd_pcm_avail_update(u->pcm_handle);
-
- if (PA_UNLIKELY((err = snd_pcm_delay(u->pcm_handle, &delay)) < 0)) {
- pa_log_warn("Failed to get delay: %s", snd_strerror(err));
+ if (PA_UNLIKELY((err = pa_alsa_safe_delay(u->pcm_handle, &delay, u->hwbuf_size, &u->source->sample_spec)) < 0)) {
+ pa_log_warn("Failed to get delay: %s", pa_alsa_strerror(err));
return;
}
- frames = u->frame_index + delay;
+ if (PA_UNLIKELY((err = snd_pcm_status(u->pcm_handle, status)) < 0))
+ pa_log_warn("Failed to get timestamp: %s", pa_alsa_strerror(err));
+ else {
+ snd_htimestamp_t htstamp = { 0, 0 };
+ snd_pcm_status_get_htstamp(status, &htstamp);
+ now1 = pa_timespec_load(&htstamp);
+ }
+
+ position = u->read_count + ((uint64_t) delay * (uint64_t) u->frame_size);
- now1 = pa_rtclock_usec();
- now2 = pa_bytes_to_usec((uint64_t) frames * u->frame_size, &u->source->sample_spec);
+ /* Hmm, if the timestamp is 0, then it wasn't set and we take the current time */
+ if (now1 <= 0)
+ now1 = pa_rtclock_now();
+
+ now2 = pa_bytes_to_usec(position, &u->source->sample_spec);
pa_smoother_put(u->smoother, now1, now2);
}
static pa_usec_t source_get_latency(struct userdata *u) {
- pa_usec_t r = 0;
- int64_t delay;
+ int64_t delay;
pa_usec_t now1, now2;
pa_assert(u);
- now1 = pa_rtclock_usec();
+ now1 = pa_rtclock_now();
now2 = pa_smoother_get(u->smoother, now1);
- delay = (int64_t) now2 - (int64_t) pa_bytes_to_usec((uint64_t) u->frame_index * u->frame_size, &u->source->sample_spec);
-
- if (delay > 0)
- r = (pa_usec_t) delay;
+ delay = (int64_t) now2 - (int64_t) pa_bytes_to_usec(u->read_count, &u->source->sample_spec);
- return r;
+ return delay >= 0 ? (pa_usec_t) delay : 0;
}
static int build_pollfd(struct userdata *u) {
pa_assert(u);
pa_assert(u->pcm_handle);
- pa_smoother_pause(u->smoother, pa_rtclock_usec());
+ pa_smoother_pause(u->smoother, pa_rtclock_now());
/* Let's suspend */
snd_pcm_close(u->pcm_handle);
pa_assert(u);
/* Use the full buffer if noone asked us for anything specific */
- u->hwbuf_unused_frames = 0;
+ u->hwbuf_unused = 0;
if (u->use_tsched) {
pa_usec_t latency;
if (PA_UNLIKELY(b < u->frame_size))
b = u->frame_size;
- u->hwbuf_unused_frames = (snd_pcm_sframes_t)
- (PA_LIKELY(b < u->hwbuf_size) ?
- ((u->hwbuf_size - b) / u->frame_size) : 0);
+ u->hwbuf_unused = PA_LIKELY(b < u->hwbuf_size) ? (u->hwbuf_size - b) : 0;
}
+ fix_min_sleep_wakeup(u);
fix_tsched_watermark(u);
}
- pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames);
+ pa_log_debug("hwbuf_unused=%lu", (unsigned long) u->hwbuf_unused);
avail_min = 1;
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));
+ pa_log("Failed to set software parameters: %s", pa_alsa_strerror(err));
return err;
}
pa_log_info("Trying resume...");
- snd_config_update_free_global();
-
if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE,
/*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));
+ pa_log("Error opening PCM device %s: %s", u->device_name, pa_alsa_strerror(err));
goto fail;
}
d = u->use_tsched;
if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, u->hwbuf_size / u->frame_size, &b, &d, TRUE)) < 0) {
- pa_log("Failed to set hardware parameters: %s", snd_strerror(err));
+ pa_log("Failed to set hardware parameters: %s", pa_alsa_strerror(err));
goto fail;
}
/* FIXME: We need to reload the volume somehow */
snd_pcm_start(u->pcm_handle);
- pa_smoother_resume(u->smoother, pa_rtclock_usec());
+ pa_smoother_resume(u->smoother, pa_rtclock_now(), TRUE);
pa_log_info("Resumed successfully...");
return pa_source_process_msg(o, code, data, offset, chunk);
}
+/* Called from main context */
+static int source_set_state_cb(pa_source *s, pa_source_state_t new_state) {
+ pa_source_state_t old_state;
+ struct userdata *u;
+
+ pa_source_assert_ref(s);
+ pa_assert_se(u = s->userdata);
+
+ old_state = pa_source_get_state(u->source);
+
+ if (PA_SINK_IS_OPENED(old_state) && new_state == PA_SINK_SUSPENDED)
+ reserve_done(u);
+ else if (old_state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(new_state))
+ if (reserve_init(u, u->device_name) < 0)
+ return -1;
+
+ return 0;
+}
+
static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) {
struct userdata *u = snd_mixer_elem_get_callback_private(elem);
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 void source_get_volume_cb(pa_source *s) {
struct userdata *u = s->userdata;
- int err;
- unsigned i;
pa_cvolume r;
char t[PA_CVOLUME_SNPRINT_MAX];
pa_assert(u);
- pa_assert(u->mixer_elem);
-
- if (u->mixer_seperate_channels) {
-
- r.channels = s->sample_spec.channels;
-
- for (i = 0; i < s->sample_spec.channels; i++) {
- long alsa_vol;
-
- if (u->hw_dB_supported) {
-
- if ((err = snd_mixer_selem_get_capture_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_capture_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_capture_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 {
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
- if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0)
- goto fail;
+ if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
+ return;
- pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol));
- }
- }
+ /* Shift down by the base volume, so that 0dB becomes maximum volume */
+ pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);
pa_log_debug("Read hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
- if (!pa_cvolume_equal(&u->hardware_volume, &r)) {
+ if (pa_cvolume_equal(&u->hardware_volume, &r))
+ return;
- s->virtual_volume = u->hardware_volume = r;
+ s->virtual_volume = u->hardware_volume = r;
- if (u->hw_dB_supported) {
- pa_cvolume reset;
+ if (u->mixer_path->has_dB) {
+ pa_cvolume reset;
- /* Hmm, so the hardware volume changed, let's reset our software volume */
- pa_cvolume_reset(&reset, s->sample_spec.channels);
- pa_source_set_soft_volume(s, &reset);
- }
+ /* Hmm, so the hardware volume changed, let's reset our software volume */
+ pa_cvolume_reset(&reset, s->sample_spec.channels);
+ pa_source_set_soft_volume(s, &reset);
}
-
- return;
-
-fail:
- pa_log_error("Unable to read volume: %s", snd_strerror(err));
}
static void source_set_volume_cb(pa_source *s) {
struct userdata *u = s->userdata;
- int err;
- unsigned i;
pa_cvolume r;
+ char t[PA_CVOLUME_SNPRINT_MAX];
pa_assert(u);
- pa_assert(u->mixer_elem);
-
- 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->virtual_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_capture_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, 1)) < 0)
- goto fail;
-
- if ((err = snd_mixer_selem_get_capture_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 {
- alsa_vol = to_alsa_volume(u, vol);
-
- if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0)
- goto fail;
-
- if ((err = snd_mixer_selem_get_capture_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->virtual_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_capture_dB_all(u->mixer_elem, alsa_vol, 1)) < 0)
- goto fail;
-
- if ((err = snd_mixer_selem_get_capture_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 {
- alsa_vol = to_alsa_volume(u, vol);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
- if ((err = snd_mixer_selem_set_capture_volume_all(u->mixer_elem, alsa_vol)) < 0)
- goto fail;
+ /* Shift up by the base volume */
+ pa_sw_cvolume_divide_scalar(&r, &s->virtual_volume, s->base_volume);
- if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0)
- goto fail;
+ if (pa_alsa_path_set_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
+ return;
- pa_cvolume_set(&r, s->sample_spec.channels, from_alsa_volume(u, alsa_vol));
- }
- }
+ /* Shift down by the base volume, so that 0dB becomes maximum volume */
+ pa_sw_cvolume_multiply_scalar(&r, &r, s->base_volume);
u->hardware_volume = r;
- if (u->hw_dB_supported) {
- char t[PA_CVOLUME_SNPRINT_MAX];
+ if (u->mixer_path->has_dB) {
/* Match exactly what the user requested by software */
-
pa_sw_cvolume_divide(&s->soft_volume, &s->virtual_volume, &u->hardware_volume);
pa_log_debug("Requested volume: %s", pa_cvolume_snprint(t, sizeof(t), &s->virtual_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), &s->soft_volume));
- } else
+ } else {
+ pa_log_debug("Wrote hardware volume: %s", pa_cvolume_snprint(t, sizeof(t), &r));
/* We can't match exactly what the user requested, hence let's
* at least tell the user about it */
s->virtual_volume = r;
-
- return;
-
-fail:
- pa_log_error("Unable to set volume: %s", snd_strerror(err));
+ }
}
static void source_get_mute_cb(pa_source *s) {
struct userdata *u = s->userdata;
- int err, sw;
+ pa_bool_t b;
pa_assert(u);
- pa_assert(u->mixer_elem);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
- if ((err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw)) < 0) {
- pa_log_error("Unable to get switch: %s", snd_strerror(err));
+ if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, &b) < 0)
return;
- }
- s->muted = !sw;
+ s->muted = b;
}
static void source_set_mute_cb(pa_source *s) {
struct userdata *u = s->userdata;
- int err;
pa_assert(u);
- pa_assert(u->mixer_elem);
+ pa_assert(u->mixer_path);
+ pa_assert(u->mixer_handle);
- if ((err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->muted)) < 0) {
- pa_log_error("Unable to set switch: %s", snd_strerror(err));
- return;
+ pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted);
+}
+
+static int source_set_port_cb(pa_source *s, pa_device_port *p) {
+ struct userdata *u = s->userdata;
+ pa_alsa_port_data *data;
+
+ pa_assert(u);
+ pa_assert(p);
+ pa_assert(u->mixer_handle);
+
+ data = PA_DEVICE_PORT_DATA(p);
+
+ pa_assert_se(u->mixer_path = data->path);
+ pa_alsa_path_select(u->mixer_path, u->mixer_handle);
+
+ if (u->mixer_path->has_volume && u->mixer_path->has_dB) {
+ s->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
+ s->n_volume_steps = PA_VOLUME_NORM+1;
+
+ if (u->mixer_path->max_dB > 0.0)
+ pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(s->base_volume));
+ else
+ pa_log_info("No particular base volume set, fixing to 0 dB");
+ } else {
+ s->base_volume = PA_VOLUME_NORM;
+ s->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
}
+
+ if (data->setting)
+ pa_alsa_setting_select(data->setting, u->mixer_handle);
+
+ if (s->set_mute)
+ s->set_mute(s);
+ if (s->set_volume)
+ s->set_volume(s);
+
+ return 0;
}
static void source_update_requested_latency_cb(pa_source *s) {
pa_make_realtime(u->core->realtime_priority);
pa_thread_mq_install(&u->thread_mq);
- pa_rtpoll_install(u->rtpoll);
for (;;) {
int ret;
-/* pa_log_debug("loop"); */
+#ifdef DEBUG_TIMING
+ pa_log_debug("Loop");
+#endif
/* Read some data and pass it to the sources */
if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
- int work_done = 0;
+ int work_done;
pa_usec_t sleep_usec = 0;
if (u->use_mmap)
/* Convert from the sound card time domain to the
* system time domain */
- cusec = pa_smoother_translate(u->smoother, pa_rtclock_usec(), sleep_usec);
+ cusec = pa_smoother_translate(u->smoother, pa_rtclock_now(), sleep_usec);
/* pa_log_debug("Waking up in %0.2fms (system clock).", (double) cusec / PA_USEC_PER_MSEC); */
pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n);
if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) {
- pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err));
+ pa_log("snd_pcm_poll_descriptors_revents() failed: %s", pa_alsa_strerror(err));
goto fail;
}
- if (revents & (POLLOUT|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) {
+ if (revents & ~POLLIN) {
if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0)
goto fail;
snd_pcm_start(u->pcm_handle);
- }
+ } else if (revents && u->use_tsched && pa_log_ratelimit())
+ pa_log_debug("Wakeup from ALSA!");
- if (revents && u->use_tsched && pa_log_ratelimit())
- pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : "");
} else
revents = 0;
}
pa_log_debug("Thread shutting down");
}
-static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name) {
+static void set_source_name(pa_source_new_data *data, pa_modargs *ma, const char *device_id, const char *device_name, pa_alsa_mapping *mapping) {
const char *n;
char *t;
data->namereg_fail = FALSE;
}
- t = pa_sprintf_malloc("alsa_input.%s", n);
+ if (mapping)
+ t = pa_sprintf_malloc("alsa_input.%s.%s", n, mapping->name);
+ else
+ t = pa_sprintf_malloc("alsa_input.%s", n);
+
pa_source_new_data_set_name(data, t);
pa_xfree(t);
}
+static void find_mixer(struct userdata *u, pa_alsa_mapping *mapping, const char *element, pa_bool_t ignore_dB) {
+
+ if (!mapping && !element)
+ return;
+
+ if (!(u->mixer_handle = pa_alsa_open_mixer_for_pcm(u->pcm_handle, &u->control_device))) {
+ pa_log_info("Failed to find a working mixer device.");
+ return;
+ }
+
+ if (element) {
+
+ if (!(u->mixer_path = pa_alsa_path_synthesize(element, PA_ALSA_DIRECTION_INPUT)))
+ goto fail;
+
+ if (pa_alsa_path_probe(u->mixer_path, u->mixer_handle, ignore_dB) < 0)
+ goto fail;
+
+ pa_log_debug("Probed mixer path %s:", u->mixer_path->name);
+ pa_alsa_path_dump(u->mixer_path);
+ } else {
+
+ if (!(u->mixer_path_set = pa_alsa_path_set_new(mapping, PA_ALSA_DIRECTION_INPUT)))
+ goto fail;
+
+ pa_alsa_path_set_probe(u->mixer_path_set, u->mixer_handle, ignore_dB);
+
+ pa_log_debug("Probed mixer paths:");
+ pa_alsa_path_set_dump(u->mixer_path_set);
+ }
+
+ return;
+
+fail:
+
+ if (u->mixer_path_set) {
+ pa_alsa_path_set_free(u->mixer_path_set);
+ u->mixer_path_set = NULL;
+ } else if (u->mixer_path) {
+ pa_alsa_path_free(u->mixer_path);
+ u->mixer_path = NULL;
+ }
+
+ if (u->mixer_handle) {
+ snd_mixer_close(u->mixer_handle);
+ u->mixer_handle = NULL;
+ }
+}
+
static int setup_mixer(struct userdata *u, pa_bool_t ignore_dB) {
pa_assert(u);
if (!u->mixer_handle)
return 0;
- pa_assert(u->mixer_elem);
+ if (u->source->active_port) {
+ pa_alsa_port_data *data;
- if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) {
- pa_bool_t suitable = FALSE;
+ /* We have a list of supported paths, so let's activate the
+ * one that has been chosen as active */
- if (snd_mixer_selem_get_capture_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;
- }
+ data = PA_DEVICE_PORT_DATA(u->source->active_port);
+ u->mixer_path = data->path;
- if (suitable) {
- if (ignore_dB || snd_mixer_selem_get_capture_dB_range(u->mixer_elem, &u->hw_dB_min, &u->hw_dB_max) < 0)
- pa_log_info("Mixer doesn't support dB information or data is ignored.");
- 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
+ pa_alsa_path_select(data->path, u->mixer_handle);
- 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) {
- u->source->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->source->base_volume));
- } else
- pa_log_info("No particular base volume set, fixing to 0 dB");
- }
- }
+ if (data->setting)
+ pa_alsa_setting_select(data->setting, u->mixer_handle);
- if (!u->hw_dB_supported &&
- u->hw_volume_max - u->hw_volume_min < 3) {
+ } else {
- pa_log_info("Device has less than 4 volume levels. Falling back to software volume control.");
- suitable = FALSE;
- }
- }
+ if (!u->mixer_path && u->mixer_path_set)
+ u->mixer_path = u->mixer_path_set->paths;
- if (suitable) {
- u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &u->source->channel_map, u->mixer_map, FALSE) >= 0;
+ if (u->mixer_path) {
+ /* Hmm, we have only a single path, then let's activate it */
- u->source->get_volume = source_get_volume_cb;
- u->source->set_volume = source_set_volume_cb;
- u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->hw_dB_supported ? PA_SOURCE_DECIBEL_VOLUME : 0);
- pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported");
+ pa_alsa_path_select(u->mixer_path, u->mixer_handle);
- if (!u->hw_dB_supported)
- u->source->n_volume_steps = u->hw_volume_max - u->hw_volume_min + 1;
+ if (u->mixer_path->settings)
+ pa_alsa_setting_select(u->mixer_path->settings, u->mixer_handle);
} else
- pa_log_info("Using software volume control.");
+ return 0;
}
- if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) {
+ if (!u->mixer_path->has_volume)
+ pa_log_info("Driver does not support hardware volume control, falling back to software volume control.");
+ else {
+
+ if (u->mixer_path->has_dB) {
+ pa_log_info("Hardware volume ranges from %0.2f dB to %0.2f dB.", u->mixer_path->min_dB, u->mixer_path->max_dB);
+
+ u->source->base_volume = pa_sw_volume_from_dB(-u->mixer_path->max_dB);
+ u->source->n_volume_steps = PA_VOLUME_NORM+1;
+
+ if (u->mixer_path->max_dB > 0.0)
+ pa_log_info("Fixing base volume to %0.2f dB", pa_sw_volume_to_dB(u->source->base_volume));
+ else
+ pa_log_info("No particular base volume set, fixing to 0 dB");
+
+ } else {
+ pa_log_info("Hardware volume ranges from %li to %li.", u->mixer_path->min_volume, u->mixer_path->max_volume);
+ u->source->base_volume = PA_VOLUME_NORM;
+ u->source->n_volume_steps = u->mixer_path->max_volume - u->mixer_path->min_volume + 1;
+ }
+
+ u->source->get_volume = source_get_volume_cb;
+ u->source->set_volume = source_set_volume_cb;
+
+ u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL | (u->mixer_path->has_dB ? PA_SOURCE_DECIBEL_VOLUME : 0);
+ pa_log_info("Using hardware volume control. Hardware dB scale %s.", u->mixer_path->has_dB ? "supported" : "not supported");
+ }
+
+ if (!u->mixer_path->has_mute) {
+ pa_log_info("Driver does not support hardware mute control, falling back to software mute control.");
+ } else {
u->source->get_mute = source_get_mute_cb;
u->source->set_mute = source_set_mute_cb;
u->source->flags |= PA_SOURCE_HW_MUTE_CTRL;
- } else
- pa_log_info("Using software mute control.");
+ pa_log_info("Using hardware mute control.");
+ }
u->mixer_fdl = pa_alsa_fdlist_new();
return -1;
}
- snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback);
- snd_mixer_elem_set_callback_private(u->mixer_elem, u);
+ if (u->mixer_path_set)
+ pa_alsa_path_set_set_callback(u->mixer_path_set, u->mixer_handle, mixer_callback, u);
+ else
+ pa_alsa_path_set_callback(u->mixer_path, u->mixer_handle, mixer_callback, u);
return 0;
}
-pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, const pa_alsa_profile_info *profile) {
+pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, pa_card *card, pa_alsa_mapping *mapping) {
struct userdata *u = NULL;
const char *dev_id = NULL;
- pa_sample_spec ss;
+ pa_sample_spec ss, requested_ss;
pa_channel_map map;
uint32_t nfrags, hwbuf_size, frag_size, tsched_size, tsched_watermark;
snd_pcm_uframes_t period_frames, tsched_frames;
size_t frame_size;
pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, ignore_dB = FALSE;
pa_source_new_data data;
+ pa_alsa_profile_set *profile_set = NULL;
pa_assert(m);
pa_assert(ma);
ss = m->core->default_sample_spec;
+ map = m->core->default_channel_map;
if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) {
pa_log("Failed to parse sample specification");
goto fail;
}
+ requested_ss = ss;
frame_size = pa_frame_size(&ss);
nfrags = m->core->default_n_fragments;
u->use_tsched = use_tsched;
u->rtpoll = pa_rtpoll_new();
pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll);
- u->alsa_rtpoll_item = NULL;
- u->smoother = pa_smoother_new(DEFAULT_TSCHED_WATERMARK_USEC, DEFAULT_TSCHED_WATERMARK_USEC, TRUE, 5);
- pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec());
+ u->smoother = pa_smoother_new(
+ DEFAULT_TSCHED_WATERMARK_USEC*2,
+ DEFAULT_TSCHED_WATERMARK_USEC*2,
+ TRUE,
+ TRUE,
+ 5,
+ pa_rtclock_now(),
+ FALSE);
+
+ dev_id = pa_modargs_get_value(
+ ma, "device_id",
+ pa_modargs_get_value(ma, "device", DEFAULT_DEVICE));
+
+ if (reserve_init(u, dev_id) < 0)
+ goto fail;
+
+ if (reserve_monitor_init(u, dev_id) < 0)
+ goto fail;
b = use_mmap;
d = use_tsched;
- if (profile) {
+ if (mapping) {
if (!(dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {
pa_log("device_id= not set");
goto fail;
}
- if (!(u->pcm_handle = pa_alsa_open_by_device_id_profile(
+ if (!(u->pcm_handle = pa_alsa_open_by_device_id_mapping(
dev_id,
&u->device_name,
&ss, &map,
SND_PCM_STREAM_CAPTURE,
&nfrags, &period_frames, tsched_frames,
- &b, &d, profile)))
+ &b, &d, mapping)))
goto fail;
} else if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) {
+ if (!(profile_set = pa_alsa_profile_set_new(NULL, &map)))
+ goto fail;
+
if (!(u->pcm_handle = pa_alsa_open_by_device_id_auto(
dev_id,
&u->device_name,
&ss, &map,
SND_PCM_STREAM_CAPTURE,
&nfrags, &period_frames, tsched_frames,
- &b, &d, &profile)))
+ &b, &d, profile_set, &mapping)))
goto fail;
} else {
pa_assert(u->device_name);
pa_log_info("Successfully opened device %s.", u->device_name);
- if (profile)
- pa_log_info("Selected configuration '%s' (%s).", profile->description, profile->name);
+ if (pa_alsa_pcm_is_modem(u->pcm_handle)) {
+ pa_log_notice("Device %s is modem, refusing further initialization.", u->device_name);
+ goto fail;
+ }
+
+ if (mapping)
+ pa_log_info("Selected mapping '%s' (%s).", mapping->description, mapping->name);
if (use_mmap && !b) {
pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode.");
u->use_tsched = use_tsched = FALSE;
}
+ if (use_tsched && !pa_alsa_pcm_is_hw(u->pcm_handle)) {
+ pa_log_info("Device is not a hardware device, disabling timer-based scheduling.");
+ u->use_tsched = use_tsched = FALSE;
+ }
+
if (u->use_mmap)
pa_log_info("Successfully enabled mmap() mode.");
/* ALSA might tweak the sample spec, so recalculate the frame size */
frame_size = pa_frame_size(&ss);
- pa_alsa_find_mixer_and_elem(u->pcm_handle, &u->mixer_handle, &u->mixer_elem);
+ find_mixer(u, mapping, pa_modargs_get_value(ma, "control", NULL), ignore_dB);
pa_source_new_data_init(&data);
data.driver = driver;
data.module = m;
data.card = card;
- set_source_name(&data, ma, dev_id, u->device_name);
+ set_source_name(&data, ma, dev_id, u->device_name, mapping);
pa_source_new_data_set_sample_spec(&data, &ss);
pa_source_new_data_set_channel_map(&data, &map);
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"));
- if (profile) {
- pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, profile->name);
- pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, profile->description);
+ if (mapping) {
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_NAME, mapping->name);
+ pa_proplist_sets(data.proplist, PA_PROP_DEVICE_PROFILE_DESCRIPTION, mapping->description);
+ }
+
+ pa_alsa_init_description(data.proplist);
+
+ if (u->control_device)
+ pa_alsa_init_proplist_ctl(data.proplist, u->control_device);
+
+ if (pa_modargs_get_proplist(ma, "source_properties", data.proplist, PA_UPDATE_REPLACE) < 0) {
+ pa_log("Invalid properties");
+ pa_source_new_data_done(&data);
+ goto fail;
}
- u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY);
+ if (u->mixer_path_set)
+ pa_alsa_add_ports(&data.ports, u->mixer_path_set);
+
+ u->source = pa_source_new(m->core, &data, PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|(u->use_tsched ? PA_SOURCE_DYNAMIC_LATENCY : 0));
pa_source_new_data_done(&data);
if (!u->source) {
u->source->parent.process_msg = source_process_msg;
u->source->update_requested_latency = source_update_requested_latency_cb;
+ u->source->set_state = source_set_state_cb;
+ u->source->set_port = source_set_port_cb;
u->source->userdata = u;
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
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;
- u->tsched_watermark = tsched_watermark;
- u->frame_index = 0;
- 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;
+ u->tsched_watermark = pa_usec_to_bytes_round_up(pa_bytes_to_usec_round_up(tsched_watermark, &requested_ss), &u->source->sample_spec);
pa_cvolume_mute(&u->hardware_volume, u->source->sample_spec.channels);
- if (use_tsched)
- fix_tsched_watermark(u);
-
- pa_source_set_latency_range(u->source,
- !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,
(double) pa_bytes_to_usec(u->hwbuf_size, &ss) / PA_USEC_PER_MSEC);
- if (use_tsched)
+ if (u->use_tsched) {
+ u->watermark_step = pa_usec_to_bytes(TSCHED_WATERMARK_STEP_USEC, &u->source->sample_spec);
+
+ fix_min_sleep_wakeup(u);
+ fix_tsched_watermark(u);
+
+ pa_source_set_latency_range(u->source,
+ 0,
+ pa_bytes_to_usec(u->hwbuf_size, &ss));
+
pa_log_info("Time scheduling watermark is %0.2fms",
(double) pa_bytes_to_usec(u->tsched_watermark, &ss) / PA_USEC_PER_MSEC);
+ } else
+ pa_source_set_fixed_latency(u->source, pa_bytes_to_usec(u->hwbuf_size, &ss));
+
+ reserve_update(u);
if (update_sw_params(u) < 0)
goto fail;
if (setup_mixer(u, ignore_dB) < 0)
goto fail;
- pa_alsa_dump(u->pcm_handle);
+ pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle);
if (!(u->thread = pa_thread_new(thread_func, u))) {
pa_log("Failed to create thread.");
pa_source_put(u->source);
+ if (profile_set)
+ pa_alsa_profile_set_free(profile_set);
+
return u->source;
fail:
- userdata_free(u);
+ if (u)
+ userdata_free(u);
+
+ if (profile_set)
+ pa_alsa_profile_set_free(profile_set);
return NULL;
}
if (u->rtpoll)
pa_rtpoll_free(u->rtpoll);
+ if (u->pcm_handle) {
+ snd_pcm_drop(u->pcm_handle);
+ snd_pcm_close(u->pcm_handle);
+ }
+
if (u->mixer_fdl)
pa_alsa_fdlist_free(u->mixer_fdl);
+ if (u->mixer_path_set)
+ pa_alsa_path_set_free(u->mixer_path_set);
+ else if (u->mixer_path)
+ pa_alsa_path_free(u->mixer_path);
+
if (u->mixer_handle)
snd_mixer_close(u->mixer_handle);
- if (u->pcm_handle) {
- snd_pcm_drop(u->pcm_handle);
- snd_pcm_close(u->pcm_handle);
- }
-
if (u->smoother)
pa_smoother_free(u->smoother);
+ reserve_done(u);
+ monitor_done(u);
+
pa_xfree(u->device_name);
+ pa_xfree(u->control_device);
pa_xfree(u);
}