diff options
Diffstat (limited to 'src/modules')
63 files changed, 7694 insertions, 678 deletions
diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c index 8abf834d..20dc400f 100644 --- a/src/modules/alsa-util.c +++ b/src/modules/alsa-util.c @@ -30,6 +30,7 @@ #include <pulse/sample.h> #include <pulse/xmalloc.h> +#include <pulse/timeval.h> #include <pulsecore/log.h> #include <pulsecore/macro.h> @@ -39,7 +40,7 @@ #include "alsa-util.h" struct pa_alsa_fdlist { - int num_fds; + unsigned num_fds; struct pollfd *fds; /* This is a temporary buffer used to avoid lots of mallocs */ struct pollfd *work_fds; @@ -50,16 +51,17 @@ struct pa_alsa_fdlist { pa_defer_event *defer; pa_io_event **ios; - int polled; + pa_bool_t polled; void (*cb)(void *userdata); void *userdata; }; -static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void *userdata) { +static void io_cb(pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { struct pa_alsa_fdlist *fdl = userdata; - int err, i; + int err; + unsigned i; unsigned short revents; pa_assert(a); @@ -71,11 +73,11 @@ static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io if (fdl->polled) return; - fdl->polled = 1; + fdl->polled = TRUE; memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); - for (i = 0;i < fdl->num_fds; i++) { + for (i = 0; i < fdl->num_fds; i++) { if (e == fdl->ios[i]) { if (events & PA_IO_EVENT_INPUT) fdl->work_fds[i].revents |= POLLIN; @@ -102,9 +104,10 @@ static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io snd_mixer_handle_events(fdl->mixer); } -static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *userdata) { +static void defer_cb(pa_mainloop_api*a, pa_defer_event* e, void *userdata) { struct pa_alsa_fdlist *fdl = userdata; - int num_fds, i, err; + unsigned num_fds, i; + int err; struct pollfd *temp; pa_assert(a); @@ -113,8 +116,7 @@ static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *u a->defer_enable(fdl->defer, 0); - num_fds = snd_mixer_poll_descriptors_count(fdl->mixer); - pa_assert(num_fds > 0); + num_fds = (unsigned) snd_mixer_poll_descriptors_count(fdl->mixer); if (num_fds != fdl->num_fds) { if (fdl->fds) @@ -132,7 +134,7 @@ static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *u return; } - fdl->polled = 0; + fdl->polled = FALSE; if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0) return; @@ -176,7 +178,7 @@ struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { fdl->m = NULL; fdl->defer = NULL; fdl->ios = NULL; - fdl->polled = 0; + fdl->polled = FALSE; return fdl; } @@ -190,9 +192,9 @@ void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { } if (fdl->ios) { - int i; + unsigned i; pa_assert(fdl->m); - for (i = 0;i < fdl->num_fds;i++) + for (i = 0; i < fdl->num_fds; i++) fdl->m->io_free(fdl->ios[i]); pa_xfree(fdl->ios); } @@ -369,11 +371,18 @@ int pa_alsa_set_hw_params( goto finish; if (_periods > 0) { - dir = 1; + + /* First we pass 0 as direction to get exactly what we asked + * for. That this is necessary is presumably a bug in ALSA */ + + dir = 0; if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { - dir = -1; - if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) - goto finish; + dir = 1; + if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) { + dir = -1; + if ((ret = snd_pcm_hw_params_set_periods_near(pcm_handle, hwparams, &_periods, &dir)) < 0) + goto finish; + } } } @@ -403,7 +412,7 @@ int pa_alsa_set_hw_params( /* If the sample rate deviates too much, we need to resample */ if (r < ss->rate*.95 || r > ss->rate*1.05) ss->rate = r; - ss->channels = c; + ss->channels = (uint8_t) c; ss->format = f; pa_assert(_periods > 0); @@ -420,6 +429,8 @@ int pa_alsa_set_hw_params( ret = 0; + snd_pcm_nonblock(pcm_handle, 1); + finish: return ret; @@ -568,40 +579,60 @@ snd_pcm_t *pa_alsa_open_by_device_id( continue; d = pa_sprintf_malloc("%s:%s", device_table[i].name, dev_id); - pa_log_debug("Trying %s...", d); - if ((err = snd_pcm_open(&pcm_handle, d, mode, - SND_PCM_NONBLOCK| - SND_PCM_NO_AUTO_RESAMPLE| - SND_PCM_NO_AUTO_CHANNELS| - SND_PCM_NO_AUTO_FORMAT | - SND_PCM_NO_SOFTVOL)) < 0) { - pa_log_info("Couldn't open PCM device %s: %s", d, snd_strerror(err)); - pa_xfree(d); - continue; - } + for (;;) { + pa_log_debug("Trying %s...", d); + + /* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <= + * 1.0.17a would then ignore the SND_PCM_NO_xxx + * flags. Instead we enable nonblock mode afterwards via + * snd_pcm_nonblock(). Also see + * http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010258.html */ + + if ((err = snd_pcm_open(&pcm_handle, d, mode, + /* SND_PCM_NONBLOCK| */ + SND_PCM_NO_AUTO_RESAMPLE| + SND_PCM_NO_AUTO_CHANNELS| + SND_PCM_NO_AUTO_FORMAT)) < 0) { + pa_log_info("Couldn't open PCM device %s: %s", d, snd_strerror(err)); + break; + } - try_ss.channels = device_table[i].map.channels; - try_ss.rate = ss->rate; - try_ss.format = ss->format; + try_ss.channels = device_table[i].map.channels; + try_ss.rate = ss->rate; + try_ss.format = ss->format; - if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, TRUE)) < 0) { - pa_log_info("PCM device %s refused our hw parameters: %s", d, snd_strerror(err)); - pa_xfree(d); - snd_pcm_close(pcm_handle); - continue; + if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, TRUE)) < 0) { + + if (!pa_startswith(d, "plug:") && !pa_startswith(d, "plughw:")) { + char *t; + + t = pa_sprintf_malloc("plug:%s", d); + pa_xfree(d); + d = t; + + snd_pcm_close(pcm_handle); + continue; + } + + pa_log_info("PCM device %s refused our hw parameters: %s", d, snd_strerror(err)); + snd_pcm_close(pcm_handle); + break; + } + + *ss = try_ss; + *map = device_table[i].map; + pa_assert(map->channels == ss->channels); + *dev = d; + return pcm_handle; } - *ss = try_ss; - *map = device_table[i].map; - pa_assert(map->channels == ss->channels); - *dev = d; - return pcm_handle; + pa_xfree(d); } /* OK, we didn't find any good device, so let's try the raw plughw: stuff */ - d = pa_sprintf_malloc("plughw:%s", dev_id); + d = pa_sprintf_malloc("hw:%s", dev_id); pa_log_debug("Trying %s as last resort...", d); pcm_handle = pa_alsa_open_by_device_string(d, dev, ss, map, mode, nfrags, period_size, tsched_size, use_mmap, use_tsched); pa_xfree(d); @@ -635,8 +666,16 @@ snd_pcm_t *pa_alsa_open_by_device_string( d = pa_xstrdup(device); for (;;) { + pa_log_debug("Trying %s...", d); + + /* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <= + * 1.0.17a would then ignore the SND_PCM_NO_xxx flags. Instead + * we enable nonblock mode afterwards via + * snd_pcm_nonblock(). Also see + * http://mailman.alsa-project.org/pipermail/alsa-devel/2008-August/010258.html */ - if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK| + if ((err = snd_pcm_open(&pcm_handle, d, mode, + /*SND_PCM_NONBLOCK|*/ SND_PCM_NO_AUTO_RESAMPLE| SND_PCM_NO_AUTO_CHANNELS| SND_PCM_NO_AUTO_FORMAT)) < 0) { @@ -647,24 +686,23 @@ snd_pcm_t *pa_alsa_open_by_device_string( if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, FALSE)) < 0) { - if (err == -EPERM) { - /* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */ + /* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */ - if (pa_startswith(d, "hw:")) { - char *t = pa_sprintf_malloc("plughw:%s", d+3); - pa_log_debug("Opening the device as '%s' didn't work, retrying with '%s'.", d, t); - pa_xfree(d); - d = t; + if (!pa_startswith(d, "plug:") && !pa_startswith(d, "plughw:")) { + char *t; - snd_pcm_close(pcm_handle); - continue; - } - - pa_log("Failed to set hardware parameters on %s: %s", d, snd_strerror(err)); + t = pa_sprintf_malloc("plug:%s", d); pa_xfree(d); + d = t; + snd_pcm_close(pcm_handle); - return NULL; + continue; } + + pa_log("Failed to set hardware parameters on %s: %s", d, snd_strerror(err)); + pa_xfree(d); + snd_pcm_close(pcm_handle); + return NULL; } *dev = d; @@ -808,7 +846,7 @@ int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel if (channel_map->channels > 1 && ((playback && snd_mixer_selem_has_playback_volume_joined(elem)) || (!playback && snd_mixer_selem_has_capture_volume_joined(elem)))) { - pa_log_info("ALSA device lacks independant volume controls for each channel, falling back to software volume control."); + pa_log_info("ALSA device lacks independant volume controls for each channel."); return -1; } @@ -820,7 +858,7 @@ int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel id = alsa_channel_ids[channel_map->map[i]]; if (!is_mono && id == SND_MIXER_SCHN_UNKNOWN) { - pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer. Falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer.", pa_channel_position_to_string(channel_map->map[i])); return -1; } @@ -832,7 +870,7 @@ int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel if ((playback && (!snd_mixer_selem_has_playback_channel(elem, id) || (is_mono && !snd_mixer_selem_is_playback_mono(elem)))) || (!playback && (!snd_mixer_selem_has_capture_channel(elem, id) || (is_mono && !snd_mixer_selem_is_capture_mono(elem))))) { - pa_log_info("ALSA device lacks separate volumes control for channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + pa_log_info("ALSA device lacks separate volumes control for channel '%s'", pa_channel_position_to_string(channel_map->map[i])); return -1; } @@ -850,56 +888,6 @@ int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel return 0; } -void pa_alsa_0dB_playback(snd_mixer_elem_t *elem) { - long min, max, v; - - pa_assert(elem); - - /* Try to enable 0 dB if possible. If ALSA cannot do dB, then use - * raw volume levels and fix them to 75% */ - - if (snd_mixer_selem_set_playback_dB_all(elem, 0, -1) >= 0) - return; - - if (snd_mixer_selem_set_playback_dB_all(elem, 0, 1) >= 0) - return; - - if (snd_mixer_selem_get_playback_volume_range(elem, &min, &max) < 0) - return; - - v = min + ((max - min) * 3) / 4; /* 75% */ - - if (v <= min) - v = max; - - snd_mixer_selem_set_playback_volume_all(elem, v); -} - -void pa_alsa_0dB_capture(snd_mixer_elem_t *elem) { - long min, max, v; - - pa_assert(elem); - - /* Try to enable 0 dB if possible. If ALSA cannot do dB, then use - * raw volume levels and fix them to 75% */ - - if (snd_mixer_selem_set_capture_dB_all(elem, 0, -1) >= 0) - return; - - if (snd_mixer_selem_set_capture_dB_all(elem, 0, 1) >= 0) - return; - - if (snd_mixer_selem_get_capture_volume_range(elem, &min, &max) < 0) - return; - - v = min + ((max - min) * 3) / 4; /* 75% */ - - if (v <= min) - v = max; - - snd_mixer_selem_set_capture_volume_all(elem, v); -} - void pa_alsa_dump(snd_pcm_t *pcm) { int err; snd_output_t *out; @@ -945,12 +933,17 @@ void pa_alsa_dump_status(snd_pcm_t *pcm) { static void alsa_error_handler(const char *file, int line, const char *function, int err, const char *fmt,...) { va_list ap; + char *alsa_file; + + alsa_file = pa_sprintf_malloc("(alsa-lib)%s", file); va_start(ap, fmt); - pa_log_levelv_meta(PA_LOG_WARN, file, line, function, fmt, ap); + pa_log_levelv_meta(PA_LOG_INFO, alsa_file, line, function, fmt, ap); va_end(ap); + + pa_xfree(alsa_file); } static pa_atomic_t n_error_handler_installed = PA_ATOMIC_INIT(0); @@ -1058,6 +1051,12 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) { pa_log_warn("Got POLLNVAL from ALSA"); if (revents & POLLHUP) pa_log_warn("Got POLLHUP from ALSA"); + if (revents & POLLPRI) + pa_log_warn("Got POLLPRI from ALSA"); + if (revents & POLLIN) + pa_log_warn("Got POLLIN from ALSA"); + if (revents & POLLOUT) + pa_log_warn("Got POLLOUT from ALSA"); state = snd_pcm_state(pcm); pa_log_warn("PCM state is %s", snd_pcm_state_name(state)); @@ -1106,10 +1105,10 @@ pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) { return NULL; } - item = pa_rtpoll_item_new(rtpoll, PA_RTPOLL_NEVER, n); + item = pa_rtpoll_item_new(rtpoll, PA_RTPOLL_NEVER, (unsigned) n); pollfd = pa_rtpoll_item_get_pollfd(item, NULL); - if ((err = snd_pcm_poll_descriptors(pcm, pollfd, n)) < 0) { + if ((err = snd_pcm_poll_descriptors(pcm, pollfd, (unsigned) n)) < 0) { pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); pa_rtpoll_item_free(item); return NULL; @@ -1117,3 +1116,62 @@ pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll) { return item; } + +snd_pcm_sframes_t pa_alsa_safe_avail_update(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss) { + snd_pcm_sframes_t n; + size_t k; + + pa_assert(pcm); + pa_assert(hwbuf_size > 0); + pa_assert(ss); + + /* Some ALSA driver expose weird bugs, let's inform the user about + * what is going on */ + + n = snd_pcm_avail_update(pcm); + + if (n <= 0) + return n; + + k = (size_t) n * pa_frame_size(ss); + + if (k >= hwbuf_size * 3 || + k >= pa_bytes_per_second(ss)*10) + pa_log("snd_pcm_avail_update() returned a value that is exceptionally large: %lu bytes (%lu ms) " + "Most likely this is an ALSA driver bug. Please report this issue to the PulseAudio developers.", + (unsigned long) k, (unsigned long) pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC); + + return n; +} + +int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames, size_t hwbuf_size, const pa_sample_spec *ss) { + int r; + snd_pcm_uframes_t before; + size_t k; + + pa_assert(pcm); + pa_assert(areas); + pa_assert(offset); + pa_assert(frames); + pa_assert(hwbuf_size > 0); + pa_assert(ss); + + before = *frames; + + r = snd_pcm_mmap_begin(pcm, areas, offset, frames); + + if (r < 0) + return r; + + k = (size_t) *frames * pa_frame_size(ss); + + if (*frames > before || + k >= hwbuf_size * 3 || + k >= pa_bytes_per_second(ss)*10) + + pa_log("snd_pcm_mmap_begin() returned a value that is exceptionally large: %lu bytes (%lu ms) " + "Most likely this is an ALSA driver bug. Please report this issue to the PulseAudio developers.", + (unsigned long) k, (unsigned long) pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC); + + return r; +} diff --git a/src/modules/alsa-util.h b/src/modules/alsa-util.h index 4de8bcd2..aaa01c78 100644 --- a/src/modules/alsa-util.h +++ b/src/modules/alsa-util.h @@ -26,6 +26,7 @@ #include <asoundlib.h> #include <pulse/sample.h> +#include <pulse/volume.h> #include <pulse/mainloop-api.h> #include <pulse/channelmap.h> #include <pulse/proplist.h> @@ -79,9 +80,6 @@ snd_pcm_t *pa_alsa_open_by_device_string( int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback); -void pa_alsa_0dB_playback(snd_mixer_elem_t *elem); -void pa_alsa_0dB_capture(snd_mixer_elem_t *elem); - void pa_alsa_dump(snd_pcm_t *pcm); void pa_alsa_dump_status(snd_pcm_t *pcm); @@ -94,4 +92,7 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents); pa_rtpoll_item* pa_alsa_build_pollfd(snd_pcm_t *pcm, pa_rtpoll *rtpoll); +snd_pcm_sframes_t pa_alsa_safe_avail_update(snd_pcm_t *pcm, size_t hwbuf_size, const pa_sample_spec *ss); +int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas, snd_pcm_uframes_t *offset, snd_pcm_uframes_t *frames, size_t hwbuf_size, const pa_sample_spec *ss); + #endif diff --git a/src/modules/bluetooth/Makefile b/src/modules/bluetooth/Makefile new file mode 120000 index 00000000..efe5a336 --- /dev/null +++ b/src/modules/bluetooth/Makefile @@ -0,0 +1 @@ +../../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/bluetooth/ipc.c b/src/modules/bluetooth/ipc.c new file mode 100644 index 00000000..98256998 --- /dev/null +++ b/src/modules/bluetooth/ipc.c @@ -0,0 +1,118 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * 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 library 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "ipc.h" + +/* This table contains the string representation for messages */ +static const char *strmsg[] = { + "BT_GETCAPABILITIES_REQ", + "BT_GETCAPABILITIES_RSP", + "BT_SETCONFIGURATION_REQ", + "BT_SETCONFIGURATION_RSP", + "BT_STREAMSTART_REQ", + "BT_STREAMSTART_RSP", + "BT_STREAMSTOP_REQ", + "BT_STREAMSTOP_RSP", + "BT_STREAMSUSPEND_IND", + "BT_STREAMRESUME_IND", + "BT_CONTROL_REQ", + "BT_CONTROL_RSP", + "BT_CONTROL_IND", + "BT_STREAMFD_IND", +}; + +int bt_audio_service_open(void) +{ + int sk; + int err; + struct sockaddr_un addr = { + AF_UNIX, BT_IPC_SOCKET_NAME + }; + + sk = socket(PF_LOCAL, SOCK_STREAM, 0); + if (sk < 0) { + err = errno; + fprintf(stderr, "%s: Cannot open socket: %s (%d)\n", + __FUNCTION__, strerror(err), err); + errno = err; + return -1; + } + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + err = errno; + fprintf(stderr, "%s: connect() failed: %s (%d)\n", + __FUNCTION__, strerror(err), err); + close(sk); + errno = err; + return -1; + } + + return sk; +} + +int bt_audio_service_close(int sk) +{ + return close(sk); +} + +int bt_audio_service_get_data_fd(int sk) +{ + char cmsg_b[CMSG_SPACE(sizeof(int))], m; + int err, ret; + struct iovec iov = { &m, sizeof(m) }; + struct msghdr msgh; + struct cmsghdr *cmsg; + + memset(&msgh, 0, sizeof(msgh)); + msgh.msg_iov = &iov; + msgh.msg_iovlen = 1; + msgh.msg_control = &cmsg_b; + msgh.msg_controllen = CMSG_LEN(sizeof(int)); + + ret = (int) recvmsg(sk, &msgh, 0); + if (ret < 0) { + err = errno; + fprintf(stderr, "%s: Unable to receive fd: %s (%d)\n", + __FUNCTION__, strerror(err), err); + errno = err; + return -1; + } + + /* Receive auxiliary data in msgh */ + for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg != NULL; + cmsg = CMSG_NXTHDR(&msgh, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET + && cmsg->cmsg_type == SCM_RIGHTS) + return (*(int *) CMSG_DATA(cmsg)); + } + + errno = EINVAL; + return -1; +} + +const char *bt_audio_strmsg(int type) +{ + if (type < 0 || (size_t) type > (sizeof(strmsg) / sizeof(strmsg[0]))) + return NULL; + + return strmsg[type]; +} diff --git a/src/modules/bluetooth/ipc.h b/src/modules/bluetooth/ipc.h new file mode 100644 index 00000000..ae85e727 --- /dev/null +++ b/src/modules/bluetooth/ipc.h @@ -0,0 +1,308 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * 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 library 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* + Message sequence chart of streaming sequence for A2DP transport + + Audio daemon User + on snd_pcm_open + <--BT_GETCAPABILITIES_REQ + + BT_GETCAPABILITIES_RSP--> + + on snd_pcm_hw_params + <--BT_SETCONFIGURATION_REQ + + BT_SETCONFIGURATION_RSP--> + + on snd_pcm_prepare + <--BT_STREAMSTART_REQ + + <Moves to streaming state> + BT_STREAMSTART_RSP--> + + BT_STREAMFD_IND --> + + < streams data > + .......... + + on snd_pcm_drop/snd_pcm_drain + + <--BT_STREAMSTOP_REQ + + <Moves to open state> + BT_STREAMSTOP_RSP--> + + on IPC close or appl crash + <Moves to idle> + + */ + +#ifndef BT_AUDIOCLIENT_H +#define BT_AUDIOCLIENT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> + +#define BT_AUDIO_IPC_PACKET_SIZE 128 +#define BT_IPC_SOCKET_NAME "\0/org/bluez/audio" + +/* Generic message header definition, except for RSP messages */ +typedef struct { + uint8_t msg_type; +} __attribute__ ((packed)) bt_audio_msg_header_t; + +/* Generic message header definition, for all RSP messages */ +typedef struct { + bt_audio_msg_header_t msg_h; + uint8_t posix_errno; +} __attribute__ ((packed)) bt_audio_rsp_msg_header_t; + +/* Messages list */ +#define BT_GETCAPABILITIES_REQ 0 +#define BT_GETCAPABILITIES_RSP 1 + +#define BT_SETCONFIGURATION_REQ 2 +#define BT_SETCONFIGURATION_RSP 3 + +#define BT_STREAMSTART_REQ 4 +#define BT_STREAMSTART_RSP 5 + +#define BT_STREAMSTOP_REQ 6 +#define BT_STREAMSTOP_RSP 7 + +#define BT_STREAMSUSPEND_IND 8 +#define BT_STREAMRESUME_IND 9 + +#define BT_CONTROL_REQ 10 +#define BT_CONTROL_RSP 11 +#define BT_CONTROL_IND 12 + +#define BT_STREAMFD_IND 13 + +/* BT_GETCAPABILITIES_REQ */ + +#define BT_CAPABILITIES_TRANSPORT_A2DP 0 +#define BT_CAPABILITIES_TRANSPORT_SCO 1 +#define BT_CAPABILITIES_TRANSPORT_ANY 2 + +#define BT_CAPABILITIES_ACCESS_MODE_READ 1 +#define BT_CAPABILITIES_ACCESS_MODE_WRITE 2 +#define BT_CAPABILITIES_ACCESS_MODE_READWRITE 3 + +#define BT_FLAG_AUTOCONNECT 1 + +struct bt_getcapabilities_req { + bt_audio_msg_header_t h; + char device[18]; /* Address of the remote Device */ + uint8_t transport; /* Requested transport */ + uint8_t flags; /* Requested flags */ +} __attribute__ ((packed)); + +/* BT_GETCAPABILITIES_RSP */ + +/** + * SBC Codec parameters as per A2DP profile 1.0 § 4.3 + */ + +#define BT_SBC_SAMPLING_FREQ_16000 (1 << 3) +#define BT_SBC_SAMPLING_FREQ_32000 (1 << 2) +#define BT_SBC_SAMPLING_FREQ_44100 (1 << 1) +#define BT_SBC_SAMPLING_FREQ_48000 1 + +#define BT_A2DP_CHANNEL_MODE_MONO (1 << 3) +#define BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +#define BT_A2DP_CHANNEL_MODE_STEREO (1 << 1) +#define BT_A2DP_CHANNEL_MODE_JOINT_STEREO 1 + +#define BT_A2DP_BLOCK_LENGTH_4 (1 << 3) +#define BT_A2DP_BLOCK_LENGTH_8 (1 << 2) +#define BT_A2DP_BLOCK_LENGTH_12 (1 << 1) +#define BT_A2DP_BLOCK_LENGTH_16 1 + +#define BT_A2DP_SUBBANDS_4 (1 << 1) +#define BT_A2DP_SUBBANDS_8 1 + +#define BT_A2DP_ALLOCATION_SNR (1 << 1) +#define BT_A2DP_ALLOCATION_LOUDNESS 1 + +#define BT_MPEG_SAMPLING_FREQ_16000 (1 << 5) +#define BT_MPEG_SAMPLING_FREQ_22050 (1 << 4) +#define BT_MPEG_SAMPLING_FREQ_24000 (1 << 3) +#define BT_MPEG_SAMPLING_FREQ_32000 (1 << 2) +#define BT_MPEG_SAMPLING_FREQ_44100 (1 << 1) +#define BT_MPEG_SAMPLING_FREQ_48000 1 + +#define BT_MPEG_LAYER_1 (1 << 2) +#define BT_MPEG_LAYER_2 (1 << 1) +#define BT_MPEG_LAYER_3 1 + +typedef struct { + uint8_t channel_mode; + uint8_t frequency; + uint8_t allocation_method; + uint8_t subbands; + uint8_t block_length; + uint8_t min_bitpool; + uint8_t max_bitpool; +} __attribute__ ((packed)) sbc_capabilities_t; + +typedef struct { + uint8_t channel_mode; + uint8_t crc; + uint8_t layer; + uint8_t frequency; + uint8_t mpf; + uint16_t bitrate; +} __attribute__ ((packed)) mpeg_capabilities_t; + +struct bt_getcapabilities_rsp { + bt_audio_rsp_msg_header_t rsp_h; + uint8_t transport; /* Granted transport */ + sbc_capabilities_t sbc_capabilities; /* A2DP only */ + mpeg_capabilities_t mpeg_capabilities; /* A2DP only */ + uint16_t sampling_rate; /* SCO only */ +} __attribute__ ((packed)); + +/* BT_SETCONFIGURATION_REQ */ +struct bt_setconfiguration_req { + bt_audio_msg_header_t h; + char device[18]; /* Address of the remote Device */ + uint8_t transport; /* Requested transport */ + uint8_t access_mode; /* Requested access mode */ + sbc_capabilities_t sbc_capabilities; /* A2DP only - only one of this field + and next one must be filled */ + mpeg_capabilities_t mpeg_capabilities; /* A2DP only */ +} __attribute__ ((packed)); + +/* BT_SETCONFIGURATION_RSP */ +struct bt_setconfiguration_rsp { + bt_audio_rsp_msg_header_t rsp_h; + uint8_t transport; /* Granted transport */ + uint8_t access_mode; /* Granted access mode */ + uint16_t link_mtu; /* Max length that transport supports */ +} __attribute__ ((packed)); + +/* BT_STREAMSTART_REQ */ +#define BT_STREAM_ACCESS_READ 0 +#define BT_STREAM_ACCESS_WRITE 1 +#define BT_STREAM_ACCESS_READWRITE 2 +struct bt_streamstart_req { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +/* BT_STREAMSTART_RSP */ +struct bt_streamstart_rsp { + bt_audio_rsp_msg_header_t rsp_h; +} __attribute__ ((packed)); + +/* BT_STREAMFD_IND */ +/* This message is followed by one byte of data containing the stream data fd + as ancilliary data */ +struct bt_streamfd_ind { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +/* BT_STREAMSTOP_REQ */ +struct bt_streamstop_req { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +/* BT_STREAMSTOP_RSP */ +struct bt_streamstop_rsp { + bt_audio_rsp_msg_header_t rsp_h; +} __attribute__ ((packed)); + +/* BT_STREAMSUSPEND_IND */ +struct bt_streamsuspend_ind { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +/* BT_STREAMRESUME_IND */ +struct bt_streamresume_ind { + bt_audio_msg_header_t h; +} __attribute__ ((packed)); + +/* BT_CONTROL_REQ */ + +#define BT_CONTROL_KEY_POWER 0x40 +#define BT_CONTROL_KEY_VOL_UP 0x41 +#define BT_CONTROL_KEY_VOL_DOWN 0x42 +#define BT_CONTROL_KEY_MUTE 0x43 +#define BT_CONTROL_KEY_PLAY 0x44 +#define BT_CONTROL_KEY_STOP 0x45 +#define BT_CONTROL_KEY_PAUSE 0x46 +#define BT_CONTROL_KEY_RECORD 0x47 +#define BT_CONTROL_KEY_REWIND 0x48 +#define BT_CONTROL_KEY_FAST_FORWARD 0x49 +#define BT_CONTROL_KEY_EJECT 0x4A +#define BT_CONTROL_KEY_FORWARD 0x4B +#define BT_CONTROL_KEY_BACKWARD 0x4C + +struct bt_control_req { + bt_audio_msg_header_t h; + uint8_t mode; /* Control Mode */ + uint8_t key; /* Control Key */ +} __attribute__ ((packed)); + +/* BT_CONTROL_RSP */ +struct bt_control_rsp { + bt_audio_rsp_msg_header_t rsp_h; + uint8_t mode; /* Control Mode */ + uint8_t key; /* Control Key */ +} __attribute__ ((packed)); + +/* BT_CONTROL_IND */ +struct bt_control_ind { + bt_audio_msg_header_t h; + uint8_t mode; /* Control Mode */ + uint8_t key; /* Control Key */ +} __attribute__ ((packed)); + +/* Function declaration */ + +/* Opens a connection to the audio service: return a socket descriptor */ +int bt_audio_service_open(void); + +/* Closes a connection to the audio service */ +int bt_audio_service_close(int sk); + +/* Receives stream data file descriptor : must be called after a +BT_STREAMFD_IND message is returned */ +int bt_audio_service_get_data_fd(int sk); + +/* Human readable message type string */ +const char *bt_audio_strmsg(int type); + +#ifdef __cplusplus +} +#endif + +#endif /* BT_AUDIOCLIENT_H */ diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c new file mode 100644 index 00000000..3460fe9a --- /dev/null +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -0,0 +1,922 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Joao Paulo Rechi Vita + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> +#include <errno.h> +#include <poll.h> +#include <sys/ioctl.h> +#include <linux/sockios.h> +#include <arpa/inet.h> + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulse/sample.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/time-smoother.h> +#include <pulsecore/rtclock.h> + +#include "../dbus-util.h" +#include "module-bluetooth-device-symdef.h" +#include "ipc.h" +#include "sbc.h" +#include "rtp.h" + +#define DEFAULT_SINK_NAME "bluetooth_sink" +#define BUFFER_SIZE 2048 +#define MAX_BITPOOL 64 +#define MIN_BITPOOL 2U +#define SOL_SCO 17 +#define SCO_TXBUFS 0x03 +#define SCO_RXBUFS 0x04 + +PA_MODULE_AUTHOR("Joao Paulo Rechi Vita"); +PA_MODULE_DESCRIPTION("Bluetooth audio sink and source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name of the device> " + "address=<address of the device> " + "profile=<a2dp|hsp>"); + +struct bt_a2dp { + sbc_capabilities_t sbc_capabilities; + sbc_t sbc; /* Codec data */ + pa_bool_t sbc_initialized; /* Keep track if the encoder is initialized */ + size_t codesize; /* SBC codesize */ + unsigned samples; /* Number of encoded samples */ + uint8_t buffer[BUFFER_SIZE]; /* Codec transfer buffer */ + size_t count; /* Codec transfer buffer counter */ + + unsigned total_samples; /* Cumulative number of codec samples */ + uint16_t seq_num; /* Cumulative packet sequence */ + unsigned frame_count; /* Current frames in buffer*/ +}; + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; + pa_thread *thread; + + uint64_t offset; + pa_smoother *smoother; + + char *name; + char *addr; + char *profile; + pa_sample_spec ss; + + int audioservice_fd; + int stream_fd; + + uint8_t transport; + char *strtransport; + size_t link_mtu; + size_t block_size; + pa_usec_t latency; + + struct bt_a2dp a2dp; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "address", + "profile", + "rate", + "channels", + NULL +}; + +static int bt_audioservice_send(int sk, const bt_audio_msg_header_t *msg) { + int e; + pa_log_debug("sending %s", bt_audio_strmsg(msg->msg_type)); + if (send(sk, msg, BT_AUDIO_IPC_PACKET_SIZE, 0) > 0) + e = 0; + else { + e = -errno; + pa_log_error("Error sending data to audio service: %s(%d)", pa_cstrerror(errno), errno); + } + return e; +} + +static int bt_audioservice_recv(int sk, bt_audio_msg_header_t *inmsg) { + int e; + const char *type; + + pa_log_debug("trying to receive msg from audio service..."); + if (recv(sk, inmsg, BT_AUDIO_IPC_PACKET_SIZE, 0) > 0) { + type = bt_audio_strmsg(inmsg->msg_type); + if (type) { + pa_log_debug("Received %s", type); + e = 0; + } + else { + e = -EINVAL; + pa_log_error("Bogus message type %d received from audio service", inmsg->msg_type); + } + } + else { + e = -errno; + pa_log_error("Error receiving data from audio service: %s(%d)", pa_cstrerror(errno), errno); + } + + return e; +} + +static int bt_audioservice_expect(int sk, bt_audio_msg_header_t *rsp_hdr, int expected_type) { + int e = bt_audioservice_recv(sk, rsp_hdr); + if (e == 0) { + if (rsp_hdr->msg_type != expected_type) { + e = -EINVAL; + pa_log_error("Bogus message %s received while %s was expected", bt_audio_strmsg(rsp_hdr->msg_type), + bt_audio_strmsg(expected_type)); + } + } + return e; +} + +static int bt_getcaps(struct userdata *u) { + int e; + union { + bt_audio_rsp_msg_header_t rsp_hdr; + struct bt_getcapabilities_req getcaps_req; + struct bt_getcapabilities_rsp getcaps_rsp; + uint8_t buf[BT_AUDIO_IPC_PACKET_SIZE]; + } msg; + + memset(msg.buf, 0, BT_AUDIO_IPC_PACKET_SIZE); + msg.getcaps_req.h.msg_type = BT_GETCAPABILITIES_REQ; + strncpy(msg.getcaps_req.device, u->addr, 18); + if (strcasecmp(u->profile, "a2dp") == 0) + msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP; + else if (strcasecmp(u->profile, "hsp") == 0) + msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_SCO; + else { + pa_log_error("Invalid profile argument: %s", u->profile); + return -1; + } + msg.getcaps_req.flags = BT_FLAG_AUTOCONNECT; + + e = bt_audioservice_send(u->audioservice_fd, &msg.getcaps_req.h); + if (e < 0) { + pa_log_error("Failed to send GETCAPABILITIES_REQ"); + return e; + } + + e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp_hdr.msg_h, BT_GETCAPABILITIES_RSP); + if (e < 0) { + pa_log_error("Failed to expect for GETCAPABILITIES_RSP"); + return e; + } + if (msg.rsp_hdr.posix_errno != 0) { + pa_log_error("BT_GETCAPABILITIES failed : %s (%d)", pa_cstrerror(msg.rsp_hdr.posix_errno), msg.rsp_hdr.posix_errno); + return -msg.rsp_hdr.posix_errno; + } + + if ((u->transport = msg.getcaps_rsp.transport) == BT_CAPABILITIES_TRANSPORT_A2DP) + u->a2dp.sbc_capabilities = msg.getcaps_rsp.sbc_capabilities; + + return 0; +} + +static uint8_t default_bitpool(uint8_t freq, uint8_t mode) { + switch (freq) { + case BT_SBC_SAMPLING_FREQ_16000: + case BT_SBC_SAMPLING_FREQ_32000: + return 53; + case BT_SBC_SAMPLING_FREQ_44100: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 31; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 53; + default: + pa_log_warn("Invalid channel mode %u", mode); + return 53; + } + case BT_SBC_SAMPLING_FREQ_48000: + switch (mode) { + case BT_A2DP_CHANNEL_MODE_MONO: + case BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL: + return 29; + case BT_A2DP_CHANNEL_MODE_STEREO: + case BT_A2DP_CHANNEL_MODE_JOINT_STEREO: + return 51; + default: + pa_log_warn("Invalid channel mode %u", mode); + return 51; + } + default: + pa_log_warn("Invalid sampling freq %u", freq); + return 53; + } +} + +static int bt_a2dp_init(struct userdata *u) { + sbc_capabilities_t *cap = &u->a2dp.sbc_capabilities; + uint8_t max_bitpool, min_bitpool; + unsigned i; + + static const struct { + uint32_t rate; + uint8_t cap; + } freq_table[] = { + { 16000U, BT_SBC_SAMPLING_FREQ_16000 }, + { 32000U, BT_SBC_SAMPLING_FREQ_32000 }, + { 44100U, BT_SBC_SAMPLING_FREQ_44100 }, + { 48000U, BT_SBC_SAMPLING_FREQ_48000 } + }; + + /* Find the lowest freq that is at least as high as the requested + * sampling rate */ + for (i = 0; i < PA_ELEMENTSOF(freq_table); i++) + if (freq_table[i].rate >= u->ss.rate || i == PA_ELEMENTSOF(freq_table)-1 ) { + u->ss.rate = freq_table[i].rate; + cap->frequency = freq_table[i].cap; + break; + } + + if (u->ss.channels >= 2) { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_JOINT_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_STEREO; + else if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL; + + u->ss.channels = 2; + } else { + if (cap->channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + cap->channel_mode = BT_A2DP_CHANNEL_MODE_MONO; + } + + if (!cap->channel_mode) { + pa_log_error("No supported channel modes"); + return -1; + } + + if (cap->block_length & BT_A2DP_BLOCK_LENGTH_16) + cap->block_length = BT_A2DP_BLOCK_LENGTH_16; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_12) + cap->block_length = BT_A2DP_BLOCK_LENGTH_12; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_8) + cap->block_length = BT_A2DP_BLOCK_LENGTH_8; + else if (cap->block_length & BT_A2DP_BLOCK_LENGTH_4) + cap->block_length = BT_A2DP_BLOCK_LENGTH_4; + else { + pa_log_error("No supported block lengths"); + return -1; + } + + if (cap->subbands & BT_A2DP_SUBBANDS_8) + cap->subbands = BT_A2DP_SUBBANDS_8; + else if (cap->subbands & BT_A2DP_SUBBANDS_4) + cap->subbands = BT_A2DP_SUBBANDS_4; + else { + pa_log_error("No supported subbands"); + return -1; + } + + if (cap->allocation_method & BT_A2DP_ALLOCATION_LOUDNESS) + cap->allocation_method = BT_A2DP_ALLOCATION_LOUDNESS; + else if (cap->allocation_method & BT_A2DP_ALLOCATION_SNR) + cap->allocation_method = BT_A2DP_ALLOCATION_SNR; + + min_bitpool = (uint8_t) PA_MAX(MIN_BITPOOL, cap->min_bitpool); + max_bitpool = (uint8_t) PA_MIN(default_bitpool(cap->frequency, cap->channel_mode), cap->max_bitpool); + + cap->min_bitpool = (uint8_t) min_bitpool; + cap->max_bitpool = (uint8_t) max_bitpool; + + return 0; +} + +static void bt_a2dp_setup(struct bt_a2dp *a2dp) { + sbc_capabilities_t active_capabilities = a2dp->sbc_capabilities; + + if (a2dp->sbc_initialized) + sbc_reinit(&a2dp->sbc, 0); + else + sbc_init(&a2dp->sbc, 0); + a2dp->sbc_initialized = TRUE; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_16000) + a2dp->sbc.frequency = SBC_FREQ_16000; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_32000) + a2dp->sbc.frequency = SBC_FREQ_32000; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_44100) + a2dp->sbc.frequency = SBC_FREQ_44100; + + if (active_capabilities.frequency & BT_SBC_SAMPLING_FREQ_48000) + a2dp->sbc.frequency = SBC_FREQ_48000; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_MONO) + a2dp->sbc.mode = SBC_MODE_MONO; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_DUAL_CHANNEL) + a2dp->sbc.mode = SBC_MODE_DUAL_CHANNEL; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_STEREO) + a2dp->sbc.mode = SBC_MODE_STEREO; + + if (active_capabilities.channel_mode & BT_A2DP_CHANNEL_MODE_JOINT_STEREO) + a2dp->sbc.mode = SBC_MODE_JOINT_STEREO; + + a2dp->sbc.allocation = (uint8_t) (active_capabilities.allocation_method == BT_A2DP_ALLOCATION_SNR ? SBC_AM_SNR : SBC_AM_LOUDNESS); + + switch (active_capabilities.subbands) { + case BT_A2DP_SUBBANDS_4: + a2dp->sbc.subbands = SBC_SB_4; + break; + case BT_A2DP_SUBBANDS_8: + a2dp->sbc.subbands = SBC_SB_8; + break; + } + + switch (active_capabilities.block_length) { + case BT_A2DP_BLOCK_LENGTH_4: + a2dp->sbc.blocks = SBC_BLK_4; + break; + case BT_A2DP_BLOCK_LENGTH_8: + a2dp->sbc.blocks = SBC_BLK_8; + break; + case BT_A2DP_BLOCK_LENGTH_12: + a2dp->sbc.blocks = SBC_BLK_12; + break; + case BT_A2DP_BLOCK_LENGTH_16: + a2dp->sbc.blocks = SBC_BLK_16; + break; + } + + a2dp->sbc.bitpool = active_capabilities.max_bitpool; + a2dp->codesize = (uint16_t) sbc_get_codesize(&a2dp->sbc); + a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); +} + +static int bt_setconf(struct userdata *u) { + int e; + union { + bt_audio_rsp_msg_header_t rsp_hdr; + struct bt_setconfiguration_req setconf_req; + struct bt_setconfiguration_rsp setconf_rsp; + uint8_t buf[BT_AUDIO_IPC_PACKET_SIZE]; + } msg; + + if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + e = bt_a2dp_init(u); + if (e < 0) { + pa_log_error("a2dp_init error"); + return e; + } + u->ss.format = PA_SAMPLE_S16LE; + } + else + u->ss.format = PA_SAMPLE_U8; + + memset(msg.buf, 0, BT_AUDIO_IPC_PACKET_SIZE); + msg.setconf_req.h.msg_type = BT_SETCONFIGURATION_REQ; + strncpy(msg.setconf_req.device, u->addr, 18); + msg.setconf_req.transport = u->transport; + if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + msg.setconf_req.sbc_capabilities = u->a2dp.sbc_capabilities; + msg.setconf_req.access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE; + + e = bt_audioservice_send(u->audioservice_fd, &msg.setconf_req.h); + if (e < 0) { + pa_log_error("Failed to send BT_SETCONFIGURATION_REQ"); + return e; + } + + e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp_hdr.msg_h, BT_SETCONFIGURATION_RSP); + if (e < 0) { + pa_log_error("Failed to expect BT_SETCONFIGURATION_RSP"); + return e; + } + + if (msg.rsp_hdr.posix_errno != 0) { + pa_log_error("BT_SETCONFIGURATION failed : %s(%d)", pa_cstrerror(msg.rsp_hdr.posix_errno), msg.rsp_hdr.posix_errno); + return -msg.rsp_hdr.posix_errno; + } + + u->transport = msg.setconf_rsp.transport; + u->strtransport = (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP ? pa_xstrdup("A2DP") : pa_xstrdup("SCO")); + u->link_mtu = msg.setconf_rsp.link_mtu; + + /* setup SBC encoder now we agree on parameters */ + if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + bt_a2dp_setup(&u->a2dp); + u->block_size = u->a2dp.codesize; + pa_log_info("sbc parameters:\n\tallocation=%u\n\tsubbands=%u\n\tblocks=%u\n\tbitpool=%u\n", + u->a2dp.sbc.allocation, u->a2dp.sbc.subbands, u->a2dp.sbc.blocks, u->a2dp.sbc.bitpool); + } + else + u->block_size = u->link_mtu; + + return 0; +} + +static int bt_getstreamfd(struct userdata *u) { + int e; +// uint32_t period_count = io->buffer_size / io->period_size; + union { + bt_audio_rsp_msg_header_t rsp_hdr; + struct bt_streamstart_req start_req; + struct bt_streamfd_ind streamfd_ind; + uint8_t buf[BT_AUDIO_IPC_PACKET_SIZE]; + } msg; + + memset(msg.buf, 0, BT_AUDIO_IPC_PACKET_SIZE); + msg.start_req.h.msg_type = BT_STREAMSTART_REQ; + + e = bt_audioservice_send(u->audioservice_fd, &msg.start_req.h); + if (e < 0) { + pa_log_error("Failed to send BT_STREAMSTART_REQ"); + return e; + } + + e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp_hdr.msg_h, BT_STREAMSTART_RSP); + if (e < 0) { + pa_log_error("Failed to expect BT_STREAMSTART_RSP"); + return e; + } + + if (msg.rsp_hdr.posix_errno != 0) { + pa_log_error("BT_START failed : %s(%d)", pa_cstrerror(msg.rsp_hdr.posix_errno), msg.rsp_hdr.posix_errno); + return -msg.rsp_hdr.posix_errno; + } + + e = bt_audioservice_expect(u->audioservice_fd, &msg.streamfd_ind.h, BT_STREAMFD_IND); + if (e < 0) { + pa_log_error("Failed to expect BT_STREAMFD_IND"); + return e; + } + + if (u->stream_fd >= 0) + pa_close(u->stream_fd); + + u->stream_fd = bt_audio_service_get_data_fd(u->audioservice_fd); + if (u->stream_fd < 0) { + pa_log_error("Failed to get data fd: %s (%d)",pa_cstrerror(errno), errno); + return -errno; + } + +// if (setsockopt(u->stream_fd, SOL_SCO, SCO_TXBUFS, &period_count, sizeof(period_count)) == 0) +// return 0; +// if (setsockopt(u->stream_fd, SOL_SCO, SO_SNDBUF, &period_count, sizeof(period_count)) == 0) +// return 0; +// /* FIXME : handle error codes */ + pa_make_fd_nonblock(u->stream_fd); +// pa_make_socket_low_delay(u->stream_fd); + + return 0; +} + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + pa_log_debug("got message: %d", code); + switch (code) { + + case PA_SINK_MESSAGE_SET_STATE: + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + break; + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + break; + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + break; + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t w, r; +/* r = pa_smoother_get(u->smoother, pa_rtclock_usec()); */ +/* /\* w = pa_bytes_to_usec(u->offset + (uint64_t) u->memchunk.length, &u->sink->sample_spec); *\/ */ + *((pa_usec_t*) data) = /*w > r ? w - r :*/ 0; + return 0; + } + + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static int sco_process_render(struct userdata *u) { + void *p; + int ret = 0; + pa_memchunk memchunk; + + pa_sink_render_full(u->sink, u->block_size, &memchunk); + + p = pa_memblock_acquire(memchunk.memblock); + + for (;;) { + ssize_t l; + + l = pa_loop_write(u->stream_fd, (uint8_t*) p, memchunk.length, NULL); + pa_log_debug("Memblock written to socket: %li bytes", (long) l); + + pa_assert(l != 0); + + if (l > 0) { + u->offset += (uint64_t) l; + break; + } + + if (errno == EINTR) + pa_log_debug("EINTR"); + else if (errno == EAGAIN) + pa_log_debug("EAGAIN"); + else { + pa_log_error("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + ret = -1; + break; + } + } + + pa_memblock_release(memchunk.memblock); + pa_memblock_unref(memchunk.memblock); + + return ret; +} + +static int a2dp_process_render(struct userdata *u) { + int written; + + struct bt_a2dp *a2dp = &u->a2dp; + struct rtp_header *header = (void *) a2dp->buffer; + struct rtp_payload *payload = (void *) (a2dp->buffer + sizeof(*header)); + + pa_assert(u); + + do { + /* Render some data */ + int frame_size, encoded; + void *p; + pa_memchunk memchunk; + + pa_sink_render_full(u->sink, u->block_size, &memchunk); + + p = pa_memblock_acquire(memchunk.memblock); + + frame_size = (uint16_t) sbc_get_frame_length(&a2dp->sbc); + pa_log_debug("SBC frame_size: %d", frame_size); + + encoded = sbc_encode(&a2dp->sbc, p, (int) a2dp->codesize, a2dp->buffer + a2dp->count, + (int) (sizeof(a2dp->buffer) - a2dp->count), &written); + pa_log_debug("SBC: encoded: %d; written: %d", encoded, written); + + pa_memblock_release(memchunk.memblock); + pa_memblock_unref(memchunk.memblock); + + if (encoded <= 0) { + pa_log_error("SBC encoding error (%d)", encoded); + return -1; + } + + a2dp->count += (size_t) written; + a2dp->frame_count++; + a2dp->samples += (unsigned) encoded / frame_size; + a2dp->total_samples += (unsigned) encoded / frame_size; + + } while (a2dp->count + (size_t) written <= u->link_mtu); + + /* write it to the fifo */ + memset(a2dp->buffer, 0, sizeof(*header) + sizeof(*payload)); + payload->frame_count = a2dp->frame_count; + header->v = 2; + header->pt = 1; + header->sequence_number = htons(a2dp->seq_num); + header->timestamp = htonl(a2dp->total_samples); + header->ssrc = htonl(1); + + for (;;) { + ssize_t l; + + l = pa_loop_write(u->stream_fd, a2dp->buffer, a2dp->count, NULL); + pa_log_debug("avdtp_write: requested %lu bytes; written %li bytes", (unsigned long) a2dp->count, (long) l); + + pa_assert(l != 0); + + if (l > 0) + break; + + if (errno == EINTR) + pa_log_debug("EINTR"); + else if (errno == EAGAIN) + pa_log_debug("EAGAIN"); + else { + pa_log_error("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + return -1; + } + } + + u->offset += a2dp->codesize*a2dp->frame_count; + + /* Reset buffer of data to send */ + a2dp->count = sizeof(struct rtp_header) + sizeof(struct rtp_payload); + a2dp->frame_count = 0; + a2dp->samples = 0; + a2dp->seq_num++; + + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("IO Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + + for (;;) { + int ret, l; + struct pollfd *pollfd; + uint64_t n; + pa_usec_t usec; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) + if (u->sink->thread_info.rewind_requested) + pa_sink_process_rewind(u->sink, 0); + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state) && pollfd->revents) { + if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) { + if ((l = a2dp_process_render(u)) < 0) + goto fail; + } else { + if ((l = sco_process_render(u)) < 0) + goto fail; + } + pollfd->revents = 0; + + /* feed the time smoother */ + n = u->offset; + if (ioctl(u->stream_fd, SIOCOUTQ, &l) >= 0 && l > 0) + n -= (uint64_t) l; + usec = pa_bytes_to_usec(n, &u->sink->sample_spec); + if (usec > u->latency) + usec -= u->latency; + else + usec = 0; + pa_smoother_put(u->smoother, pa_rtclock_usec(), usec); + } + + /* Hmm, nothing to do. Let's sleep */ + pa_log_debug("IO thread going to sleep"); + pollfd->events = (short) (PA_SINK_IS_OPENED(u->sink->thread_info.state) ? POLLOUT : 0); + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) { + pa_log_error("rtpoll_run < 0"); + goto fail; + } + pa_log_debug("IO thread waking up"); + + if (ret == 0) { + pa_log_debug("rtpoll_run == 0"); + goto finish; + } + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + if (pollfd->revents & ~POLLOUT) { + pa_log_error("FIFO shutdown."); + goto fail; + } + } + +fail: + /* If this was no regular exit from the loop we have to continue processing messages until we receive PA_MESSAGE_SHUTDOWN */ + pa_log_debug("IO thread failed"); + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("IO thread shutting down"); +} + +int pa__init(pa_module* m) { + int e; + pa_modargs *ma; + uint32_t channels; + pa_sink_new_data data; + struct pollfd *pollfd; + struct userdata *u; + + pa_assert(m); + m->userdata = u = pa_xnew0(struct userdata, 1); + u->module = m; + u->core = m->core; + u->audioservice_fd = -1; + u->stream_fd = -1; + u->transport = (uint8_t) -1; + u->offset = 0; + u->latency = 0; + u->a2dp.sbc_initialized = FALSE; + u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, u->core->mainloop, u->rtpoll); + u->rtpoll_item = NULL; + u->ss = m->core->default_sample_spec; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log_error("Failed to parse module arguments"); + goto fail; + } + if (!(u->name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)))) { + pa_log_error("Failed to get device name from module arguments"); + goto fail; + } + if (!(u->addr = pa_xstrdup(pa_modargs_get_value(ma, "address", NULL)))) { + pa_log_error("Failed to get device address from module arguments"); + goto fail; + } + if (!(u->profile = pa_xstrdup(pa_modargs_get_value(ma, "profile", NULL)))) { + pa_log_error("Failed to get profile from module arguments"); + goto fail; + } + if (pa_modargs_get_value_u32(ma, "rate", &u->ss.rate) < 0) { + pa_log_error("Failed to get rate from module arguments"); + goto fail; + } + + channels = u->ss.channels; + if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0) { + pa_log_error("Failed to get channels from module arguments"); + goto fail; + } + u->ss.channels = (uint8_t) channels; + + /* connect to the bluez audio service */ + u->audioservice_fd = bt_audio_service_open(); + if (u->audioservice_fd <= 0) { + pa_log_error("Couldn't connect to bluetooth audio service"); + goto fail; + } + pa_log_debug("Connected to the bluetooth audio service"); + + /* queries device capabilities */ + e = bt_getcaps(u); + if (e < 0) { + pa_log_error("Failed to get device capabilities"); + goto fail; + } + pa_log_debug("Got device capabilities"); + + /* configures the connection */ + e = bt_setconf(u); + if (e < 0) { + pa_log_error("Failed to set config"); + goto fail; + } + pa_log_debug("Connection to the device configured"); + + /* gets the device socket */ + e = bt_getstreamfd(u); + if (e < 0) { + pa_log_error("Failed to get stream fd (%d)", e); + goto fail; + } + pa_log_debug("Got the device socket"); + + /* create sink */ + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, u->name); + pa_sink_new_data_set_sample_spec(&data, &u->ss); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, u->name); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Bluetooth %s '%s' (%s)", u->strtransport, u->name, u->addr); + pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_API, "bluez"); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_CLASS, "sound"); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_CONNECTOR, "bluetooth"); +/* pa_proplist_setf(data.proplist, PA_PROP_DEVICE_FORM_FACTOR, "headset"); /\*FIXME*\/ */ +/* pa_proplist_setf(data.proplist, PA_PROP_DEVICE_VENDOR_PRODUCT_ID, "product_id"); /\*FIXME*\/ */ +/* pa_proplist_setf(data.proplist, PA_PROP_DEVICE_SERIAL, "serial"); /\*FIXME*\/ */ + u->sink = pa_sink_new(m->core, &data, PA_SINK_HARDWARE|PA_SINK_LATENCY); + pa_sink_new_data_done(&data); + if (!u->sink) { + pa_log_error("Failed to create sink"); + goto fail; + } + u->sink->userdata = u; + u->sink->parent.process_msg = sink_process_msg; + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->stream_fd; + pollfd->events = pollfd->revents = 0; + + /* start rt thread */ + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log_error("Failed to create IO thread"); + goto fail; + } + pa_sink_put(u->sink); + + pa_modargs_free(ma); + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + return -1; +} + +void pa__done(pa_module *m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + if (u->sink) + pa_sink_unref(u->sink); + + pa_thread_mq_done(&u->thread_mq); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->smoother) + pa_smoother_free(u->smoother); + + pa_xfree(u->name); + pa_xfree(u->addr); + pa_xfree(u->profile); + pa_xfree(u->strtransport); + + if (u->stream_fd >= 0) + pa_close(u->stream_fd); + + if (u->audioservice_fd >= 0) + pa_close(u->audioservice_fd); + + pa_xfree(u); +} diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c new file mode 100644 index 00000000..36c0a35e --- /dev/null +++ b/src/modules/bluetooth/module-bluetooth-discover.c @@ -0,0 +1,558 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Joao Paulo Rechi Vita + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/macro.h> +#include <pulsecore/llist.h> +#include <pulsecore/core-util.h> + +#include "../dbus-util.h" +#include "module-bluetooth-discover-symdef.h" + +PA_MODULE_AUTHOR("Joao Paulo Rechi Vita"); +PA_MODULE_DESCRIPTION("Detect available bluetooth audio devices and load bluetooth audio drivers"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_USAGE(""); + +struct module { + char *profile; + pa_module *pa_m; + PA_LLIST_FIELDS(struct module); +}; + +struct uuid { + char *uuid; + PA_LLIST_FIELDS(struct uuid); +}; + +struct device { + char *name; + char *object_path; + int paired; + char *alias; + int connected; + PA_LLIST_HEAD(struct uuid, uuid_list); + char *address; + int class; + int trusted; + PA_LLIST_HEAD(struct module, module_list); + PA_LLIST_FIELDS(struct device); +}; + +struct userdata { + pa_module *module; + pa_dbus_connection *conn; + PA_LLIST_HEAD(struct device, device_list); +}; + +static struct module *module_new(const char *profile, pa_module *pa_m) { + struct module *m; + + m = pa_xnew(struct module, 1); + m->profile = pa_xstrdup(profile); + m->pa_m = pa_m; + PA_LLIST_INIT(struct module, m); + + return m; +} + +static void module_free(struct module *m) { + pa_assert(m); + + pa_xfree(m->profile); + pa_xfree(m); +} + +static struct module* module_find(struct device *d, const char *profile) { + struct module *m; + + for (m = d->module_list; d; d = d->next) + if (pa_streq(m->profile, profile)) + return m; + + return NULL; +} + +static struct uuid *uuid_new(const char *uuid) { + struct uuid *node; + + node = pa_xnew(struct uuid, 1); + node->uuid = pa_xstrdup(uuid); + PA_LLIST_INIT(struct uuid, node); + + return node; +} + +static void uuid_free(struct uuid *uuid) { + pa_assert(uuid); + + pa_xfree(uuid->uuid); + pa_xfree(uuid); +} + +static struct device *device_new(const char *object_path) { + struct device *node; + + node = pa_xnew(struct device, 1); + node->name = NULL; + node->object_path = pa_xstrdup(object_path); + node->paired = -1; + node->alias = NULL; + node->connected = -1; + PA_LLIST_HEAD_INIT(struct uuid, node->uuid_list); + node->address = NULL; + node->class = -1; + node->trusted = -1; + PA_LLIST_HEAD_INIT(struct module, node->module_list); + PA_LLIST_INIT(struct device, node); + + return node; +} + +static void device_free(struct device *device) { + struct module *m; + struct uuid *i; + + pa_assert(device); + + while ((m = device->module_list)) { + PA_LLIST_REMOVE(struct module, device->module_list, m); + module_free(m); + } + + while ((i = device->uuid_list)) { + PA_LLIST_REMOVE(struct uuid, device->uuid_list, i); + uuid_free(i); + } + + pa_xfree(device->name); + pa_xfree(device->object_path); + pa_xfree(device->alias); + pa_xfree(device->address); + pa_xfree(device); +} + +static struct device* device_find(struct userdata *u, const char *path) { + struct device *i; + + for (i = u->device_list; i; i = i->next) + if (pa_streq(i->object_path, path)) + return i; + + return NULL; +} + +static int parse_device_property(struct userdata *u, struct device *d, DBusMessageIter *i) { + const char *key; + DBusMessageIter variant_i; + + pa_assert(u); + pa_assert(d); + pa_assert(i); + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_STRING) { + pa_log("Property name not a string."); + return -1; + } + + dbus_message_iter_get_basic(i, &key); + + if (!dbus_message_iter_next(i)) { + pa_log("Property value missing"); + return -1; + } + + if (dbus_message_iter_get_arg_type(i) != DBUS_TYPE_VARIANT) { + pa_log("Property value not a variant."); + return -1; + } + + dbus_message_iter_recurse(i, &variant_i); + + pa_log_debug("Parsing device property %s", key); + + switch (dbus_message_iter_get_arg_type(&variant_i)) { + + case DBUS_TYPE_STRING: { + + const char *value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "Name")) { + pa_xfree(d->name); + d->name = pa_xstrdup(value); + } else if (pa_streq(key, "Alias")) { + pa_xfree(d->alias); + d->alias = pa_xstrdup(value); + } else if (pa_streq(key, "Address")) { + pa_xfree(d->address); + d->address = pa_xstrdup(value); + } + + break; + } + + case DBUS_TYPE_BOOLEAN: { + + dbus_bool_t value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "Paired")) + d->paired = !!value; + else if (pa_streq(key, "Connected")) + d->connected = !!value; + else if (pa_streq(key, "Trusted")) + d->trusted = !!value; + + break; + } + + case DBUS_TYPE_UINT32: { + + uint32_t value; + dbus_message_iter_get_basic(&variant_i, &value); + + if (pa_streq(key, "Class")) + d->class = (int) value; + + break; + } + + case DBUS_TYPE_ARRAY: { + + DBusMessageIter ai; + dbus_message_iter_recurse(&variant_i, &ai); + + if (dbus_message_iter_get_arg_type(&ai) == DBUS_TYPE_STRING && + pa_streq(key, "UUIDs")) { + + while (dbus_message_iter_get_arg_type(&ai) != DBUS_TYPE_INVALID) { + struct uuid *node; + const char *value; + + dbus_message_iter_get_basic(&ai, &value); + node = uuid_new(value); + PA_LLIST_PREPEND(struct uuid, d->uuid_list, node); + + if (!dbus_message_iter_next(&ai)) + break; + } + } + + break; + } + } + + return 0; +} + +static int get_device_properties(struct userdata *u, struct device *d) { + DBusError e; + DBusMessage *m = NULL, *r = NULL; + DBusMessageIter arg_i, element_i; + int ret = -1; + + pa_assert(u); + pa_assert(d); + + dbus_error_init(&e); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", d->object_path, "org.bluez.Device", "GetProperties")); + + r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->conn), m, -1, &e); + + if (!r) { + pa_log("org.bluez.Device.GetProperties failed: %s", e.message); + goto finish; + } + + if (!dbus_message_iter_init(r, &arg_i)) { + pa_log("org.bluez.Device.GetProperties reply has no arguments"); + goto finish; + } + + if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_ARRAY) { + pa_log("org.bluez.Device.GetProperties argument is not an array"); + goto finish; + } + + dbus_message_iter_recurse(&arg_i, &element_i); + while (dbus_message_iter_get_arg_type(&element_i) != DBUS_TYPE_INVALID) { + + if (dbus_message_iter_get_arg_type(&element_i) == DBUS_TYPE_DICT_ENTRY) { + DBusMessageIter dict_i; + + dbus_message_iter_recurse(&element_i, &dict_i); + + if (parse_device_property(u, d, &dict_i) < 0) + goto finish; + } + + if (!dbus_message_iter_next(&element_i)) + break; + } + + ret = 0; + +finish: + if (m) + dbus_message_unref(m); + if (r) + dbus_message_unref(r); + + dbus_error_free(&e); + + return ret; +} + +static void load_module_for_device(struct userdata *u, struct device *d, const char *profile) { + char *args; + pa_module *pa_m; + struct module *m; + + pa_assert(u); + pa_assert(d); + + get_device_properties(u, d); + args = pa_sprintf_malloc("sink_name=\"%s\" address=\"%s\" profile=\"%s\"", d->name, d->address, profile); + pa_m = pa_module_load(u->module->core, "module-bluetooth-device", args); + pa_xfree(args); + + if (!pa_m) { + pa_log_debug("Failed to load module for device %s", d->object_path); + return; + } + + m = module_new(profile, pa_m); + PA_LLIST_PREPEND(struct module, d->module_list, m); +} + +static void unload_module_for_device(struct userdata *u, struct device *d, const char *profile) { + struct module *m; + + pa_assert(u); + pa_assert(d); + + if (!(m = module_find(d, profile))) + return; + + pa_module_unload_request(m->pa_m, TRUE); + + PA_LLIST_REMOVE(struct module, d->module_list, m); + module_free(m); +} + +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *msg, void *userdata) { + DBusMessageIter arg_i; + DBusError err; + const char *value; + struct userdata *u; + + pa_assert(bus); + pa_assert(msg); + pa_assert(userdata); + u = userdata; + + dbus_error_init(&err); + + pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", + dbus_message_get_interface(msg), + dbus_message_get_path(msg), + dbus_message_get_member(msg)); + + if (dbus_message_is_signal(msg, "org.bluez.Adapter", "DeviceRemoved")) { + + if (!dbus_message_iter_init(msg, &arg_i)) + pa_log("dbus: message has no parameters"); + else if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_OBJECT_PATH) + pa_log("dbus: argument is not object path"); + else { + struct device *d; + + dbus_message_iter_get_basic(&arg_i, &value); + pa_log_debug("hcid: device %s removed", value); + + if ((d = device_find(u, value))) { + PA_LLIST_REMOVE(struct device, u->device_list, d); + device_free(d); + } + } + + } else if (dbus_message_is_signal(msg, "org.bluez.Headset", "PropertyChanged") || + dbus_message_is_signal(msg, "org.bluez.AudioSink", "PropertyChanged")) { + + struct device *d; + const char *profile; + DBusMessageIter variant_i; + dbus_bool_t connected; + + if (!dbus_message_iter_init(msg, &arg_i)) { + pa_log("dbus: message has no parameters"); + goto done; + } + + if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_STRING) { + pa_log("Property name not a string."); + goto done; + } + + dbus_message_iter_get_basic(&arg_i, &value); + + if (!pa_streq(value, "Connected")) + goto done; + + if (!dbus_message_iter_next(&arg_i)) { + pa_log("Property value missing"); + goto done; + } + + if (dbus_message_iter_get_arg_type(&arg_i) != DBUS_TYPE_VARIANT) { + pa_log("Property value not a variant."); + goto done; + } + + dbus_message_iter_recurse(&arg_i, &variant_i); + + if (dbus_message_iter_get_arg_type(&variant_i) != DBUS_TYPE_BOOLEAN) { + pa_log("Property value not a boolean."); + goto done; + } + + dbus_message_iter_get_basic(&variant_i, &connected); + + if (dbus_message_is_signal(msg, "org.bluez.Headset", "PropertyChanged")) + profile = "hsp"; + else + profile = "a2dp"; + + d = device_find(u, dbus_message_get_path(msg)); + + if (connected) { + if (!d) { + d = device_new(dbus_message_get_path(msg)); + PA_LLIST_PREPEND(struct device, u->device_list, d); + } + + load_module_for_device(u, d, profile); + } else if (d) + unload_module_for_device(u, d, profile); + } + +done: + dbus_error_free(&err); + return DBUS_HANDLER_RESULT_HANDLED; +} + +void pa__done(pa_module* m) { + struct userdata *u; + struct device *i; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + while ((i = u->device_list)) { + PA_LLIST_REMOVE(struct device, u->device_list, i); + device_free(i); + } + + if (u->conn) { + DBusError error; + dbus_error_init(&error); + + dbus_bus_remove_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", &error); + dbus_error_free(&error); + + dbus_bus_remove_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", &error); + dbus_error_free(&error); + + dbus_bus_remove_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", &error); + dbus_error_free(&error); + + dbus_connection_remove_filter(pa_dbus_connection_get(u->conn), filter_cb, u); + + pa_dbus_connection_unref(u->conn); + } + + pa_xfree(u); +} + +int pa__init(pa_module* m) { + DBusError err; + struct userdata *u; + + pa_assert(m); + dbus_error_init(&err); + + m->userdata = u = pa_xnew(struct userdata, 1); + u->module = m; + PA_LLIST_HEAD_INIT(struct device, u->device_list); + + /* connect to the bus */ + u->conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &err); + if (dbus_error_is_set(&err) || (u->conn == NULL) ) { + pa_log("Failed to get D-Bus connection: %s", err.message); + goto fail; + } + + /* dynamic detection of bluetooth audio devices */ + if (!dbus_connection_add_filter(pa_dbus_connection_get(u->conn), filter_cb, u, NULL)) { + pa_log_error("Failed to add filter function"); + goto fail; + } + + dbus_bus_add_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='DeviceRemoved'", &err); + if (dbus_error_is_set(&err)) { + pa_log_error("Unable to subscribe to org.bluez.Adapter signals: %s: %s", err.name, err.message); + goto fail; + } + + dbus_bus_add_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.Headset',member='PropertyChanged'", &err); + if (dbus_error_is_set(&err)) { + pa_log_error("Unable to subscribe to org.bluez.Headset signals: %s: %s", err.name, err.message); + goto fail; + } + + dbus_bus_add_match(pa_dbus_connection_get(u->conn), "type='signal',sender='org.bluez',interface='org.bluez.AudioSink',member='PropertyChanged'", &err); + if (dbus_error_is_set(&err)) { + pa_log_error("Unable to subscribe to org.bluez.AudioSink signals: %s: %s", err.name, err.message); + goto fail; + } + + return 0; + +fail: + dbus_error_free(&err); + pa__done(m); + + return -1; +} diff --git a/src/modules/module-bt-proximity.c b/src/modules/bluetooth/module-bluetooth-proximity.c index f924c3cb..4cfaaf5b 100644 --- a/src/modules/module-bt-proximity.c +++ b/src/modules/bluetooth/module-bluetooth-proximity.c @@ -43,8 +43,8 @@ #include <pulsecore/core-error.h> #include <pulsecore/start-child.h> -#include "dbus-util.h" -#include "module-bt-proximity-symdef.h" +#include "../dbus-util.h" +#include "module-bluetooth-proximity-symdef.h" PA_MODULE_AUTHOR("Lennart Poettering"); PA_MODULE_DESCRIPTION("Bluetooth Proximity Volume Control"); diff --git a/src/modules/bt-proximity-helper.c b/src/modules/bluetooth/proximity-helper.c index 3767f01c..3767f01c 100644 --- a/src/modules/bt-proximity-helper.c +++ b/src/modules/bluetooth/proximity-helper.c diff --git a/src/modules/bluetooth/rtp.h b/src/modules/bluetooth/rtp.h new file mode 100644 index 00000000..690bd43a --- /dev/null +++ b/src/modules/bluetooth/rtp.h @@ -0,0 +1,76 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * 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 library 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +struct rtp_header { + uint8_t cc:4; + uint8_t x:1; + uint8_t p:1; + uint8_t v:2; + + uint8_t pt:7; + uint8_t m:1; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { + uint8_t frame_count:4; + uint8_t rfa0:1; + uint8_t is_last_fragment:1; + uint8_t is_first_fragment:1; + uint8_t is_fragmented:1; +} __attribute__ ((packed)); + +#elif __BYTE_ORDER == __BIG_ENDIAN + +struct rtp_header { + uint8_t v:2; + uint8_t p:1; + uint8_t x:1; + uint8_t cc:4; + + uint8_t m:1; + uint8_t pt:7; + + uint16_t sequence_number; + uint32_t timestamp; + uint32_t ssrc; + uint32_t csrc[0]; +} __attribute__ ((packed)); + +struct rtp_payload { + uint8_t is_fragmented:1; + uint8_t is_first_fragment:1; + uint8_t is_last_fragment:1; + uint8_t rfa0:1; + uint8_t frame_count:4; +} __attribute__ ((packed)); + +#else +#error "Unknown byte order" +#endif diff --git a/src/modules/bluetooth/sbc.c b/src/modules/bluetooth/sbc.c new file mode 100644 index 00000000..02a6143d --- /dev/null +++ b/src/modules/bluetooth/sbc.c @@ -0,0 +1,1411 @@ +/* + * + * Bluetooth low-complexity, subband codec (SBC) library + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> + * Copyright (C) 2005-2008 Brad Midgley <bmidgley@xmission.com> + * + * + * 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 library 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* todo items: + + use a log2 table for byte integer scale factors calculation (sum log2 results + for high and low bytes) fill bitpool by 16 bits instead of one at a time in + bits allocation/bitpool generation port to the dsp + +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> + +#include "sbc_math.h" +#include "sbc_tables.h" + +#include "sbc.h" + +#define SBC_SYNCWORD 0x9C + +/* This structure contains an unpacked SBC frame. + Yes, there is probably quite some unused space herein */ +struct sbc_frame { + uint8_t frequency; + uint8_t block_mode; + uint8_t blocks; + enum { + MONO = SBC_MODE_MONO, + DUAL_CHANNEL = SBC_MODE_DUAL_CHANNEL, + STEREO = SBC_MODE_STEREO, + JOINT_STEREO = SBC_MODE_JOINT_STEREO + } mode; + uint8_t channels; + enum { + LOUDNESS = SBC_AM_LOUDNESS, + SNR = SBC_AM_SNR + } allocation; + uint8_t subband_mode; + uint8_t subbands; + uint8_t bitpool; + uint8_t codesize; + uint8_t length; + + /* bit number x set means joint stereo has been used in subband x */ + uint8_t joint; + + /* only the lower 4 bits of every element are to be used */ + uint8_t scale_factor[2][8]; + + /* raw integer subband samples in the frame */ + + int32_t sb_sample_f[16][2][8]; + int32_t sb_sample[16][2][8]; /* modified subband samples */ + int16_t pcm_sample[2][16*8]; /* original pcm audio samples */ +}; + +struct sbc_decoder_state { + int subbands; + int32_t V[2][170]; + int offset[2][16]; +}; + +struct sbc_encoder_state { + int subbands; + int position[2]; + int32_t X[2][160]; +}; + +/* + * Calculates the CRC-8 of the first len bits in data + */ +static const uint8_t crc_table[256] = { + 0x00, 0x1D, 0x3A, 0x27, 0x74, 0x69, 0x4E, 0x53, + 0xE8, 0xF5, 0xD2, 0xCF, 0x9C, 0x81, 0xA6, 0xBB, + 0xCD, 0xD0, 0xF7, 0xEA, 0xB9, 0xA4, 0x83, 0x9E, + 0x25, 0x38, 0x1F, 0x02, 0x51, 0x4C, 0x6B, 0x76, + 0x87, 0x9A, 0xBD, 0xA0, 0xF3, 0xEE, 0xC9, 0xD4, + 0x6F, 0x72, 0x55, 0x48, 0x1B, 0x06, 0x21, 0x3C, + 0x4A, 0x57, 0x70, 0x6D, 0x3E, 0x23, 0x04, 0x19, + 0xA2, 0xBF, 0x98, 0x85, 0xD6, 0xCB, 0xEC, 0xF1, + 0x13, 0x0E, 0x29, 0x34, 0x67, 0x7A, 0x5D, 0x40, + 0xFB, 0xE6, 0xC1, 0xDC, 0x8F, 0x92, 0xB5, 0xA8, + 0xDE, 0xC3, 0xE4, 0xF9, 0xAA, 0xB7, 0x90, 0x8D, + 0x36, 0x2B, 0x0C, 0x11, 0x42, 0x5F, 0x78, 0x65, + 0x94, 0x89, 0xAE, 0xB3, 0xE0, 0xFD, 0xDA, 0xC7, + 0x7C, 0x61, 0x46, 0x5B, 0x08, 0x15, 0x32, 0x2F, + 0x59, 0x44, 0x63, 0x7E, 0x2D, 0x30, 0x17, 0x0A, + 0xB1, 0xAC, 0x8B, 0x96, 0xC5, 0xD8, 0xFF, 0xE2, + 0x26, 0x3B, 0x1C, 0x01, 0x52, 0x4F, 0x68, 0x75, + 0xCE, 0xD3, 0xF4, 0xE9, 0xBA, 0xA7, 0x80, 0x9D, + 0xEB, 0xF6, 0xD1, 0xCC, 0x9F, 0x82, 0xA5, 0xB8, + 0x03, 0x1E, 0x39, 0x24, 0x77, 0x6A, 0x4D, 0x50, + 0xA1, 0xBC, 0x9B, 0x86, 0xD5, 0xC8, 0xEF, 0xF2, + 0x49, 0x54, 0x73, 0x6E, 0x3D, 0x20, 0x07, 0x1A, + 0x6C, 0x71, 0x56, 0x4B, 0x18, 0x05, 0x22, 0x3F, + 0x84, 0x99, 0xBE, 0xA3, 0xF0, 0xED, 0xCA, 0xD7, + 0x35, 0x28, 0x0F, 0x12, 0x41, 0x5C, 0x7B, 0x66, + 0xDD, 0xC0, 0xE7, 0xFA, 0xA9, 0xB4, 0x93, 0x8E, + 0xF8, 0xE5, 0xC2, 0xDF, 0x8C, 0x91, 0xB6, 0xAB, + 0x10, 0x0D, 0x2A, 0x37, 0x64, 0x79, 0x5E, 0x43, + 0xB2, 0xAF, 0x88, 0x95, 0xC6, 0xDB, 0xFC, 0xE1, + 0x5A, 0x47, 0x60, 0x7D, 0x2E, 0x33, 0x14, 0x09, + 0x7F, 0x62, 0x45, 0x58, 0x0B, 0x16, 0x31, 0x2C, + 0x97, 0x8A, 0xAD, 0xB0, 0xE3, 0xFE, 0xD9, 0xC4 +}; + +static uint8_t sbc_crc8(const uint8_t *data, size_t len) +{ + uint8_t crc = 0x0f; + size_t i; + uint8_t octet; + + for (i = 0; i < len / 8; i++) + crc = crc_table[crc ^ data[i]]; + + octet = data[i]; + for (i = 0; i < len % 8; i++) { + unsigned char bit = ((octet ^ crc) & 0x80) >> 7; + + crc = ((crc & 0x7f) << 1) ^ (bit ? 0x1d : 0); + + octet = octet << 1; + } + + return crc; +} + +/* + * Code straight from the spec to calculate the bits array + * Takes a pointer to the frame in question, a pointer to the bits array and + * the sampling frequency (as 2 bit integer) + */ +static void sbc_calculate_bits(const struct sbc_frame *frame, int (*bits)[8]) +{ + uint8_t sf = frame->frequency; + + if (frame->mode == MONO || frame->mode == DUAL_CHANNEL) { + int bitneed[2][8], loudness, max_bitneed, bitcount, slicecount, bitslice; + int ch, sb; + + for (ch = 0; ch < frame->channels; ch++) { + max_bitneed = 0; + if (frame->allocation == SNR) { + for (sb = 0; sb < frame->subbands; sb++) { + bitneed[ch][sb] = frame->scale_factor[ch][sb]; + if (bitneed[ch][sb] > max_bitneed) + max_bitneed = bitneed[ch][sb]; + } + } else { + for (sb = 0; sb < frame->subbands; sb++) { + if (frame->scale_factor[ch][sb] == 0) + bitneed[ch][sb] = -5; + else { + if (frame->subbands == 4) + loudness = frame->scale_factor[ch][sb] - sbc_offset4[sf][sb]; + else + loudness = frame->scale_factor[ch][sb] - sbc_offset8[sf][sb]; + if (loudness > 0) + bitneed[ch][sb] = loudness / 2; + else + bitneed[ch][sb] = loudness; + } + if (bitneed[ch][sb] > max_bitneed) + max_bitneed = bitneed[ch][sb]; + } + } + + bitcount = 0; + slicecount = 0; + bitslice = max_bitneed + 1; + do { + bitslice--; + bitcount += slicecount; + slicecount = 0; + for (sb = 0; sb < frame->subbands; sb++) { + if ((bitneed[ch][sb] > bitslice + 1) && (bitneed[ch][sb] < bitslice + 16)) + slicecount++; + else if (bitneed[ch][sb] == bitslice + 1) + slicecount += 2; + } + } while (bitcount + slicecount < frame->bitpool); + + if (bitcount + slicecount == frame->bitpool) { + bitcount += slicecount; + bitslice--; + } + + for (sb = 0; sb < frame->subbands; sb++) { + if (bitneed[ch][sb] < bitslice + 2) + bits[ch][sb] = 0; + else { + bits[ch][sb] = bitneed[ch][sb] - bitslice; + if (bits[ch][sb] > 16) + bits[ch][sb] = 16; + } + } + + for (sb = 0; bitcount < frame->bitpool && sb < frame->subbands; sb++) { + if ((bits[ch][sb] >= 2) && (bits[ch][sb] < 16)) { + bits[ch][sb]++; + bitcount++; + } else if ((bitneed[ch][sb] == bitslice + 1) && (frame->bitpool > bitcount + 1)) { + bits[ch][sb] = 2; + bitcount += 2; + } + } + + for (sb = 0; bitcount < frame->bitpool && sb < frame->subbands; sb++) { + if (bits[ch][sb] < 16) { + bits[ch][sb]++; + bitcount++; + } + } + + } + + } else if (frame->mode == STEREO || frame->mode == JOINT_STEREO) { + int bitneed[2][8], loudness, max_bitneed, bitcount, slicecount, bitslice; + int ch, sb; + + max_bitneed = 0; + if (frame->allocation == SNR) { + for (ch = 0; ch < 2; ch++) { + for (sb = 0; sb < frame->subbands; sb++) { + bitneed[ch][sb] = frame->scale_factor[ch][sb]; + if (bitneed[ch][sb] > max_bitneed) + max_bitneed = bitneed[ch][sb]; + } + } + } else { + for (ch = 0; ch < 2; ch++) { + for (sb = 0; sb < frame->subbands; sb++) { + if (frame->scale_factor[ch][sb] == 0) + bitneed[ch][sb] = -5; + else { + if (frame->subbands == 4) + loudness = frame->scale_factor[ch][sb] - sbc_offset4[sf][sb]; + else + loudness = frame->scale_factor[ch][sb] - sbc_offset8[sf][sb]; + if (loudness > 0) + bitneed[ch][sb] = loudness / 2; + else + bitneed[ch][sb] = loudness; + } + if (bitneed[ch][sb] > max_bitneed) + max_bitneed = bitneed[ch][sb]; + } + } + } + + bitcount = 0; + slicecount = 0; + bitslice = max_bitneed + 1; + do { + bitslice--; + bitcount += slicecount; + slicecount = 0; + for (ch = 0; ch < 2; ch++) { + for (sb = 0; sb < frame->subbands; sb++) { + if ((bitneed[ch][sb] > bitslice + 1) && (bitneed[ch][sb] < bitslice + 16)) + slicecount++; + else if (bitneed[ch][sb] == bitslice + 1) + slicecount += 2; + } + } + } while (bitcount + slicecount < frame->bitpool); + + if (bitcount + slicecount == frame->bitpool) { + bitcount += slicecount; + bitslice--; + } + + for (ch = 0; ch < 2; ch++) { + for (sb = 0; sb < frame->subbands; sb++) { + if (bitneed[ch][sb] < bitslice + 2) { + bits[ch][sb] = 0; + } else { + bits[ch][sb] = bitneed[ch][sb] - bitslice; + if (bits[ch][sb] > 16) + bits[ch][sb] = 16; + } + } + } + + ch = 0; + sb = 0; + while (bitcount < frame->bitpool) { + if ((bits[ch][sb] >= 2) && (bits[ch][sb] < 16)) { + bits[ch][sb]++; + bitcount++; + } else if ((bitneed[ch][sb] == bitslice + 1) && (frame->bitpool > bitcount + 1)) { + bits[ch][sb] = 2; + bitcount += 2; + } + if (ch == 1) { + ch = 0; + sb++; + if (sb >= frame->subbands) break; + } else + ch = 1; + } + + ch = 0; + sb = 0; + while (bitcount < frame->bitpool) { + if (bits[ch][sb] < 16) { + bits[ch][sb]++; + bitcount++; + } + if (ch == 1) { + ch = 0; + sb++; + if (sb >= frame->subbands) break; + } else + ch = 1; + } + + } + +} + +/* + * Unpacks a SBC frame at the beginning of the stream in data, + * which has at most len bytes into frame. + * Returns the length in bytes of the packed frame, or a negative + * value on error. The error codes are: + * + * -1 Data stream too short + * -2 Sync byte incorrect + * -3 CRC8 incorrect + * -4 Bitpool value out of bounds + */ +static int sbc_unpack_frame(const uint8_t *data, struct sbc_frame *frame, + size_t len) +{ + int consumed; + /* Will copy the parts of the header that are relevant to crc + * calculation here */ + uint8_t crc_header[11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int crc_pos = 0; + int32_t temp; + + int audio_sample; + int ch, sb, blk, bit; /* channel, subband, block and bit standard + counters */ + int bits[2][8]; /* bits distribution */ + uint32_t levels[2][8]; /* levels derived from that */ + + if (len < 4) + return -1; + + if (data[0] != SBC_SYNCWORD) + return -2; + + frame->frequency = (data[1] >> 6) & 0x03; + + frame->block_mode = (data[1] >> 4) & 0x03; + switch (frame->block_mode) { + case SBC_BLK_4: + frame->blocks = 4; + break; + case SBC_BLK_8: + frame->blocks = 8; + break; + case SBC_BLK_12: + frame->blocks = 12; + break; + case SBC_BLK_16: + frame->blocks = 16; + break; + } + + frame->mode = (data[1] >> 2) & 0x03; + switch (frame->mode) { + case MONO: + frame->channels = 1; + break; + case DUAL_CHANNEL: /* fall-through */ + case STEREO: + case JOINT_STEREO: + frame->channels = 2; + break; + } + + frame->allocation = (data[1] >> 1) & 0x01; + + frame->subband_mode = (data[1] & 0x01); + frame->subbands = frame->subband_mode ? 8 : 4; + + frame->bitpool = data[2]; + + if ((frame->mode == MONO || frame->mode == DUAL_CHANNEL) && + frame->bitpool > 16 * frame->subbands) + return -4; + + if ((frame->mode == STEREO || frame->mode == JOINT_STEREO) && + frame->bitpool > 32 * frame->subbands) + return -4; + + /* data[3] is crc, we're checking it later */ + + consumed = 32; + + crc_header[0] = data[1]; + crc_header[1] = data[2]; + crc_pos = 16; + + if (frame->mode == JOINT_STEREO) { + if (len * 8 < consumed + frame->subbands) + return -1; + + frame->joint = 0x00; + for (sb = 0; sb < frame->subbands - 1; sb++) + frame->joint |= ((data[4] >> (7 - sb)) & 0x01) << sb; + if (frame->subbands == 4) + crc_header[crc_pos / 8] = data[4] & 0xf0; + else + crc_header[crc_pos / 8] = data[4]; + + consumed += frame->subbands; + crc_pos += frame->subbands; + } + + if (len * 8 < consumed + (4 * frame->subbands * frame->channels)) + return -1; + + for (ch = 0; ch < frame->channels; ch++) { + for (sb = 0; sb < frame->subbands; sb++) { + /* FIXME assert(consumed % 4 == 0); */ + frame->scale_factor[ch][sb] = + (data[consumed >> 3] >> (4 - (consumed & 0x7))) & 0x0F; + crc_header[crc_pos >> 3] |= + frame->scale_factor[ch][sb] << (4 - (crc_pos & 0x7)); + + consumed += 4; + crc_pos += 4; + } + } + + if (data[3] != sbc_crc8(crc_header, crc_pos)) + return -3; + + sbc_calculate_bits(frame, bits); + + for (ch = 0; ch < frame->channels; ch++) { + for (sb = 0; sb < frame->subbands; sb++) + levels[ch][sb] = (1 << bits[ch][sb]) - 1; + } + + for (blk = 0; blk < frame->blocks; blk++) { + for (ch = 0; ch < frame->channels; ch++) { + for (sb = 0; sb < frame->subbands; sb++) { + if (levels[ch][sb] > 0) { + audio_sample = 0; + for (bit = 0; bit < bits[ch][sb]; bit++) { + if (consumed > len * 8) + return -1; + + if ((data[consumed >> 3] >> (7 - (consumed & 0x7))) & 0x01) + audio_sample |= 1 << (bits[ch][sb] - bit - 1); + + consumed++; + } + + frame->sb_sample[blk][ch][sb] = + (((audio_sample << 1) | 1) << frame->scale_factor[ch][sb]) / + levels[ch][sb] - (1 << frame->scale_factor[ch][sb]); + } else + frame->sb_sample[blk][ch][sb] = 0; + } + } + } + + if (frame->mode == JOINT_STEREO) { + for (blk = 0; blk < frame->blocks; blk++) { + for (sb = 0; sb < frame->subbands; sb++) { + if (frame->joint & (0x01 << sb)) { + temp = frame->sb_sample[blk][0][sb] + + frame->sb_sample[blk][1][sb]; + frame->sb_sample[blk][1][sb] = + frame->sb_sample[blk][0][sb] - + frame->sb_sample[blk][1][sb]; + frame->sb_sample[blk][0][sb] = temp; + } + } + } + } + + if ((consumed & 0x7) != 0) + consumed += 8 - (consumed & 0x7); + + return consumed >> 3; +} + +static void sbc_decoder_init(struct sbc_decoder_state *state, + const struct sbc_frame *frame) +{ + int i, ch; + + memset(state->V, 0, sizeof(state->V)); + state->subbands = frame->subbands; + + for (ch = 0; ch < 2; ch++) + for (i = 0; i < frame->subbands * 2; i++) + state->offset[ch][i] = (10 * i + 10); +} + +static inline void sbc_synthesize_four(struct sbc_decoder_state *state, + struct sbc_frame *frame, int ch, int blk) +{ + int i, k, idx; + int32_t *v = state->V[ch]; + int *offset = state->offset[ch]; + + for (i = 0; i < 8; i++) { + /* Shifting */ + offset[i]--; + if (offset[i] < 0) { + offset[i] = 79; + memcpy(v + 80, v, 9 * sizeof(*v)); + } + + /* Distribute the new matrix value to the shifted position */ + v[offset[i]] = SCALE4_STAGED1( + MULA(synmatrix4[i][0], frame->sb_sample[blk][ch][0], + MULA(synmatrix4[i][1], frame->sb_sample[blk][ch][1], + MULA(synmatrix4[i][2], frame->sb_sample[blk][ch][2], + MUL (synmatrix4[i][3], frame->sb_sample[blk][ch][3]))))); + } + + /* Compute the samples */ + for (idx = 0, i = 0; i < 4; i++, idx += 5) { + k = (i + 4) & 0xf; + + /* Store in output, Q0 */ + frame->pcm_sample[ch][blk * 4 + i] = SCALE4_STAGED2( + MULA(v[offset[i] + 0], sbc_proto_4_40m0[idx + 0], + MULA(v[offset[k] + 1], sbc_proto_4_40m1[idx + 0], + MULA(v[offset[i] + 2], sbc_proto_4_40m0[idx + 1], + MULA(v[offset[k] + 3], sbc_proto_4_40m1[idx + 1], + MULA(v[offset[i] + 4], sbc_proto_4_40m0[idx + 2], + MULA(v[offset[k] + 5], sbc_proto_4_40m1[idx + 2], + MULA(v[offset[i] + 6], sbc_proto_4_40m0[idx + 3], + MULA(v[offset[k] + 7], sbc_proto_4_40m1[idx + 3], + MULA(v[offset[i] + 8], sbc_proto_4_40m0[idx + 4], + MUL( v[offset[k] + 9], sbc_proto_4_40m1[idx + 4]))))))))))); + } +} + +static inline void sbc_synthesize_eight(struct sbc_decoder_state *state, + struct sbc_frame *frame, int ch, int blk) +{ + int i, j, k, idx; + int *offset = state->offset[ch]; + + for (i = 0; i < 16; i++) { + /* Shifting */ + offset[i]--; + if (offset[i] < 0) { + offset[i] = 159; + for (j = 0; j < 9; j++) + state->V[ch][j + 160] = state->V[ch][j]; + } + + /* Distribute the new matrix value to the shifted position */ + state->V[ch][offset[i]] = SCALE8_STAGED1( + MULA(synmatrix8[i][0], frame->sb_sample[blk][ch][0], + MULA(synmatrix8[i][1], frame->sb_sample[blk][ch][1], + MULA(synmatrix8[i][2], frame->sb_sample[blk][ch][2], + MULA(synmatrix8[i][3], frame->sb_sample[blk][ch][3], + MULA(synmatrix8[i][4], frame->sb_sample[blk][ch][4], + MULA(synmatrix8[i][5], frame->sb_sample[blk][ch][5], + MULA(synmatrix8[i][6], frame->sb_sample[blk][ch][6], + MUL( synmatrix8[i][7], frame->sb_sample[blk][ch][7]))))))))); + } + + /* Compute the samples */ + for (idx = 0, i = 0; i < 8; i++, idx += 5) { + k = (i + 8) & 0xf; + + /* Store in output */ + frame->pcm_sample[ch][blk * 8 + i] = SCALE8_STAGED2( // Q0 + MULA(state->V[ch][offset[i] + 0], sbc_proto_8_80m0[idx + 0], + MULA(state->V[ch][offset[k] + 1], sbc_proto_8_80m1[idx + 0], + MULA(state->V[ch][offset[i] + 2], sbc_proto_8_80m0[idx + 1], + MULA(state->V[ch][offset[k] + 3], sbc_proto_8_80m1[idx + 1], + MULA(state->V[ch][offset[i] + 4], sbc_proto_8_80m0[idx + 2], + MULA(state->V[ch][offset[k] + 5], sbc_proto_8_80m1[idx + 2], + MULA(state->V[ch][offset[i] + 6], sbc_proto_8_80m0[idx + 3], + MULA(state->V[ch][offset[k] + 7], sbc_proto_8_80m1[idx + 3], + MULA(state->V[ch][offset[i] + 8], sbc_proto_8_80m0[idx + 4], + MUL( state->V[ch][offset[k] + 9], sbc_proto_8_80m1[idx + 4]))))))))))); + } +} + +static int sbc_synthesize_audio(struct sbc_decoder_state *state, + struct sbc_frame *frame) +{ + int ch, blk; + + switch (frame->subbands) { + case 4: + for (ch = 0; ch < frame->channels; ch++) { + for (blk = 0; blk < frame->blocks; blk++) + sbc_synthesize_four(state, frame, ch, blk); + } + return frame->blocks * 4; + + case 8: + for (ch = 0; ch < frame->channels; ch++) { + for (blk = 0; blk < frame->blocks; blk++) + sbc_synthesize_eight(state, frame, ch, blk); + } + return frame->blocks * 8; + + default: + return -EIO; + } +} + +static void sbc_encoder_init(struct sbc_encoder_state *state, + const struct sbc_frame *frame) +{ + memset(&state->X, 0, sizeof(state->X)); + state->subbands = frame->subbands; + state->position[0] = state->position[1] = 9 * frame->subbands; +} + +static inline void _sbc_analyze_four(const int32_t *in, int32_t *out) +{ + sbc_fixed_t t[8], s[5]; + + t[0] = SCALE4_STAGE1( /* Q8 */ + MULA(_sbc_proto_4[0], in[8] - in[32], /* Q18 */ + MUL( _sbc_proto_4[1], in[16] - in[24]))); + + t[1] = SCALE4_STAGE1( + MULA(_sbc_proto_4[2], in[1], + MULA(_sbc_proto_4[3], in[9], + MULA(_sbc_proto_4[4], in[17], + MULA(_sbc_proto_4[5], in[25], + MUL( _sbc_proto_4[6], in[33])))))); + + t[2] = SCALE4_STAGE1( + MULA(_sbc_proto_4[7], in[2], + MULA(_sbc_proto_4[8], in[10], + MULA(_sbc_proto_4[9], in[18], + MULA(_sbc_proto_4[10], in[26], + MUL( _sbc_proto_4[11], in[34])))))); + + t[3] = SCALE4_STAGE1( + MULA(_sbc_proto_4[12], in[3], + MULA(_sbc_proto_4[13], in[11], + MULA(_sbc_proto_4[14], in[19], + MULA(_sbc_proto_4[15], in[27], + MUL( _sbc_proto_4[16], in[35])))))); + + t[4] = SCALE4_STAGE1( + MULA(_sbc_proto_4[17], in[4] + in[36], + MULA(_sbc_proto_4[18], in[12] + in[28], + MUL( _sbc_proto_4[19], in[20])))); + + t[5] = SCALE4_STAGE1( + MULA(_sbc_proto_4[16], in[5], + MULA(_sbc_proto_4[15], in[13], + MULA(_sbc_proto_4[14], in[21], + MULA(_sbc_proto_4[13], in[29], + MUL( _sbc_proto_4[12], in[37])))))); + + /* don't compute t[6]... this term always multiplies + * with cos(pi/2) = 0 */ + + t[7] = SCALE4_STAGE1( + MULA(_sbc_proto_4[6], in[7], + MULA(_sbc_proto_4[5], in[15], + MULA(_sbc_proto_4[4], in[23], + MULA(_sbc_proto_4[3], in[31], + MUL( _sbc_proto_4[2], in[39])))))); + + s[0] = MUL( _anamatrix4[0], t[0] + t[4]); + s[1] = MUL( _anamatrix4[2], t[2]); + s[2] = MULA(_anamatrix4[1], t[1] + t[3], + MUL(_anamatrix4[3], t[5])); + s[3] = MULA(_anamatrix4[3], t[1] + t[3], + MUL(_anamatrix4[1], -t[5] + t[7])); + s[4] = MUL( _anamatrix4[3], t[7]); + + out[0] = SCALE4_STAGE2( s[0] + s[1] + s[2] + s[4]); /* Q0 */ + out[1] = SCALE4_STAGE2(-s[0] + s[1] + s[3]); + out[2] = SCALE4_STAGE2(-s[0] + s[1] - s[3]); + out[3] = SCALE4_STAGE2( s[0] + s[1] - s[2] - s[4]); +} + +static inline void sbc_analyze_four(struct sbc_encoder_state *state, + struct sbc_frame *frame, int ch, int blk) +{ + int32_t *x = &state->X[ch][state->position[ch]]; + int16_t *pcm = &frame->pcm_sample[ch][blk * 4]; + + /* Input 4 Audio Samples */ + x[40] = x[0] = pcm[3]; + x[41] = x[1] = pcm[2]; + x[42] = x[2] = pcm[1]; + x[43] = x[3] = pcm[0]; + + _sbc_analyze_four(x, frame->sb_sample_f[blk][ch]); + + state->position[ch] -= 4; + if (state->position[ch] < 0) + state->position[ch] = 36; +} + +static inline void _sbc_analyze_eight(const int32_t *in, int32_t *out) +{ + sbc_fixed_t t[8], s[8]; + + t[0] = SCALE8_STAGE1( /* Q10 */ + MULA(_sbc_proto_8[0], (in[16] - in[64]), /* Q18 = Q18 * Q0 */ + MULA(_sbc_proto_8[1], (in[32] - in[48]), + MULA(_sbc_proto_8[2], in[4], + MULA(_sbc_proto_8[3], in[20], + MULA(_sbc_proto_8[4], in[36], + MUL( _sbc_proto_8[5], in[52]))))))); + + t[1] = SCALE8_STAGE1( + MULA(_sbc_proto_8[6], in[2], + MULA(_sbc_proto_8[7], in[18], + MULA(_sbc_proto_8[8], in[34], + MULA(_sbc_proto_8[9], in[50], + MUL(_sbc_proto_8[10], in[66])))))); + + t[2] = SCALE8_STAGE1( + MULA(_sbc_proto_8[11], in[1], + MULA(_sbc_proto_8[12], in[17], + MULA(_sbc_proto_8[13], in[33], + MULA(_sbc_proto_8[14], in[49], + MULA(_sbc_proto_8[15], in[65], + MULA(_sbc_proto_8[16], in[3], + MULA(_sbc_proto_8[17], in[19], + MULA(_sbc_proto_8[18], in[35], + MULA(_sbc_proto_8[19], in[51], + MUL( _sbc_proto_8[20], in[67]))))))))))); + + t[3] = SCALE8_STAGE1( + MULA( _sbc_proto_8[21], in[5], + MULA( _sbc_proto_8[22], in[21], + MULA( _sbc_proto_8[23], in[37], + MULA( _sbc_proto_8[24], in[53], + MULA( _sbc_proto_8[25], in[69], + MULA(-_sbc_proto_8[15], in[15], + MULA(-_sbc_proto_8[14], in[31], + MULA(-_sbc_proto_8[13], in[47], + MULA(-_sbc_proto_8[12], in[63], + MUL( -_sbc_proto_8[11], in[79]))))))))))); + + t[4] = SCALE8_STAGE1( + MULA( _sbc_proto_8[26], in[6], + MULA( _sbc_proto_8[27], in[22], + MULA( _sbc_proto_8[28], in[38], + MULA( _sbc_proto_8[29], in[54], + MULA( _sbc_proto_8[30], in[70], + MULA(-_sbc_proto_8[10], in[14], + MULA(-_sbc_proto_8[9], in[30], + MULA(-_sbc_proto_8[8], in[46], + MULA(-_sbc_proto_8[7], in[62], + MUL( -_sbc_proto_8[6], in[78]))))))))))); + + t[5] = SCALE8_STAGE1( + MULA( _sbc_proto_8[31], in[7], + MULA( _sbc_proto_8[32], in[23], + MULA( _sbc_proto_8[33], in[39], + MULA( _sbc_proto_8[34], in[55], + MULA( _sbc_proto_8[35], in[71], + MULA(-_sbc_proto_8[20], in[13], + MULA(-_sbc_proto_8[19], in[29], + MULA(-_sbc_proto_8[18], in[45], + MULA(-_sbc_proto_8[17], in[61], + MUL( -_sbc_proto_8[16], in[77]))))))))))); + + t[6] = SCALE8_STAGE1( + MULA( _sbc_proto_8[36], (in[8] + in[72]), + MULA( _sbc_proto_8[37], (in[24] + in[56]), + MULA( _sbc_proto_8[38], in[40], + MULA(-_sbc_proto_8[39], in[12], + MULA(-_sbc_proto_8[5], in[28], + MULA(-_sbc_proto_8[4], in[44], + MULA(-_sbc_proto_8[3], in[60], + MUL( -_sbc_proto_8[2], in[76]))))))))); + + t[7] = SCALE8_STAGE1( + MULA( _sbc_proto_8[35], in[9], + MULA( _sbc_proto_8[34], in[25], + MULA( _sbc_proto_8[33], in[41], + MULA( _sbc_proto_8[32], in[57], + MULA( _sbc_proto_8[31], in[73], + MULA(-_sbc_proto_8[25], in[11], + MULA(-_sbc_proto_8[24], in[27], + MULA(-_sbc_proto_8[23], in[43], + MULA(-_sbc_proto_8[22], in[59], + MUL( -_sbc_proto_8[21], in[75]))))))))))); + + s[0] = MULA( _anamatrix8[0], t[0], + MUL( _anamatrix8[1], t[6])); + s[1] = MUL( _anamatrix8[7], t[1]); + s[2] = MULA( _anamatrix8[2], t[2], + MULA( _anamatrix8[3], t[3], + MULA( _anamatrix8[4], t[5], + MUL( _anamatrix8[5], t[7])))); + s[3] = MUL( _anamatrix8[6], t[4]); + s[4] = MULA( _anamatrix8[3], t[2], + MULA(-_anamatrix8[5], t[3], + MULA(-_anamatrix8[2], t[5], + MUL( -_anamatrix8[4], t[7])))); + s[5] = MULA( _anamatrix8[4], t[2], + MULA(-_anamatrix8[2], t[3], + MULA( _anamatrix8[5], t[5], + MUL( _anamatrix8[3], t[7])))); + s[6] = MULA( _anamatrix8[1], t[0], + MUL( -_anamatrix8[0], t[6])); + s[7] = MULA( _anamatrix8[5], t[2], + MULA(-_anamatrix8[4], t[3], + MULA( _anamatrix8[3], t[5], + MUL( -_anamatrix8[2], t[7])))); + + out[0] = SCALE8_STAGE2( s[0] + s[1] + s[2] + s[3]); + out[1] = SCALE8_STAGE2( s[1] - s[3] + s[4] + s[6]); + out[2] = SCALE8_STAGE2( s[1] - s[3] + s[5] - s[6]); + out[3] = SCALE8_STAGE2(-s[0] + s[1] + s[3] + s[7]); + out[4] = SCALE8_STAGE2(-s[0] + s[1] + s[3] - s[7]); + out[5] = SCALE8_STAGE2( s[1] - s[3] - s[5] - s[6]); + out[6] = SCALE8_STAGE2( s[1] - s[3] - s[4] + s[6]); + out[7] = SCALE8_STAGE2( s[0] + s[1] - s[2] + s[3]); +} + +static inline void sbc_analyze_eight(struct sbc_encoder_state *state, + struct sbc_frame *frame, int ch, + int blk) +{ + int32_t *x = &state->X[ch][state->position[ch]]; + int16_t *pcm = &frame->pcm_sample[ch][blk * 8]; + + /* Input 8 Audio Samples */ + x[80] = x[0] = pcm[7]; + x[81] = x[1] = pcm[6]; + x[82] = x[2] = pcm[5]; + x[83] = x[3] = pcm[4]; + x[84] = x[4] = pcm[3]; + x[85] = x[5] = pcm[2]; + x[86] = x[6] = pcm[1]; + x[87] = x[7] = pcm[0]; + + _sbc_analyze_eight(x, frame->sb_sample_f[blk][ch]); + + state->position[ch] -= 8; + if (state->position[ch] < 0) + state->position[ch] = 72; +} + +static int sbc_analyze_audio(struct sbc_encoder_state *state, + struct sbc_frame *frame) +{ + int ch, blk; + + switch (frame->subbands) { + case 4: + for (ch = 0; ch < frame->channels; ch++) + for (blk = 0; blk < frame->blocks; blk++) + sbc_analyze_four(state, frame, ch, blk); + return frame->blocks * 4; + + case 8: + for (ch = 0; ch < frame->channels; ch++) + for (blk = 0; blk < frame->blocks; blk++) + sbc_analyze_eight(state, frame, ch, blk); + return frame->blocks * 8; + + default: + return -EIO; + } +} + +/* + * Packs the SBC frame from frame into the memory at data. At most len + * bytes will be used, should more memory be needed an appropriate + * error code will be returned. Returns the length of the packed frame + * on success or a negative value on error. + * + * The error codes are: + * -1 Not enough memory reserved + * -2 Unsupported sampling rate + * -3 Unsupported number of blocks + * -4 Unsupported number of subbands + * -5 Bitpool value out of bounds + * -99 not implemented + */ + +static int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len) +{ + int produced; + /* Will copy the header parts for CRC-8 calculation here */ + uint8_t crc_header[11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + int crc_pos = 0; + + uint16_t audio_sample; + + int ch, sb, blk, bit; /* channel, subband, block and bit counters */ + int bits[2][8]; /* bits distribution */ + int levels[2][8]; /* levels are derived from that */ + + u_int32_t scalefactor[2][8]; /* derived from frame->scale_factor */ + + data[0] = SBC_SYNCWORD; + + data[1] = (frame->frequency & 0x03) << 6; + + data[1] |= (frame->block_mode & 0x03) << 4; + + data[1] |= (frame->mode & 0x03) << 2; + + data[1] |= (frame->allocation & 0x01) << 1; + + switch (frame->subbands) { + case 4: + /* Nothing to do */ + break; + case 8: + data[1] |= 0x01; + break; + default: + return -4; + break; + } + + data[2] = frame->bitpool; + + if ((frame->mode == MONO || frame->mode == DUAL_CHANNEL) && + frame->bitpool > frame->subbands << 4) + return -5; + + if ((frame->mode == STEREO || frame->mode == JOINT_STEREO) && + frame->bitpool > frame->subbands << 5) + return -5; + + /* Can't fill in crc yet */ + + produced = 32; + + crc_header[0] = data[1]; + crc_header[1] = data[2]; + crc_pos = 16; + + for (ch = 0; ch < frame->channels; ch++) { + for (sb = 0; sb < frame->subbands; sb++) { + frame->scale_factor[ch][sb] = 0; + scalefactor[ch][sb] = 2; + for (blk = 0; blk < frame->blocks; blk++) { + while (scalefactor[ch][sb] < fabs(frame->sb_sample_f[blk][ch][sb])) { + frame->scale_factor[ch][sb]++; + scalefactor[ch][sb] *= 2; + } + } + } + } + + if (frame->mode == JOINT_STEREO) { + /* like frame->sb_sample but joint stereo */ + int32_t sb_sample_j[16][2]; + /* scalefactor and scale_factor in joint case */ + u_int32_t scalefactor_j[2]; + uint8_t scale_factor_j[2]; + + frame->joint = 0; + + for (sb = 0; sb < frame->subbands - 1; sb++) { + scale_factor_j[0] = 0; + scalefactor_j[0] = 2; + scale_factor_j[1] = 0; + scalefactor_j[1] = 2; + + for (blk = 0; blk < frame->blocks; blk++) { + /* Calculate joint stereo signal */ + sb_sample_j[blk][0] = + (frame->sb_sample_f[blk][0][sb] + + frame->sb_sample_f[blk][1][sb]) >> 1; + sb_sample_j[blk][1] = + (frame->sb_sample_f[blk][0][sb] - + frame->sb_sample_f[blk][1][sb]) >> 1; + + /* calculate scale_factor_j and scalefactor_j for joint case */ + while (scalefactor_j[0] < fabs(sb_sample_j[blk][0])) { + scale_factor_j[0]++; + scalefactor_j[0] *= 2; + } + while (scalefactor_j[1] < fabs(sb_sample_j[blk][1])) { + scale_factor_j[1]++; + scalefactor_j[1] *= 2; + } + } + + /* decide whether to join this subband */ + if ((scalefactor[0][sb] + scalefactor[1][sb]) > + (scalefactor_j[0] + scalefactor_j[1]) ) { + /* use joint stereo for this subband */ + frame->joint |= 1 << sb; + frame->scale_factor[0][sb] = scale_factor_j[0]; + frame->scale_factor[1][sb] = scale_factor_j[1]; + scalefactor[0][sb] = scalefactor_j[0]; + scalefactor[1][sb] = scalefactor_j[1]; + for (blk = 0; blk < frame->blocks; blk++) { + frame->sb_sample_f[blk][0][sb] = + sb_sample_j[blk][0]; + frame->sb_sample_f[blk][1][sb] = + sb_sample_j[blk][1]; + } + } + } + + data[4] = 0; + for (sb = 0; sb < frame->subbands - 1; sb++) + data[4] |= ((frame->joint >> sb) & 0x01) << (frame->subbands - 1 - sb); + + crc_header[crc_pos >> 3] = data[4]; + + produced += frame->subbands; + crc_pos += frame->subbands; + } + + for (ch = 0; ch < frame->channels; ch++) { + for (sb = 0; sb < frame->subbands; sb++) { + data[produced >> 3] <<= 4; + crc_header[crc_pos >> 3] <<= 4; + data[produced >> 3] |= frame->scale_factor[ch][sb] & 0x0F; + crc_header[crc_pos >> 3] |= frame->scale_factor[ch][sb] & 0x0F; + + produced += 4; + crc_pos += 4; + } + } + + /* align the last crc byte */ + if (crc_pos % 8) + crc_header[crc_pos >> 3] <<= 8 - (crc_pos % 8); + + data[3] = sbc_crc8(crc_header, crc_pos); + + sbc_calculate_bits(frame, bits); + + for (ch = 0; ch < frame->channels; ch++) { + for (sb = 0; sb < frame->subbands; sb++) + levels[ch][sb] = (1 << bits[ch][sb]) - 1; + } + + for (blk = 0; blk < frame->blocks; blk++) { + for (ch = 0; ch < frame->channels; ch++) { + for (sb = 0; sb < frame->subbands; sb++) { + if (levels[ch][sb] > 0) { + audio_sample = + (uint16_t) ((((frame->sb_sample_f[blk][ch][sb]*levels[ch][sb]) >> + (frame->scale_factor[ch][sb] + 1)) + + levels[ch][sb]) >> 1); + audio_sample <<= 16 - bits[ch][sb]; + for (bit = 0; bit < bits[ch][sb]; bit++) { + data[produced >> 3] <<= 1; + if (audio_sample & 0x8000) + data[produced >> 3] |= 0x1; + audio_sample <<= 1; + produced++; + } + } + } + } + } + + /* align the last byte */ + if (produced % 8) { + data[produced >> 3] <<= 8 - (produced % 8); + } + + return (produced + 7) >> 3; +} + +struct sbc_priv { + int init; + struct sbc_frame frame; + struct sbc_decoder_state dec_state; + struct sbc_encoder_state enc_state; +}; + +static void sbc_set_defaults(sbc_t *sbc, unsigned long flags) +{ + sbc->frequency = SBC_FREQ_44100; + sbc->mode = SBC_MODE_STEREO; + sbc->subbands = SBC_SB_8; + sbc->blocks = SBC_BLK_16; + sbc->bitpool = 32; +#if __BYTE_ORDER == __LITTLE_ENDIAN + sbc->endian = SBC_LE; +#elif __BYTE_ORDER == __BIG_ENDIAN + sbc->endian = SBC_BE; +#else +#error "Unknown byte order" +#endif +} + +int sbc_init(sbc_t *sbc, unsigned long flags) +{ + if (!sbc) + return -EIO; + + memset(sbc, 0, sizeof(sbc_t)); + + sbc->priv = malloc(sizeof(struct sbc_priv)); + if (!sbc->priv) + return -ENOMEM; + + memset(sbc->priv, 0, sizeof(struct sbc_priv)); + + sbc_set_defaults(sbc, flags); + + return 0; +} + +int sbc_parse(sbc_t *sbc, void *input, int input_len) +{ + return sbc_decode(sbc, input, input_len, NULL, 0, NULL); +} + +int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output, + int output_len, int *written) +{ + struct sbc_priv *priv; + char *ptr; + int i, ch, framelen, samples; + + if (!sbc && !input) + return -EIO; + + priv = sbc->priv; + + framelen = sbc_unpack_frame(input, &priv->frame, input_len); + + if (!priv->init) { + sbc_decoder_init(&priv->dec_state, &priv->frame); + priv->init = 1; + + sbc->frequency = priv->frame.frequency; + sbc->mode = priv->frame.mode; + sbc->subbands = priv->frame.subband_mode; + sbc->blocks = priv->frame.block_mode; + sbc->allocation = priv->frame.allocation; + sbc->bitpool = priv->frame.bitpool; + + priv->frame.codesize = sbc_get_codesize(sbc); + priv->frame.length = sbc_get_frame_length(sbc); + } + + if (!output) + return framelen; + + if (written) + *written = 0; + + samples = sbc_synthesize_audio(&priv->dec_state, &priv->frame); + + ptr = output; + + if (output_len < samples * priv->frame.channels * 2) + samples = output_len / (priv->frame.channels * 2); + + for (i = 0; i < samples; i++) { + for (ch = 0; ch < priv->frame.channels; ch++) { + int16_t s; + s = priv->frame.pcm_sample[ch][i]; + +#if __BYTE_ORDER == __LITTLE_ENDIAN + if (sbc->endian == SBC_BE) { +#elif __BYTE_ORDER == __BIG_ENDIAN + if (sbc->endian == SBC_LE) { +#else +#error "Unknown byte order" +#endif + *ptr++ = (s & 0xff00) >> 8; + *ptr++ = (s & 0x00ff); + } else { + *ptr++ = (s & 0x00ff); + *ptr++ = (s & 0xff00) >> 8; + } + } + } + + if (written) + *written = samples * priv->frame.channels * 2; + + return framelen; +} + +int sbc_encode(sbc_t *sbc, void *input, int input_len, void *output, + int output_len, int *written) +{ + struct sbc_priv *priv; + char *ptr; + int i, ch, framelen, samples; + + if (!sbc && !input) + return -EIO; + + priv = sbc->priv; + + if (written) + *written = 0; + + if (!priv->init) { + priv->frame.frequency = sbc->frequency; + priv->frame.mode = sbc->mode; + priv->frame.channels = sbc->mode == SBC_MODE_MONO ? 1 : 2; + priv->frame.allocation = sbc->allocation; + priv->frame.subband_mode = sbc->subbands; + priv->frame.subbands = sbc->subbands ? 8 : 4; + priv->frame.block_mode = sbc->blocks; + priv->frame.blocks = 4 + (sbc->blocks * 4); + priv->frame.bitpool = sbc->bitpool; + priv->frame.codesize = sbc_get_codesize(sbc); + priv->frame.length = sbc_get_frame_length(sbc); + + sbc_encoder_init(&priv->enc_state, &priv->frame); + priv->init = 1; + } + + /* input must be large enough to encode a complete frame */ + if (input_len < priv->frame.codesize) + return 0; + + /* output must be large enough to receive the encoded frame */ + if (!output || output_len < priv->frame.length) + return -ENOSPC; + + ptr = input; + + for (i = 0; i < priv->frame.subbands * priv->frame.blocks; i++) { + for (ch = 0; ch < priv->frame.channels; ch++) { + int16_t s; +#if __BYTE_ORDER == __LITTLE_ENDIAN + if (sbc->endian == SBC_BE) +#elif __BYTE_ORDER == __BIG_ENDIAN + if (sbc->endian == SBC_LE) +#else +#error "Unknown byte order" +#endif + s = (ptr[0] & 0xff) << 8 | (ptr[1] & 0xff); + else + s = (ptr[0] & 0xff) | (ptr[1] & 0xff) << 8; + ptr += 2; + priv->frame.pcm_sample[ch][i] = s; + } + } + + samples = sbc_analyze_audio(&priv->enc_state, &priv->frame); + + framelen = sbc_pack_frame(output, &priv->frame, output_len); + + if (written) + *written = framelen; + + return samples * priv->frame.channels * 2; +} + +void sbc_finish(sbc_t *sbc) +{ + if (!sbc) + return; + + if (sbc->priv) + free(sbc->priv); + + memset(sbc, 0, sizeof(sbc_t)); +} + +int sbc_get_frame_length(sbc_t *sbc) +{ + int ret; + uint8_t subbands, channels, blocks, joint; + struct sbc_priv *priv; + + priv = sbc->priv; + if (!priv->init) { + subbands = sbc->subbands ? 8 : 4; + blocks = 4 + (sbc->blocks * 4); + channels = sbc->mode == SBC_MODE_MONO ? 1 : 2; + joint = sbc->mode == SBC_MODE_JOINT_STEREO ? 1 : 0; + } else { + subbands = priv->frame.subbands; + blocks = priv->frame.blocks; + channels = priv->frame.channels; + joint = priv->frame.joint; + } + + ret = 4 + (4 * subbands * channels) / 8; + + /* This term is not always evenly divide so we round it up */ + if (channels == 1) + ret += ((blocks * channels * sbc->bitpool) + 7) / 8; + else + ret += (((joint ? subbands : 0) + blocks * sbc->bitpool) + 7) + / 8; + + return ret; +} + +int sbc_get_frame_duration(sbc_t *sbc) +{ + uint8_t subbands, blocks; + uint16_t frequency; + struct sbc_priv *priv; + + priv = sbc->priv; + if (!priv->init) { + subbands = sbc->subbands ? 8 : 4; + blocks = 4 + (sbc->blocks * 4); + } else { + subbands = priv->frame.subbands; + blocks = priv->frame.blocks; + } + + switch (sbc->frequency) { + case SBC_FREQ_16000: + frequency = 16000; + break; + + case SBC_FREQ_32000: + frequency = 32000; + break; + + case SBC_FREQ_44100: + frequency = 44100; + break; + + case SBC_FREQ_48000: + frequency = 48000; + break; + default: + return 0; + } + + return (1000000 * blocks * subbands) / frequency; +} + +int sbc_get_codesize(sbc_t *sbc) +{ + uint8_t subbands, channels, blocks; + struct sbc_priv *priv; + + priv = sbc->priv; + if (!priv->init) { + subbands = sbc->subbands ? 8 : 4; + blocks = 4 + (sbc->blocks * 4); + channels = sbc->mode == SBC_MODE_MONO ? 1 : 2; + } else { + subbands = priv->frame.subbands; + blocks = priv->frame.blocks; + channels = priv->frame.channels; + } + + return subbands * blocks * channels * 2; +} + +int sbc_reinit(sbc_t *sbc, unsigned long flags) +{ + struct sbc_priv *priv; + + if (!sbc || !sbc->priv) + return -EIO; + + priv = sbc->priv; + + if (priv->init == 1) + memset(sbc->priv, 0, sizeof(struct sbc_priv)); + + sbc_set_defaults(sbc, flags); + + return 0; +} diff --git a/src/modules/bluetooth/sbc.h b/src/modules/bluetooth/sbc.h new file mode 100644 index 00000000..ab47e329 --- /dev/null +++ b/src/modules/bluetooth/sbc.h @@ -0,0 +1,97 @@ +/* + * + * Bluetooth low-complexity, subband codec (SBC) library + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> + * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> + * + * + * 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 library 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __SBC_H +#define __SBC_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdint.h> + +/* sampling frequency */ +#define SBC_FREQ_16000 0x00 +#define SBC_FREQ_32000 0x01 +#define SBC_FREQ_44100 0x02 +#define SBC_FREQ_48000 0x03 + +/* blocks */ +#define SBC_BLK_4 0x00 +#define SBC_BLK_8 0x01 +#define SBC_BLK_12 0x02 +#define SBC_BLK_16 0x03 + +/* channel mode */ +#define SBC_MODE_MONO 0x00 +#define SBC_MODE_DUAL_CHANNEL 0x01 +#define SBC_MODE_STEREO 0x02 +#define SBC_MODE_JOINT_STEREO 0x03 + +/* allocation method */ +#define SBC_AM_LOUDNESS 0x00 +#define SBC_AM_SNR 0x01 + +/* subbands */ +#define SBC_SB_4 0x00 +#define SBC_SB_8 0x01 + +/* Data endianess */ +#define SBC_LE 0x00 +#define SBC_BE 0x01 + +struct sbc_struct { + unsigned long flags; + + uint8_t frequency; + uint8_t blocks; + uint8_t subbands; + uint8_t mode; + uint8_t allocation; + uint8_t bitpool; + uint8_t endian; + + void *priv; +}; + +typedef struct sbc_struct sbc_t; + +int sbc_init(sbc_t *sbc, unsigned long flags); +int sbc_reinit(sbc_t *sbc, unsigned long flags); +int sbc_parse(sbc_t *sbc, void *input, int input_len); +int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output, + int output_len, int *len); +int sbc_encode(sbc_t *sbc, void *input, int input_len, void *output, + int output_len, int *written); +int sbc_get_frame_length(sbc_t *sbc); +int sbc_get_frame_duration(sbc_t *sbc); +int sbc_get_codesize(sbc_t *sbc); +void sbc_finish(sbc_t *sbc); + +#ifdef __cplusplus +} +#endif + +#endif /* __SBC_H */ diff --git a/src/modules/bluetooth/sbc_math.h b/src/modules/bluetooth/sbc_math.h new file mode 100644 index 00000000..b3d87a62 --- /dev/null +++ b/src/modules/bluetooth/sbc_math.h @@ -0,0 +1,72 @@ +/* + * + * Bluetooth low-complexity, subband codec (SBC) library + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> + * Copyright (C) 2005-2008 Brad Midgley <bmidgley@xmission.com> + * + * + * 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 library 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#define fabs(x) ((x) < 0 ? -(x) : (x)) +/* C does not provide an explicit arithmetic shift right but this will + always be correct and every compiler *should* generate optimal code */ +#define ASR(val, bits) ((-2 >> 1 == -1) ? \ + ((int32_t)(val)) >> (bits) : ((int32_t) (val)) / (1 << (bits))) + +#define SCALE_PROTO4_TBL 15 +#define SCALE_ANA4_TBL 17 +#define SCALE_PROTO8_TBL 16 +#define SCALE_ANA8_TBL 17 +#define SCALE_SPROTO4_TBL 12 +#define SCALE_SPROTO8_TBL 14 +#define SCALE_NPROTO4_TBL 11 +#define SCALE_NPROTO8_TBL 11 +#define SCALE4_STAGE1_BITS 15 +#define SCALE4_STAGE2_BITS 16 +#define SCALE4_STAGED1_BITS 15 +#define SCALE4_STAGED2_BITS 16 +#define SCALE8_STAGE1_BITS 15 +#define SCALE8_STAGE2_BITS 15 +#define SCALE8_STAGED1_BITS 15 +#define SCALE8_STAGED2_BITS 16 + +typedef int32_t sbc_fixed_t; + +#define SCALE4_STAGE1(src) ASR(src, SCALE4_STAGE1_BITS) +#define SCALE4_STAGE2(src) ASR(src, SCALE4_STAGE2_BITS) +#define SCALE4_STAGED1(src) ASR(src, SCALE4_STAGED1_BITS) +#define SCALE4_STAGED2(src) ASR(src, SCALE4_STAGED2_BITS) +#define SCALE8_STAGE1(src) ASR(src, SCALE8_STAGE1_BITS) +#define SCALE8_STAGE2(src) ASR(src, SCALE8_STAGE2_BITS) +#define SCALE8_STAGED1(src) ASR(src, SCALE8_STAGED1_BITS) +#define SCALE8_STAGED2(src) ASR(src, SCALE8_STAGED2_BITS) + +#define SBC_FIXED_0(val) { val = 0; } +#define MUL(a, b) ((a) * (b)) +#ifdef __arm__ +#define MULA(a, b, res) ({ \ + int tmp = res; \ + __asm__( \ + "mla %0, %2, %3, %0" \ + : "=&r" (tmp) \ + : "0" (tmp), "r" (a), "r" (b)); \ + tmp; }) +#else +#define MULA(a, b, res) ((a) * (b) + (res)) +#endif diff --git a/src/modules/bluetooth/sbc_tables.h b/src/modules/bluetooth/sbc_tables.h new file mode 100644 index 00000000..7ac4e68b --- /dev/null +++ b/src/modules/bluetooth/sbc_tables.h @@ -0,0 +1,167 @@ +/* + * + * Bluetooth low-complexity, subband codec (SBC) library + * + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch> + * Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com> + * + * + * 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 library 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 St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +/* A2DP specification: Appendix B, page 69 */ +static const int sbc_offset4[4][4] = { + { -1, 0, 0, 0 }, + { -2, 0, 0, 1 }, + { -2, 0, 0, 1 }, + { -2, 0, 0, 1 } +}; + +/* A2DP specification: Appendix B, page 69 */ +static const int sbc_offset8[4][8] = { + { -2, 0, 0, 0, 0, 0, 0, 1 }, + { -3, 0, 0, 0, 0, 0, 1, 2 }, + { -4, 0, 0, 0, 0, 0, 1, 2 }, + { -4, 0, 0, 0, 0, 0, 1, 2 } +}; + +#define SP4(val) ASR(val, SCALE_PROTO4_TBL) +#define SA4(val) ASR(val, SCALE_ANA4_TBL) +#define SP8(val) ASR(val, SCALE_PROTO8_TBL) +#define SA8(val) ASR(val, SCALE_ANA8_TBL) +#define SS4(val) ASR(val, SCALE_SPROTO4_TBL) +#define SS8(val) ASR(val, SCALE_SPROTO8_TBL) +#define SN4(val) ASR(val, SCALE_NPROTO4_TBL) +#define SN8(val) ASR(val, SCALE_NPROTO8_TBL) + +static const int32_t _sbc_proto_4[20] = { + SP4(0x02cb3e8c), SP4(0x22b63dc0), SP4(0x002329cc), SP4(0x053b7548), + SP4(0x31eab940), SP4(0xec1f5e60), SP4(0xff3773a8), SP4(0x0061c5a7), + SP4(0x07646680), SP4(0x3f239480), SP4(0xf89f23a8), SP4(0x007a4737), + SP4(0x00b32807), SP4(0x083ddc80), SP4(0x4825e480), SP4(0x0191e578), + SP4(0x00ff11ca), SP4(0x00fb7991), SP4(0x069fdc58), SP4(0x4b584000) +}; + +static const int32_t _anamatrix4[4] = { + SA4(0x2d413cc0), SA4(0x3b20d780), SA4(0x40000000), SA4(0x187de2a0) +}; + +static const int32_t _sbc_proto_8[40] = { + SP8(0x02e5cd20), SP8(0x22d0c200), SP8(0x006bfe27), SP8(0x07808930), + SP8(0x3f1c8800), SP8(0xf8810d70), SP8(0x002cfdc6), SP8(0x055acf28), + SP8(0x31f566c0), SP8(0xebfe57e0), SP8(0xff27c437), SP8(0x001485cc), + SP8(0x041c6e58), SP8(0x2a7cfa80), SP8(0xe4c4a240), SP8(0xfe359e4c), + SP8(0x0048b1f8), SP8(0x0686ce30), SP8(0x38eec5c0), SP8(0xf2a1b9f0), + SP8(0xffe8904a), SP8(0x0095698a), SP8(0x0824a480), SP8(0x443b3c00), + SP8(0xfd7badc8), SP8(0x00d3e2d9), SP8(0x00c183d2), SP8(0x084e1950), + SP8(0x4810d800), SP8(0x017f43fe), SP8(0x01056dd8), SP8(0x00e9cb9f), + SP8(0x07d7d090), SP8(0x4a708980), SP8(0x0488fae8), SP8(0x0113bd20), + SP8(0x0107b1a8), SP8(0x069fb3c0), SP8(0x4b3db200), SP8(0x00763f48) +}; + +static const int32_t sbc_proto_4_40m0[] = { + SS4(0x00000000), SS4(0xffa6982f), SS4(0xfba93848), SS4(0x0456c7b8), + SS4(0x005967d1), SS4(0xfffb9ac7), SS4(0xff589157), SS4(0xf9c2a8d8), + SS4(0x027c1434), SS4(0x0019118b), SS4(0xfff3c74c), SS4(0xff137330), + SS4(0xf81b8d70), SS4(0x00ec1b8b), SS4(0xfff0b71a), SS4(0xffe99b00), + SS4(0xfef84470), SS4(0xf6fb4370), SS4(0xffcdc351), SS4(0xffe01dc7) +}; + +static const int32_t sbc_proto_4_40m1[] = { + SS4(0xffe090ce), SS4(0xff2c0475), SS4(0xf694f800), SS4(0xff2c0475), + SS4(0xffe090ce), SS4(0xffe01dc7), SS4(0xffcdc351), SS4(0xf6fb4370), + SS4(0xfef84470), SS4(0xffe99b00), SS4(0xfff0b71a), SS4(0x00ec1b8b), + SS4(0xf81b8d70), SS4(0xff137330), SS4(0xfff3c74c), SS4(0x0019118b), + SS4(0x027c1434), SS4(0xf9c2a8d8), SS4(0xff589157), SS4(0xfffb9ac7) +}; + +static const int32_t sbc_proto_8_80m0[] = { + SS8(0x00000000), SS8(0xfe8d1970), SS8(0xee979f00), SS8(0x11686100), + SS8(0x0172e690), SS8(0xfff5bd1a), SS8(0xfdf1c8d4), SS8(0xeac182c0), + SS8(0x0d9daee0), SS8(0x00e530da), SS8(0xffe9811d), SS8(0xfd52986c), + SS8(0xe7054ca0), SS8(0x0a00d410), SS8(0x006c1de4), SS8(0xffdba705), + SS8(0xfcbc98e8), SS8(0xe3889d20), SS8(0x06af2308), SS8(0x000bb7db), + SS8(0xffca00ed), SS8(0xfc3fbb68), SS8(0xe071bc00), SS8(0x03bf7948), + SS8(0xffc4e05c), SS8(0xffb54b3b), SS8(0xfbedadc0), SS8(0xdde26200), + SS8(0x0142291c), SS8(0xff960e94), SS8(0xff9f3e17), SS8(0xfbd8f358), + SS8(0xdbf79400), SS8(0xff405e01), SS8(0xff7d4914), SS8(0xff8b1a31), + SS8(0xfc1417b8), SS8(0xdac7bb40), SS8(0xfdbb828c), SS8(0xff762170) +}; + +static const int32_t sbc_proto_8_80m1[] = { + SS8(0xff7c272c), SS8(0xfcb02620), SS8(0xda612700), SS8(0xfcb02620), + SS8(0xff7c272c), SS8(0xff762170), SS8(0xfdbb828c), SS8(0xdac7bb40), + SS8(0xfc1417b8), SS8(0xff8b1a31), SS8(0xff7d4914), SS8(0xff405e01), + SS8(0xdbf79400), SS8(0xfbd8f358), SS8(0xff9f3e17), SS8(0xff960e94), + SS8(0x0142291c), SS8(0xdde26200), SS8(0xfbedadc0), SS8(0xffb54b3b), + SS8(0xffc4e05c), SS8(0x03bf7948), SS8(0xe071bc00), SS8(0xfc3fbb68), + SS8(0xffca00ed), SS8(0x000bb7db), SS8(0x06af2308), SS8(0xe3889d20), + SS8(0xfcbc98e8), SS8(0xffdba705), SS8(0x006c1de4), SS8(0x0a00d410), + SS8(0xe7054ca0), SS8(0xfd52986c), SS8(0xffe9811d), SS8(0x00e530da), + SS8(0x0d9daee0), SS8(0xeac182c0), SS8(0xfdf1c8d4), SS8(0xfff5bd1a) +}; + +static const int32_t _anamatrix8[8] = { + SA8(0x3b20d780), SA8(0x187de2a0), SA8(0x3ec52f80), SA8(0x3536cc40), + SA8(0x238e7680), SA8(0x0c7c5c20), SA8(0x2d413cc0), SA8(0x40000000) +}; + +static const int32_t synmatrix4[8][4] = { + { SN4(0x05a82798), SN4(0xfa57d868), SN4(0xfa57d868), SN4(0x05a82798) }, + { SN4(0x030fbc54), SN4(0xf89be510), SN4(0x07641af0), SN4(0xfcf043ac) }, + { SN4(0x00000000), SN4(0x00000000), SN4(0x00000000), SN4(0x00000000) }, + { SN4(0xfcf043ac), SN4(0x07641af0), SN4(0xf89be510), SN4(0x030fbc54) }, + { SN4(0xfa57d868), SN4(0x05a82798), SN4(0x05a82798), SN4(0xfa57d868) }, + { SN4(0xf89be510), SN4(0xfcf043ac), SN4(0x030fbc54), SN4(0x07641af0) }, + { SN4(0xf8000000), SN4(0xf8000000), SN4(0xf8000000), SN4(0xf8000000) }, + { SN4(0xf89be510), SN4(0xfcf043ac), SN4(0x030fbc54), SN4(0x07641af0) } +}; + +static const int32_t synmatrix8[16][8] = { + { SN8(0x05a82798), SN8(0xfa57d868), SN8(0xfa57d868), SN8(0x05a82798), + SN8(0x05a82798), SN8(0xfa57d868), SN8(0xfa57d868), SN8(0x05a82798) }, + { SN8(0x0471ced0), SN8(0xf8275a10), SN8(0x018f8b84), SN8(0x06a6d988), + SN8(0xf9592678), SN8(0xfe70747c), SN8(0x07d8a5f0), SN8(0xfb8e3130) }, + { SN8(0x030fbc54), SN8(0xf89be510), SN8(0x07641af0), SN8(0xfcf043ac), + SN8(0xfcf043ac), SN8(0x07641af0), SN8(0xf89be510), SN8(0x030fbc54) }, + { SN8(0x018f8b84), SN8(0xfb8e3130), SN8(0x06a6d988), SN8(0xf8275a10), + SN8(0x07d8a5f0), SN8(0xf9592678), SN8(0x0471ced0), SN8(0xfe70747c) }, + { SN8(0x00000000), SN8(0x00000000), SN8(0x00000000), SN8(0x00000000), + SN8(0x00000000), SN8(0x00000000), SN8(0x00000000), SN8(0x00000000) }, + { SN8(0xfe70747c), SN8(0x0471ced0), SN8(0xf9592678), SN8(0x07d8a5f0), + SN8(0xf8275a10), SN8(0x06a6d988), SN8(0xfb8e3130), SN8(0x018f8b84) }, + { SN8(0xfcf043ac), SN8(0x07641af0), SN8(0xf89be510), SN8(0x030fbc54), + SN8(0x030fbc54), SN8(0xf89be510), SN8(0x07641af0), SN8(0xfcf043ac) }, + { SN8(0xfb8e3130), SN8(0x07d8a5f0), SN8(0xfe70747c), SN8(0xf9592678), + SN8(0x06a6d988), SN8(0x018f8b84), SN8(0xf8275a10), SN8(0x0471ced0) }, + { SN8(0xfa57d868), SN8(0x05a82798), SN8(0x05a82798), SN8(0xfa57d868), + SN8(0xfa57d868), SN8(0x05a82798), SN8(0x05a82798), SN8(0xfa57d868) }, + { SN8(0xf9592678), SN8(0x018f8b84), SN8(0x07d8a5f0), SN8(0x0471ced0), + SN8(0xfb8e3130), SN8(0xf8275a10), SN8(0xfe70747c), SN8(0x06a6d988) }, + { SN8(0xf89be510), SN8(0xfcf043ac), SN8(0x030fbc54), SN8(0x07641af0), + SN8(0x07641af0), SN8(0x030fbc54), SN8(0xfcf043ac), SN8(0xf89be510) }, + { SN8(0xf8275a10), SN8(0xf9592678), SN8(0xfb8e3130), SN8(0xfe70747c), + SN8(0x018f8b84), SN8(0x0471ced0), SN8(0x06a6d988), SN8(0x07d8a5f0) }, + { SN8(0xf8000000), SN8(0xf8000000), SN8(0xf8000000), SN8(0xf8000000), + SN8(0xf8000000), SN8(0xf8000000), SN8(0xf8000000), SN8(0xf8000000) }, + { SN8(0xf8275a10), SN8(0xf9592678), SN8(0xfb8e3130), SN8(0xfe70747c), + SN8(0x018f8b84), SN8(0x0471ced0), SN8(0x06a6d988), SN8(0x07d8a5f0) }, + { SN8(0xf89be510), SN8(0xfcf043ac), SN8(0x030fbc54), SN8(0x07641af0), + SN8(0x07641af0), SN8(0x030fbc54), SN8(0xfcf043ac), SN8(0xf89be510) }, + { SN8(0xf9592678), SN8(0x018f8b84), SN8(0x07d8a5f0), SN8(0x0471ced0), + SN8(0xfb8e3130), SN8(0xf8275a10), SN8(0xfe70747c), SN8(0x06a6d988) } +}; diff --git a/src/modules/dbus-util.c b/src/modules/dbus-util.c index 8e0066bc..d2abf087 100644 --- a/src/modules/dbus-util.c +++ b/src/modules/dbus-util.c @@ -90,7 +90,7 @@ static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) { } /* pa_io_event_cb_t IO event handler */ -static void handle_io_event(PA_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { +static void handle_io_event(pa_mainloop_api *ea, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { unsigned int flags = 0; DBusWatch *watch = userdata; @@ -126,7 +126,7 @@ static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, const struc dbus_timeout_handle(timeout); /* restart it for the next scheduled time */ - pa_timeval_add(&next, dbus_timeout_get_interval(timeout) * 1000); + pa_timeval_add(&next, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); ea->time_restart(e, &next); } } @@ -192,7 +192,7 @@ static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { return FALSE; pa_gettimeofday(&tv); - pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event, timeout); @@ -227,7 +227,7 @@ static void toggle_timeout(DBusTimeout *timeout, void *data) { struct timeval tv; pa_gettimeofday(&tv); - pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000); + pa_timeval_add(&tv, (pa_usec_t) dbus_timeout_get_interval(timeout) * 1000); c->mainloop->time_restart(ev, &tv); } else diff --git a/src/modules/gconf/Makefile b/src/modules/gconf/Makefile index 316beb72..efe5a336 100644..120000 --- a/src/modules/gconf/Makefile +++ b/src/modules/gconf/Makefile @@ -1,13 +1 @@ -# This is a dirty trick just to ease compilation with emacs -# -# This file is not intended to be distributed or anything -# -# So: don't touch it, even better ignore it! - -all: - $(MAKE) -C ../.. - -clean: - $(MAKE) -C ../.. clean - -.PHONY: all clean +../../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c index e2b0f7c0..845ede50 100644 --- a/src/modules/gconf/module-gconf.c +++ b/src/modules/gconf/module-gconf.c @@ -96,7 +96,7 @@ static int fill_buf(struct userdata *u) { if ((r = pa_read(u->fd, u->buf + u->buf_fill, BUF_MAX - u->buf_fill, &u->fd_type)) <= 0) return -1; - u->buf_fill += r; + u->buf_fill += (size_t) r; return 0; } @@ -123,7 +123,7 @@ static char *read_string(struct userdata *u) { if ((e = memchr(u->buf, 0, u->buf_fill))) { char *ret = pa_xstrdup(u->buf); - u->buf_fill -= e - u->buf +1; + u->buf_fill -= (size_t) (e - u->buf +1); memmove(u->buf, e+1, u->buf_fill); return ret; } @@ -164,10 +164,10 @@ static void unload_all_modules(struct userdata *u, struct module_info*m) { static void load_module( struct userdata *u, struct module_info *m, - int i, + unsigned i, const char *name, const char *args, - int is_new) { + pa_bool_t is_new) { pa_module *mod; @@ -378,7 +378,16 @@ void pa__done(pa_module*m) { if (u->pid != (pid_t) -1) { kill(u->pid, SIGTERM); - waitpid(u->pid, NULL, 0); + + for (;;) { + if (waitpid(u->pid, NULL, 0) >= 0) + break; + + if (errno != EINTR) { + pa_log("waitpid() failed: %s", pa_cstrerror(errno)); + break; + } + } } if (u->io_event) @@ -387,7 +396,6 @@ void pa__done(pa_module*m) { if (u->fd >= 0) pa_close(u->fd); - if (u->module_infos) pa_hashmap_free(u->module_infos, module_info_free, u); diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c index aad6801e..6dea172f 100644 --- a/src/modules/module-alsa-sink.c +++ b/src/modules/module-alsa-sink.c @@ -28,6 +28,10 @@ #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> @@ -68,8 +72,7 @@ PA_MODULE_USAGE( "mmap=<enable memory mapping?> " "tsched=<enable system timer based scheduling mode?> " "tsched_buffer_size=<buffer size when using timer based scheduling> " - "tsched_buffer_watermark=<lower fill watermark> " - "mixer_reset=<reset hw volume and mute settings to sane defaults when falling back to software?>"); + "tsched_buffer_watermark=<lower fill watermark>"); static const char* const valid_modargs[] = { "sink_name", @@ -85,7 +88,6 @@ static const char* const valid_modargs[] = { "tsched", "tsched_buffer_size", "tsched_buffer_watermark", - "mixer_reset", NULL }; @@ -112,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; @@ -139,7 +143,7 @@ static void fix_tsched_watermark(struct userdata *u) { size_t min_sleep, min_wakeup; pa_assert(u); - max_use = u->hwbuf_size - u->hwbuf_unused_frames * u->frame_size; + 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); @@ -212,8 +216,8 @@ static int try_recover(struct userdata *u, const char *call, int err) { static size_t check_left_to_play(struct userdata *u, snd_pcm_sframes_t n) { size_t left_to_play; - if (n*u->frame_size < u->hwbuf_size) - left_to_play = u->hwbuf_size - (n*u->frame_size); + 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; @@ -237,9 +241,9 @@ static size_t check_left_to_play(struct userdata *u, snd_pcm_sframes_t n) { return left_to_play; } -static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) { +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, process_usec; + pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_play; pa_assert(u); @@ -257,9 +261,9 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) { /* 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)) { - if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) continue; return r; @@ -275,14 +279,23 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) { * need to guarantee that clients only have to keep around * a single hw buffer length. */ - if (pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + 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)) + 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; + polled = FALSE; + /* pa_log_debug("Filling up"); */ for (;;) { @@ -291,10 +304,11 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) { 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_debug("%lu frames to write", (unsigned long) frames); */ - if (PA_UNLIKELY((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0)) { + if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->sink->sample_spec)) < 0)) { if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) continue; @@ -326,9 +340,9 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) { * a little bit longer around? */ pa_memblock_unref_fixed(chunk.memblock); - if (PA_UNLIKELY((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { - if ((r = try_recover(u, "snd_pcm_mmap_commit", err)) == 0) + if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0) continue; return r; @@ -336,7 +350,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) { work_done = 1; - u->frame_index += frames; + u->frame_index += (int64_t) frames; u->since_start += frames * u->frame_size; /* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ @@ -344,7 +358,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) { if (frames >= (snd_pcm_uframes_t) n) break; - n -= frames; + n -= (snd_pcm_sframes_t) frames; } } @@ -352,9 +366,9 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) { return work_done; } -static int unix_write(struct userdata *u, pa_usec_t *sleep_usec) { +static int unix_write(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { int work_done = 0; - pa_usec_t max_sleep_usec, process_usec; + pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_play; pa_assert(u); @@ -369,9 +383,9 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec) { snd_pcm_hwsync(u->pcm_handle); - 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)) { - if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) continue; return r; @@ -387,14 +401,23 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec) { * need to guarantee that clients only have to keep around * a single hw buffer length. */ - if (pa_bytes_to_usec(left_to_play, &u->sink->sample_spec) > process_usec+max_sleep_usec/2) + 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)) + 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; + polled = FALSE; + for (;;) { snd_pcm_sframes_t frames; void *p; @@ -402,31 +425,31 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec) { /* pa_log_debug("%lu frames to write", (unsigned long) frames); */ if (u->memchunk.length <= 0) - pa_sink_render(u->sink, n * u->frame_size, &u->memchunk); + pa_sink_render(u->sink, (size_t) n * u->frame_size, &u->memchunk); pa_assert(u->memchunk.length > 0); - frames = u->memchunk.length / u->frame_size; + frames = (snd_pcm_sframes_t) (u->memchunk.length / u->frame_size); if (frames > n) frames = n; p = pa_memblock_acquire(u->memchunk.memblock); - frames = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, frames); + 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); pa_assert(frames != 0); if (PA_UNLIKELY(frames < 0)) { - if ((r = try_recover(u, "snd_pcm_writei", n)) == 0) + if ((r = try_recover(u, "snd_pcm_writei", (int) frames)) == 0) continue; return r; } - u->memchunk.index += frames * u->frame_size; - u->memchunk.length -= frames * u->frame_size; + 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); @@ -436,7 +459,7 @@ static int unix_write(struct userdata *u, pa_usec_t *sleep_usec) { work_done = 1; u->frame_index += frames; - u->since_start += frames * u->frame_size; + u->since_start += (size_t) frames * u->frame_size; /* pa_log_debug("wrote %lu frames", (unsigned long) frames); */ @@ -490,7 +513,7 @@ static void update_smoother(struct userdata *u) { /* 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); } @@ -504,7 +527,7 @@ static pa_usec_t sink_get_latency(struct userdata *u) { now1 = pa_rtclock_usec(); now2 = pa_smoother_get(u->smoother, now1); - delay = (int64_t) pa_bytes_to_usec(u->frame_index * u->frame_size, &u->sink->sample_spec) - (int64_t) now2; + 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_usec_t) delay; @@ -534,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; @@ -564,7 +587,7 @@ static int update_sw_params(struct userdata *u) { if ((latency = pa_sink_get_requested_latency_within_thread(u->sink)) != (pa_usec_t) -1) { size_t b; - pa_log_debug("latency set to %0.2f", (double) latency / PA_USEC_PER_MSEC); + pa_log_debug("latency set to %0.2fms", (double) latency / PA_USEC_PER_MSEC); b = pa_usec_to_bytes(latency, &u->sink->sample_spec); @@ -573,9 +596,9 @@ static int update_sw_params(struct userdata *u) { 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; + u->hwbuf_unused_frames = (snd_pcm_sframes_t) + (PA_LIKELY(b < u->hwbuf_size) ? + ((u->hwbuf_size - b) / u->frame_size) : 0); fix_tsched_watermark(u); } @@ -584,7 +607,7 @@ static int update_sw_params(struct userdata *u) { pa_log_debug("hwbuf_unused_frames=%lu", (unsigned long) u->hwbuf_unused_frames); /* We need at last one frame in the used part of the buffer */ - avail_min = u->hwbuf_unused_frames + 1; + avail_min = (snd_pcm_uframes_t) u->hwbuf_unused_frames + 1; if (u->use_tsched) { pa_usec_t sleep_usec, process_usec; @@ -600,7 +623,7 @@ static int update_sw_params(struct userdata *u) { return err; } - pa_sink_set_max_request(u->sink, u->hwbuf_size - u->hwbuf_unused_frames * u->frame_size); + pa_sink_set_max_request(u->sink, u->hwbuf_size - (size_t) u->hwbuf_unused_frames * u->frame_size); return 0; } @@ -618,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; } @@ -645,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; } @@ -737,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 { + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; - u->hw_dB_supported = FALSE; + 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; @@ -784,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) { - pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); + 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); - vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM); + 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; + + 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_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; @@ -903,7 +1050,7 @@ static int process_rewind(struct userdata *u) { 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)); + pa_log("snd_pcm_avail_update() failed: %s", snd_strerror((int) unused)); return -1; } @@ -922,15 +1069,15 @@ static int process_rewind(struct userdata *u) { pa_log_debug("Limited to %lu bytes.", (unsigned long) rewind_nbytes); - in_frames = (snd_pcm_sframes_t) rewind_nbytes / u->frame_size; + 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, in_frames)) < 0) { - pa_log("snd_pcm_rewind() failed: %s", snd_strerror(out_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 = out_frames * u->frame_size; + rewind_nbytes = (size_t) out_frames * u->frame_size; if (rewind_nbytes <= 0) pa_log_info("Tried rewind, but was apparently not possible."); @@ -955,6 +1102,7 @@ finish: static void thread_func(void *userdata) { struct userdata *u = userdata; + unsigned short revents = 0; pa_assert(u); @@ -974,16 +1122,16 @@ static void thread_func(void *userdata) { /* Render some data and write it to the dsp */ if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { int work_done; - pa_usec_t sleep_usec; + pa_usec_t sleep_usec = 0; if (u->sink->thread_info.rewind_requested) if (process_rewind(u) < 0) goto fail; if (u->use_mmap) - work_done = mmap_write(u, &sleep_usec); + work_done = mmap_write(u, &sleep_usec, revents & POLLOUT); else - work_done = unix_write(u, &sleep_usec); + work_done = unix_write(u, &sleep_usec, revents & POLLOUT); if (work_done < 0) goto fail; @@ -1042,7 +1190,7 @@ static void thread_func(void *userdata) { 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) @@ -1051,7 +1199,6 @@ static void thread_func(void *userdata) { /* Tell ALSA about this and process its response */ if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { struct pollfd *pollfd; - unsigned short revents = 0; int err; unsigned n; @@ -1062,7 +1209,7 @@ static void thread_func(void *userdata) { goto fail; } - if (revents & (POLLERR|POLLNVAL|POLLHUP)) { + if (revents & (POLLIN|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) { if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) goto fail; @@ -1071,8 +1218,9 @@ static void thread_func(void *userdata) { } if (revents && u->use_tsched) - pa_log_debug("Wakeup from ALSA! (%i)", revents); - } + pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : ""); + } else + revents = 0; } fail: @@ -1100,7 +1248,7 @@ int pa__init(pa_module*m) { const char *name; char *name_buf = NULL; pa_bool_t namereg_fail; - pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, mixer_reset = TRUE; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d; pa_usec_t usec; pa_sink_new_data data; @@ -1124,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 || @@ -1153,15 +1301,10 @@ int pa__init(pa_module*m) { } 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; } - if (pa_modargs_get_value_boolean(ma, "mixer_reset", &mixer_reset) < 0) { - pa_log("Failed to parse mixer_reset argument."); - goto fail; - } - u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; @@ -1313,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; @@ -1322,6 +1465,8 @@ 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); if (use_tsched) fix_tsched_watermark(u); @@ -1349,76 +1494,60 @@ 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) { - - /* u->hw_dB_max = 0; u->hw_dB_min = -3000; Use this to make valgrind shut up */ - - 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; + 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) { + 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; + } + } - 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 && + !u->hw_dB_supported && + u->hw_volume_max - u->hw_volume_min < 3) { - } else { + 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 (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; - } + if (suitable) { + u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0; - u->hw_dB_supported = TRUE; - } - } + 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"); - 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. Hardware dB scale %s.", u->hw_dB_supported ? "supported" : "not supported"); - - } else if (mixer_reset) { - pa_log_info("Using software volume control. Trying to reset sound card to 0 dB."); - pa_alsa_0dB_playback(u->mixer_elem); - } else - pa_log_info("Using software volume control. Leaving hw mixer controls untouched."); - } + } 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(); diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c index 1cc467d9..f796ef14 100644 --- a/src/modules/module-alsa-source.c +++ b/src/modules/module-alsa-source.c @@ -28,6 +28,10 @@ #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> @@ -69,8 +73,7 @@ PA_MODULE_USAGE( "mmap=<enable memory mapping?> " "tsched=<enable system timer based scheduling mode?> " "tsched_buffer_size=<buffer size when using timer based scheduling> " - "tsched_buffer_watermark=<upper fill watermark> " - "mixer_reset=<reset hw volume and mute settings to sane defaults when falling back to software?>"); + "tsched_buffer_watermark=<upper fill watermark>"); static const char* const valid_modargs[] = { "source_name", @@ -86,7 +89,6 @@ static const char* const valid_modargs[] = { "tsched", "tsched_buffer_size", "tsched_buffer_watermark", - "mixer_reset", NULL }; @@ -113,6 +115,9 @@ 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; @@ -136,7 +141,7 @@ static void fix_tsched_watermark(struct userdata *u) { size_t min_sleep, min_wakeup; pa_assert(u); - max_use = u->hwbuf_size - u->hwbuf_unused_frames * u->frame_size; + 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->source->sample_spec); min_wakeup = pa_usec_to_bytes(TSCHED_MIN_WAKEUP_USEC, &u->source->sample_spec); @@ -206,9 +211,10 @@ static int try_recover(struct userdata *u, const char *call, int err) { static size_t check_left_to_record(struct userdata *u, snd_pcm_sframes_t n) { size_t left_to_record; + size_t rec_space = u->hwbuf_size - (size_t) u->hwbuf_unused_frames*u->frame_size; - if (n*u->frame_size < u->hwbuf_size) - left_to_record = u->hwbuf_size - (n*u->frame_size); + 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; @@ -232,9 +238,9 @@ static size_t check_left_to_record(struct userdata *u, snd_pcm_sframes_t n) { return left_to_record; } -static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) { +static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { int work_done = 0; - pa_usec_t max_sleep_usec, process_usec; + pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_record; pa_assert(u); @@ -249,9 +255,9 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) { snd_pcm_hwsync(u->pcm_handle); - 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->source->sample_spec)) < 0)) { - if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) continue; return r; @@ -260,11 +266,20 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) { left_to_record = check_left_to_record(u, n); if (u->use_tsched) - if (pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) + 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 <= 0)) { + + if (polled) + 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 PulseAudio device."); + break; + } + + polled = FALSE; for (;;) { int err; @@ -272,10 +287,11 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) { snd_pcm_uframes_t offset, frames = (snd_pcm_uframes_t) n; pa_memchunk chunk; void *p; + snd_pcm_sframes_t sframes; /* pa_log_debug("%lu frames to read", (unsigned long) frames); */ - if (PA_UNLIKELY((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0)) { + if (PA_UNLIKELY((err = pa_alsa_safe_mmap_begin(u->pcm_handle, &areas, &offset, &frames, u->hwbuf_size, &u->source->sample_spec)) < 0)) { if ((r = try_recover(u, "snd_pcm_mmap_begin", err)) == 0) continue; @@ -304,9 +320,9 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) { pa_source_post(u->source, &chunk); pa_memblock_unref_fixed(chunk.memblock); - if (PA_UNLIKELY((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { + if (PA_UNLIKELY((sframes = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0)) { - if ((r = try_recover(u, "snd_pcm_mmap_commit", err)) == 0) + if ((r = try_recover(u, "snd_pcm_mmap_commit", (int) sframes)) == 0) continue; return r; @@ -314,14 +330,14 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) { work_done = 1; - u->frame_index += frames; + u->frame_index += (int64_t) frames; /* pa_log_debug("read %lu frames", (unsigned long) frames); */ if (frames >= (snd_pcm_uframes_t) n) break; - n -= frames; + n -= (snd_pcm_sframes_t) frames; } } @@ -329,9 +345,9 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) { return work_done; } -static int unix_read(struct userdata *u, pa_usec_t *sleep_usec) { +static int unix_read(struct userdata *u, pa_usec_t *sleep_usec, pa_bool_t polled) { int work_done = 0; - pa_usec_t max_sleep_usec, process_usec; + pa_usec_t max_sleep_usec = 0, process_usec = 0; size_t left_to_record; pa_assert(u); @@ -346,9 +362,9 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec) { snd_pcm_hwsync(u->pcm_handle); - 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->source->sample_spec)) < 0)) { - if ((r = try_recover(u, "snd_pcm_avail_update", n)) == 0) + if ((r = try_recover(u, "snd_pcm_avail_update", (int) n)) == 0) continue; return r; @@ -357,11 +373,20 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec) { left_to_record = check_left_to_record(u, n); if (u->use_tsched) - if (pa_bytes_to_usec(left_to_record, &u->source->sample_spec) > process_usec+max_sleep_usec/2) + 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 <= 0)) { + + if (polled) + 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 PulseAudio developers."); + return work_done; + } + + polled = FALSE; for (;;) { void *p; @@ -370,7 +395,7 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec) { chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); - frames = pa_memblock_get_length(chunk.memblock) / u->frame_size; + frames = (snd_pcm_sframes_t) (pa_memblock_get_length(chunk.memblock) / u->frame_size); if (frames > n) frames = n; @@ -378,7 +403,7 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec) { /* pa_log_debug("%lu frames to read", (unsigned long) n); */ p = pa_memblock_acquire(chunk.memblock); - frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, frames); + frames = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, (snd_pcm_uframes_t) frames); pa_memblock_release(chunk.memblock); pa_assert(frames != 0); @@ -386,14 +411,14 @@ static int unix_read(struct userdata *u, pa_usec_t *sleep_usec) { if (PA_UNLIKELY(frames < 0)) { pa_memblock_unref(chunk.memblock); - if ((r = try_recover(u, "snd_pcm_readi", n)) == 0) + if ((r = try_recover(u, "snd_pcm_readi", (int) (frames))) == 0) continue; return r; } chunk.index = 0; - chunk.length = frames * u->frame_size; + chunk.length = (size_t) frames * u->frame_size; pa_source_post(u->source, &chunk); pa_memblock_unref(chunk.memblock); @@ -437,7 +462,7 @@ static void update_smoother(struct userdata *u) { frames = u->frame_index + delay; now1 = pa_rtclock_usec(); - now2 = pa_bytes_to_usec(frames * u->frame_size, &u->source->sample_spec); + now2 = pa_bytes_to_usec((uint64_t) frames * u->frame_size, &u->source->sample_spec); pa_smoother_put(u->smoother, now1, now2); } @@ -452,7 +477,7 @@ static pa_usec_t source_get_latency(struct userdata *u) { now1 = pa_rtclock_usec(); now2 = pa_smoother_get(u->smoother, now1); - delay = (int64_t) now2 - pa_bytes_to_usec(u->frame_index * u->frame_size, &u->source->sample_spec); + 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; @@ -508,7 +533,7 @@ static int update_sw_params(struct userdata *u) { if ((latency = pa_source_get_requested_latency_within_thread(u->source)) != (pa_usec_t) -1) { size_t b; - pa_log_debug("latency set to %0.2f", (double) latency / PA_USEC_PER_MSEC); + pa_log_debug("latency set to %0.2fms", (double) latency / PA_USEC_PER_MSEC); b = pa_usec_to_bytes(latency, &u->source->sample_spec); @@ -517,9 +542,9 @@ static int update_sw_params(struct userdata *u) { 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; + u->hwbuf_unused_frames = (snd_pcm_sframes_t) + (PA_LIKELY(b < u->hwbuf_size) ? + ((u->hwbuf_size - b) / u->frame_size) : 0); fix_tsched_watermark(u); } @@ -559,7 +584,12 @@ 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_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + + 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)); goto fail; } @@ -586,7 +616,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; } @@ -680,40 +712,101 @@ static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { return 0; if (mask & SND_CTL_EVENT_MASK_VALUE) { - pa_source_get_volume(u->source); - pa_source_get_mute(u->source); + pa_source_get_volume(u->source, TRUE); + pa_source_get_mute(u->source, 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 source_get_volume_cb(pa_source *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_capture_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_capture_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_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 { - u->hw_dB_supported = FALSE; + if ((err = snd_mixer_selem_get_capture_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_capture_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_source_set_soft_volume(s, &reset); + } } return 0; @@ -727,44 +820,101 @@ fail: static int source_set_volume_cb(pa_source *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; - pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i])); + vol = s->volume.values[i]; - vol = PA_MIN(s->volume.values[i], PA_VOLUME_NORM); + 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; + + 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->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_set_capture_dB(u->mixer_elem, u->mixer_map[i], alsa_vol, -1)) >= 0) { + if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0) + goto fail; - if (snd_mixer_selem_get_capture_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); + + if ((err = snd_mixer_selem_set_capture_volume_all(u->mixer_elem, alsa_vol)) < 0) + goto fail; - u->hw_dB_supported = FALSE; + if ((err = snd_mixer_selem_get_capture_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_capture_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_capture_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_source_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; @@ -818,6 +968,7 @@ static void source_update_requested_latency_cb(pa_source *s) { static void thread_func(void *userdata) { struct userdata *u = userdata; + unsigned short revents = 0; pa_assert(u); @@ -837,12 +988,12 @@ static void thread_func(void *userdata) { /* Read some data and pass it to the sources */ if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { int work_done = 0; - pa_usec_t sleep_usec; + pa_usec_t sleep_usec = 0; if (u->use_mmap) - work_done = mmap_read(u, &sleep_usec); + work_done = mmap_read(u, &sleep_usec, revents & POLLIN); else - work_done = unix_read(u, &sleep_usec); + work_done = unix_read(u, &sleep_usec, revents & POLLIN); if (work_done < 0) goto fail; @@ -875,7 +1026,7 @@ static void thread_func(void *userdata) { 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) @@ -884,7 +1035,6 @@ static void thread_func(void *userdata) { /* Tell ALSA about this and process its response */ if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) { struct pollfd *pollfd; - unsigned short revents = 0; int err; unsigned n; @@ -895,7 +1045,7 @@ static void thread_func(void *userdata) { goto fail; } - if (revents & (POLLERR|POLLNVAL|POLLHUP)) { + if (revents & (POLLOUT|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) { if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0) goto fail; @@ -903,8 +1053,9 @@ static void thread_func(void *userdata) { } if (revents && u->use_tsched) - pa_log_debug("Wakeup from ALSA! (%i)", revents); - } + pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : ""); + } else + revents = 0; } fail: @@ -932,7 +1083,7 @@ int pa__init(pa_module*m) { const char *name; char *name_buf = NULL; pa_bool_t namereg_fail; - pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d, mixer_reset = TRUE; + pa_bool_t use_mmap = TRUE, b, use_tsched = TRUE, d; pa_source_new_data data; snd_pcm_info_alloca(&pcm_info); @@ -955,11 +1106,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 || @@ -984,15 +1135,10 @@ int pa__init(pa_module*m) { } 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; } - if (pa_modargs_get_value_boolean(ma, "mixer_reset", &mixer_reset) < 0) { - pa_log("Failed to parse mixer_reset argument."); - goto fail; - } - u = pa_xnew0(struct userdata, 1); u->core = m->core; u->module = m; @@ -1137,7 +1283,7 @@ int pa__init(pa_module*m) { pa_source_set_rtpoll(u->source, 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; @@ -1146,6 +1292,8 @@ 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->source->sample_spec.channels); if (use_tsched) fix_tsched_watermark(u); @@ -1168,67 +1316,59 @@ int pa__init(pa_module*m) { if (u->mixer_handle) { pa_assert(u->mixer_elem); - if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) - if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0 && - snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max) >= 0) { - - pa_bool_t suitable = TRUE; + if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) { + pa_bool_t suitable = FALSE; + 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; + } - 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_capture_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 (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."); + 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 - u->hw_dB_supported = TRUE; + 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 (suitable) { - 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"); - - } else if (mixer_reset) { - pa_log_info("Using software volume control. Trying to reset sound card to 0 dB."); - pa_alsa_0dB_capture(u->mixer_elem); - } else - pa_log_info("Using software volume control. Leaving hw mixer controls untouched."); + if (suitable && + !u->hw_dB_supported && + 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; } + if (suitable) { + u->mixer_seperate_channels = pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0; + + 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"); + } else + pa_log_info("Using software volume control."); + } if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { 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."); u->mixer_fdl = pa_alsa_fdlist_new(); diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c index 1bfe4b4c..d61d127a 100644 --- a/src/modules/module-combine.c +++ b/src/modules/module-combine.c @@ -206,9 +206,9 @@ static void adjust_rates(struct userdata *u) { continue; if (o->total_latency < target_latency) - r -= (uint32_t) (((((double) target_latency - o->total_latency))/u->adjust_time)*r/PA_USEC_PER_SEC); + r -= (uint32_t) ((((double) (target_latency - o->total_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC); else if (o->total_latency > target_latency) - r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/PA_USEC_PER_SEC); + r += (uint32_t) ((((double) (o->total_latency - target_latency))/(double)u->adjust_time)*(double)r/PA_USEC_PER_SEC); if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) { pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", pa_proplist_gets(o->sink_input->proplist, PA_PROP_MEDIA_NAME), base_rate, r); @@ -233,7 +233,7 @@ static void time_callback(pa_mainloop_api*a, pa_time_event* e, const struct time adjust_rates(u); pa_gettimeofday(&n); - n.tv_sec += u->adjust_time; + n.tv_sec += (time_t) u->adjust_time; u->sink->core->mainloop->time_restart(e, &n); } @@ -389,7 +389,7 @@ static void request_memblock(struct output *o, size_t length) { /* OK, we need to prepare new data, but only if the sink is actually running */ if (pa_atomic_load(&o->userdata->thread_info.running)) - pa_asyncmsgq_send(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_NEED, o, length, NULL); + pa_asyncmsgq_send(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_NEED, o, (int64_t) length, NULL); } /* Called from I/O thread context */ @@ -1159,7 +1159,7 @@ int pa__init(pa_module*m) { if (u->adjust_time > 0) { struct timeval tv; pa_gettimeofday(&tv); - tv.tv_sec += u->adjust_time; + tv.tv_sec += (time_t) u->adjust_time; u->time_event = m->core->mainloop->time_new(m->core->mainloop, &tv, time_callback, u); } diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c index 09b720df..1616d47c 100644 --- a/src/modules/module-detect.c +++ b/src/modules/module-detect.c @@ -236,16 +236,16 @@ int pa__init(pa_module*m) { goto fail; } -#if HAVE_ALSA +#ifdef HAVE_ALSA if ((n = detect_alsa(m->core, just_one)) <= 0) #endif #if HAVE_OSS if ((n = detect_oss(m->core, just_one)) <= 0) #endif -#if HAVE_SOLARIS +#ifdef HAVE_SOLARIS if ((n = detect_solaris(m->core, just_one)) <= 0) #endif -#if OS_IS_WIN32 +#ifdef OS_IS_WIN32 if ((n = detect_waveout(m->core, just_one)) <= 0) #endif { diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c index 3d731f12..86a78810 100644 --- a/src/modules/module-device-restore.c +++ b/src/modules/module-device-restore.c @@ -106,7 +106,7 @@ static struct entry* read_entry(struct userdata *u, char *name) { pa_assert(name); key.dptr = name; - key.dsize = strlen(name); + key.dsize = (int) strlen(name); data = gdbm_fetch(u->gdbm_file, key); @@ -179,8 +179,8 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 name = pa_sprintf_malloc("sink:%s", sink->name); entry.channel_map = sink->channel_map; - entry.volume = *pa_sink_get_volume(sink); - entry.muted = pa_sink_get_mute(sink); + entry.volume = *pa_sink_get_volume(sink, FALSE); + entry.muted = pa_sink_get_mute(sink, FALSE); } else { pa_source *source; @@ -192,8 +192,8 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 name = pa_sprintf_malloc("source:%s", source->name); entry.channel_map = source->channel_map; - entry.volume = *pa_source_get_volume(source); - entry.muted = pa_source_get_mute(source); + entry.volume = *pa_source_get_volume(source, FALSE); + entry.muted = pa_source_get_mute(source, FALSE); } if ((old = read_entry(u, name))) { @@ -210,7 +210,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 } key.dptr = name; - key.dsize = strlen(name); + key.dsize = (int) strlen(name); data.dptr = (void*) &entry; data.dsize = sizeof(entry); @@ -288,6 +288,7 @@ int pa__init(pa_module*m) { pa_source *source; uint32_t idx; pa_bool_t restore_volume = TRUE, restore_muted = TRUE; + int gdbm_cache_size; pa_assert(m); @@ -337,6 +338,10 @@ int pa__init(pa_module*m) { goto fail; } + /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ + gdbm_cache_size = 10; + gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); + pa_log_info("Sucessfully opened database file '%s'.", fname); pa_xfree(fname); diff --git a/src/modules/module-esound-compat-spawnpid.c b/src/modules/module-esound-compat-spawnpid.c index f75335d5..882dba8c 100644 --- a/src/modules/module-esound-compat-spawnpid.c +++ b/src/modules/module-esound-compat-spawnpid.c @@ -62,7 +62,7 @@ int pa__init(pa_module*m) { goto finish; } - if (kill(pid, SIGUSR1) < 0) + if (kill((pid_t) pid, SIGUSR1) < 0) pa_log_warn("kill(%u) failed: %s", pid, pa_cstrerror(errno)); pa_module_unload_request(m, TRUE); diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c index f748808e..14f1810a 100644 --- a/src/modules/module-esound-sink.c +++ b/src/modules/module-esound-sink.c @@ -165,7 +165,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_usec_t w, r; r = pa_smoother_get(u->smoother, pa_rtclock_usec()); - w = pa_bytes_to_usec(u->offset + u->memchunk.length, &u->sink->sample_spec); + w = pa_bytes_to_usec((uint64_t) u->offset + u->memchunk.length, &u->sink->sample_spec); *((pa_usec_t*) data) = w > r ? w - r : 0; break; @@ -250,8 +250,8 @@ static void thread_func(void *userdata) { } else { u->offset += l; - u->memchunk.index += l; - u->memchunk.length -= l; + u->memchunk.index += (size_t) l; + u->memchunk.length -= (size_t) l; if (u->memchunk.length <= 0) { pa_memblock_unref(u->memchunk.memblock); @@ -285,7 +285,7 @@ static void thread_func(void *userdata) { } #endif - usec = pa_bytes_to_usec(n, &u->sink->sample_spec); + usec = pa_bytes_to_usec((uint64_t) n, &u->sink->sample_spec); if (usec > u->latency) usec -= u->latency; @@ -296,7 +296,7 @@ static void thread_func(void *userdata) { } /* Hmm, nothing to do. Let's sleep */ - pollfd->events = PA_SINK_IS_OPENED(u->sink->thread_info.state) ? POLLOUT : 0; + pollfd->events = (short) (PA_SINK_IS_OPENED(u->sink->thread_info.state) ? POLLOUT : 0); } if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) @@ -342,7 +342,7 @@ static int do_write(struct userdata *u) { return -1; } - u->write_index += r; + u->write_index += (size_t) r; pa_assert(u->write_index <= u->write_length); if (u->write_index == u->write_length) { @@ -458,7 +458,7 @@ static int do_read(struct userdata *u) { return -1; } - u->read_index += r; + u->read_index += (size_t) r; pa_assert(u->read_index <= u->read_length); if (u->read_index == u->read_length) @@ -468,7 +468,7 @@ static int do_read(struct userdata *u) { return 0; } -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { +static void io_callback(pa_iochannel *io, void*userdata) { struct userdata *u = userdata; pa_assert(u); @@ -483,7 +483,7 @@ static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { } } -static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, void *userdata) { +static void on_connection(pa_socket_client *c, pa_iochannel*io, void *userdata) { struct userdata *u = userdata; pa_socket_client_unref(u->client); @@ -545,7 +545,7 @@ int pa__init(pa_module*m) { u->format = (ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) | (ss.channels == 2 ? ESD_STEREO : ESD_MONO); - u->rate = ss.rate; + u->rate = (int32_t) ss.rate; u->block_size = pa_usec_to_bytes(PA_USEC_PER_SEC/20, &ss); u->read_data = u->write_data = NULL; diff --git a/src/modules/module-flat-volume.c b/src/modules/module-flat-volume.c new file mode 100644 index 00000000..9bc8055a --- /dev/null +++ b/src/modules/module-flat-volume.c @@ -0,0 +1,224 @@ +/*** + This file is part of PulseAudio. + + Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). + Copyright 2004-2006, 2008 Lennart Poettering + + Contact: Marc-Andre Lureau <marc-andre.lureau@nokia.com> + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "module-flat-volume-symdef.h" + +PA_MODULE_AUTHOR("Marc-Andre Lureau"); +PA_MODULE_DESCRIPTION("Flat volume"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE(""); + +struct userdata { + pa_subscription *subscription; + pa_hook_slot *sink_input_set_volume_hook_slot; + pa_hook_slot *sink_input_fixate_hook_slot; +}; + +static void process_input_volume_change( + pa_cvolume *dest_volume, + const pa_cvolume *dest_virtual_volume, + pa_channel_map *dest_channel_map, + pa_sink_input *this, + pa_sink *sink) { + + pa_sink_input *i; + uint32_t idx; + pa_cvolume max_volume, sink_volume; + + pa_assert(dest_volume); + pa_assert(dest_virtual_volume); + pa_assert(dest_channel_map); + pa_assert(sink); + + if (!(sink->flags & PA_SINK_DECIBEL_VOLUME)) + return; + + pa_log_debug("Sink input volume changed"); + + max_volume = *dest_virtual_volume; + pa_cvolume_remap(&max_volume, dest_channel_map, &sink->channel_map); + + for (i = PA_SINK_INPUT(pa_idxset_first(sink->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(sink->inputs, &idx))) { + /* skip this sink-input if we are processing a volume change request */ + if (this && this == i) + continue; + + if (pa_cvolume_max(&i->virtual_volume) > pa_cvolume_max(&max_volume)) { + max_volume = i->virtual_volume; + pa_cvolume_remap(&max_volume, &i->channel_map, &sink->channel_map); + } + } + + /* Set the master volume, and normalize inputs */ + if (!pa_cvolume_equal(&max_volume, &sink->volume)) { + + pa_sink_set_volume(sink, &max_volume); + + pa_log_debug("sink = %.2f (changed)", (double)pa_cvolume_avg(&sink->volume)/PA_VOLUME_NORM); + + /* Now, normalize each of the internal volume (client sink-input volume / sink master volume) */ + for (i = PA_SINK_INPUT(pa_idxset_first(sink->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(sink->inputs, &idx))) { + /* skip this sink-input if we are processing a volume change request */ + if (this && this == i) + continue; + + sink_volume = max_volume; + pa_cvolume_remap(&sink_volume, &sink->channel_map, &i->channel_map); + pa_sw_cvolume_divide(&i->volume, &i->virtual_volume, &sink_volume); + pa_log_debug("sink input { id = %d, flat = %.2f, true = %.2f }", + i->index, + (double)pa_cvolume_avg(&i->virtual_volume)/PA_VOLUME_NORM, + (double)pa_cvolume_avg(&i->volume)/PA_VOLUME_NORM); + pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_VOLUME, pa_xnewdup(struct pa_cvolume, &i->volume, 1), 0, NULL, pa_xfree); + } + } else + pa_log_debug("sink = %.2f", (double)pa_cvolume_avg(&sink->volume)/PA_VOLUME_NORM); + + /* and this one */ + + sink_volume = max_volume; + pa_cvolume_remap(&sink_volume, &sink->channel_map, dest_channel_map); + pa_sw_cvolume_divide(dest_volume, dest_virtual_volume, &sink_volume); + pa_log_debug("caller sink input: { id = %d, flat = %.2f, true = %.2f }", + this ? (int)this->index : -1, + (double)pa_cvolume_avg(dest_virtual_volume)/PA_VOLUME_NORM, + (double)pa_cvolume_avg(dest_volume)/PA_VOLUME_NORM); +} + +static pa_hook_result_t sink_input_set_volume_hook_callback(pa_core *c, pa_sink_input_set_volume_data *this, struct userdata *u) { + pa_assert(this); + pa_assert(this->sink_input); + + process_input_volume_change(&this->volume, &this->virtual_volume, &this->sink_input->channel_map, + this->sink_input, this->sink_input->sink); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *core, pa_sink_input_new_data *this, struct userdata *u) { + pa_assert(this); + pa_assert(this->sink); + + process_input_volume_change(&this->volume, &this->virtual_volume, &this->channel_map, + NULL, this->sink); + + return PA_HOOK_OK; +} + +static void subscribe_callback(pa_core *core, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + pa_sink *sink; + pa_sink_input *i; + uint32_t iidx; + pa_cvolume sink_volume; + + pa_assert(core); + pa_assert(u); + + if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE)) + return; + + if (!(sink = pa_idxset_get_by_index(core->sinks, idx))) + return; + + if (!(sink->flags & PA_SINK_DECIBEL_VOLUME)) + return; + + pa_log_debug("Sink volume changed"); + pa_log_debug("sink = %.2f", (double)pa_cvolume_avg(pa_sink_get_volume(sink, FALSE)) / PA_VOLUME_NORM); + + sink_volume = *pa_sink_get_volume(sink, FALSE); + + for (i = PA_SINK_INPUT(pa_idxset_first(sink->inputs, &iidx)); i; i = PA_SINK_INPUT(pa_idxset_next(sink->inputs, &iidx))) { + pa_cvolume si_volume; + + si_volume = sink_volume; + pa_cvolume_remap(&si_volume, &sink->channel_map, &i->channel_map); + pa_sw_cvolume_multiply(&i->virtual_volume, &i->volume, &si_volume); + pa_log_debug("sink input = { id = %d, flat = %.2f, true = %.2f }", + i->index, + (double)pa_cvolume_avg(&i->virtual_volume)/PA_VOLUME_NORM, + (double)pa_cvolume_avg(&i->volume)/PA_VOLUME_NORM); + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + } +} + +int pa__init(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + u = pa_xnew(struct userdata, 1); + m->userdata = u; + + u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_fixate_hook_callback, u); + u->sink_input_set_volume_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_set_volume_hook_callback, u); + + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK, subscribe_callback, u); + + return 0; +} + +void pa__done(pa_module*m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->subscription) + pa_subscription_free(u->subscription); + + if (u->sink_input_set_volume_hook_slot) + pa_hook_slot_free(u->sink_input_set_volume_hook_slot); + if (u->sink_input_fixate_hook_slot) + pa_hook_slot_free(u->sink_input_fixate_hook_slot); + + pa_xfree(u); +} diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c index caa7a1fa..c76a366c 100644 --- a/src/modules/module-hal-detect.c +++ b/src/modules/module-hal-detect.c @@ -108,7 +108,7 @@ static void hal_device_free(struct device* d) { pa_xfree(d); } -static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) { +static void hal_device_free_cb(void *d, void *data) { hal_device_free(d); } @@ -405,7 +405,7 @@ static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const s dbus_error_init(&error); if (!pa_hashmap_get(td->u->devices, td->udi)) { - int b; + dbus_bool_t b; struct device *d; b = libhal_device_exists(td->u->context, td->udi, &error); @@ -433,7 +433,7 @@ static void device_added_cb(LibHalContext *context, const char *udi) { struct timeval tv; struct timerdata *t; struct userdata *u; - int good = 0; + pa_bool_t good = FALSE; pa_assert_se(u = libhal_ctx_get_user_data(context)); @@ -749,17 +749,17 @@ int pa__init(pa_module*m) { } if ((api = pa_modargs_get_value(ma, "api", NULL))) { - int good = 0; + pa_bool_t good = FALSE; #ifdef HAVE_ALSA if (strcmp(api, CAPABILITY_ALSA) == 0) { - good = 1; + good = TRUE; api = CAPABILITY_ALSA; } #endif #ifdef HAVE_OSS if (strcmp(api, CAPABILITY_OSS) == 0) { - good = 1; + good = TRUE; api = CAPABILITY_OSS; } #endif diff --git a/src/modules/module-jack-sink.c b/src/modules/module-jack-sink.c index edc543a8..555cb825 100644 --- a/src/modules/module-jack-sink.c +++ b/src/modules/module-jack-sink.c @@ -130,12 +130,12 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse void *p; pa_assert(offset > 0); - nbytes = offset * pa_frame_size(&u->sink->sample_spec); + nbytes = (size_t) offset * pa_frame_size(&u->sink->sample_spec); pa_sink_render_full(u->sink, nbytes, &chunk); p = (uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index; - pa_deinterleave(p, u->buffer, u->channels, sizeof(float), offset); + pa_deinterleave(p, u->buffer, u->channels, sizeof(float), (unsigned) offset); pa_memblock_release(chunk.memblock); pa_memblock_unref(chunk.memblock); @@ -149,10 +149,10 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse ss.channels = 1; for (c = 0; c < u->channels; c++) - pa_silence_memory(u->buffer[c], offset * pa_sample_size(&ss), &ss); + pa_silence_memory(u->buffer[c], (size_t) offset * pa_sample_size(&ss), &ss); } - u->frames_in_buffer = offset; + u->frames_in_buffer = (jack_nframes_t) offset; u->saved_frame_time = * (jack_nframes_t*) data; u->saved_frame_time_valid = TRUE; @@ -342,7 +342,7 @@ int pa__init(pa_module*m) { pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client)); - ss.channels = u->channels = channels; + u->channels = ss.channels = (uint8_t) channels; ss.rate = jack_get_sample_rate(u->client); ss.format = PA_SAMPLE_FLOAT32NE; diff --git a/src/modules/module-jack-source.c b/src/modules/module-jack-source.c index 03f9d15c..9eccbbfa 100644 --- a/src/modules/module-jack-source.c +++ b/src/modules/module-jack-source.c @@ -116,7 +116,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off if (u->source->thread_info.state == PA_SOURCE_RUNNING) pa_source_post(u->source, chunk); - u->saved_frame_time = offset; + u->saved_frame_time = (jack_nframes_t) offset; u->saved_frame_time_valid = TRUE; return 0; @@ -309,7 +309,7 @@ int pa__init(pa_module*m) { pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client)); - ss.channels = u->channels = channels; + u->channels = ss.channels = (uint8_t) channels; ss.rate = jack_get_sample_rate(u->client); ss.format = PA_SAMPLE_FLOAT32NE; diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c index 23eeb340..a27ed712 100644 --- a/src/modules/module-ladspa-sink.c +++ b/src/modules/module-ladspa-sink.c @@ -187,7 +187,7 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk pa_assert(tchunk.length > 0); fs = pa_frame_size(&i->sample_spec); - n = PA_MIN(tchunk.length, u->block_size) / fs; + n = (unsigned) (PA_MIN(tchunk.length, u->block_size) / fs); pa_assert(n > 0); @@ -359,6 +359,16 @@ static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t s } } +/* Called from main context */ +static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + return u->sink != dest; +} + int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; @@ -502,9 +512,9 @@ int pa__init(pa_module*m) { u->block_size = pa_frame_align(pa_mempool_block_size_max(m->core->mempool), &ss); - u->input = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size); + u->input = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size); if (LADSPA_IS_INPLACE_BROKEN(d->Properties)) - u->output = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size); + u->output = (LADSPA_Data*) pa_xnew(uint8_t, (unsigned) u->block_size); else u->output = u->input; @@ -530,8 +540,8 @@ int pa__init(pa_module*m) { char *k; unsigned long h; - u->control = pa_xnew(LADSPA_Data, n_control); - use_default = pa_xnew(pa_bool_t, n_control); + u->control = pa_xnew(LADSPA_Data, (unsigned) n_control); + use_default = pa_xnew(pa_bool_t, (unsigned) n_control); p = 0; while ((k = pa_split(cdata, ",", &state)) && p < n_control) { @@ -552,7 +562,7 @@ int pa__init(pa_module*m) { pa_xfree(k); use_default[p] = FALSE; - u->control[p++] = f; + u->control[p++] = (LADSPA_Data) f; } /* The previous loop doesn't take the last control value into account @@ -601,8 +611,8 @@ int pa__init(pa_module*m) { upper = d->PortRangeHints[p].UpperBound; if (LADSPA_IS_HINT_SAMPLE_RATE(hint)) { - lower *= ss.rate; - upper *= ss.rate; + lower *= (LADSPA_Data) ss.rate; + upper *= (LADSPA_Data) ss.rate; } switch (hint & LADSPA_HINT_DEFAULT_MASK) { @@ -617,23 +627,23 @@ int pa__init(pa_module*m) { case LADSPA_HINT_DEFAULT_LOW: if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - u->control[h] = exp(log(lower) * 0.75 + log(upper) * 0.25); + u->control[h] = (LADSPA_Data) exp(log(lower) * 0.75 + log(upper) * 0.25); else - u->control[h] = lower * 0.75 + upper * 0.25; + u->control[h] = (LADSPA_Data) (lower * 0.75 + upper * 0.25); break; case LADSPA_HINT_DEFAULT_MIDDLE: if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - u->control[h] = exp(log(lower) * 0.5 + log(upper) * 0.5); + u->control[h] = (LADSPA_Data) exp(log(lower) * 0.5 + log(upper) * 0.5); else - u->control[h] = lower * 0.5 + upper * 0.5; + u->control[h] = (LADSPA_Data) (lower * 0.5 + upper * 0.5); break; case LADSPA_HINT_DEFAULT_HIGH: if (LADSPA_IS_HINT_LOGARITHMIC(hint)) - u->control[h] = exp(log(lower) * 0.25 + log(upper) * 0.75); + u->control[h] = (LADSPA_Data) exp(log(lower) * 0.25 + log(upper) * 0.75); else - u->control[h] = lower * 0.25 + upper * 0.75; + u->control[h] = (LADSPA_Data) (lower * 0.25 + upper * 0.75); break; case LADSPA_HINT_DEFAULT_0: @@ -737,6 +747,7 @@ int pa__init(pa_module*m) { u->sink_input->attach = sink_input_attach_cb; u->sink_input->detach = sink_input_detach_cb; u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->may_move_to = sink_input_may_move_to_cb; u->sink_input->userdata = u; pa_sink_put(u->sink); diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c index f34f7be3..97e97dc7 100644 --- a/src/modules/module-lirc.c +++ b/src/modules/module-lirc.c @@ -65,7 +65,7 @@ struct userdata { static int lirc_in_use = 0; -static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) { +static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) { struct userdata *u = userdata; char *name = NULL, *code = NULL; @@ -122,7 +122,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC pa_log("Failed to get sink '%s'", u->sink_name); else { int i; - pa_cvolume cv = *pa_sink_get_volume(s); + pa_cvolume cv = *pa_sink_get_volume(s, FALSE); #define DELTA (PA_VOLUME_NORM/20) @@ -159,7 +159,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC case MUTE_TOGGLE: - pa_sink_set_mute(s, !pa_sink_get_mute(s)); + pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE)); break; case INVALID: diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c index 7da77c0d..21f176a4 100644 --- a/src/modules/module-mmkbd-evdev.c +++ b/src/modules/module-mmkbd-evdev.c @@ -76,7 +76,7 @@ struct userdata { pa_module *module; }; -static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) { +static void io_callback(pa_mainloop_api *io, pa_io_event *e, int fd, pa_io_event_flags_t events, void*userdata) { struct userdata *u = userdata; pa_assert(io); @@ -113,7 +113,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC pa_log("Failed to get sink '%s'", u->sink_name); else { int i; - pa_cvolume cv = *pa_sink_get_volume(s); + pa_cvolume cv = *pa_sink_get_volume(s, FALSE); #define DELTA (PA_VOLUME_NORM/20) @@ -142,7 +142,7 @@ static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GC case MUTE_TOGGLE: - pa_sink_set_mute(s, !pa_sink_get_mute(s)); + pa_sink_set_mute(s, !pa_sink_get_mute(s, FALSE)); break; case INVALID: diff --git a/src/modules/module-native-protocol-fd.c b/src/modules/module-native-protocol-fd.c index fa9c0e4f..f17f435a 100644 --- a/src/modules/module-native-protocol-fd.c +++ b/src/modules/module-native-protocol-fd.c @@ -48,7 +48,8 @@ static const char* const valid_modargs[] = { int pa__init(pa_module*m) { pa_iochannel *io; pa_modargs *ma; - int fd, r = -1; + int32_t fd; + int r = -1; pa_native_options *options = NULL; pa_assert(m); @@ -63,18 +64,16 @@ int pa__init(pa_module*m) { goto finish; } - options = pa_native_options_new(); - options->module = m; - options->auth_anonymous = TRUE; + m->userdata = pa_native_protocol_get(m->core); io = pa_iochannel_new(m->core->mainloop, fd, fd); - m->userdata = pa_native_protocol_get(m->core); + options = pa_native_options_new(); + options->module = m; + options->auth_anonymous = TRUE; pa_native_protocol_connect(m->userdata, io, options); - pa_native_options_unref(options); - r = 0; finish: diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index 9162960f..470c622e 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -137,13 +137,13 @@ static void process_rewind(struct userdata *u, pa_usec_t now) { pa_log_debug("Requested to rewind %lu bytes.", (unsigned long) rewind_nbytes); if (u->timestamp <= now) - return; + goto do_nothing; delay = u->timestamp - now; in_buffer = pa_usec_to_bytes(delay, &u->sink->sample_spec); if (in_buffer <= 0) - return; + goto do_nothing; if (rewind_nbytes > in_buffer) rewind_nbytes = in_buffer; @@ -152,6 +152,11 @@ static void process_rewind(struct userdata *u, pa_usec_t now) { u->timestamp -= pa_bytes_to_usec(rewind_nbytes, &u->sink->sample_spec); pa_log_debug("Rewound %lu bytes.", (unsigned long) rewind_nbytes); + return; + +do_nothing: + + pa_sink_process_rewind(u->sink, 0); } static void process_render(struct userdata *u, pa_usec_t now) { diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c index 15b1e956..23a32549 100644 --- a/src/modules/module-oss.c +++ b/src/modules/module-oss.c @@ -258,7 +258,7 @@ static int mmap_write(struct userdata *u) { u->out_mmap_saved_nfrags = 0; if (info.blocks > 0) - mmap_fill_memblocks(u, info.blocks); + mmap_fill_memblocks(u, (unsigned) info.blocks); return info.blocks; } @@ -336,7 +336,7 @@ static int mmap_read(struct userdata *u) { u->in_mmap_saved_nfrags = 0; if (info.blocks > 0) { - mmap_post_memblocks(u, info.blocks); + mmap_post_memblocks(u, (unsigned) info.blocks); mmap_clear_memblocks(u, u->in_nfrags/2); } @@ -356,12 +356,12 @@ static pa_usec_t mmap_sink_get_latency(struct userdata *u) { u->out_mmap_saved_nfrags += info.blocks; - bpos = ((u->out_mmap_current + u->out_mmap_saved_nfrags) * u->out_fragment_size) % u->out_hwbuf_size; + bpos = ((u->out_mmap_current + (unsigned) u->out_mmap_saved_nfrags) * u->out_fragment_size) % u->out_hwbuf_size; if (bpos <= (size_t) info.ptr) - n = u->out_hwbuf_size - (info.ptr - bpos); + n = u->out_hwbuf_size - ((size_t) info.ptr - bpos); else - n = bpos - info.ptr; + n = bpos - (size_t) info.ptr; /* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->out_fragment_size, u->out_fragments); */ @@ -380,12 +380,12 @@ static pa_usec_t mmap_source_get_latency(struct userdata *u) { } u->in_mmap_saved_nfrags += info.blocks; - bpos = ((u->in_mmap_current + u->in_mmap_saved_nfrags) * u->in_fragment_size) % u->in_hwbuf_size; + bpos = ((u->in_mmap_current + (unsigned) u->in_mmap_saved_nfrags) * u->in_fragment_size) % u->in_hwbuf_size; if (bpos <= (size_t) info.ptr) - n = info.ptr - bpos; + n = (size_t) info.ptr - bpos; else - n = u->in_hwbuf_size - bpos + info.ptr; + n = u->in_hwbuf_size - bpos + (size_t) info.ptr; /* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->in_fragment_size, u->in_fragments); */ @@ -404,7 +404,7 @@ static pa_usec_t io_sink_get_latency(struct userdata *u) { pa_log_info("Device doesn't support SNDCTL_DSP_GETODELAY: %s", pa_cstrerror(errno)); u->use_getodelay = 0; } else - r = pa_bytes_to_usec(arg, &u->sink->sample_spec); + r = pa_bytes_to_usec((size_t) arg, &u->sink->sample_spec); } @@ -415,7 +415,7 @@ static pa_usec_t io_sink_get_latency(struct userdata *u) { pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); u->use_getospace = 0; } else - r = pa_bytes_to_usec(info.bytes, &u->sink->sample_spec); + r = pa_bytes_to_usec((size_t) info.bytes, &u->sink->sample_spec); } if (u->memchunk.memblock) @@ -437,7 +437,7 @@ static pa_usec_t io_source_get_latency(struct userdata *u) { pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); u->use_getispace = 0; } else - r = pa_bytes_to_usec(info.bytes, &u->source->sample_spec); + r = pa_bytes_to_usec((size_t) info.bytes, &u->source->sample_spec); } return r; @@ -528,8 +528,9 @@ static int unsuspend(struct userdata *u) { if ((u->fd = pa_oss_open(u->device_name, &m, NULL)) < 0) { pa_log_warn("Resume failed, device busy (%s)", pa_cstrerror(errno)); return -1; + } - if (m != u->mode) + if (m != u->mode) { pa_log_warn("Resume failed, couldn't open device with original access mode."); goto fail; } @@ -859,7 +860,7 @@ static int source_set_volume(pa_source *s) { static void thread_func(void *userdata) { struct userdata *u = userdata; int write_type = 0, read_type = 0; - unsigned short revents = 0; + short revents = 0; pa_assert(u); @@ -898,7 +899,7 @@ static void thread_func(void *userdata) { ssize_t l; pa_bool_t loop = FALSE, work_done = FALSE; - l = u->out_fragment_size; + l = (ssize_t) u->out_fragment_size; if (u->use_getospace) { audio_buf_info info; @@ -919,14 +920,14 @@ static void thread_func(void *userdata) { /* Round down to multiples of the fragment size, * because OSS needs that (at least some versions * do) */ - l = (l/u->out_fragment_size) * u->out_fragment_size; + l = (l/(ssize_t) u->out_fragment_size) * (ssize_t) u->out_fragment_size; /* Hmm, so poll() signalled us that we can read * something, but GETOSPACE told us there was nothing? * Hmm, make the best of it, try to read some data, to * avoid spinning forever. */ if (l <= 0 && (revents & POLLOUT)) { - l = u->out_fragment_size; + l = (ssize_t) u->out_fragment_size; loop = FALSE; } @@ -935,7 +936,7 @@ static void thread_func(void *userdata) { ssize_t t; if (u->memchunk.length <= 0) - pa_sink_render(u->sink, l, &u->memchunk); + pa_sink_render(u->sink, (size_t) l, &u->memchunk); pa_assert(u->memchunk.length > 0); @@ -965,8 +966,8 @@ static void thread_func(void *userdata) { } else { - u->memchunk.index += t; - u->memchunk.length -= t; + u->memchunk.index += (size_t) t; + u->memchunk.length -= (size_t) t; if (u->memchunk.length <= 0) { pa_memblock_unref(u->memchunk.memblock); @@ -1009,7 +1010,7 @@ static void thread_func(void *userdata) { pa_memchunk memchunk; pa_bool_t loop = FALSE, work_done = FALSE; - l = u->in_fragment_size; + l = (ssize_t) u->in_fragment_size; if (u->use_getispace) { audio_buf_info info; @@ -1023,15 +1024,16 @@ static void thread_func(void *userdata) { } } - l = (l/u->in_fragment_size) * u->in_fragment_size; + l = (l/(ssize_t) u->in_fragment_size) * (ssize_t) u->in_fragment_size; if (l <= 0 && (revents & POLLIN)) { - l = u->in_fragment_size; + l = (ssize_t) u->in_fragment_size; loop = FALSE; } while (l > 0) { - ssize_t t, k; + ssize_t t; + size_t k; pa_assert(l > 0); @@ -1039,8 +1041,8 @@ static void thread_func(void *userdata) { k = pa_memblock_get_length(memchunk.memblock); - if (k > l) - k = l; + if (k > (size_t) l) + k = (size_t) l; k = (k/u->frame_size)*u->frame_size; @@ -1071,7 +1073,7 @@ static void thread_func(void *userdata) { } else { memchunk.index = 0; - memchunk.length = t; + memchunk.length = (size_t) t; pa_source_post(u->source, &memchunk); pa_memblock_unref(memchunk.memblock); @@ -1099,9 +1101,9 @@ static void thread_func(void *userdata) { pa_assert(u->fd >= 0); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); - pollfd->events = - ((u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) ? POLLIN : 0) | - ((u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0); + pollfd->events = (short) + (((u->source && PA_SOURCE_IS_OPENED(u->source->thread_info.state)) ? POLLIN : 0) | + ((u->sink && PA_SINK_IS_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0)); } /* Hmm, nothing to do. Let's sleep */ @@ -1179,10 +1181,10 @@ int pa__init(pa_module*m) { goto fail; } - nfrags = m->core->default_n_fragments; - frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + nfrags = (int) m->core->default_n_fragments; + frag_size = (int) pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); if (frag_size <= 0) - frag_size = pa_frame_size(&ss); + frag_size = (int) pa_frame_size(&ss); if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) { pa_log("Failed to parse fragments arguments"); @@ -1238,8 +1240,8 @@ int pa__init(pa_module*m) { u->mode = mode; u->frame_size = pa_frame_size(&ss); u->device_name = pa_xstrdup(dev); - u->in_nfrags = u->out_nfrags = u->nfrags = nfrags; - u->out_fragment_size = u->in_fragment_size = u->frag_size = frag_size; + u->in_nfrags = u->out_nfrags = (uint32_t) (u->nfrags = nfrags); + u->out_fragment_size = u->in_fragment_size = (uint32_t) (u->frag_size = frag_size); u->use_mmap = use_mmap; u->rtpoll = pa_rtpoll_new(); pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); @@ -1248,15 +1250,15 @@ int pa__init(pa_module*m) { if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { pa_log_info("Input -- %u fragments of size %u.", info.fragstotal, info.fragsize); - u->in_fragment_size = info.fragsize; - u->in_nfrags = info.fragstotal; + u->in_fragment_size = (uint32_t) info.fragsize; + u->in_nfrags = (uint32_t) info.fragstotal; u->use_getispace = TRUE; } if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { pa_log_info("Output -- %u fragments of size %u.", info.fragstotal, info.fragsize); - u->out_fragment_size = info.fragsize; - u->out_nfrags = info.fragstotal; + u->out_fragment_size = (uint32_t) info.fragsize; + u->out_nfrags = (uint32_t) info.fragstotal; u->use_getospace = TRUE; } diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c index f389cd06..ae230b2c 100644 --- a/src/modules/module-pipe-sink.c +++ b/src/modules/module-pipe-sink.c @@ -145,8 +145,8 @@ static int process_render(struct userdata *u) { } else { - u->memchunk.index += l; - u->memchunk.length -= l; + u->memchunk.index += (size_t) l; + u->memchunk.length -= (size_t) l; if (u->memchunk.length <= 0) { pa_memblock_unref(u->memchunk.memblock); @@ -189,7 +189,7 @@ static void thread_func(void *userdata) { } /* Hmm, nothing to do. Let's sleep */ - pollfd->events = u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0; + pollfd->events = (short) (u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0); if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) goto fail; diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c index b0de34ca..25151d95 100644 --- a/src/modules/module-pipe-source.c +++ b/src/modules/module-pipe-source.c @@ -135,9 +135,9 @@ static void thread_func(void *userdata) { } else { - u->memchunk.length = l; + u->memchunk.length = (size_t) l; pa_source_post(u->source, &u->memchunk); - u->memchunk.index += l; + u->memchunk.index += (size_t) l; if (u->memchunk.index >= pa_memblock_get_length(u->memchunk.memblock)) { pa_memblock_unref(u->memchunk.memblock); @@ -149,7 +149,7 @@ static void thread_func(void *userdata) { } /* Hmm, nothing to do. Let's sleep */ - pollfd->events = u->source->thread_info.state == PA_SOURCE_RUNNING ? POLLIN : 0; + pollfd->events = (short) (u->source->thread_info.state == PA_SOURCE_RUNNING ? POLLIN : 0); if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) goto fail; diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c index 8136c6fc..aefd4020 100644 --- a/src/modules/module-protocol-stub.c +++ b/src/modules/module-protocol-stub.c @@ -148,7 +148,7 @@ # include <pulsecore/esound.h> # define TCPWRAP_SERVICE "esound" # define IPV4_PORT ESD_DEFAULT_PORT -# define MODULE_ARGUMENTS_COMMON "sink", "source", "auth-anonymous", "cookie", "auth-cookie", "auth-cookie-enabled" +# define MODULE_ARGUMENTS_COMMON "sink", "source", "auth-anonymous", "cookie", "auth-cookie", "auth-cookie-enabled", # ifdef USE_TCP_SOCKETS # include "module-esound-protocol-tcp-symdef.h" @@ -260,7 +260,7 @@ int pa__init(pa_module*m) { goto fail; } - u = pa_xnew0(struct userdata, 1); + m->userdata = u = pa_xnew0(struct userdata, 1); u->module = m; #if defined(USE_PROTOCOL_SIMPLE) @@ -299,11 +299,11 @@ int pa__init(pa_module*m) { listen_on = pa_modargs_get_value(ma, "listen", NULL); if (listen_on) { - u->socket_server_ipv6 = pa_socket_server_new_ipv6_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE); - u->socket_server_ipv4 = pa_socket_server_new_ipv4_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE); + u->socket_server_ipv6 = pa_socket_server_new_ipv6_string(m->core->mainloop, listen_on, (uint16_t) port, TCPWRAP_SERVICE); + u->socket_server_ipv4 = pa_socket_server_new_ipv4_string(m->core->mainloop, listen_on, (uint16_t) port, TCPWRAP_SERVICE); } else { - u->socket_server_ipv6 = pa_socket_server_new_ipv6_any(m->core->mainloop, port, TCPWRAP_SERVICE); - u->socket_server_ipv4 = pa_socket_server_new_ipv4_any(m->core->mainloop, port, TCPWRAP_SERVICE); + u->socket_server_ipv6 = pa_socket_server_new_ipv6_any(m->core->mainloop, (uint16_t) port, TCPWRAP_SERVICE); + u->socket_server_ipv4 = pa_socket_server_new_ipv4_any(m->core->mainloop, (uint16_t) port, TCPWRAP_SERVICE); } if (!u->socket_server_ipv4 && !u->socket_server_ipv6) @@ -327,7 +327,7 @@ int pa__init(pa_module*m) { /* This socket doesn't reside in our own runtime dir but in * /tmp/.esd/, hence we have to create the dir first */ - if (pa_make_secure_parent_dir(u->socket_path, pa_in_system_mode() ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) { + if (pa_make_secure_parent_dir(u->socket_path, pa_in_system_mode() ? 0755U : 0700U, (uid_t)-1, (gid_t)-1) < 0) { pa_log("Failed to create socket directory '%s': %s\n", u->socket_path, pa_cstrerror(errno)); goto fail; } @@ -368,8 +368,6 @@ int pa__init(pa_module*m) { # endif #endif - m->userdata = u; - if (ma) pa_modargs_free(ma); @@ -390,7 +388,8 @@ void pa__done(pa_module*m) { pa_assert(m); - u = m->userdata; + if (!(u = m->userdata)) + return; #if defined(USE_PROTOCOL_SIMPLE) if (u->simple_protocol) { diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c new file mode 100644 index 00000000..3706d921 --- /dev/null +++ b/src/modules/module-raop-discover.c @@ -0,0 +1,380 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2008 Colin Guthrie + + 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, or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <avahi-client/client.h> +#include <avahi-client/lookup.h> +#include <avahi-common/alternative.h> +#include <avahi-common/error.h> +#include <avahi-common/domain.h> +#include <avahi-common/malloc.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/native-common.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/avahi-wrap.h> + +#include "module-raop-discover-symdef.h" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of Airtunes"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define SERVICE_TYPE_SINK "_raop._tcp" + +static const char* const valid_modargs[] = { + NULL +}; + +struct tunnel { + AvahiIfIndex interface; + AvahiProtocol protocol; + char *name, *type, *domain; + uint32_t module_index; +}; + +struct userdata { + pa_core *core; + pa_module *module; + AvahiPoll *avahi_poll; + AvahiClient *client; + AvahiServiceBrowser *sink_browser; + + pa_hashmap *tunnels; +}; + +static unsigned tunnel_hash(const void *p) { + const struct tunnel *t = p; + + return + (unsigned) t->interface + + (unsigned) t->protocol + + pa_idxset_string_hash_func(t->name) + + pa_idxset_string_hash_func(t->type) + + pa_idxset_string_hash_func(t->domain); +} + +static int tunnel_compare(const void *a, const void *b) { + const struct tunnel *ta = a, *tb = b; + int r; + + if (ta->interface != tb->interface) + return 1; + if (ta->protocol != tb->protocol) + return 1; + if ((r = strcmp(ta->name, tb->name))) + return r; + if ((r = strcmp(ta->type, tb->type))) + return r; + if ((r = strcmp(ta->domain, tb->domain))) + return r; + + return 0; +} + +static struct tunnel *tunnel_new( + AvahiIfIndex interface, AvahiProtocol protocol, + const char *name, const char *type, const char *domain) { + + struct tunnel *t; + t = pa_xnew(struct tunnel, 1); + t->interface = interface; + t->protocol = protocol; + t->name = pa_xstrdup(name); + t->type = pa_xstrdup(type); + t->domain = pa_xstrdup(domain); + t->module_index = PA_IDXSET_INVALID; + return t; +} + +static void tunnel_free(struct tunnel *t) { + pa_assert(t); + pa_xfree(t->name); + pa_xfree(t->type); + pa_xfree(t->domain); + pa_xfree(t); +} + +static void resolver_cb( + AvahiServiceResolver *r, + AvahiIfIndex interface, AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, const char *type, const char *domain, + const char *host_name, const AvahiAddress *a, uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *userdata) { + + struct userdata *u = userdata; + struct tunnel *tnl; + + pa_assert(u); + + tnl = tunnel_new(interface, protocol, name, type, domain); + + if (event != AVAHI_RESOLVER_FOUND) + pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client))); + else { + char *device = NULL, *dname, *vname, *args; + char at[AVAHI_ADDRESS_STR_MAX]; + AvahiStringList *l; + pa_module *m; + + for (l = txt; l; l = l->next) { + char *key, *value; + pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0); + + pa_log_debug("Found key: '%s' with value: '%s'", key, value); + if (strcmp(key, "device") == 0) { + pa_xfree(device); + device = value; + value = NULL; + } + avahi_free(key); + avahi_free(value); + } + + if (device) + dname = pa_sprintf_malloc("airtunes.%s.%s", host_name, device); + else + dname = pa_sprintf_malloc("airtunes.%s", host_name); + + if (!(vname = pa_namereg_make_valid_name(dname))) { + pa_log("Cannot construct valid device name from '%s'.", dname); + avahi_free(device); + pa_xfree(dname); + goto finish; + } + pa_xfree(dname); + + /* + TODO: allow this syntax of server name in things.... + args = pa_sprintf_malloc("server=[%s]:%u " + "sink_name=%s", + avahi_address_snprint(at, sizeof(at), a), port, + vname);*/ + args = pa_sprintf_malloc("server=%s " + "sink_name=%s", + avahi_address_snprint(at, sizeof(at), a), + vname); + + pa_log_debug("Loading module-raop-sink with arguments '%s'", args); + + if ((m = pa_module_load(u->core, "module-raop-sink", args))) { + tnl->module_index = m->index; + pa_hashmap_put(u->tunnels, tnl, tnl); + tnl = NULL; + } + + pa_xfree(vname); + pa_xfree(args); + avahi_free(device); + } + +finish: + + avahi_service_resolver_free(r); + + if (tnl) + tunnel_free(tnl); +} + +static void browser_cb( + AvahiServiceBrowser *b, + AvahiIfIndex interface, AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, + AvahiLookupResultFlags flags, + void *userdata) { + + struct userdata *u = userdata; + struct tunnel *t; + + pa_assert(u); + + if (flags & AVAHI_LOOKUP_RESULT_LOCAL) + return; + + t = tunnel_new(interface, protocol, name, type, domain); + + if (event == AVAHI_BROWSER_NEW) { + + if (!pa_hashmap_get(u->tunnels, t)) + if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u))) + pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client))); + + /* We ignore the returned resolver object here, since the we don't + * need to attach any special data to it, and we can still destory + * it from the callback */ + + } else if (event == AVAHI_BROWSER_REMOVE) { + struct tunnel *t2; + + if ((t2 = pa_hashmap_get(u->tunnels, t))) { + pa_module_unload_by_index(u->core, t2->module_index, TRUE); + pa_hashmap_remove(u->tunnels, t2); + tunnel_free(t2); + } + } + + tunnel_free(t); +} + +static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) { + struct userdata *u = userdata; + + pa_assert(c); + pa_assert(u); + + u->client = c; + + switch (state) { + case AVAHI_CLIENT_S_REGISTERING: + case AVAHI_CLIENT_S_RUNNING: + case AVAHI_CLIENT_S_COLLISION: + + if (!u->sink_browser) { + + if (!(u->sink_browser = avahi_service_browser_new( + c, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + SERVICE_TYPE_SINK, + NULL, + 0, + browser_cb, u))) { + + pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c))); + pa_module_unload_request(u->module, TRUE); + } + } + + break; + + case AVAHI_CLIENT_FAILURE: + if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { + int error; + + pa_log_debug("Avahi daemon disconnected."); + + if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { + pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); + pa_module_unload_request(u->module, TRUE); + } + } + + /* Fall through */ + + case AVAHI_CLIENT_CONNECTING: + + if (u->sink_browser) { + avahi_service_browser_free(u->sink_browser); + u->sink_browser = NULL; + } + + break; + + default: ; + } +} + +int pa__init(pa_module*m) { + + struct userdata *u; + pa_modargs *ma = NULL; + int error; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; + u->sink_browser = NULL; + + u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare); + + u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); + + if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { + pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error)); + goto fail; + } + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata*u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->client) + avahi_client_free(u->client); + + if (u->avahi_poll) + pa_avahi_poll_free(u->avahi_poll); + + if (u->tunnels) { + struct tunnel *t; + + while ((t = pa_hashmap_steal_first(u->tunnels))) { + pa_module_unload_by_index(u->core, t->module_index, TRUE); + tunnel_free(t); + } + + pa_hashmap_free(u->tunnels, NULL, NULL); + } + + pa_xfree(u); +} diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c new file mode 100644 index 00000000..62f0a73c --- /dev/null +++ b/src/modules/module-raop-sink.c @@ -0,0 +1,675 @@ +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2008 Colin Guthrie + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <sys/stat.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <poll.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <sys/ioctl.h> + +#ifdef HAVE_LINUX_SOCKIOS_H +#include <linux/sockios.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/socket-client.h> +#include <pulsecore/authkey.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/thread.h> +#include <pulsecore/time-smoother.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/socket-util.h> + +#include "module-raop-sink-symdef.h" +#include "rtp.h" +#include "sdp.h" +#include "sap.h" +#include "raop_client.h" + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("RAOP Sink (Apple Airtunes)"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "server=<address> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate>"); + +#define DEFAULT_SINK_NAME "airtunes" + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; + pa_thread *thread; + + pa_memchunk raw_memchunk; + pa_memchunk encoded_memchunk; + + void *write_data; + size_t write_length, write_index; + + void *read_data; + size_t read_length, read_index; + + pa_usec_t latency; + + pa_volume_t volume; + pa_bool_t muted; + + /*esd_format_t format;*/ + int32_t rate; + + pa_smoother *smoother; + int fd; + + int64_t offset; + int64_t encoding_overhead; + int32_t next_encoding_overhead; + double encoding_ratio; + + pa_raop_client *raop; + + size_t block_size; +}; + +static const char* const valid_modargs[] = { + "server", + "rate", + "format", + "channels", + "sink_name", + NULL +}; + +enum { + SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_RIP_SOCKET +}; + +static void on_connection(PA_GCC_UNUSED int fd, void*userdata) { + struct userdata *u = userdata; + pa_assert(u); + + pa_assert(u->fd < 0); + u->fd = fd; + + /* Set the initial volume */ + pa_raop_client_set_volume(u->raop, u->volume); + + pa_log_debug("Connection authenticated, handing fd to IO thread..."); + + pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL); +} + +static void on_close(void*userdata) { + struct userdata *u = userdata; + pa_assert(u); + + pa_log_debug("Connection closed, informing IO thread..."); + + pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_RIP_SOCKET, NULL, 0, NULL, NULL); +} + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_SET_STATE: + + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); + + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + + /* Issue a FLUSH if we are connected */ + if (u->fd >= 0) { + pa_raop_flush(u->raop); + } + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + + /* The connection can be closed when idle, so check to + see if we need to reestablish it */ + if (u->fd < 0) + pa_raop_connect(u->raop); + else + pa_raop_flush(u->raop); + } + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t w, r; + + r = pa_smoother_get(u->smoother, pa_rtclock_usec()); + w = pa_bytes_to_usec((u->offset - u->encoding_overhead + (u->encoded_memchunk.length / u->encoding_ratio)), &u->sink->sample_spec); + + *((pa_usec_t*) data) = w > r ? w - r : 0; + break; + } + + case SINK_MESSAGE_PASS_SOCKET: { + struct pollfd *pollfd; + + pa_assert(!u->rtpoll_item); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->fd; + pollfd->events = POLLOUT; + /*pollfd->events = */pollfd->revents = 0; + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + /* Our stream has been suspended so we just flush it.... */ + pa_raop_flush(u->raop); + } + return 0; + } + + case SINK_MESSAGE_RIP_SOCKET: { + pa_assert(u->fd >= 0); + + pa_close(u->fd); + u->fd = -1; + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + + pa_log_debug("RTSP control connection closed, but we're suspended so let's not worry about it... we'll open it again later"); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } else { + /* Quesiton: is this valid here: or should we do some sort of: + return pa_sink_process_msg(PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL); + ?? */ + pa_module_unload_request(u->module, TRUE); + } + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static int sink_get_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int i; + + pa_assert(u); + + for (i = 0; i < s->sample_spec.channels; i++) { + s->volume.values[i] = u->volume; + } + + return 0; +} + +static int sink_set_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int rv; + + pa_assert(u); + + /* If we're muted, we fake it */ + if (u->muted) + return 0; + + pa_assert(s->sample_spec.channels > 0); + + /* Avoid pointless volume sets */ + if (u->volume == s->volume.values[0]) + return 0; + + rv = pa_raop_client_set_volume(u->raop, s->volume.values[0]); + if (0 == rv) + u->volume = s->volume.values[0]; + + return rv; +} + +static int sink_get_mute_cb(pa_sink *s) { + struct userdata *u = s->userdata; + + pa_assert(u); + + s->muted = u->muted; + return 0; +} + +static int sink_set_mute_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int rv; + + pa_assert(u); + + rv = pa_raop_client_set_volume(u->raop, (s->muted ? PA_VOLUME_MUTED : u->volume)); + u->muted = s->muted; + return rv; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int write_type = 0; + pa_memchunk silence; + uint32_t silence_overhead = 0; + double silence_ratio = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + + /* Create a chunk of memory that is our encoded silence sample. */ + pa_memchunk_reset(&silence); + + for (;;) { + int ret; + + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) + if (u->sink->thread_info.rewind_requested) + pa_sink_process_rewind(u->sink, 0); + + if (u->rtpoll_item) { + struct pollfd *pollfd; + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + /* Render some data and write it to the fifo */ + if (/*PA_SINK_IS_OPENED(u->sink->thread_info.state) && */pollfd->revents) { + pa_usec_t usec; + int64_t n; + void *p; + + if (!silence.memblock) { + pa_memchunk silence_tmp; + + pa_memchunk_reset(&silence_tmp); + silence_tmp.memblock = pa_memblock_new(u->core->mempool, 4096); + silence_tmp.length = 4096; + p = pa_memblock_acquire(silence_tmp.memblock); + memset(p, 0, 4096); + pa_memblock_release(silence_tmp.memblock); + pa_raop_client_encode_sample(u->raop, &silence_tmp, &silence); + pa_assert(0 == silence_tmp.length); + silence_overhead = silence_tmp.length - 4096; + silence_ratio = silence_tmp.length / 4096; + pa_memblock_unref(silence_tmp.memblock); + } + + for (;;) { + ssize_t l; + + if (u->encoded_memchunk.length <= 0) { + if (u->encoded_memchunk.memblock) + pa_memblock_unref(u->encoded_memchunk.memblock); + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { + size_t rl; + + /* We render real data */ + if (u->raw_memchunk.length <= 0) { + if (u->raw_memchunk.memblock) + pa_memblock_unref(u->raw_memchunk.memblock); + pa_memchunk_reset(&u->raw_memchunk); + + /* Grab unencoded data */ + pa_sink_render(u->sink, u->block_size, &u->raw_memchunk); + } + pa_assert(u->raw_memchunk.length > 0); + + /* Encode it */ + rl = u->raw_memchunk.length; + u->encoding_overhead += u->next_encoding_overhead; + pa_raop_client_encode_sample(u->raop, &u->raw_memchunk, &u->encoded_memchunk); + u->next_encoding_overhead = (u->encoded_memchunk.length - (rl - u->raw_memchunk.length)); + u->encoding_ratio = u->encoded_memchunk.length / (rl - u->raw_memchunk.length); + } else { + /* We render some silence into our memchunk */ + memcpy(&u->encoded_memchunk, &silence, sizeof(pa_memchunk)); + pa_memblock_ref(silence.memblock); + + /* Calculate/store some values to be used with the smoother */ + u->next_encoding_overhead = silence_overhead; + u->encoding_ratio = silence_ratio; + } + } + pa_assert(u->encoded_memchunk.length > 0); + + p = pa_memblock_acquire(u->encoded_memchunk.memblock); + l = pa_write(u->fd, (uint8_t*) p + u->encoded_memchunk.index, u->encoded_memchunk.length, &write_type); + pa_memblock_release(u->encoded_memchunk.memblock); + + pa_assert(l != 0); + + if (l < 0) { + + if (errno == EINTR) + continue; + else if (errno == EAGAIN) { + + /* OK, we filled all socket buffers up + * now. */ + goto filled_up; + + } else { + pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + u->offset += l; + + u->encoded_memchunk.index += l; + u->encoded_memchunk.length -= l; + + pollfd->revents = 0; + + if (u->encoded_memchunk.length > 0) { + /* we've completely written the encoded data, so update our overhead */ + u->encoding_overhead += u->next_encoding_overhead; + + /* OK, we wrote less that we asked for, + * hence we can assume that the socket + * buffers are full now */ + goto filled_up; + } + } + } + + filled_up: + + /* At this spot we know that the socket buffers are + * fully filled up. This is the best time to estimate + * the playback position of the server */ + + n = u->offset - u->encoding_overhead; + +#ifdef SIOCOUTQ + { + int l; + if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0) + n -= (l / u->encoding_ratio); + } +#endif + + usec = pa_bytes_to_usec(n, &u->sink->sample_spec); + + if (usec > u->latency) + usec -= u->latency; + else + usec = 0; + + pa_smoother_put(u->smoother, pa_rtclock_usec(), usec); + } + + /* Hmm, nothing to do. Let's sleep */ + pollfd->events = POLLOUT; /*PA_SINK_IS_OPENED(u->sink->thread_info.state) ? POLLOUT : 0;*/ + } + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + if (u->rtpoll_item) { + struct pollfd* pollfd; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~POLLOUT) { + if (u->sink->thread_info.state != PA_SINK_SUSPENDED) { + pa_log("FIFO shutdown."); + goto fail; + } + + /* We expect this to happen on occasion if we are not sending data. + It's perfectly natural and normal and natural */ + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + if (silence.memblock) + pa_memblock_unref(silence.memblock); + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_modargs *ma = NULL; + const char *server; + pa_sink_new_data data; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments"); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec(ma, &ss) < 0) { + pa_log("invalid sample format specification"); + goto fail; + } + + if ((/*ss.format != PA_SAMPLE_U8 &&*/ ss.format != PA_SAMPLE_S16NE) || + (ss.channels > 2)) { + pa_log("sample type support is limited to mono/stereo and U8 or S16NE sample data"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->fd = -1; + u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); + pa_memchunk_reset(&u->raw_memchunk); + pa_memchunk_reset(&u->encoded_memchunk); + u->offset = 0; + u->encoding_overhead = 0; + u->next_encoding_overhead = 0; + u->encoding_ratio = 1.0; + + u->volume = roundf(0.7 * PA_VOLUME_NORM); + u->muted = FALSE; + + u->rtpoll = pa_rtpoll_new(); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); + u->rtpoll_item = NULL; + + /*u->format = + (ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) | + (ss.channels == 2 ? ESD_STEREO : ESD_MONO);*/ + u->rate = ss.rate; + u->block_size = pa_usec_to_bytes(PA_USEC_PER_SEC/20, &ss); + + u->read_data = u->write_data = NULL; + u->read_index = u->write_index = u->read_length = u->write_length = 0; + + /*u->state = STATE_AUTH;*/ + u->latency = 0; + + if (!(server = pa_modargs_get_value(ma, "server", NULL))) { + pa_log("No server argument given."); + goto fail; + } + + pa_sink_new_data_init(&data); + data.driver = __FILE__; + data.module = m; + pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); + pa_sink_new_data_set_sample_spec(&data, &ss); + pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server); + pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Airtunes sink '%s'", server); + + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK); + pa_sink_new_data_done(&data); + + if (!u->sink) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + u->sink->get_volume = sink_get_volume_cb; + u->sink->set_volume = sink_set_volume_cb; + u->sink->get_mute = sink_get_mute_cb; + u->sink->set_mute = sink_set_mute_cb; + u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK|PA_SINK_HW_VOLUME_CTRL; + + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + if (!(u->raop = pa_raop_client_new(u->core, server))) { + pa_log("Failed to connect to server."); + goto fail; + } + + pa_raop_client_set_callback(u->raop, on_connection, u); + pa_raop_client_set_closed_callback(u->raop, on_close, u); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + pa_sink_put(u->sink); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->raw_memchunk.memblock) + pa_memblock_unref(u->raw_memchunk.memblock); + + if (u->encoded_memchunk.memblock) + pa_memblock_unref(u->encoded_memchunk.memblock); + + if (u->raop) + pa_raop_client_free(u->raop); + + pa_xfree(u->read_data); + pa_xfree(u->write_data); + + if (u->smoother) + pa_smoother_free(u->smoother); + + if (u->fd >= 0) + pa_close(u->fd); + + pa_xfree(u); +} diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c index 5b2be118..976a8ce5 100644 --- a/src/modules/module-remap-sink.c +++ b/src/modules/module-remap-sink.c @@ -274,6 +274,16 @@ static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t s } } +/* Called from main context */ +static pa_bool_t sink_input_may_move_to_cb(pa_sink_input *i, pa_sink *dest) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + return u->sink != dest; +} + int pa__init(pa_module*m) { struct userdata *u; pa_sample_spec ss; @@ -386,6 +396,7 @@ int pa__init(pa_module*m) { u->sink_input->detach = sink_input_detach_cb; u->sink_input->kill = sink_input_kill_cb; u->sink_input->state_change = sink_input_state_change_cb; + u->sink_input->may_move_to = sink_input_may_move_to_cb; u->sink_input->userdata = u; pa_sink_put(u->sink); diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c index a6324526..21565cc4 100644 --- a/src/modules/module-sine.c +++ b/src/modules/module-sine.c @@ -116,13 +116,13 @@ static void sink_input_state_change_cb(pa_sink_input *i, pa_sink_input_state_t s pa_sink_input_request_rewind(i, 0, FALSE, TRUE); } -static void calc_sine(float *f, size_t l, float freq) { +static void calc_sine(float *f, size_t l, double freq) { size_t i; l /= sizeof(float); for (i = 0; i < l; i++) - f[i] = (float) sin((double) i/l*M_PI*2*freq)/2; + f[i] = (float) sin((double) i/(double)l*M_PI*2*freq)/2; } int pa__init(pa_module*m) { @@ -163,7 +163,7 @@ int pa__init(pa_module*m) { u->memblock = pa_memblock_new(m->core->mempool, pa_bytes_per_second(&ss)); p = pa_memblock_acquire(u->memblock); - calc_sine(p, pa_memblock_get_length(u->memblock), frequency); + calc_sine(p, pa_memblock_get_length(u->memblock), (double) frequency); pa_memblock_release(u->memblock); pa_sink_input_new_data_init(&data); diff --git a/src/modules/module-stream-restore.c b/src/modules/module-stream-restore.c index ec4e7c79..55897004 100644 --- a/src/modules/module-stream-restore.c +++ b/src/modules/module-stream-restore.c @@ -134,7 +134,7 @@ static char *get_name(pa_proplist *p, const char *prefix) { else if ((r = pa_proplist_gets(p, PA_PROP_MEDIA_NAME))) return pa_sprintf_malloc("%s-by-media-name:%s", prefix, r); - return NULL; + return pa_sprintf_malloc("%s-fallback:%s", prefix, r); } static struct entry* read_entry(struct userdata *u, char *name) { @@ -145,7 +145,7 @@ static struct entry* read_entry(struct userdata *u, char *name) { pa_assert(name); key.dptr = name; - key.dsize = strlen(name); + key.dsize = (int) strlen(name); data = gdbm_fetch(u->gdbm_file, key); @@ -277,7 +277,7 @@ static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint3 } key.dptr = name; - key.dsize = strlen(name); + key.dsize = (int) strlen(name); data.dptr = (void*) &entry; data.dsize = sizeof(entry); @@ -306,8 +306,11 @@ static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_n if (u->restore_device && (s = pa_namereg_get(c, e->device, PA_NAMEREG_SINK, TRUE))) { - pa_log_info("Restoring device for stream %s.", name); - new_data->sink = s; + if (!new_data->sink) { + pa_log_info("Restoring device for stream %s.", name); + new_data->sink = s; + } else + pa_log_info("Not restore device for stream %s, because already set.", name); } pa_xfree(e); @@ -330,13 +333,20 @@ static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_inpu if ((e = read_entry(u, name))) { if (u->restore_volume) { - pa_log_info("Restoring volume for sink input %s.", name); - pa_sink_input_new_data_set_volume(new_data, pa_cvolume_remap(&e->volume, &e->channel_map, &new_data->channel_map)); + + if (!new_data->volume_is_set) { + pa_log_info("Restoring volume for sink input %s.", name); + pa_sink_input_new_data_set_volume(new_data, pa_cvolume_remap(&e->volume, &e->channel_map, &new_data->channel_map)); + } else + pa_log_debug("Not restoring volume for sink input %s, because already set.", name); } if (u->restore_muted) { - pa_log_info("Restoring mute state for sink input %s.", name); - pa_sink_input_new_data_set_muted(new_data, e->muted); + if (!new_data->muted_is_set) { + pa_log_info("Restoring mute state for sink input %s.", name); + pa_sink_input_new_data_set_muted(new_data, e->muted); + } else + pa_log_debug("Not restoring mute state for sink input %s, because already set.", name); } pa_xfree(e); @@ -360,10 +370,14 @@ static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_ou pa_source *s; if (u->restore_device && + !new_data->direct_on_input && (s = pa_namereg_get(c, e->device, PA_NAMEREG_SOURCE, TRUE))) { - pa_log_info("Restoring device for stream %s.", name); - new_data->source = s; + if (!new_data->source) { + pa_log_info("Restoring device for stream %s.", name); + new_data->source = s; + } else + pa_log_info("Not restoring device for stream %s, because already set", name); } pa_xfree(e); @@ -408,7 +422,7 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { char *n; pa_sink *s; - if (!(n = get_name(si->proplist, "sink_input"))) + if (!(n = get_name(si->proplist, "sink-input"))) continue; if (strcmp(name, n)) { @@ -417,8 +431,9 @@ static void apply_entry(struct userdata *u, const char *name, struct entry *e) { } if (u->restore_volume) { + pa_cvolume v = e->volume; pa_log_info("Restoring volume for sink input %s.", name); - pa_sink_input_set_volume(si, pa_cvolume_remap(&e->volume, &e->channel_map, &si->channel_map)); + pa_sink_input_set_volume(si, pa_cvolume_remap(&v, &e->channel_map, &si->channel_map)); } if (u->restore_muted) { @@ -490,7 +505,7 @@ static void dump_database(struct userdata *u) { static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connection *c, uint32_t tag, pa_tagstruct *t) { struct userdata *u; uint32_t command; - pa_tagstruct *reply; + pa_tagstruct *reply = NULL; pa_assert(p); pa_assert(m); @@ -529,7 +544,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio next_key = gdbm_nextkey(u->gdbm_file, key); - name = pa_xstrndup(key.dptr, key.dsize); + name = pa_xstrndup(key.dptr, (size_t) key.dsize); pa_xfree(key.dptr); if ((e = read_entry(u, name))) { @@ -552,7 +567,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio case SUBCOMMAND_WRITE: { uint32_t mode; - pa_bool_t apply_immediately; + pa_bool_t apply_immediately = FALSE; if (pa_tagstruct_getu32(t, &mode) < 0 || pa_tagstruct_get_boolean(t, &apply_immediately) < 0) @@ -571,6 +586,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio pa_bool_t muted; struct entry entry; datum key, data; + int k; memset(&entry, 0, sizeof(entry)); @@ -588,12 +604,12 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio pa_strlcpy(entry.device, device, sizeof(entry.device)); key.dptr = (void*) name; - key.dsize = strlen(name); + key.dsize = (int) strlen(name); data.dptr = (void*) &entry; data.dsize = sizeof(entry); - if (gdbm_store(u->gdbm_file, key, data, mode == PA_UPDATE_REPLACE ? GDBM_REPLACE : GDBM_INSERT) == 1) + if ((k = gdbm_store(u->gdbm_file, key, data, mode == PA_UPDATE_REPLACE ? GDBM_REPLACE : GDBM_INSERT)) == 0) if (apply_immediately) apply_entry(u, name, &entry); } @@ -613,7 +629,7 @@ static int extension_cb(pa_native_protocol *p, pa_module *m, pa_native_connectio goto fail; key.dptr = (void*) name; - key.dsize = strlen(name); + key.dsize = (int) strlen(name); gdbm_delete(u->gdbm_file, key); } @@ -670,6 +686,7 @@ int pa__init(pa_module*m) { pa_source_output *so; uint32_t idx; pa_bool_t restore_device = TRUE, restore_volume = TRUE, restore_muted = TRUE; + int gdbm_cache_size; pa_assert(m); @@ -730,6 +747,10 @@ int pa__init(pa_module*m) { goto fail; } + /* By default the cache of gdbm is rather large, let's reduce it a bit to save memory */ + gdbm_cache_size = 10; + gdbm_setopt(u->gdbm_file, GDBM_CACHESIZE, &gdbm_cache_size, sizeof(gdbm_cache_size)); + pa_log_info("Sucessfully opened database file '%s'.", fname); pa_xfree(fname); diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c index bc7c023c..8ab84e08 100644 --- a/src/modules/module-suspend-on-idle.c +++ b/src/modules/module-suspend-on-idle.c @@ -83,12 +83,12 @@ static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval d->userdata->core->mainloop->time_restart(d->time_event, NULL); - if (d->sink && pa_sink_used_by(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) { + if (d->sink && pa_sink_check_suspend(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) { pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name); pa_sink_suspend(d->sink, TRUE); } - if (d->source && pa_source_used_by(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) { + if (d->source && pa_source_check_suspend(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) { pa_log_info("Source %s idle for too long, suspending ...", d->source->name); pa_source_suspend(d->source, TRUE); } @@ -158,7 +158,7 @@ static pa_hook_result_t sink_input_unlink_hook_cb(pa_core *c, pa_sink_input *s, pa_sink_input_assert_ref(s); pa_assert(u); - if (pa_sink_used_by(s->sink) <= 0) { + if (pa_sink_check_suspend(s->sink) <= 0) { struct device_info *d; if ((d = pa_hashmap_get(u->device_infos, s->sink))) restart(d); @@ -172,7 +172,7 @@ static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_outpu pa_source_output_assert_ref(s); pa_assert(u); - if (pa_source_used_by(s->source) <= 0) { + if (pa_source_check_suspend(s->source) <= 0) { struct device_info *d; if ((d = pa_hashmap_get(u->device_infos, s->source))) restart(d); @@ -191,7 +191,7 @@ static pa_hook_result_t sink_input_move_hook_cb(pa_core *c, pa_sink_input_move_h if ((d = pa_hashmap_get(u->device_infos, data->destination))) resume(d); - if (pa_sink_used_by(data->sink_input->sink) <= 1) + if (pa_sink_check_suspend(data->sink_input->sink) <= 1) if ((d = pa_hashmap_get(u->device_infos, data->sink_input->sink))) restart(d); @@ -208,7 +208,7 @@ static pa_hook_result_t source_output_move_hook_cb(pa_core *c, pa_source_output_ if ((d = pa_hashmap_get(u->device_infos, data->destination))) resume(d); - if (pa_source_used_by(data->source_output->source) <= 1) + if (pa_source_check_suspend(data->source_output->source) <= 1) if ((d = pa_hashmap_get(u->device_infos, data->source_output->source))) restart(d); @@ -266,8 +266,8 @@ static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct user d->time_event = c->mainloop->time_new(c->mainloop, NULL, timeout_cb, d); pa_hashmap_put(u->device_infos, o, d); - if ((d->sink && pa_sink_used_by(d->sink) <= 0) || - (d->source && pa_source_used_by(d->source) <= 0)) + if ((d->sink && pa_sink_check_suspend(d->sink) <= 0) || + (d->source && pa_source_check_suspend(d->source) <= 0)) restart(d); return PA_HOOK_OK; @@ -313,7 +313,7 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s pa_sink *s = PA_SINK(o); pa_sink_state_t state = pa_sink_get_state(s); - if (pa_sink_used_by(s) <= 0) { + if (pa_sink_check_suspend(s) <= 0) { if (PA_SINK_IS_OPENED(state)) restart(d); @@ -324,7 +324,7 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s pa_source *s = PA_SOURCE(o); pa_source_state_t state = pa_source_get_state(s); - if (pa_source_used_by(s) <= 0) { + if (pa_source_check_suspend(s) <= 0) { if (PA_SOURCE_IS_OPENED(state)) restart(d); @@ -337,7 +337,7 @@ static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, s int pa__init(pa_module*m) { pa_modargs *ma = NULL; struct userdata *u; - uint32_t timeout = 1; + uint32_t timeout = 5; uint32_t idx; pa_sink *sink; pa_source *source; diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c index 79ce1dd2..a46d6e59 100644 --- a/src/modules/module-tunnel.c +++ b/src/modules/module-tunnel.c @@ -159,7 +159,7 @@ static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = command_suspended, [PA_COMMAND_RECORD_STREAM_SUSPENDED] = command_suspended, [PA_COMMAND_PLAYBACK_STREAM_MOVED] = command_moved, - [PA_COMMAND_RECORD_STREAM_MOVED] = command_moved, + [PA_COMMAND_RECORD_STREAM_MOVED] = command_moved }; struct userdata { @@ -178,7 +178,7 @@ struct userdata { #ifdef TUNNEL_SINK char *sink_name; pa_sink *sink; - int32_t requested_bytes; + size_t requested_bytes; #else char *source_name; pa_source *source; @@ -389,7 +389,7 @@ static void send_data(struct userdata *u) { u->requested_bytes -= memchunk.length; - u->counter += memchunk.length; + u->counter += (int64_t) memchunk.length; } } @@ -417,7 +417,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case PA_SINK_MESSAGE_GET_LATENCY: { pa_usec_t yl, yr, *usec = data; - yl = pa_bytes_to_usec(u->counter, &u->sink->sample_spec); + yl = pa_bytes_to_usec((uint64_t) u->counter, &u->sink->sample_spec); yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); *usec = yl > yr ? yl - yr : 0; @@ -444,10 +444,10 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse case SINK_MESSAGE_UPDATE_LATENCY: { pa_usec_t y; - y = pa_bytes_to_usec(u->counter, &u->sink->sample_spec); + y = pa_bytes_to_usec((uint64_t) u->counter, &u->sink->sample_spec); if (y > (pa_usec_t) offset || offset < 0) - y -= offset; + y -= (pa_usec_t) offset; else y = 0; @@ -465,7 +465,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_pstream_send_memblock(u->pstream, u->channel, 0, PA_SEEK_RELATIVE, chunk); - u->counter_delta += chunk->length; + u->counter_delta += (int64_t) chunk->length; return 0; } @@ -508,7 +508,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off switch (code) { - case PA_SINK_MESSAGE_SET_STATE: { + case PA_SOURCE_MESSAGE_SET_STATE: { int r; if ((r = pa_source_process_msg(o, code, data, offset, chunk)) >= 0) @@ -520,7 +520,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off case PA_SOURCE_MESSAGE_GET_LATENCY: { pa_usec_t yr, yl, *usec = data; - yl = pa_bytes_to_usec(u->counter, &PA_SINK(o)->sample_spec); + yl = pa_bytes_to_usec((uint64_t) u->counter, &PA_SOURCE(o)->sample_spec); yr = pa_smoother_get(u->smoother, pa_rtclock_usec()); *usec = yr > yl ? yr - yl : 0; @@ -532,7 +532,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) pa_source_post(u->source, chunk); - u->counter += chunk->length; + u->counter += (int64_t) chunk->length; return 0; @@ -544,10 +544,10 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off case SOURCE_MESSAGE_UPDATE_LATENCY: { pa_usec_t y; - y = pa_bytes_to_usec(u->counter, &u->source->sample_spec); + y = pa_bytes_to_usec((uint64_t) u->counter, &u->source->sample_spec); if (offset >= 0 || y > (pa_usec_t) -offset) - y += offset; + y += (pa_usec_t) offset; else y = 0; @@ -736,9 +736,9 @@ static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, uint /* Add the length of our server-side buffer */ if (write_index >= read_index) - delay += (int64_t) pa_bytes_to_usec(write_index-read_index, ss); + delay += (int64_t) pa_bytes_to_usec((uint64_t) (write_index-read_index), ss); else - delay -= (int64_t) pa_bytes_to_usec(read_index-write_index, ss); + delay -= (int64_t) pa_bytes_to_usec((uint64_t) (read_index-write_index), ss); /* Our measurements are already out of date, hence correct by the * * transport latency */ @@ -750,9 +750,9 @@ static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, uint /* Now correct by what we have have read/written since we requested the update */ #ifdef TUNNEL_SINK - delay += (int64_t) pa_bytes_to_usec(u->counter_delta, ss); + delay += (int64_t) pa_bytes_to_usec((uint64_t) u->counter_delta, ss); #else - delay -= (int64_t) pa_bytes_to_usec(u->counter_delta, ss); + delay -= (int64_t) pa_bytes_to_usec((uint64_t) u->counter_delta, ss); #endif #ifdef TUNNEL_SINK @@ -1425,11 +1425,11 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t u->maxlength = 4*1024*1024; #ifdef TUNNEL_SINK - u->tlength = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_TLENGTH_MSEC, &u->sink->sample_spec); - u->minreq = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MINREQ_MSEC, &u->sink->sample_spec); + u->tlength = (uint32_t) pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_TLENGTH_MSEC, &u->sink->sample_spec); + u->minreq = (uint32_t) pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MINREQ_MSEC, &u->sink->sample_spec); u->prebuf = u->tlength; #else - u->fragsize = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_FRAGSIZE_MSEC, &u->source->sample_spec); + u->fragsize = (uint32_t) pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_FRAGSIZE_MSEC, &u->source->sample_spec); #endif #ifdef TUNNEL_SINK @@ -1494,6 +1494,13 @@ static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t #endif } + if (u->version >= 14) { +#ifdef TUNNEL_SINK + pa_tagstruct_put_boolean(reply, FALSE); /* volume_set */ +#endif + pa_tagstruct_put_boolean(reply, TRUE); /* early rquests */ + } + pa_pstream_send_tagstruct(u->pstream, reply); pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u, NULL); @@ -1548,7 +1555,7 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, PA_UINT_TO_PTR(seek), offset, chunk); - u->counter_delta += chunk->length; + u->counter_delta += (int64_t) chunk->length; } #endif diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c index 0fb17a0d..aac0d046 100644 --- a/src/modules/module-volume-restore.c +++ b/src/modules/module-volume-restore.c @@ -103,7 +103,7 @@ static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) { if (k <= 0 || k > (long) PA_CHANNELS_MAX) return NULL; - v->channels = (unsigned) k; + v->channels = (uint8_t) k; for (i = 0; i < v->channels; i++) { p += strspn(p, WHITESPACE); diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c index ae16b9ae..e93721c1 100644 --- a/src/modules/module-x11-bell.c +++ b/src/modules/module-x11-bell.c @@ -82,7 +82,7 @@ static int x11_event_cb(pa_x11_wrapper *w, XEvent *e, void *userdata) { bne = (XkbBellNotifyEvent*) e; - if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, TRUE, (bne->percent*PA_VOLUME_NORM)/100, NULL, NULL) < 0) { + if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, TRUE, ((pa_volume_t) bne->percent*PA_VOLUME_NORM)/100U, NULL, NULL) < 0) { pa_log_info("Ringing bell failed, reverting to X11 device bell."); XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent); } diff --git a/src/modules/module-x11-xsmp.c b/src/modules/module-x11-xsmp.c index 12e100b7..57d182fd 100644 --- a/src/modules/module-x11-xsmp.c +++ b/src/modules/module-x11-xsmp.c @@ -181,7 +181,7 @@ int pa__init(pa_module*m) { prop_program.name = (char*) SmProgram; prop_program.type = (char*) SmARRAY8; val_program.value = (char*) PACKAGE_NAME; - val_program.length = strlen(val_program.value); + val_program.length = (int) strlen(val_program.value); prop_program.num_vals = 1; prop_program.vals = &val_program; prop_list[0] = &prop_program; @@ -190,7 +190,7 @@ int pa__init(pa_module*m) { prop_user.type = (char*) SmARRAY8; pa_get_user_name(t, sizeof(t)); val_user.value = t; - val_user.length = strlen(val_user.value); + val_user.length = (int) strlen(val_user.value); prop_user.num_vals = 1; prop_user.vals = &val_user; prop_list[1] = &prop_user; diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c index a4fbf020..c8087abb 100644 --- a/src/modules/module-zeroconf-discover.c +++ b/src/modules/module-zeroconf-discover.c @@ -173,9 +173,9 @@ static void resolver_cb( device = value; value = NULL; } else if (strcmp(key, "rate") == 0) - ss.rate = atoi(value); + ss.rate = (uint32_t) atoi(value); else if (strcmp(key, "channels") == 0) - ss.channels = atoi(value); + ss.channels = (uint8_t) atoi(value); else if (strcmp(key, "format") == 0) ss.format = pa_parse_sample_format(value); else if (strcmp(key, "channel_map") == 0) { diff --git a/src/modules/oss-util.c b/src/modules/oss-util.c index 2791e165..f766030d 100644 --- a/src/modules/oss-util.c +++ b/src/modules/oss-util.c @@ -204,10 +204,10 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) { if (ss->channels != channels) { pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels); - ss->channels = channels; + ss->channels = (uint8_t) channels; } - speed = ss->rate; + speed = (int) ss->rate; if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0) { pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno)); return -1; @@ -219,7 +219,7 @@ int pa_oss_auto_format(int fd, pa_sample_spec *ss) { /* If the sample rate deviates too much, we need to resample */ if (speed < ss->rate*.95 || speed > ss->rate*1.05) - ss->rate = speed; + ss->rate = (uint32_t) speed; } return 0; diff --git a/src/modules/raop/base64.c b/src/modules/raop/base64.c new file mode 100644 index 00000000..8918def8 --- /dev/null +++ b/src/modules/raop/base64.c @@ -0,0 +1,126 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* + This file was originally inspired by a file developed by + Kungliga Tekniska H�gskolan +*/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include "base64.h" + +static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int pos(char c) +{ + if (c >= 'A' && c <= 'Z') return c - 'A' + 0; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; +} + +int pa_base64_encode(const void *data, int size, char **str) +{ + char *s, *p; + int i; + int c; + const unsigned char *q; + + p = s = pa_xnew(char, size * 4 / 3 + 4); + q = (const unsigned char *) data; + i = 0; + for (i = 0; i < size;) { + c = q[i++]; + c *= 256; + if (i < size) + c += q[i]; + i++; + c *= 256; + if (i < size) + c += q[i]; + i++; + p[0] = base64_chars[(c & 0x00fc0000) >> 18]; + p[1] = base64_chars[(c & 0x0003f000) >> 12]; + p[2] = base64_chars[(c & 0x00000fc0) >> 6]; + p[3] = base64_chars[(c & 0x0000003f) >> 0]; + if (i > size) + p[3] = '='; + if (i > size + 1) + p[2] = '='; + p += 4; + } + *p = 0; + *str = s; + return strlen(s); +} + +#define DECODE_ERROR 0xffffffff + +static unsigned int token_decode(const char *token) +{ + int i; + unsigned int val = 0; + int marker = 0; + if (strlen(token) < 4) + return DECODE_ERROR; + for (i = 0; i < 4; i++) { + val *= 64; + if (token[i] == '=') + marker++; + else if (marker > 0) + return DECODE_ERROR; + else + val += pos(token[i]); + } + if (marker > 2) + return DECODE_ERROR; + return (marker << 24) | val; +} + +int pa_base64_decode(const char *str, void *data) +{ + const char *p; + unsigned char *q; + + q = data; + for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) { + unsigned int val = token_decode(p); + unsigned int marker = (val >> 24) & 0xff; + if (val == DECODE_ERROR) + return -1; + *q++ = (val >> 16) & 0xff; + if (marker < 2) + *q++ = (val >> 8) & 0xff; + if (marker < 1) + *q++ = val & 0xff; + } + return q - (unsigned char *) data; +} diff --git a/src/modules/raop/base64.h b/src/modules/raop/base64.h new file mode 100644 index 00000000..dac0e707 --- /dev/null +++ b/src/modules/raop/base64.h @@ -0,0 +1,34 @@ +#ifndef foobase64hfoo +#define foobase64hfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + Copyright Kungliga Tekniska Høgskolan + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* + This file was originally inspired by a file developed by + Kungliga Tekniska Høgskolan +*/ + +int pa_base64_encode(const void *data, int size, char **str); +int pa_base64_decode(const char *str, void *data); + +#endif diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c new file mode 100644 index 00000000..4627545e --- /dev/null +++ b/src/modules/raop/raop_client.c @@ -0,0 +1,561 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <sys/ioctl.h> + +#ifdef HAVE_SYS_FILIO_H +#include <sys/filio.h> +#endif + +/* TODO: Replace OpenSSL with NSS */ +#include <openssl/err.h> +#include <openssl/rand.h> +#include <openssl/aes.h> +#include <openssl/rsa.h> +#include <openssl/engine.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/random.h> +#include <pulsecore/poll.h> + +#include "raop_client.h" +#include "rtsp_client.h" +#include "base64.h" + +#define AES_CHUNKSIZE 16 + +#define JACK_STATUS_DISCONNECTED 0 +#define JACK_STATUS_CONNECTED 1 + +#define JACK_TYPE_ANALOG 0 +#define JACK_TYPE_DIGITAL 1 + +#define VOLUME_DEF -30 +#define VOLUME_MIN -144 +#define VOLUME_MAX 0 + + +struct pa_raop_client { + pa_core *core; + char *host; + char *sid; + pa_rtsp_client *rtsp; + + uint8_t jack_type; + uint8_t jack_status; + + /* Encryption Related bits */ + AES_KEY aes; + uint8_t aes_iv[AES_CHUNKSIZE]; /* initialization vector for aes-cbc */ + uint8_t aes_nv[AES_CHUNKSIZE]; /* next vector for aes-cbc */ + uint8_t aes_key[AES_CHUNKSIZE]; /* key for aes-cbc */ + + pa_socket_client *sc; + int fd; + + uint16_t seq; + uint32_t rtptime; + + pa_raop_client_cb_t callback; + void* userdata; + pa_raop_client_closed_cb_t closed_callback; + void* closed_userdata; +}; + +/** + * Function to write bits into a buffer. + * @param buffer Handle to the buffer. It will be incremented if new data requires it. + * @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB) + * @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks + * @param data The data to write + * @param data_bit_len The number of bits from data to write + */ +static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uint8_t data, uint8_t data_bit_len) { + int bits_left, bit_overflow; + uint8_t bit_data; + + if (!data_bit_len) + return; + + /* If bit pos is zero, we will definatly use at least one bit from the current byte so size increments. */ + if (!*bit_pos) + *size += 1; + + /* Calc the number of bits left in the current byte of buffer */ + bits_left = 7 - *bit_pos + 1; + /* Calc the overflow of bits in relation to how much space we have left... */ + bit_overflow = bits_left - data_bit_len; + if (bit_overflow >= 0) { + /* We can fit the new data in our current byte */ + /* As we write from MSB->LSB we need to left shift by the overflow amount */ + bit_data = data << bit_overflow; + if (*bit_pos) + **buffer |= bit_data; + else + **buffer = bit_data; + /* If our data fits exactly into the current byte, we need to increment our pointer */ + if (0 == bit_overflow) { + /* Do not increment size as it will be incremeneted on next call as bit_pos is zero */ + *buffer += 1; + *bit_pos = 0; + } else { + *bit_pos += data_bit_len; + } + } else { + /* bit_overflow is negative, there for we will need a new byte from our buffer */ + /* Firstly fill up what's left in the current byte */ + bit_data = data >> -bit_overflow; + **buffer |= bit_data; + /* Increment our buffer pointer and size counter*/ + *buffer += 1; + *size += 1; + **buffer = data << (8 + bit_overflow); + *bit_pos = -bit_overflow; + } +} + +static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { + const char n[] = + "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" + "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" + "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" + "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" + "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" + "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; + const char e[] = "AQAB"; + uint8_t modules[256]; + uint8_t exponent[8]; + int size; + RSA *rsa; + + rsa = RSA_new(); + size = pa_base64_decode(n, modules); + rsa->n = BN_bin2bn(modules, size, NULL); + size = pa_base64_decode(e, exponent); + rsa->e = BN_bin2bn(exponent, size, NULL); + + size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); + RSA_free(rsa); + return size; +} + +static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) +{ + uint8_t *buf; + int i=0, j; + + pa_assert(c); + + memcpy(c->aes_nv, c->aes_iv, AES_CHUNKSIZE); + while (i+AES_CHUNKSIZE <= size) { + buf = data + i; + for (j=0; j<AES_CHUNKSIZE; ++j) + buf[j] ^= c->aes_nv[j]; + + AES_encrypt(buf, buf, &c->aes); + memcpy(c->aes_nv, buf, AES_CHUNKSIZE); + i += AES_CHUNKSIZE; + } + return i; +} + +static inline void rtrimchar(char *str, char rc) +{ + char *sp = str + strlen(str) - 1; + while (sp >= str && *sp == rc) { + *sp = '\0'; + sp -= 1; + } +} + +static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { + pa_raop_client *c = userdata; + + pa_assert(sc); + pa_assert(c); + pa_assert(c->sc == sc); + pa_assert(c->fd < 0); + pa_assert(c->callback); + + pa_socket_client_unref(c->sc); + c->sc = NULL; + + if (!io) { + pa_log("Connection failed: %s", pa_cstrerror(errno)); + return; + } + + c->fd = pa_iochannel_get_send_fd(io); + + pa_iochannel_set_noclose(io, TRUE); + pa_iochannel_socket_set_sndbuf(io, 1024); + pa_iochannel_free(io); + + pa_make_tcp_socket_low_delay(c->fd); + + pa_log_debug("Connection established"); + c->callback(c->fd, c->userdata); +} + +static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) +{ + pa_raop_client* c = userdata; + pa_assert(c); + pa_assert(rtsp); + pa_assert(rtsp == c->rtsp); + + switch (state) { + case STATE_CONNECT: { + int i; + uint8_t rsakey[512]; + char *key, *iv, *sac, *sdp; + uint16_t rand_data; + const char *ip; + char *url; + + pa_log_debug("RAOP: CONNECTED"); + ip = pa_rtsp_localip(c->rtsp); + /* First of all set the url properly */ + url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid); + pa_rtsp_set_url(c->rtsp, url); + pa_xfree(url); + + /* Now encrypt our aes_public key to send to the device */ + i = rsa_encrypt(c->aes_key, AES_CHUNKSIZE, rsakey); + pa_base64_encode(rsakey, i, &key); + rtrimchar(key, '='); + pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv); + rtrimchar(iv, '='); + + pa_random(&rand_data, sizeof(rand_data)); + pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac); + rtrimchar(sac, '='); + pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); + sdp = pa_sprintf_malloc( + "v=0\r\n" + "o=iTunes %s 0 IN IP4 %s\r\n" + "s=iTunes\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 96\r\n" + "a=rtpmap:96 AppleLossless\r\n" + "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n" + "a=rsaaeskey:%s\r\n" + "a=aesiv:%s\r\n", + c->sid, ip, c->host, key, iv); + pa_rtsp_announce(c->rtsp, sdp); + pa_xfree(key); + pa_xfree(iv); + pa_xfree(sac); + pa_xfree(sdp); + break; + } + + case STATE_ANNOUNCE: + pa_log_debug("RAOP: ANNOUNCED"); + pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); + pa_rtsp_setup(c->rtsp); + break; + + case STATE_SETUP: { + char *aj = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); + pa_log_debug("RAOP: SETUP"); + if (aj) { + char *token, *pc; + char delimiters[] = ";"; + const char* token_state = NULL; + c->jack_type = JACK_TYPE_ANALOG; + c->jack_status = JACK_STATUS_DISCONNECTED; + + while ((token = pa_split(aj, delimiters, &token_state))) { + if ((pc = strstr(token, "="))) { + *pc = 0; + if (!strcmp(token, "type") && !strcmp(pc+1, "digital")) { + c->jack_type = JACK_TYPE_DIGITAL; + } + } else { + if (!strcmp(token,"connected")) + c->jack_status = JACK_STATUS_CONNECTED; + } + pa_xfree(token); + } + pa_xfree(aj); + } else { + pa_log_warn("Audio Jack Status missing"); + } + pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); + break; + } + + case STATE_RECORD: { + uint32_t port = pa_rtsp_serverport(c->rtsp); + pa_log_debug("RAOP: RECORDED"); + + if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, c->host, port))) { + pa_log("failed to connect to server '%s:%d'", c->host, port); + return; + } + pa_socket_client_set_callback(c->sc, on_connection, c); + break; + } + + case STATE_FLUSH: + pa_log_debug("RAOP: FLUSHED"); + break; + + case STATE_TEARDOWN: + case STATE_SET_PARAMETER: + pa_log_debug("RAOP: SET_PARAMETER"); + break; + case STATE_DISCONNECTED: + pa_assert(c->closed_callback); + pa_assert(c->rtsp); + + pa_log_debug("RTSP control channel closed"); + pa_rtsp_client_free(c->rtsp); + c->rtsp = NULL; + if (c->fd > 0) { + /* We do not close the fd, we leave it to the closed callback to do that */ + c->fd = -1; + } + if (c->sc) { + pa_socket_client_unref(c->sc); + c->sc = NULL; + } + pa_xfree(c->sid); + c->sid = NULL; + c->closed_callback(c->closed_userdata); + break; + } +} + +pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) +{ + pa_raop_client* c = pa_xnew0(pa_raop_client, 1); + + pa_assert(core); + pa_assert(host); + + c->core = core; + c->fd = -1; + c->host = pa_xstrdup(host); + + if (pa_raop_connect(c)) { + pa_raop_client_free(c); + return NULL; + } + return c; +} + + +void pa_raop_client_free(pa_raop_client* c) +{ + pa_assert(c); + + if (c->rtsp) + pa_rtsp_client_free(c->rtsp); + pa_xfree(c->host); + pa_xfree(c); +} + + +int pa_raop_connect(pa_raop_client* c) +{ + char *sci; + struct { + uint32_t a; + uint32_t b; + uint32_t c; + } rand_data; + + pa_assert(c); + + if (c->rtsp) { + pa_log_debug("Connection already in progress"); + return 0; + } + + c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, 5000, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); + + /* Initialise the AES encryption system */ + pa_random(c->aes_iv, sizeof(c->aes_iv)); + pa_random(c->aes_key, sizeof(c->aes_key)); + memcpy(c->aes_nv, c->aes_iv, sizeof(c->aes_nv)); + AES_set_encrypt_key(c->aes_key, 128, &c->aes); + + /* Generate random instance id */ + pa_random(&rand_data, sizeof(rand_data)); + c->sid = pa_sprintf_malloc("%u", rand_data.a); + sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c); + pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); + pa_xfree(sci); + pa_rtsp_set_callback(c->rtsp, rtsp_cb, c); + return pa_rtsp_connect(c->rtsp); +} + + +int pa_raop_flush(pa_raop_client* c) +{ + pa_assert(c); + + pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); + return 0; +} + + +int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume) +{ + int rv; + double db; + char *param; + + pa_assert(c); + + db = pa_sw_volume_to_dB(volume); + if (db < VOLUME_MIN) + db = VOLUME_MIN; + else if (db > VOLUME_MAX) + db = VOLUME_MAX; + + param = pa_sprintf_malloc("volume: %0.6f\r\n", db); + + /* We just hit and hope, cannot wait for the callback */ + rv = pa_rtsp_setparameter(c->rtsp, param); + pa_xfree(param); + return rv; +} + + +int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) +{ + uint16_t len; + size_t bufmax; + uint8_t *bp, bpos; + uint8_t *ibp, *maxibp; + int size; + uint8_t *b, *p; + uint32_t bsize; + size_t length; + static uint8_t header[] = { + 0x24, 0x00, 0x00, 0x00, + 0xF0, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + int header_size = sizeof(header); + + pa_assert(c); + pa_assert(c->fd > 0); + pa_assert(raw); + pa_assert(raw->memblock); + pa_assert(raw->length > 0); + pa_assert(encoded); + + /* We have to send 4 byte chunks */ + bsize = (int)(raw->length / 4); + length = bsize * 4; + + /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ + bufmax = length + header_size + 16; + pa_memchunk_reset(encoded); + encoded->memblock = pa_memblock_new(c->core->mempool, bufmax); + b = pa_memblock_acquire(encoded->memblock); + memcpy(b, header, header_size); + + /* Now write the actual samples */ + bp = b + header_size; + size = bpos = 0; + bit_writer(&bp,&bpos,&size,1,3); /* channel=1, stereo */ + bit_writer(&bp,&bpos,&size,0,4); /* unknown */ + bit_writer(&bp,&bpos,&size,0,8); /* unknown */ + bit_writer(&bp,&bpos,&size,0,4); /* unknown */ + bit_writer(&bp,&bpos,&size,1,1); /* hassize */ + bit_writer(&bp,&bpos,&size,0,2); /* unused */ + bit_writer(&bp,&bpos,&size,1,1); /* is-not-compressed */ + + /* size of data, integer, big endian */ + bit_writer(&bp,&bpos,&size,(bsize>>24)&0xff,8); + bit_writer(&bp,&bpos,&size,(bsize>>16)&0xff,8); + bit_writer(&bp,&bpos,&size,(bsize>>8)&0xff,8); + bit_writer(&bp,&bpos,&size,(bsize)&0xff,8); + + ibp = p = pa_memblock_acquire(raw->memblock); + maxibp = p + raw->length - 4; + while (ibp <= maxibp) { + /* Byte swap stereo data */ + bit_writer(&bp,&bpos,&size,*(ibp+1),8); + bit_writer(&bp,&bpos,&size,*(ibp+0),8); + bit_writer(&bp,&bpos,&size,*(ibp+3),8); + bit_writer(&bp,&bpos,&size,*(ibp+2),8); + ibp += 4; + raw->index += 4; + raw->length -= 4; + } + pa_memblock_release(raw->memblock); + encoded->length = header_size + size; + + /* store the lenght (endian swapped: make this better) */ + len = size + header_size - 4; + *(b + 2) = len >> 8; + *(b + 3) = len & 0xff; + + /* encrypt our data */ + aes_encrypt(c, (b + header_size), size); + + /* We're done with the chunk */ + pa_memblock_release(encoded->memblock); + + return 0; +} + + +void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata) +{ + pa_assert(c); + + c->callback = callback; + c->userdata = userdata; +} + +void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata) +{ + pa_assert(c); + + c->closed_callback = callback; + c->closed_userdata = userdata; +} diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h new file mode 100644 index 00000000..ec3136a7 --- /dev/null +++ b/src/modules/raop/raop_client.h @@ -0,0 +1,46 @@ +#ifndef fooraopclientfoo +#define fooraopclientfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <pulse/mainloop-api.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/core.h> + +typedef struct pa_raop_client pa_raop_client; + +pa_raop_client* pa_raop_client_new(pa_core *core, const char* host); +void pa_raop_client_free(pa_raop_client* c); + +int pa_raop_connect(pa_raop_client* c); +int pa_raop_flush(pa_raop_client* c); + +int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume); +int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded); + +typedef void (*pa_raop_client_cb_t)(int fd, void *userdata); +void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata); + +typedef void (*pa_raop_client_closed_cb_t)(void *userdata); +void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata); + +#endif diff --git a/src/modules/rtp/Makefile b/src/modules/rtp/Makefile index 316beb72..efe5a336 100644..120000 --- a/src/modules/rtp/Makefile +++ b/src/modules/rtp/Makefile @@ -1,13 +1 @@ -# This is a dirty trick just to ease compilation with emacs -# -# This file is not intended to be distributed or anything -# -# So: don't touch it, even better ignore it! - -all: - $(MAKE) -C ../.. - -clean: - $(MAKE) -C ../.. clean - -.PHONY: all clean +../../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/rtp/headerlist.c b/src/modules/rtp/headerlist.c new file mode 100644 index 00000000..0fef835b --- /dev/null +++ b/src/modules/rtp/headerlist.c @@ -0,0 +1,186 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + Copyright 2007 Lennart Poettering + + 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.1 of the + License, or (at your option) any later version. + + PulseAudio 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 PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/hashmap.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/core-util.h> + +#include "headerlist.h" + +struct header { + char *key; + void *value; + size_t nbytes; +}; + +#define MAKE_HASHMAP(p) ((pa_hashmap*) (p)) +#define MAKE_HEADERLIST(p) ((pa_headerlist*) (p)) + +static void header_free(struct header *hdr) { + pa_assert(hdr); + + pa_xfree(hdr->key); + pa_xfree(hdr->value); + pa_xfree(hdr); +} + +pa_headerlist* pa_headerlist_new(void) { + return MAKE_HEADERLIST(pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func)); +} + +void pa_headerlist_free(pa_headerlist* p) { + struct header *hdr; + + while ((hdr = pa_hashmap_steal_first(MAKE_HASHMAP(p)))) + header_free(hdr); + + pa_hashmap_free(MAKE_HASHMAP(p), NULL, NULL); +} + +int pa_headerlist_puts(pa_headerlist *p, const char *key, const char *value) { + struct header *hdr; + pa_bool_t add = FALSE; + + pa_assert(p); + pa_assert(key); + + if (!(hdr = pa_hashmap_get(MAKE_HASHMAP(p), key))) { + hdr = pa_xnew(struct header, 1); + hdr->key = pa_xstrdup(key); + add = TRUE; + } else + pa_xfree(hdr->value); + + hdr->value = pa_xstrdup(value); + hdr->nbytes = strlen(value)+1; + + if (add) + pa_hashmap_put(MAKE_HASHMAP(p), hdr->key, hdr); + + return 0; +} + +int pa_headerlist_putsappend(pa_headerlist *p, const char *key, const char *value) { + struct header *hdr; + pa_bool_t add = FALSE; + + pa_assert(p); + pa_assert(key); + + if (!(hdr = pa_hashmap_get(MAKE_HASHMAP(p), key))) { + hdr = pa_xnew(struct header, 1); + hdr->key = pa_xstrdup(key); + hdr->value = pa_xstrdup(value); + add = TRUE; + } else { + void *newval = pa_sprintf_malloc("%s%s", (char*)hdr->value, value); + pa_xfree(hdr->value); + hdr->value = newval; + } + hdr->nbytes = strlen(hdr->value)+1; + + if (add) + pa_hashmap_put(MAKE_HASHMAP(p), hdr->key, hdr); + + return 0; +} + +const char *pa_headerlist_gets(pa_headerlist *p, const char *key) { + struct header *hdr; + + pa_assert(p); + pa_assert(key); + + if (!(hdr = pa_hashmap_get(MAKE_HASHMAP(p), key))) + return NULL; + + if (hdr->nbytes <= 0) + return NULL; + + if (((char*) hdr->value)[hdr->nbytes-1] != 0) + return NULL; + + if (strlen((char*) hdr->value) != hdr->nbytes-1) + return NULL; + + return (char*) hdr->value; +} + +int pa_headerlist_remove(pa_headerlist *p, const char *key) { + struct header *hdr; + + pa_assert(p); + pa_assert(key); + + if (!(hdr = pa_hashmap_remove(MAKE_HASHMAP(p), key))) + return -1; + + header_free(hdr); + return 0; +} + +const char *pa_headerlist_iterate(pa_headerlist *p, void **state) { + struct header *hdr; + + if (!(hdr = pa_hashmap_iterate(MAKE_HASHMAP(p), state, NULL))) + return NULL; + + return hdr->key; +} + +char *pa_headerlist_to_string(pa_headerlist *p) { + const char *key; + void *state = NULL; + pa_strbuf *buf; + + pa_assert(p); + + buf = pa_strbuf_new(); + + while ((key = pa_headerlist_iterate(p, &state))) { + + const char *v; + + if ((v = pa_headerlist_gets(p, key))) + pa_strbuf_printf(buf, "%s: %s\r\n", key, v); + } + + return pa_strbuf_tostring_free(buf); +} + +int pa_headerlist_contains(pa_headerlist *p, const char *key) { + pa_assert(p); + pa_assert(key); + + if (!(pa_hashmap_get(MAKE_HASHMAP(p), key))) + return 0; + + return 1; +} diff --git a/src/modules/rtp/headerlist.h b/src/modules/rtp/headerlist.h new file mode 100644 index 00000000..4b9c6433 --- /dev/null +++ b/src/modules/rtp/headerlist.h @@ -0,0 +1,46 @@ +#ifndef foopulseheaderlisthfoo +#define foopulseheaderlisthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + Copyright 2007 Lennart Poettering + + 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.1 of the + License, or (at your option) any later version. + + PulseAudio 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 PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <pulsecore/macro.h> + +typedef struct pa_headerlist pa_headerlist; + +pa_headerlist* pa_headerlist_new(void); +void pa_headerlist_free(pa_headerlist* p); + +int pa_headerlist_puts(pa_headerlist *p, const char *key, const char *value); +int pa_headerlist_putsappend(pa_headerlist *p, const char *key, const char *value); + +const char *pa_headerlist_gets(pa_headerlist *p, const char *key); + +int pa_headerlist_remove(pa_headerlist *p, const char *key); + +const char *pa_headerlist_iterate(pa_headerlist *p, void **state); + +char *pa_headerlist_to_string(pa_headerlist *p); + +int pa_headerlist_contains(pa_headerlist *p, const char *key); + +#endif diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index e04e4611..e35773cc 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -238,25 +238,25 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { else delta = j; - pa_memblockq_seek(s->memblockq, delta * s->rtp_context.frame_size, PA_SEEK_RELATIVE); + pa_memblockq_seek(s->memblockq, delta * (int64_t) s->rtp_context.frame_size, PA_SEEK_RELATIVE); pa_rtclock_get(&now); - pa_smoother_put(s->smoother, pa_timeval_load(&now), pa_bytes_to_usec(pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec)); + pa_smoother_put(s->smoother, pa_timeval_load(&now), pa_bytes_to_usec((uint64_t) pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec)); if (pa_memblockq_push(s->memblockq, &chunk) < 0) { pa_log_warn("Queue overrun"); - pa_memblockq_seek(s->memblockq, chunk.length, PA_SEEK_RELATIVE); + pa_memblockq_seek(s->memblockq, (int64_t) chunk.length, PA_SEEK_RELATIVE); } - pa_log("blocks in q: %u", pa_memblockq_get_nblocks(s->memblockq)); +/* pa_log("blocks in q: %u", pa_memblockq_get_nblocks(s->memblockq)); */ pa_memblock_unref(chunk.memblock); /* The next timestamp we expect */ - s->offset = s->rtp_context.timestamp + (chunk.length / s->rtp_context.frame_size); + s->offset = s->rtp_context.timestamp + (uint32_t) (chunk.length / s->rtp_context.frame_size); - pa_atomic_store(&s->timestamp, now.tv_sec); + pa_atomic_store(&s->timestamp, (int) now.tv_sec); if (s->last_rate_update + RATE_UPDATE_INTERVAL < pa_timeval_load(&now)) { pa_usec_t wi, ri, render_delay, sink_delay = 0, latency, fix; @@ -265,7 +265,7 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { pa_log("Updating sample rate"); wi = pa_smoother_get(s->smoother, pa_timeval_load(&now)); - ri = pa_bytes_to_usec(pa_memblockq_get_read_index(s->memblockq), &s->sink_input->sample_spec); + ri = pa_bytes_to_usec((uint64_t) pa_memblockq_get_read_index(s->memblockq), &s->sink_input->sample_spec); if (PA_MSGOBJECT(s->sink_input->sink)->process_msg(PA_MSGOBJECT(s->sink_input->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_delay, 0, NULL) < 0) sink_delay = 0; @@ -291,7 +291,7 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { fix = latency - s->intended_latency; /* How many samples is this per second? */ - fix_samples = fix * s->sink_input->thread_info.sample_spec.rate / RATE_UPDATE_INTERVAL; + fix_samples = (unsigned) (fix * (pa_usec_t) s->sink_input->thread_info.sample_spec.rate / (pa_usec_t) RATE_UPDATE_INTERVAL); /* Check if deviation is in bounds */ if (fix_samples > s->sink_input->sample_spec.rate*.20) @@ -431,7 +431,7 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in s->smoother = pa_smoother_new(PA_USEC_PER_SEC*5, PA_USEC_PER_SEC*2, TRUE, 10); pa_smoother_set_time_offset(s->smoother, pa_timeval_load(&now)); s->last_rate_update = pa_timeval_load(&now); - pa_atomic_store(&s->timestamp, now.tv_sec); + pa_atomic_store(&s->timestamp, (int) now.tv_sec); if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0) goto fail; @@ -566,7 +566,7 @@ static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event } else { struct timeval now; pa_rtclock_get(&now); - pa_atomic_store(&s->timestamp, now.tv_sec); + pa_atomic_store(&s->timestamp, (int) now.tv_sec); pa_sdp_info_destroy(&info); } diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index 5e542253..8d1e92fe 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -67,10 +67,12 @@ PA_MODULE_USAGE( "destination=<destination IP address> " "port=<port number> " "mtu=<maximum transfer unit> " - "loop=<loopback to local host?>" + "loop=<loopback to local host?> " + "ttl=<ttl value>" ); #define DEFAULT_PORT 46000 +#define DEFAULT_TTL 1 #define SAP_PORT 9875 #define DEFAULT_DESTINATION "224.0.0.56" #define MEMBLOCKQ_MAXLENGTH (1024*170) @@ -86,6 +88,7 @@ static const char* const valid_modargs[] = { "port", "mtu" , "loop", + "ttl", NULL }; @@ -167,7 +170,9 @@ int pa__init(pa_module*m) { pa_modargs *ma = NULL; const char *dest; uint32_t port = DEFAULT_PORT, mtu; - int af, fd = -1, sap_fd = -1; + uint32_t ttl = DEFAULT_TTL; + sa_family_t af; + int fd = -1, sap_fd = -1; pa_source *s; pa_sample_spec ss; pa_channel_map cm; @@ -219,14 +224,14 @@ int pa__init(pa_module*m) { payload = pa_rtp_payload_from_sample_spec(&ss); - mtu = pa_frame_align(DEFAULT_MTU, &ss); + mtu = (uint32_t) pa_frame_align(DEFAULT_MTU, &ss); if (pa_modargs_get_value_u32(ma, "mtu", &mtu) < 0 || mtu < 1 || mtu % pa_frame_size(&ss) != 0) { pa_log("Invalid MTU."); goto fail; } - port = DEFAULT_PORT + ((rand() % 512) << 1); + port = DEFAULT_PORT + ((uint32_t) (rand() % 512) << 1); if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port < 1 || port > 0xFFFF) { pa_log("port= expects a numerical argument between 1 and 65535."); goto fail; @@ -235,16 +240,21 @@ int pa__init(pa_module*m) { if (port & 1) pa_log_warn("Port number not even as suggested in RFC3550!"); + if (pa_modargs_get_value_u32(ma, "ttl", &ttl) < 0 || ttl < 1 || ttl > 0xFF) { + pa_log("ttl= expects a numerical argument between 1 and 255."); + goto fail; + } + dest = pa_modargs_get_value(ma, "destination", DEFAULT_DESTINATION); if (inet_pton(AF_INET6, dest, &sa6.sin6_addr) > 0) { sa6.sin6_family = af = AF_INET6; - sa6.sin6_port = htons(port); + sa6.sin6_port = htons((uint16_t) port); sap_sa6 = sa6; sap_sa6.sin6_port = htons(SAP_PORT); } else if (inet_pton(AF_INET, dest, &sa4.sin_addr) > 0) { sa4.sin_family = af = AF_INET; - sa4.sin_port = htons(port); + sa4.sin_port = htons((uint16_t) port); sap_sa4 = sa4; sap_sa4.sin_port = htons(SAP_PORT); } else { @@ -257,7 +267,7 @@ int pa__init(pa_module*m) { goto fail; } - if (connect(fd, af == AF_INET ? (struct sockaddr*) &sa4 : (struct sockaddr*) &sa6, af == AF_INET ? sizeof(sa4) : sizeof(sa6)) < 0) { + if (connect(fd, af == AF_INET ? (struct sockaddr*) &sa4 : (struct sockaddr*) &sa6, (socklen_t) (af == AF_INET ? sizeof(sa4) : sizeof(sa6))) < 0) { pa_log("connect() failed: %s", pa_cstrerror(errno)); goto fail; } @@ -267,7 +277,7 @@ int pa__init(pa_module*m) { goto fail; } - if (connect(sap_fd, af == AF_INET ? (struct sockaddr*) &sap_sa4 : (struct sockaddr*) &sap_sa6, af == AF_INET ? sizeof(sap_sa4) : sizeof(sap_sa6)) < 0) { + if (connect(sap_fd, af == AF_INET ? (struct sockaddr*) &sap_sa4 : (struct sockaddr*) &sap_sa6, (socklen_t) (af == AF_INET ? sizeof(sap_sa4) : sizeof(sap_sa6))) < 0) { pa_log("connect() failed: %s", pa_cstrerror(errno)); goto fail; } @@ -279,6 +289,15 @@ int pa__init(pa_module*m) { goto fail; } + if (ttl != DEFAULT_TTL) { + int _ttl = (int) ttl; + + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL, &_ttl, sizeof(_ttl)) < 0) { + pa_log("IP_MULTICAST_TTL failed: %s", pa_cstrerror(errno)); + goto fail; + } + } + /* If the socket queue is full, let's drop packets */ pa_make_fd_nonblock(fd); pa_make_udp_socket_low_delay(fd); @@ -290,13 +309,14 @@ int pa__init(pa_module*m) { pa_proplist_sets(data.proplist, "rtp.destination", dest); pa_proplist_setf(data.proplist, "rtp.mtu", "%lu", (unsigned long) mtu); pa_proplist_setf(data.proplist, "rtp.port", "%lu", (unsigned long) port); + pa_proplist_setf(data.proplist, "rtp.ttl", "%lu", (unsigned long) ttl); data.driver = __FILE__; data.module = m; data.source = s; pa_source_output_new_data_set_sample_spec(&data, &ss); pa_source_output_new_data_set_channel_map(&data, &cm); - o = pa_source_output_new(m->core, &data, 0); + o = pa_source_output_new(m->core, &data, PA_SOURCE_OUTPUT_DONT_INHIBIT_AUTO_SUSPEND); pa_source_output_new_data_done(&data); if (!o) { @@ -335,14 +355,14 @@ int pa__init(pa_module*m) { p = pa_sdp_build(af, af == AF_INET ? (void*) &((struct sockaddr_in*) &sa_dst)->sin_addr : (void*) &((struct sockaddr_in6*) &sa_dst)->sin6_addr, af == AF_INET ? (void*) &sa4.sin_addr : (void*) &sa6.sin6_addr, - n, port, payload, &ss); + n, (uint16_t) port, payload, &ss); pa_xfree(n); pa_rtp_context_init_send(&u->rtp_context, fd, m->core->cookie, payload, pa_frame_size(&ss)); pa_sap_context_init_send(&u->sap_context, sap_fd, p); - pa_log_info("RTP stream initialized with mtu %u on %s:%u, SSRC=0x%08x, payload=%u, initial sequence #%u", mtu, dest, port, u->rtp_context.ssrc, payload, u->rtp_context.sequence); + pa_log_info("RTP stream initialized with mtu %u on %s:%u ttl=%u, SSRC=0x%08x, payload=%u, initial sequence #%u", mtu, dest, port, ttl, u->rtp_context.ssrc, payload, u->rtp_context.sequence); pa_log_info("SDP-Data:\n%s\nEOF", p); pa_sap_send(&u->sap_context, 0); diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c index 5a33ebc2..88351010 100644 --- a/src/modules/rtp/rtp.c +++ b/src/modules/rtp/rtp.c @@ -50,7 +50,7 @@ pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssr c->sequence = (uint16_t) (rand()*rand()); c->timestamp = 0; c->ssrc = ssrc ? ssrc : (uint32_t) (rand()*rand()); - c->payload = payload & 127; + c->payload = (uint8_t) (payload & 127U); c->frame_size = frame_size; pa_memchunk_reset(&c->memchunk); @@ -99,7 +99,8 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { if (r < 0 || n >= size || iov_idx >= MAX_IOVECS) { uint32_t header[3]; struct msghdr m; - int k, i; + ssize_t k; + int i; if (n > 0) { header[0] = htonl(((uint32_t) 2 << 30) | ((uint32_t) c->payload << 16) | ((uint32_t) c->sequence)); @@ -112,7 +113,7 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { m.msg_name = NULL; m.msg_namelen = 0; m.msg_iov = iov; - m.msg_iovlen = iov_idx; + m.msg_iovlen = (size_t) iov_idx; m.msg_control = NULL; m.msg_controllen = 0; m.msg_flags = 0; @@ -128,7 +129,7 @@ int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { } else k = 0; - c->timestamp += n/c->frame_size; + c->timestamp += (unsigned) (n/c->frame_size); if (k < 0) { if (errno != EAGAIN && errno != EINTR) /* If the queue is full, just ignore it */ @@ -162,7 +163,7 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { struct msghdr m; struct iovec iov; uint32_t header; - int cc; + unsigned cc; ssize_t r; pa_assert(c); @@ -197,7 +198,7 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { chunk->index = c->memchunk.index; iov.iov_base = (uint8_t*) pa_memblock_acquire(chunk->memblock) + chunk->index; - iov.iov_len = size; + iov.iov_len = (size_t) size; m.msg_name = NULL; m.msg_namelen = 0; @@ -246,16 +247,16 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { } cc = (header >> 24) & 0xF; - c->payload = (header >> 16) & 127; - c->sequence = header & 0xFFFF; + c->payload = (uint8_t) ((header >> 16) & 127U); + c->sequence = (uint16_t) (header & 0xFFFFU); - if (12 + cc*4 > size) { + if (12 + cc*4 > (unsigned) size) { pa_log_warn("RTP packet too short. (CSRC)"); goto fail; } chunk->index += 12 + cc*4; - chunk->length = size - 12 + cc*4; + chunk->length = (size_t) size - 12 + cc*4; if (chunk->length % c->frame_size != 0) { pa_log_warn("Bad RTP packet size."); diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c new file mode 100644 index 00000000..9eb3d964 --- /dev/null +++ b/src/modules/rtp/rtsp_client.c @@ -0,0 +1,542 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <arpa/inet.h> +#include <unistd.h> +#include <sys/ioctl.h> + +#ifdef HAVE_SYS_FILIO_H +#include <sys/filio.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/poll.h> +#include <pulsecore/ioline.h> + +#include "rtsp_client.h" + +struct pa_rtsp_client { + pa_mainloop_api *mainloop; + char *hostname; + uint16_t port; + + pa_socket_client *sc; + pa_iochannel *io; + pa_ioline *ioline; + + pa_rtsp_cb_t callback; + + void *userdata; + const char *useragent; + + pa_rtsp_state state; + uint8_t waiting; + + pa_headerlist* headers; + char *last_header; + pa_strbuf *header_buffer; + pa_headerlist* response_headers; + + char *localip; + char *url; + uint16_t rtp_port; + uint32_t cseq; + char *session; + char *transport; +}; + +pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char* hostname, uint16_t port, const char* useragent) { + pa_rtsp_client *c; + + pa_assert(mainloop); + pa_assert(hostname); + pa_assert(port > 0); + + c = pa_xnew0(pa_rtsp_client, 1); + c->mainloop = mainloop; + c->hostname = pa_xstrdup(hostname); + c->port = port; + c->headers = pa_headerlist_new(); + + if (useragent) + c->useragent = useragent; + else + c->useragent = "PulseAudio RTSP Client"; + + return c; +} + + +void pa_rtsp_client_free(pa_rtsp_client* c) { + if (c) { + if (c->sc) + pa_socket_client_unref(c->sc); + if (c->ioline) + pa_ioline_close(c->ioline); + else if (c->io) + pa_iochannel_free(c->io); + + pa_xfree(c->hostname); + pa_xfree(c->url); + pa_xfree(c->localip); + pa_xfree(c->session); + pa_xfree(c->transport); + pa_xfree(c->last_header); + if (c->header_buffer) + pa_strbuf_free(c->header_buffer); + if (c->response_headers) + pa_headerlist_free(c->response_headers); + pa_headerlist_free(c->headers); + } + pa_xfree(c); +} + + +static void headers_read(pa_rtsp_client *c) { + char* token; + char delimiters[] = ";"; + + pa_assert(c); + pa_assert(c->response_headers); + pa_assert(c->callback); + + /* Deal with a SETUP response */ + if (STATE_SETUP == c->state) { + const char* token_state = NULL; + const char* pc = NULL; + c->session = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Session")); + c->transport = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Transport")); + + if (!c->session || !c->transport) { + pa_headerlist_free(c->response_headers); + c->response_headers = NULL; + pa_log("Invalid SETUP response."); + return; + } + + /* Now parse out the server port component of the response. */ + while ((token = pa_split(c->transport, delimiters, &token_state))) { + if ((pc = strstr(token, "="))) { + if (0 == strncmp(token, "server_port", 11)) { + pa_atou(pc+1, (uint32_t*)(&c->rtp_port)); + pa_xfree(token); + break; + } + } + pa_xfree(token); + } + if (0 == c->rtp_port) { + /* Error no server_port in response */ + pa_headerlist_free(c->response_headers); + c->response_headers = NULL; + pa_log("Invalid SETUP response (no port number)."); + return; + } + } + + /* Call our callback */ + c->callback(c, c->state, c->response_headers, c->userdata); + + pa_headerlist_free(c->response_headers); + c->response_headers = NULL; +} + + +static void line_callback(pa_ioline *line, const char *s, void *userdata) { + char *delimpos; + char *s2, *s2p; + + pa_rtsp_client *c = userdata; + pa_assert(line); + pa_assert(c); + pa_assert(c->callback); + + if (!s) { + /* Keep the ioline/iochannel open as they will be freed automatically */ + c->ioline = NULL; + c->io = NULL; + c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); + return; + } + + s2 = pa_xstrdup(s); + /* Trim trailing carriage returns */ + s2p = s2 + strlen(s2) - 1; + while (s2p >= s2 && '\r' == *s2p) { + *s2p = '\0'; + s2p -= 1; + } + if (c->waiting && 0 == strcmp("RTSP/1.0 200 OK", s2)) { + c->waiting = 0; + pa_assert(!c->response_headers); + c->response_headers = pa_headerlist_new(); + goto exit; + } + if (c->waiting) { + pa_log_warn("Unexpected response: %s", s2); + goto exit;; + } + if (!strlen(s2)) { + /* End of headers */ + /* We will have a header left from our looping itteration, so add it in :) */ + if (c->last_header) { + /* This is not a continuation header so let's dump it into our proplist */ + pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); + pa_xfree(c->last_header); + c->last_header = NULL; + c->header_buffer= NULL; + } + + pa_log_debug("Full response received. Dispatching"); + headers_read(c); + c->waiting = 1; + goto exit; + } + + /* Read and parse a header (we know it's not empty) */ + /* TODO: Move header reading into the headerlist. */ + + /* If the first character is a space, it's a continuation header */ + if (c->last_header && ' ' == s2[0]) { + pa_assert(c->header_buffer); + + /* Add this line to the buffer (sans the space. */ + pa_strbuf_puts(c->header_buffer, &(s2[1])); + goto exit; + } + + if (c->last_header) { + /* This is not a continuation header so let's dump the full + header/value into our proplist */ + pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); + pa_xfree(c->last_header); + c->last_header = NULL; + c->header_buffer = NULL; + } + + delimpos = strstr(s2, ":"); + if (!delimpos) { + pa_log_warn("Unexpected response when expecting header: %s", s); + goto exit; + } + + pa_assert(!c->header_buffer); + pa_assert(!c->last_header); + + c->header_buffer = pa_strbuf_new(); + if (strlen(delimpos) > 1) { + /* Cut our line off so we can copy the header name out */ + *delimpos++ = '\0'; + + /* Trim the front of any spaces */ + while (' ' == *delimpos) + ++delimpos; + + pa_strbuf_puts(c->header_buffer, delimpos); + } else { + /* Cut our line off so we can copy the header name out */ + *delimpos = '\0'; + } + + /* Save the header name */ + c->last_header = pa_xstrdup(s2); + exit: + pa_xfree(s2); +} + + +static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { + pa_rtsp_client *c = userdata; + union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; + } sa; + socklen_t sa_len = sizeof(sa); + + pa_assert(sc); + pa_assert(c); + pa_assert(STATE_CONNECT == c->state); + pa_assert(c->sc == sc); + pa_socket_client_unref(c->sc); + c->sc = NULL; + + if (!io) { + pa_log("Connection failed: %s", pa_cstrerror(errno)); + return; + } + pa_assert(!c->io); + c->io = io; + + c->ioline = pa_ioline_new(io); + pa_ioline_set_callback(c->ioline, line_callback, c); + + /* Get the local IP address for use externally */ + if (0 == getsockname(pa_iochannel_get_recv_fd(io), &sa.sa, &sa_len)) { + char buf[INET6_ADDRSTRLEN]; + const char *res = NULL; + + if (AF_INET == sa.sa.sa_family) { + if ((res = inet_ntop(sa.sa.sa_family, &sa.in.sin_addr, buf, sizeof(buf)))) { + c->localip = pa_xstrdup(res); + } + } else if (AF_INET6 == sa.sa.sa_family) { + if ((res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)))) { + c->localip = pa_sprintf_malloc("[%s]", res); + } + } + } + pa_log_debug("Established RTSP connection from local ip %s", c->localip); + + if (c->callback) + c->callback(c, c->state, NULL, c->userdata); +} + +int pa_rtsp_connect(pa_rtsp_client *c) { + pa_assert(c); + pa_assert(!c->sc); + + pa_xfree(c->session); + c->session = NULL; + + if (!(c->sc = pa_socket_client_new_string(c->mainloop, c->hostname, c->port))) { + pa_log("failed to connect to server '%s:%d'", c->hostname, c->port); + return -1; + } + + pa_socket_client_set_callback(c->sc, on_connection, c); + c->waiting = 1; + c->state = STATE_CONNECT; + return 0; +} + +void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata) { + pa_assert(c); + + c->callback = callback; + c->userdata = userdata; +} + +void pa_rtsp_disconnect(pa_rtsp_client *c) { + pa_assert(c); + + if (c->io) + pa_iochannel_free(c->io); + c->io = NULL; +} + + +const char* pa_rtsp_localip(pa_rtsp_client* c) { + pa_assert(c); + + return c->localip; +} + +uint32_t pa_rtsp_serverport(pa_rtsp_client* c) { + pa_assert(c); + + return c->rtp_port; +} + +void pa_rtsp_set_url(pa_rtsp_client* c, const char* url) { + pa_assert(c); + + c->url = pa_xstrdup(url); +} + +void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value) +{ + pa_assert(c); + pa_assert(key); + pa_assert(value); + + pa_headerlist_puts(c->headers, key, value); +} + +void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key) +{ + pa_assert(c); + pa_assert(key); + + pa_headerlist_remove(c->headers, key); +} + +static int rtsp_exec(pa_rtsp_client* c, const char* cmd, + const char* content_type, const char* content, + int expect_response, + pa_headerlist* headers) { + pa_strbuf* buf; + char* hdrs; + ssize_t l; + + pa_assert(c); + pa_assert(c->url); + + if (!cmd) + return -1; + + pa_log_debug("Sending command: %s", cmd); + + buf = pa_strbuf_new(); + pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, c->url, ++c->cseq); + if (c->session) + pa_strbuf_printf(buf, "Session: %s\r\n", c->session); + + /* Add the headers */ + if (headers) { + hdrs = pa_headerlist_to_string(headers); + pa_strbuf_puts(buf, hdrs); + pa_xfree(hdrs); + } + + if (content_type && content) { + pa_strbuf_printf(buf, "Content-Type: %s\r\nContent-Length: %d\r\n", + content_type, (int)strlen(content)); + } + + pa_strbuf_printf(buf, "User-Agent: %s\r\n", c->useragent); + + if (c->headers) { + hdrs = pa_headerlist_to_string(c->headers); + pa_strbuf_puts(buf, hdrs); + pa_xfree(hdrs); + } + + pa_strbuf_puts(buf, "\r\n"); + + if (content_type && content) { + pa_strbuf_puts(buf, content); + } + + /* Our packet is created... now we can send it :) */ + hdrs = pa_strbuf_tostring_free(buf); + /*pa_log_debug("Submitting request:"); + pa_log_debug(hdrs);*/ + l = pa_iochannel_write(c->io, hdrs, strlen(hdrs)); + pa_xfree(hdrs); + + return 0; +} + + +int pa_rtsp_announce(pa_rtsp_client *c, const char* sdp) { + pa_assert(c); + if (!sdp) + return -1; + + c->state = STATE_ANNOUNCE; + return rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL); +} + + +int pa_rtsp_setup(pa_rtsp_client* c) { + pa_headerlist* headers; + int rv; + + pa_assert(c); + + headers = pa_headerlist_new(); + pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); + + c->state = STATE_SETUP; + rv = rtsp_exec(c, "SETUP", NULL, NULL, 1, headers); + pa_headerlist_free(headers); + return rv; +} + + +int pa_rtsp_record(pa_rtsp_client* c, uint16_t* seq, uint32_t* rtptime) { + pa_headerlist* headers; + int rv; + char *info; + + pa_assert(c); + if (!c->session) { + /* No seesion in progres */ + return -1; + } + + /* Todo: Generate these values randomly as per spec */ + *seq = *rtptime = 0; + + headers = pa_headerlist_new(); + pa_headerlist_puts(headers, "Range", "npt=0-"); + info = pa_sprintf_malloc("seq=%u;rtptime=%u", *seq, *rtptime); + pa_headerlist_puts(headers, "RTP-Info", info); + pa_xfree(info); + + c->state = STATE_RECORD; + rv = rtsp_exec(c, "RECORD", NULL, NULL, 1, headers); + pa_headerlist_free(headers); + return rv; +} + + +int pa_rtsp_teardown(pa_rtsp_client *c) { + pa_assert(c); + + c->state = STATE_TEARDOWN; + return rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); +} + + +int pa_rtsp_setparameter(pa_rtsp_client *c, const char* param) { + pa_assert(c); + if (!param) + return -1; + + c->state = STATE_SET_PARAMETER; + return rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL); +} + + +int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) { + pa_headerlist* headers; + int rv; + char *info; + + pa_assert(c); + + headers = pa_headerlist_new(); + info = pa_sprintf_malloc("seq=%u;rtptime=%u", seq, rtptime); + pa_headerlist_puts(headers, "RTP-Info", info); + pa_xfree(info); + + c->state = STATE_FLUSH; + rv = rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers); + pa_headerlist_free(headers); + return rv; +} diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h new file mode 100644 index 00000000..88fb3839 --- /dev/null +++ b/src/modules/rtp/rtsp_client.h @@ -0,0 +1,73 @@ +#ifndef foortspclienthfoo +#define foortspclienthfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + 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, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include <inttypes.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <netdb.h> + +#include <pulsecore/memblockq.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/socket-client.h> +#include <pulse/mainloop-api.h> + +#include "headerlist.h" + +typedef struct pa_rtsp_client pa_rtsp_client; +typedef enum { + STATE_CONNECT, + STATE_ANNOUNCE, + STATE_SETUP, + STATE_RECORD, + STATE_FLUSH, + STATE_TEARDOWN, + STATE_SET_PARAMETER, + STATE_DISCONNECTED +} pa_rtsp_state; +typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_headerlist* hl, void *userdata); + +pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char* hostname, uint16_t port, const char* useragent); +void pa_rtsp_client_free(pa_rtsp_client* c); + +int pa_rtsp_connect(pa_rtsp_client* c); +void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata); + +void pa_rtsp_disconnect(pa_rtsp_client* c); + +const char* pa_rtsp_localip(pa_rtsp_client* c); +uint32_t pa_rtsp_serverport(pa_rtsp_client* c); +void pa_rtsp_set_url(pa_rtsp_client* c, const char* url); +void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value); +void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key); + +int pa_rtsp_announce(pa_rtsp_client* c, const char* sdp); + +int pa_rtsp_setup(pa_rtsp_client* c); +int pa_rtsp_record(pa_rtsp_client* c, uint16_t* seq, uint32_t* rtptime); +int pa_rtsp_teardown(pa_rtsp_client* c); + +int pa_rtsp_setparameter(pa_rtsp_client* c, const char* param); +int pa_rtsp_flush(pa_rtsp_client* c, uint16_t seq, uint32_t rtptime); + +#endif diff --git a/src/modules/rtp/sap.c b/src/modules/rtp/sap.c index 5d9b58fa..b0c95aa5 100644 --- a/src/modules/rtp/sap.c +++ b/src/modules/rtp/sap.c @@ -76,7 +76,7 @@ int pa_sap_send(pa_sap_context *c, pa_bool_t goodbye) { socklen_t salen = sizeof(sa_buf); struct iovec iov[4]; struct msghdr m; - int k; + ssize_t k; if (getsockname(c->fd, sa, &salen) < 0) { pa_log("getsockname() failed: %s\n", pa_cstrerror(errno)); @@ -94,7 +94,7 @@ int pa_sap_send(pa_sap_context *c, pa_bool_t goodbye) { iov[0].iov_len = sizeof(header); iov[1].iov_base = sa->sa_family == AF_INET ? (void*) &((struct sockaddr_in*) sa)->sin_addr : (void*) &((struct sockaddr_in6*) sa)->sin6_addr; - iov[1].iov_len = sa->sa_family == AF_INET ? 4 : 16; + iov[1].iov_len = sa->sa_family == AF_INET ? 4U : 16U; iov[2].iov_base = (char*) MIME_TYPE; iov[2].iov_len = sizeof(MIME_TYPE); @@ -113,7 +113,7 @@ int pa_sap_send(pa_sap_context *c, pa_bool_t goodbye) { if ((k = sendmsg(c->fd, &m, MSG_DONTWAIT)) < 0) pa_log_warn("sendmsg() failed: %s\n", pa_cstrerror(errno)); - return k; + return (int) k; } pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd) { @@ -128,10 +128,10 @@ pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd) { int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye) { struct msghdr m; struct iovec iov; - int size, k; + int size; char *buf = NULL, *e; uint32_t header; - int six, ac; + unsigned six, ac, k; ssize_t r; pa_assert(c); @@ -142,11 +142,11 @@ int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye) { goto fail; } - buf = pa_xnew(char, size+1); + buf = pa_xnew(char, (unsigned) size+1); buf[size] = 0; iov.iov_base = buf; - iov.iov_len = size; + iov.iov_len = (size_t) size; m.msg_name = NULL; m.msg_namelen = 0; @@ -184,21 +184,21 @@ int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye) { goto fail; } - six = (header >> 28) & 1; - ac = (header >> 16) & 0xFF; + six = (header >> 28) & 1U; + ac = (header >> 16) & 0xFFU; - k = 4 + (six ? 16 : 4) + ac*4; - if (size < k) { + k = 4 + (six ? 16U : 4U) + ac*4U; + if ((unsigned) size < k) { pa_log_warn("SAP packet too short (AD)."); goto fail; } e = buf + k; - size -= k; + size -= (int) k; if ((unsigned) size >= sizeof(MIME_TYPE) && !strcmp(e, MIME_TYPE)) { e += sizeof(MIME_TYPE); - size -= sizeof(MIME_TYPE); + size -= (int) sizeof(MIME_TYPE); } else if ((unsigned) size < sizeof(PA_SDP_HEADER)-1 || strncmp(e, PA_SDP_HEADER, sizeof(PA_SDP_HEADER)-1)) { pa_log_warn("Invalid SDP header."); goto fail; @@ -207,7 +207,7 @@ int pa_sap_recv(pa_sap_context *c, pa_bool_t *goodbye) { if (c->sdp_data) pa_xfree(c->sdp_data); - c->sdp_data = pa_xstrndup(e, size); + c->sdp_data = pa_xstrndup(e, (unsigned) size); pa_xfree(buf); *goodbye = !!((header >> 26) & 1); diff --git a/src/modules/rtp/sdp.c b/src/modules/rtp/sdp.c index cef90433..59989e1a 100644 --- a/src/modules/rtp/sdp.c +++ b/src/modules/rtp/sdp.c @@ -55,7 +55,7 @@ char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, u if (!(u = pa_get_user_name(un, sizeof(un)))) u = "-"; - ntp = time(NULL) + 2208988800U; + ntp = (uint32_t) time(NULL) + 2208988800U; pa_assert_se(a = inet_ntop(af, src, buf_src, sizeof(buf_src))); pa_assert_se(a = inet_ntop(af, dst, buf_dst, sizeof(buf_dst))); @@ -99,10 +99,10 @@ static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) { return NULL; if (sscanf(c, "%u/%u", &rate, &channels) == 2) { - ss->rate = rate; - ss->channels = channels; + ss->rate = (uint32_t) rate; + ss->channels = (uint8_t) channels; } else if (sscanf(c, "%u", &rate) == 2) { - ss->rate = rate; + ss->rate = (uint32_t) rate; ss->channels = 1; } else return NULL; |