/*-*- linux-c -*-*/ /* * ALSA <-> PulseAudio PCM I/O plugin * * Copyright (c) 2006 by Pierre Ossman * * This library 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.1 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "pulse.h" typedef struct snd_pcm_pulse { snd_pcm_ioplug_t io; snd_pulse_t *p; char *device; /* Since ALSA expects a ring buffer we must do some voodoo. */ size_t last_size; size_t ptr; int underrun; int handle_underrun; size_t offset; int64_t written; pa_stream *stream; pa_sample_spec ss; size_t frame_size; pa_buffer_attr buffer_attr; } snd_pcm_pulse_t; static int check_stream(snd_pcm_pulse_t *pcm) { int err; pa_stream_state_t state; assert(pcm); if (!pcm->p) return -EBADFD; err = pulse_check_connection(pcm->p); if (err < 0) return err; if (!pcm->stream) return -EBADFD; state = pa_stream_get_state(pcm->stream); if (!PA_STREAM_IS_GOOD(state)) return -EIO; err = 0; return err; } static int update_ptr(snd_pcm_pulse_t *pcm) { size_t size; if (pcm->io.stream == SND_PCM_STREAM_PLAYBACK) size = pa_stream_writable_size(pcm->stream); else size = pa_stream_readable_size(pcm->stream); if (size == (size_t) -1) return -EIO; if (pcm->io.stream == SND_PCM_STREAM_CAPTURE) size -= pcm->offset; /* Prevent accidental overrun of the fake ringbuffer */ if (size > pcm->buffer_attr.tlength - pcm->frame_size) size = pcm->buffer_attr.tlength - pcm->frame_size; if (size > pcm->last_size) { pcm->ptr += size - pcm->last_size; pcm->ptr %= pcm->buffer_attr.tlength; } pcm->last_size = size; return 0; } static int check_active(snd_pcm_pulse_t *pcm) { assert(pcm); /* * ALSA thinks in periods, not bytes, samples or frames. */ if (pcm->io.stream == SND_PCM_STREAM_PLAYBACK) { size_t wsize; wsize = pa_stream_writable_size(pcm->stream); if (wsize == (size_t) -1) return -EIO; return wsize >= pcm->buffer_attr.minreq; } else { size_t rsize; rsize = pa_stream_readable_size(pcm->stream); if (rsize == (size_t) -1) return -EIO; return rsize >= pcm->buffer_attr.fragsize; } } static int update_active(snd_pcm_pulse_t *pcm) { int ret; assert(pcm); if (!pcm->p) return -EBADFD; ret = check_stream(pcm); if (ret < 0) goto finish; ret = check_active(pcm); finish: if (ret != 0) /* On error signal the caller, too */ pulse_poll_activate(pcm->p); else pulse_poll_deactivate(pcm->p); return ret; } static int wait_stream_state(snd_pcm_pulse_t *pcm, pa_stream_state_t target) { pa_stream_state_t state; assert(pcm); if (!pcm->p) return -EBADFD; for (;;) { int err; err = pulse_check_connection(pcm->p); if (err < 0) return err; if (!pcm->stream) return -EBADFD; state = pa_stream_get_state(pcm->stream); if (state == target) break; if (!PA_STREAM_IS_GOOD(state)) return -EIO; pa_threaded_mainloop_wait(pcm->p->mainloop); } return 0; } static void stream_success_cb(pa_stream * p, int success, void *userdata) { snd_pcm_pulse_t *pcm = userdata; assert(pcm); if (!pcm->p) return; pa_threaded_mainloop_signal(pcm->p->mainloop, 0); } static int pulse_start(snd_pcm_ioplug_t * io) { snd_pcm_pulse_t *pcm = io->private_data; pa_operation *o, *u; int err = 0, err_o = 0, err_u = 0; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); err = check_stream(pcm); if (err < 0) goto finish; o = pa_stream_cork(pcm->stream, 0, stream_success_cb, pcm); if (!o) { err = -EIO; goto finish; } u = pa_stream_trigger(pcm->stream, stream_success_cb, pcm); pcm->underrun = 0; err_o = pulse_wait_operation(pcm->p, o); if (u) err_u = pulse_wait_operation(pcm->p, u); pa_operation_unref(o); if (u) pa_operation_unref(u); if (err_o < 0 || err_u < 0) { err = -EIO; goto finish; } finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return err; } static int pulse_stop(snd_pcm_ioplug_t * io) { snd_pcm_pulse_t *pcm = io->private_data; pa_operation *o, *u; int err = 0, err_o = 0, err_u = 0; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); err = check_stream(pcm); if (err < 0) goto finish; o = pa_stream_cork(pcm->stream, 1, stream_success_cb, pcm); if (!o) { err = -EIO; goto finish; } u = pa_stream_flush(pcm->stream, stream_success_cb, pcm); if (!u) { pa_operation_unref(o); err = -EIO; goto finish; } err_o = pulse_wait_operation(pcm->p, o); err_u = pulse_wait_operation(pcm->p, u); pa_operation_unref(o); pa_operation_unref(u); if (err_o < 0 || err_u < 0) { err = -EIO; goto finish; } finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return err; } static int pulse_drain(snd_pcm_ioplug_t * io) { snd_pcm_pulse_t *pcm = io->private_data; pa_operation *o; int err = 0; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); err = check_stream(pcm); if (err < 0) goto finish; o = pa_stream_drain(pcm->stream, stream_success_cb, pcm); if (!o) { err = -EIO; goto finish; } err = pulse_wait_operation(pcm->p, o); pa_operation_unref(o); if (err < 0) { err = -EIO; goto finish; } finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return err; } static snd_pcm_sframes_t pulse_pointer(snd_pcm_ioplug_t * io) { snd_pcm_pulse_t *pcm = io->private_data; snd_pcm_sframes_t ret = 0; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; if (io->state == SND_PCM_STATE_XRUN) return -EPIPE; if (io->state != SND_PCM_STATE_RUNNING) return 0; pa_threaded_mainloop_lock(pcm->p->mainloop); ret = check_stream(pcm); if (ret < 0) goto finish; if (pcm->underrun) { ret = -EPIPE; goto finish; } ret = update_ptr(pcm); if (ret < 0) { ret = -EPIPE; goto finish; } if (pcm->underrun) ret = -EPIPE; else ret = snd_pcm_bytes_to_frames(io->pcm, pcm->ptr); finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return ret; } static int pulse_delay(snd_pcm_ioplug_t * io, snd_pcm_sframes_t * delayp) { snd_pcm_pulse_t *pcm = io->private_data; int err = 0; pa_usec_t lat = 0; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); for (;;) { err = check_stream(pcm); if (err < 0) goto finish; err = pa_stream_get_latency(pcm->stream, &lat, NULL); if (err) { if (err != PA_ERR_NODATA) { err = -EIO; goto finish; } } else break; pa_threaded_mainloop_wait(pcm->p->mainloop); } *delayp = snd_pcm_bytes_to_frames(io->pcm, pa_usec_to_bytes(lat, &pcm->ss)); err = 0; finish: if (pcm->underrun && pcm->io.state == SND_PCM_STATE_RUNNING) snd_pcm_ioplug_set_state(io, SND_PCM_STATE_XRUN); pa_threaded_mainloop_unlock(pcm->p->mainloop); return err; } static snd_pcm_sframes_t pulse_write(snd_pcm_ioplug_t * io, const snd_pcm_channel_area_t * areas, snd_pcm_uframes_t offset, snd_pcm_uframes_t size) { snd_pcm_pulse_t *pcm = io->private_data; const char *buf; snd_pcm_sframes_t ret = 0; size_t writebytes; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); ret = check_stream(pcm); if (ret < 0) goto finish; /* Make sure the buffer pointer is in sync */ ret = update_ptr(pcm); if (ret < 0) goto finish; buf = (char *) areas->addr + (areas->first + areas->step * offset) / 8; writebytes = size * pcm->frame_size; ret = pa_stream_write(pcm->stream, buf, writebytes, NULL, 0, 0); if (ret < 0) { ret = -EIO; goto finish; } /* Make sure the buffer pointer is in sync */ pcm->last_size -= writebytes; pcm->written += writebytes; ret = update_ptr(pcm); if (ret < 0) goto finish; ret = update_active(pcm); if (ret < 0) goto finish; ret = size; pcm->underrun = 0; finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return ret; } static snd_pcm_sframes_t pulse_read(snd_pcm_ioplug_t * io, const snd_pcm_channel_area_t * areas, snd_pcm_uframes_t offset, snd_pcm_uframes_t size) { snd_pcm_pulse_t *pcm = io->private_data; void *dst_buf; size_t remain_size, frag_length; snd_pcm_sframes_t ret = 0; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); ret = check_stream(pcm); if (ret < 0) goto finish; /* Make sure the buffer pointer is in sync */ ret = update_ptr(pcm); if (ret < 0) goto finish; remain_size = size * pcm->frame_size; dst_buf = (char *) areas->addr + (areas->first + areas->step * offset) / 8; while (remain_size > 0) { const void *src_buf; if (pa_stream_peek(pcm->stream, &src_buf, &frag_length) < 0) { ret = -EIO; goto finish; } if (frag_length == 0) break; src_buf = (char *) src_buf + pcm->offset; frag_length -= pcm->offset; if (frag_length > remain_size) { pcm->offset += remain_size; frag_length = remain_size; } else pcm->offset = 0; memcpy(dst_buf, src_buf, frag_length); if (pcm->offset == 0) pa_stream_drop(pcm->stream); dst_buf = (char *) dst_buf + frag_length; remain_size -= frag_length; pcm->last_size -= frag_length; } /* Make sure the buffer pointer is in sync */ ret = update_ptr(pcm); if (ret < 0) goto finish; ret = update_active(pcm); if (ret < 0) goto finish; ret = size - (remain_size / pcm->frame_size); finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return ret; } static void stream_state_cb(pa_stream * p, void *userdata) { snd_pcm_pulse_t *pcm = userdata; pa_stream_state_t state; assert(pcm); if (!pcm->p) return; state = pa_stream_get_state(p); if (!PA_STREAM_IS_GOOD(state)) pulse_poll_activate(pcm->p); pa_threaded_mainloop_signal(pcm->p->mainloop, 0); } static void stream_request_cb(pa_stream * p, size_t length, void *userdata) { snd_pcm_pulse_t *pcm = userdata; assert(pcm); if (!pcm->p) return; update_active(pcm); } #ifndef PA_CHECK_VERSION #define PA_CHECK_VERSION(x, y, z) 0 #endif #if PA_CHECK_VERSION(0,99,0) #define DEFAULT_HANDLE_UNDERRUN 1 #define do_underrun_detect(pcm, p) \ ((pcm)->written <= pa_stream_get_underflow_index(p)) #else #define DEFAULT_HANDLE_UNDERRUN 0 #define do_underrun_detect(pcm, p) 1 /* always true */ #endif static void stream_underrun_cb(pa_stream * p, void *userdata) { snd_pcm_pulse_t *pcm = userdata; assert(pcm); if (!pcm->p) return; if (do_underrun_detect(pcm, p)) pcm->underrun = 1; } static void stream_latency_cb(pa_stream *p, void *userdata) { snd_pcm_pulse_t *pcm = userdata; assert(pcm); if (!pcm->p) return; pa_threaded_mainloop_signal(pcm->p->mainloop, 0); } static int pulse_pcm_poll_revents(snd_pcm_ioplug_t * io, struct pollfd *pfd, unsigned int nfds, unsigned short *revents) { int err = 0; snd_pcm_pulse_t *pcm = io->private_data; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); err = check_stream(pcm); if (err < 0) goto finish; err = check_active(pcm); if (err < 0) goto finish; if (err > 0) *revents = io->stream == SND_PCM_STREAM_PLAYBACK ? POLLOUT : POLLIN; else *revents = 0; err = 0; finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return err; } static int pulse_prepare(snd_pcm_ioplug_t * io) { pa_channel_map map; snd_pcm_pulse_t *pcm = io->private_data; int err = 0, r; unsigned c, d; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); if (pcm->stream) { pa_stream_disconnect(pcm->stream); wait_stream_state(pcm, PA_STREAM_TERMINATED); pa_stream_unref(pcm->stream); pcm->stream = NULL; } err = pulse_check_connection(pcm->p); if (err < 0) goto finish; assert(pcm->stream == NULL); for (c = pcm->ss.channels; c > 0; c--) if (pa_channel_map_init_auto(&map, c, PA_CHANNEL_MAP_ALSA)) break; /* Extend if nessary */ for (d = c; d < pcm->ss.channels; d++) map.map[d] = PA_CHANNEL_POSITION_AUX0+(d-c); map.channels = pcm->ss.channels; if (io->stream == SND_PCM_STREAM_PLAYBACK) pcm->stream = pa_stream_new(pcm->p->context, "ALSA Playback", &pcm->ss, &map); else pcm->stream = pa_stream_new(pcm->p->context, "ALSA Capture", &pcm->ss, &map); if (!pcm->stream) { err = -ENOMEM; goto finish; } pa_stream_set_state_callback(pcm->stream, stream_state_cb, pcm); pa_stream_set_latency_update_callback(pcm->stream, stream_latency_cb, pcm); if (io->stream == SND_PCM_STREAM_PLAYBACK) { pa_stream_set_write_callback(pcm->stream, stream_request_cb, pcm); if (pcm->handle_underrun) pa_stream_set_underflow_callback(pcm->stream, stream_underrun_cb, pcm); r = pa_stream_connect_playback(pcm->stream, pcm->device, &pcm->buffer_attr, PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING #ifdef PA_STREAM_EARLY_REQUESTS | PA_STREAM_EARLY_REQUESTS #endif , NULL, NULL); } else { pa_stream_set_read_callback(pcm->stream, stream_request_cb, pcm); r = pa_stream_connect_record(pcm->stream, pcm->device, &pcm->buffer_attr, PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING #ifdef PA_STREAM_EARLY_REQUESTS | PA_STREAM_EARLY_REQUESTS #endif ); } if (r < 0) { SNDERR("PulseAudio: Unable to create stream: %s\n", pa_strerror(pa_context_errno(pcm->p->context))); pa_stream_unref(pcm->stream); pcm->stream = NULL; r = -EIO; goto finish; } err = wait_stream_state(pcm, PA_STREAM_READY); if (err < 0) { SNDERR("PulseAudio: Unable to create stream: %s\n", pa_strerror(pa_context_errno(pcm->p->context))); pa_stream_unref(pcm->stream); pcm->stream = NULL; goto finish; } pcm->offset = 0; pcm->underrun = 0; pcm->written = 0; /* Reset fake ringbuffer */ pcm->last_size = 0; pcm->ptr = 0; update_ptr(pcm); finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return err; } static int pulse_hw_params(snd_pcm_ioplug_t * io, snd_pcm_hw_params_t * params) { snd_pcm_pulse_t *pcm = io->private_data; int err = 0; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); pcm->frame_size = (snd_pcm_format_physical_width(io->format) * io->channels) / 8; switch (io->format) { case SND_PCM_FORMAT_U8: pcm->ss.format = PA_SAMPLE_U8; break; case SND_PCM_FORMAT_A_LAW: pcm->ss.format = PA_SAMPLE_ALAW; break; case SND_PCM_FORMAT_MU_LAW: pcm->ss.format = PA_SAMPLE_ULAW; break; case SND_PCM_FORMAT_S16_LE: pcm->ss.format = PA_SAMPLE_S16LE; break; case SND_PCM_FORMAT_S16_BE: pcm->ss.format = PA_SAMPLE_S16BE; break; #ifdef PA_SAMPLE_FLOAT32LE case SND_PCM_FORMAT_FLOAT_LE: pcm->ss.format = PA_SAMPLE_FLOAT32LE; break; #endif #ifdef PA_SAMPLE_FLOAT32BE case SND_PCM_FORMAT_FLOAT_BE: pcm->ss.format = PA_SAMPLE_FLOAT32BE; break; #endif #ifdef PA_SAMPLE_S32LE case SND_PCM_FORMAT_S32_LE: pcm->ss.format = PA_SAMPLE_S32LE; break; #endif #ifdef PA_SAMPLE_S32BE case SND_PCM_FORMAT_S32_BE: pcm->ss.format = PA_SAMPLE_S32BE; break; #endif #ifdef PA_SAMPLE_S24LE case SND_PCM_FORMAT_S24_3LE: pcm->ss.format = PA_SAMPLE_S24LE; break; #endif #ifdef PA_SAMPLE_S24BE case SND_PCM_FORMAT_S24_3BE: pcm->ss.format = PA_SAMPLE_S24BE; break; #endif #ifdef PA_SAMPLE_S24_32LE case SND_PCM_FORMAT_S24_LE: pcm->ss.format = PA_SAMPLE_S24_32LE; break; #endif #ifdef PA_SAMPLE_S24_32BE case SND_PCM_FORMAT_S24_BE: pcm->ss.format = PA_SAMPLE_S24_32BE; break; #endif default: SNDERR("PulseAudio: Unsupported format %s\n", snd_pcm_format_name(io->format)); err = -EINVAL; goto finish; } pcm->ss.rate = io->rate; pcm->ss.channels = io->channels; pcm->buffer_attr.maxlength = 4 * 1024 * 1024; pcm->buffer_attr.tlength = io->buffer_size * pcm->frame_size; if (pcm->buffer_attr.prebuf == (uint32_t)-1) pcm->buffer_attr.prebuf = (io->buffer_size - io->period_size) * pcm->frame_size; pcm->buffer_attr.minreq = io->period_size * pcm->frame_size; pcm->buffer_attr.fragsize = io->period_size * pcm->frame_size; finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return err; } static int pulse_sw_params(snd_pcm_ioplug_t *io, snd_pcm_sw_params_t *params) { snd_pcm_pulse_t *pcm = io->private_data; snd_pcm_uframes_t start_threshold; assert(pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); snd_pcm_sw_params_get_start_threshold(params, &start_threshold); /* At least one period to keep PulseAudio happy */ if (start_threshold < io->period_size) start_threshold = io->period_size; pcm->buffer_attr.prebuf = start_threshold * pcm->frame_size; pa_threaded_mainloop_unlock(pcm->p->mainloop); return 0; } static int pulse_close(snd_pcm_ioplug_t * io) { snd_pcm_pulse_t *pcm = io->private_data; assert(pcm); if (pcm->p && pcm->p->mainloop) { pa_threaded_mainloop_lock(pcm->p->mainloop); if (pcm->stream) { pa_stream_disconnect(pcm->stream); pa_stream_unref(pcm->stream); } pa_threaded_mainloop_unlock(pcm->p->mainloop); } if (pcm->p) pulse_free(pcm->p); free(pcm->device); free(pcm); return 0; } static int pulse_pause(snd_pcm_ioplug_t * io, int enable) { snd_pcm_pulse_t *pcm = io->private_data; int err = 0; pa_operation *o; assert (pcm); if (!pcm->p || !pcm->p->mainloop) return -EBADFD; pa_threaded_mainloop_lock(pcm->p->mainloop); err = check_stream(pcm); if (err < 0) goto finish; o = pa_stream_cork(pcm->stream, enable, NULL, NULL); if (o) pa_operation_unref(o); else err = -EIO; finish: pa_threaded_mainloop_unlock(pcm->p->mainloop); return err; } static const snd_pcm_ioplug_callback_t pulse_playback_callback = { .start = pulse_start, .stop = pulse_stop, .drain = pulse_drain, .pointer = pulse_pointer, .transfer = pulse_write, .delay = pulse_delay, .poll_revents = pulse_pcm_poll_revents, .prepare = pulse_prepare, .hw_params = pulse_hw_params, .sw_params = pulse_sw_params, .close = pulse_close, .pause = pulse_pause }; static const snd_pcm_ioplug_callback_t pulse_capture_callback = { .start = pulse_start, .stop = pulse_stop, .pointer = pulse_pointer, .transfer = pulse_read, .delay = pulse_delay, .poll_revents = pulse_pcm_poll_revents, .prepare = pulse_prepare, .hw_params = pulse_hw_params, .close = pulse_close, }; static int pulse_hw_constraint(snd_pcm_pulse_t * pcm) { snd_pcm_ioplug_t *io = &pcm->io; static const snd_pcm_access_t access_list[] = { SND_PCM_ACCESS_RW_INTERLEAVED }; static const unsigned int formats[] = { SND_PCM_FORMAT_U8, SND_PCM_FORMAT_A_LAW, SND_PCM_FORMAT_MU_LAW, SND_PCM_FORMAT_S16_LE, SND_PCM_FORMAT_S16_BE, SND_PCM_FORMAT_FLOAT_LE, SND_PCM_FORMAT_FLOAT_BE, SND_PCM_FORMAT_S32_LE, SND_PCM_FORMAT_S32_BE, SND_PCM_FORMAT_S24_3LE, SND_PCM_FORMAT_S24_3BE, SND_PCM_FORMAT_S24_LE, SND_PCM_FORMAT_S24_BE }; int err; err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_ACCESS, ARRAY_SIZE(access_list), access_list); if (err < 0) return err; err = snd_pcm_ioplug_set_param_list(io, SND_PCM_IOPLUG_HW_FORMAT, ARRAY_SIZE(formats), formats); if (err < 0) return err; err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_CHANNELS, 1, PA_CHANNELS_MAX); if (err < 0) return err; err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_RATE, 1, PA_RATE_MAX); if (err < 0) return err; err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_BUFFER_BYTES, 1, 4 * 1024 * 1024); if (err < 0) return err; err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIOD_BYTES, 128, 2 * 1024 * 1024); if (err < 0) return err; err = snd_pcm_ioplug_set_param_minmax(io, SND_PCM_IOPLUG_HW_PERIODS, 3, 1024); if (err < 0) return err; return 0; } SND_PCM_PLUGIN_DEFINE_FUNC(pulse) { snd_config_iterator_t i, next; const char *server = NULL; const char *device = NULL; const char *fallback_name = NULL; int handle_underrun = DEFAULT_HANDLE_UNDERRUN; int err; snd_pcm_pulse_t *pcm; snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id; if (snd_config_get_id(n, &id) < 0) continue; if (strcmp(id, "comment") == 0 || strcmp(id, "type") == 0 || strcmp(id, "hint") == 0) continue; if (strcmp(id, "server") == 0) { if (snd_config_get_string(n, &server) < 0) { SNDERR("Invalid type for %s", id); return -EINVAL; } continue; } if (strcmp(id, "device") == 0) { if (snd_config_get_string(n, &device) < 0) { SNDERR("Invalid type for %s", id); return -EINVAL; } continue; } if (strcmp(id, "handle_underrun") == 0) { if ((err = snd_config_get_bool(n)) < 0) { SNDERR("Invalid value for %s", id); return -EINVAL; } handle_underrun = err; continue; } if (strcmp(id, "fallback") == 0) { if (snd_config_get_string(n, &fallback_name) < 0) { SNDERR("Invalid value for %s", id); return -EINVAL; } continue; } SNDERR("Unknown field %s", id); return -EINVAL; } if (fallback_name && name && !strcmp(name, fallback_name)) fallback_name = NULL; /* no fallback for the same name */ pcm = calloc(1, sizeof(*pcm)); if (!pcm) return -ENOMEM; if (device) { pcm->device = strdup(device); if (!pcm->device) { err = -ENOMEM; goto error; } } pcm->p = pulse_new(); if (!pcm->p) { err = -EIO; goto error; } pcm->handle_underrun = handle_underrun; pcm->buffer_attr.prebuf = -1; err = pulse_connect(pcm->p, server, fallback_name != NULL); if (err < 0) goto error; pcm->io.version = SND_PCM_IOPLUG_VERSION; pcm->io.name = "ALSA <-> PulseAudio PCM I/O Plugin"; pcm->io.poll_fd = pcm->p->main_fd; pcm->io.poll_events = POLLIN; pcm->io.mmap_rw = 0; pcm->io.callback = stream == SND_PCM_STREAM_PLAYBACK ? &pulse_playback_callback : &pulse_capture_callback; pcm->io.private_data = pcm; err = snd_pcm_ioplug_create(&pcm->io, name, stream, mode); if (err < 0) goto error; err = pulse_hw_constraint(pcm); if (err < 0) { snd_pcm_ioplug_delete(&pcm->io); goto error2; } *pcmp = pcm->io.pcm; return 0; error: if (pcm->p) pulse_free(pcm->p); free(pcm->device); free(pcm); error2: if (fallback_name) return snd_pcm_open_fallback(pcmp, root, fallback_name, name, stream, mode); return err; } SND_PCM_PLUGIN_SYMBOL(pulse);