summaryrefslogtreecommitdiffstats
path: root/src/modules
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules')
-rw-r--r--src/modules/alsa-util.c178
-rw-r--r--src/modules/alsa-util.h5
-rw-r--r--src/modules/bluetooth/ipc.c52
-rw-r--r--src/modules/bluetooth/ipc.h190
-rw-r--r--src/modules/bluetooth/module-bluetooth-device.c157
-rw-r--r--src/modules/bluetooth/module-bluetooth-discover.c27
-rw-r--r--src/modules/bluetooth/sbc.c537
-rw-r--r--src/modules/bluetooth/sbc.h4
-rw-r--r--src/modules/bluetooth/sbc_math.h16
-rw-r--r--src/modules/bluetooth/sbc_tables.h250
-rw-r--r--src/modules/module-alsa-sink.c59
-rw-r--r--src/modules/module-alsa-source.c59
-rw-r--r--src/modules/module-always-sink.c28
-rw-r--r--src/modules/module-device-restore.c2
-rw-r--r--src/modules/module-flat-volume.c224
-rw-r--r--src/modules/module-hal-detect.c2
-rw-r--r--src/modules/module-ladspa-sink.c11
-rw-r--r--src/modules/module-pipe-sink.c4
-rw-r--r--src/modules/module-pipe-source.c30
-rw-r--r--src/modules/module-raop-discover.c380
-rw-r--r--src/modules/module-raop-sink.c675
-rw-r--r--src/modules/module-remap-sink.c11
-rw-r--r--src/modules/module-stream-restore.c4
-rw-r--r--src/modules/module-suspend-on-idle.c20
-rw-r--r--src/modules/module-tunnel.c4
-rw-r--r--src/modules/module-zeroconf-discover.c4
-rw-r--r--src/modules/raop/base64.c126
-rw-r--r--src/modules/raop/base64.h34
-rw-r--r--src/modules/raop/raop_client.c561
-rw-r--r--src/modules/raop/raop_client.h46
-rw-r--r--src/modules/rtp/headerlist.c186
-rw-r--r--src/modules/rtp/headerlist.h46
-rw-r--r--src/modules/rtp/module-rtp-send.c7
-rw-r--r--src/modules/rtp/rtsp_client.c542
-rw-r--r--src/modules/rtp/rtsp_client.h73
35 files changed, 3941 insertions, 613 deletions
diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c
index ffe7795e..ff3af19d 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>
@@ -555,6 +556,7 @@ snd_pcm_t *pa_alsa_open_by_device_id(
for (i = 0;; i += direction) {
pa_sample_spec try_ss;
+ pa_bool_t reformat;
if (i < 0) {
pa_assert(direction == -1);
@@ -579,8 +581,9 @@ snd_pcm_t *pa_alsa_open_by_device_id(
d = pa_sprintf_malloc("%s:%s", device_table[i].name, dev_id);
+ reformat = FALSE;
for (;;) {
- pa_log_debug("Trying %s...", d);
+ pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with");
/* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <=
* 1.0.17a would then ignore the SND_PCM_NO_xxx
@@ -592,7 +595,7 @@ snd_pcm_t *pa_alsa_open_by_device_id(
/* SND_PCM_NONBLOCK| */
SND_PCM_NO_AUTO_RESAMPLE|
SND_PCM_NO_AUTO_CHANNELS|
- SND_PCM_NO_AUTO_FORMAT)) < 0) {
+ (reformat ? 0 : SND_PCM_NO_AUTO_FORMAT))) < 0) {
pa_log_info("Couldn't open PCM device %s: %s", d, snd_strerror(err));
break;
}
@@ -603,6 +606,12 @@ snd_pcm_t *pa_alsa_open_by_device_id(
if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, tsched_size, use_mmap, use_tsched, TRUE)) < 0) {
+ if (!reformat) {
+ reformat = TRUE;
+ snd_pcm_close(pcm_handle);
+ continue;
+ }
+
if (!pa_startswith(d, "plug:") && !pa_startswith(d, "plughw:")) {
char *t;
@@ -610,6 +619,8 @@ snd_pcm_t *pa_alsa_open_by_device_id(
pa_xfree(d);
d = t;
+ reformat = FALSE;
+
snd_pcm_close(pcm_handle);
continue;
}
@@ -654,6 +665,7 @@ snd_pcm_t *pa_alsa_open_by_device_string(
int err;
char *d;
snd_pcm_t *pcm_handle;
+ pa_bool_t reformat = FALSE;
pa_assert(device);
pa_assert(dev);
@@ -665,7 +677,7 @@ snd_pcm_t *pa_alsa_open_by_device_string(
d = pa_xstrdup(device);
for (;;) {
- pa_log_debug("Trying %s...", d);
+ pa_log_debug("Trying %s %s SND_PCM_NO_AUTO_FORMAT ...", d, reformat ? "without" : "with");
/* We don't pass SND_PCM_NONBLOCK here, since alsa-lib <=
* 1.0.17a would then ignore the SND_PCM_NO_xxx flags. Instead
@@ -677,7 +689,7 @@ snd_pcm_t *pa_alsa_open_by_device_string(
/*SND_PCM_NONBLOCK|*/
SND_PCM_NO_AUTO_RESAMPLE|
SND_PCM_NO_AUTO_CHANNELS|
- SND_PCM_NO_AUTO_FORMAT)) < 0) {
+ (reformat ? 0 : SND_PCM_NO_AUTO_FORMAT))) < 0) {
pa_log("Error opening PCM device %s: %s", d, snd_strerror(err));
pa_xfree(d);
return NULL;
@@ -685,6 +697,13 @@ 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 (!reformat) {
+ reformat = TRUE;
+
+ snd_pcm_close(pcm_handle);
+ continue;
+ }
+
/* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */
if (!pa_startswith(d, "plug:") && !pa_startswith(d, "plughw:")) {
@@ -694,6 +713,8 @@ snd_pcm_t *pa_alsa_open_by_device_string(
pa_xfree(d);
d = t;
+ reformat = FALSE;
+
snd_pcm_close(pcm_handle);
continue;
}
@@ -739,8 +760,32 @@ int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) {
return 0;
}
-snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback) {
- snd_mixer_elem_t *elem;
+static pa_bool_t elem_has_volume(snd_mixer_elem_t *elem, pa_bool_t playback) {
+ pa_assert(elem);
+
+ if (playback && snd_mixer_selem_has_playback_volume(elem))
+ return TRUE;
+
+ if (!playback && snd_mixer_selem_has_capture_volume(elem))
+ return TRUE;
+
+ return FALSE;
+}
+
+static pa_bool_t elem_has_switch(snd_mixer_elem_t *elem, pa_bool_t playback) {
+ pa_assert(elem);
+
+ if (playback && snd_mixer_selem_has_playback_switch(elem))
+ return TRUE;
+
+ if (!playback && snd_mixer_selem_has_capture_switch(elem))
+ return TRUE;
+
+ return FALSE;
+}
+
+snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback) {
+ snd_mixer_elem_t *elem = NULL, *fallback_elem = NULL;
snd_mixer_selem_id_t *sid = NULL;
snd_mixer_selem_id_alloca(&sid);
@@ -750,17 +795,57 @@ snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const
snd_mixer_selem_id_set_name(sid, name);
- if (!(elem = snd_mixer_find_selem(mixer, sid))) {
- pa_log_info("Cannot find mixer control \"%s\".", snd_mixer_selem_id_get_name(sid));
+ if ((elem = snd_mixer_find_selem(mixer, sid))) {
+
+ if (elem_has_volume(elem, playback) &&
+ elem_has_switch(elem, playback))
+ goto success;
- if (fallback) {
- snd_mixer_selem_id_set_name(sid, fallback);
+ if (!elem_has_volume(elem, playback) &&
+ !elem_has_switch(elem, playback))
+ elem = NULL;
+ }
+
+ pa_log_info("Cannot find mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid));
+
+ if (fallback) {
+ snd_mixer_selem_id_set_name(sid, fallback);
+
+ if ((fallback_elem = snd_mixer_find_selem(mixer, sid))) {
- if (!(elem = snd_mixer_find_selem(mixer, sid)))
- pa_log_warn("Cannot find fallback mixer control \"%s\".", snd_mixer_selem_id_get_name(sid));
+ if (elem_has_volume(fallback_elem, playback) &&
+ elem_has_switch(fallback_elem, playback)) {
+ elem = fallback_elem;
+ goto success;
+ }
+
+ if (!elem_has_volume(fallback_elem, playback) &&
+ !elem_has_switch(fallback_elem, playback))
+ fallback_elem = NULL;
}
+
+ pa_log_warn("Cannot find fallback mixer control \"%s\" or mixer control is no combination of switch/volume.", snd_mixer_selem_id_get_name(sid));
}
+ if (elem && fallback_elem) {
+
+ /* Hmm, so we have both elements, but neither has both mute
+ * and volume. Let's prefer the one with the volume */
+
+ if (elem_has_volume(elem, playback))
+ goto success;
+
+ if (elem_has_volume(fallback_elem, playback)) {
+ elem = fallback_elem;
+ goto success;
+ }
+ }
+
+ if (!elem && fallback_elem)
+ elem = fallback_elem;
+
+success:
+
if (elem)
pa_log_info("Using mixer control \"%s\".", snd_mixer_selem_id_get_name(sid));
@@ -1045,14 +1130,20 @@ int pa_alsa_recover_from_poll(snd_pcm_t *pcm, int revents) {
pa_assert(pcm);
if (revents & POLLERR)
- pa_log_warn("Got POLLERR from ALSA");
+ pa_log_debug("Got POLLERR from ALSA");
if (revents & POLLNVAL)
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_debug("Got POLLIN from ALSA");
+ if (revents & POLLOUT)
+ pa_log_debug("Got POLLOUT from ALSA");
state = snd_pcm_state(pcm);
- pa_log_warn("PCM state is %s", snd_pcm_state_name(state));
+ pa_log_debug("PCM state is %s", snd_pcm_state_name(state));
/* Try to recover from this error */
@@ -1109,3 +1200,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 b66adc13..95bb983a 100644
--- a/src/modules/alsa-util.h
+++ b/src/modules/alsa-util.h
@@ -52,7 +52,7 @@ int pa_alsa_set_hw_params(
int pa_alsa_set_sw_params(snd_pcm_t *pcm, snd_pcm_uframes_t avail_min);
int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev);
-snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback);
+snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback, pa_bool_t playback);
snd_pcm_t *pa_alsa_open_by_device_id(
const char *dev_id,
@@ -92,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/ipc.c b/src/modules/bluetooth/ipc.c
index 98256998..67785309 100644
--- a/src/modules/bluetooth/ipc.c
+++ b/src/modules/bluetooth/ipc.c
@@ -22,22 +22,26 @@
#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",
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+/* This table contains the string representation for messages types */
+static const char *strtypes[] = {
+ "BT_REQUEST",
+ "BT_RESPONSE",
+ "BT_INDICATION",
+ "BT_ERROR",
+};
+
+/* This table contains the string representation for messages names */
+static const char *strnames[] = {
+ "BT_GET_CAPABILITIES",
+ "BT_SET_CONFIGURATION",
+ "BT_NEW_STREAM",
+ "BT_START_STREAM",
+ "BT_STOP_STREAM",
+ "BT_SUSPEND_STREAM",
+ "BT_RESUME_STREAM",
+ "BT_CONTROL",
};
int bt_audio_service_open(void)
@@ -88,7 +92,7 @@ int bt_audio_service_get_data_fd(int sk)
msgh.msg_control = &cmsg_b;
msgh.msg_controllen = CMSG_LEN(sizeof(int));
- ret = (int) recvmsg(sk, &msgh, 0);
+ ret = recvmsg(sk, &msgh, 0);
if (ret < 0) {
err = errno;
fprintf(stderr, "%s: Unable to receive fd: %s (%d)\n",
@@ -109,10 +113,18 @@ int bt_audio_service_get_data_fd(int sk)
return -1;
}
-const char *bt_audio_strmsg(int type)
+const char *bt_audio_strtype(uint8_t type)
+{
+ if (type >= ARRAY_SIZE(strtypes))
+ return NULL;
+
+ return strtypes[type];
+}
+
+const char *bt_audio_strname(uint8_t name)
{
- if (type < 0 || (size_t) type > (sizeof(strmsg) / sizeof(strmsg[0])))
+ if (name >= ARRAY_SIZE(strnames))
return NULL;
- return strmsg[type];
+ return strnames[name];
}
diff --git a/src/modules/bluetooth/ipc.h b/src/modules/bluetooth/ipc.h
index ae85e727..0e985c3a 100644
--- a/src/modules/bluetooth/ipc.h
+++ b/src/modules/bluetooth/ipc.h
@@ -23,36 +23,36 @@
/*
Message sequence chart of streaming sequence for A2DP transport
- Audio daemon User
- on snd_pcm_open
- <--BT_GETCAPABILITIES_REQ
+ Audio daemon User
+ on snd_pcm_open
+ <--BT_GET_CAPABILITIES_REQ
- BT_GETCAPABILITIES_RSP-->
+ BT_GET_CAPABILITIES_RSP-->
- on snd_pcm_hw_params
- <--BT_SETCONFIGURATION_REQ
+ on snd_pcm_hw_params
+ <--BT_SETCONFIGURATION_REQ
- BT_SETCONFIGURATION_RSP-->
+ BT_SET_CONFIGURATION_RSP-->
- on snd_pcm_prepare
- <--BT_STREAMSTART_REQ
+ on snd_pcm_prepare
+ <--BT_START_STREAM_REQ
<Moves to streaming state>
- BT_STREAMSTART_RSP-->
+ BT_START_STREAM_RSP-->
- BT_STREAMFD_IND -->
+ BT_NEW_STREAM_IND -->
- < streams data >
- ..........
+ < streams data >
+ ..........
- on snd_pcm_drop/snd_pcm_drain
+ on snd_pcm_drop/snd_pcm_drain
- <--BT_STREAMSTOP_REQ
+ <--BT_STOP_STREAM_REQ
<Moves to open state>
- BT_STREAMSTOP_RSP-->
+ BT_STOP_STREAM_RSP-->
- on IPC close or appl crash
+ on IPC close or appl crash
<Moves to idle>
*/
@@ -71,43 +71,36 @@ extern "C" {
#include <sys/un.h>
#include <errno.h>
-#define BT_AUDIO_IPC_PACKET_SIZE 128
+#define BT_SUGGESTED_BUFFER_SIZE 128
#define BT_IPC_SOCKET_NAME "\0/org/bluez/audio"
-/* Generic message header definition, except for RSP messages */
+/* Generic message header definition, except for RESPONSE messages */
typedef struct {
- uint8_t msg_type;
+ uint8_t type;
+ uint8_t name;
+ uint16_t length;
} __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 */
+ bt_audio_msg_header_t h;
+ uint8_t posix_errno;
+} __attribute__ ((packed)) bt_audio_error_t;
+
+/* Message types */
+#define BT_REQUEST 0
+#define BT_RESPONSE 1
+#define BT_INDICATION 2
+#define BT_ERROR 3
+
+/* Messages names */
+#define BT_GET_CAPABILITIES 0
+#define BT_SET_CONFIGURATION 1
+#define BT_NEW_STREAM 2
+#define BT_START_STREAM 3
+#define BT_STOP_STREAM 4
+#define BT_SUSPEND_STREAM 5
+#define BT_RESUME_STREAM 6
+#define BT_CONTROL 7
#define BT_CAPABILITIES_TRANSPORT_A2DP 0
#define BT_CAPABILITIES_TRANSPORT_SCO 1
@@ -119,19 +112,22 @@ typedef struct {
#define BT_FLAG_AUTOCONNECT 1
-struct bt_getcapabilities_req {
+struct bt_get_capabilities_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_A2DP_CODEC_SBC 0x00
+#define BT_A2DP_CODEC_MPEG12 0x01
+#define BT_A2DP_CODEC_MPEG24 0x02
+#define BT_A2DP_CODEC_ATRAC 0x03
+
#define BT_SBC_SAMPLING_FREQ_16000 (1 << 3)
#define BT_SBC_SAMPLING_FREQ_32000 (1 << 2)
#define BT_SBC_SAMPLING_FREQ_44100 (1 << 1)
@@ -164,7 +160,19 @@ struct bt_getcapabilities_req {
#define BT_MPEG_LAYER_2 (1 << 1)
#define BT_MPEG_LAYER_3 1
+#define BT_HFP_CODEC_PCM 0x00
+
+#define BT_PCM_FLAG_NREC 1
+
+typedef struct {
+ uint8_t transport;
+ uint8_t type;
+ uint8_t length;
+ uint8_t data[0];
+} __attribute__ ((packed)) codec_capabilities_t;
+
typedef struct {
+ codec_capabilities_t capability;
uint8_t channel_mode;
uint8_t frequency;
uint8_t allocation_method;
@@ -175,6 +183,7 @@ typedef struct {
} __attribute__ ((packed)) sbc_capabilities_t;
typedef struct {
+ codec_capabilities_t capability;
uint8_t channel_mode;
uint8_t crc;
uint8_t layer;
@@ -183,75 +192,65 @@ typedef struct {
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 */
+typedef struct {
+ codec_capabilities_t capability;
+ uint8_t flags;
+ uint16_t sampling_rate;
+} __attribute__ ((packed)) pcm_capabilities_t;
+
+
+struct bt_get_capabilities_rsp {
+ bt_audio_msg_header_t h;
+ uint8_t data[0]; /* First codec_capabilities_t */
} __attribute__ ((packed));
-/* BT_SETCONFIGURATION_REQ */
-struct bt_setconfiguration_req {
+struct bt_set_configuration_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 */
+ char device[18]; /* Address of the remote Device */
+ uint8_t access_mode; /* Requested access mode */
+ codec_capabilities_t codec; /* Requested codec */
} __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 */
+struct bt_set_configuration_rsp {
+ bt_audio_msg_header_t 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 {
+struct bt_start_stream_req {
bt_audio_msg_header_t h;
} __attribute__ ((packed));
-/* BT_STREAMSTART_RSP */
-struct bt_streamstart_rsp {
- bt_audio_rsp_msg_header_t rsp_h;
+struct bt_start_stream_rsp {
+ bt_audio_msg_header_t 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 {
+struct bt_new_stream_ind {
bt_audio_msg_header_t h;
} __attribute__ ((packed));
-/* BT_STREAMSTOP_REQ */
-struct bt_streamstop_req {
+struct bt_stop_stream_req {
bt_audio_msg_header_t h;
} __attribute__ ((packed));
-/* BT_STREAMSTOP_RSP */
-struct bt_streamstop_rsp {
- bt_audio_rsp_msg_header_t rsp_h;
+struct bt_stop_stream_rsp {
+ bt_audio_msg_header_t h;
} __attribute__ ((packed));
-/* BT_STREAMSUSPEND_IND */
-struct bt_streamsuspend_ind {
+struct bt_suspend_stream_ind {
bt_audio_msg_header_t h;
} __attribute__ ((packed));
-/* BT_STREAMRESUME_IND */
-struct bt_streamresume_ind {
+struct bt_resume_stream_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
@@ -272,14 +271,12 @@ struct bt_control_req {
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 */
+ bt_audio_msg_header_t 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 */
@@ -299,7 +296,10 @@ 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);
+const char *bt_audio_strtype(uint8_t type);
+
+/* Human readable message name string */
+const char *bt_audio_strname(uint8_t name);
#ifdef __cplusplus
}
diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c
index 3460fe9a..cb4746a4 100644
--- a/src/modules/bluetooth/module-bluetooth-device.c
+++ b/src/modules/bluetooth/module-bluetooth-device.c
@@ -122,8 +122,14 @@ static const char* const valid_modargs[] = {
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)
+ const char *type, *name;
+ uint16_t length;
+
+ length = msg->length ? msg->length : BT_SUGGESTED_BUFFER_SIZE;
+ type = bt_audio_strtype(msg->type);
+ name = bt_audio_strname(msg->name);
+ pa_log_debug("sending: %s -> %s", type, name);
+ if (send(sk, msg, length, 0) > 0)
e = 0;
else {
e = -errno;
@@ -132,20 +138,25 @@ static int bt_audioservice_send(int sk, const bt_audio_msg_header_t *msg) {
return e;
}
-static int bt_audioservice_recv(int sk, bt_audio_msg_header_t *inmsg) {
+static int bt_audioservice_recv(int sk, bt_audio_msg_header_t *inmsg, uint16_t expected_length) {
int e;
- const char *type;
+ const char *type, *name;
+ uint16_t length;
+
+ length = expected_length ? expected_length : BT_SUGGESTED_BUFFER_SIZE;
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);
+ if (recv(sk, inmsg, length, 0) > 0) {
+ type = bt_audio_strtype(inmsg->type);
+ name = bt_audio_strname(inmsg->name);
+ if (type && name) {
+ pa_log_debug("Received: %s <- %s", type, name);
e = 0;
}
else {
e = -EINVAL;
- pa_log_error("Bogus message type %d received from audio service", inmsg->msg_type);
+ pa_log_error("Bogus message type %d name %d received from audio service",
+ inmsg->type, inmsg->name);
}
}
else {
@@ -156,29 +167,66 @@ static int bt_audioservice_recv(int sk, bt_audio_msg_header_t *inmsg) {
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) {
+static int bt_audioservice_expect(int sk, bt_audio_msg_header_t *rsp, uint8_t expected_name, uint16_t expected_length) {
+ int e = bt_audioservice_recv(sk, rsp, expected_length);
+
+ if (e < 0) {
+ if (rsp->name != expected_name) {
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));
+ pa_log_error("Bogus message %s received while %s was expected",
+ bt_audio_strname(rsp->name),
+ bt_audio_strname(expected_name));
}
}
+
+ if (rsp->type == BT_ERROR) {
+ bt_audio_error_t *error = (void *) rsp;
+ pa_log_error("%s failed : %s(%d)", bt_audio_strname(rsp->name), pa_cstrerror(error->posix_errno), error->posix_errno);
+ return -error->posix_errno;
+ }
+
return e;
}
+static int bt_parsecaps(struct userdata *u, struct bt_get_capabilities_rsp *rsp) {
+ uint16_t bytes_left = rsp->h.length - sizeof(*rsp);
+ codec_capabilities_t *codec = (void *) rsp->data;
+
+ u->transport = codec->transport;
+
+ if (codec->transport != BT_CAPABILITIES_TRANSPORT_A2DP)
+ return 0;
+
+ while (bytes_left > 0) {
+ if (codec->type == BT_A2DP_CODEC_SBC)
+ break;
+
+ bytes_left -= codec->length;
+ codec = (void *) (codec + codec->length);
+ }
+
+ if (bytes_left <= 0 || codec->length != sizeof(u->a2dp.sbc_capabilities))
+ return -EINVAL;
+
+ memcpy(&u->a2dp.sbc_capabilities, codec, codec->length);
+
+ return 0;
+}
+
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];
+ bt_audio_msg_header_t rsp;
+ struct bt_get_capabilities_req getcaps_req;
+ struct bt_get_capabilities_rsp getcaps_rsp;
+ uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
} msg;
- memset(msg.buf, 0, BT_AUDIO_IPC_PACKET_SIZE);
- msg.getcaps_req.h.msg_type = BT_GETCAPABILITIES_REQ;
+ memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE);
+ msg.getcaps_req.h.type = BT_REQUEST;
+ msg.getcaps_req.h.name = BT_GET_CAPABILITIES;
+ msg.getcaps_req.h.length = sizeof(msg.getcaps_req);
+
strncpy(msg.getcaps_req.device, u->addr, 18);
if (strcasecmp(u->profile, "a2dp") == 0)
msg.getcaps_req.transport = BT_CAPABILITIES_TRANSPORT_A2DP;
@@ -196,20 +244,13 @@ static int bt_getcaps(struct userdata *u) {
return e;
}
- e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp_hdr.msg_h, BT_GETCAPABILITIES_RSP);
+ e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp, BT_GET_CAPABILITIES, 0);
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;
+ return bt_parsecaps(u, &msg.getcaps_rsp);
}
static uint8_t default_bitpool(uint8_t freq, uint8_t mode) {
@@ -393,10 +434,10 @@ static void bt_a2dp_setup(struct bt_a2dp *a2dp) {
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];
+ bt_audio_msg_header_t rsp;
+ struct bt_set_configuration_req setconf_req;
+ struct bt_set_configuration_rsp setconf_rsp;
+ uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
} msg;
if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
@@ -410,12 +451,19 @@ static int bt_setconf(struct userdata *u) {
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;
+ memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE);
+ msg.setconf_req.h.type = BT_REQUEST;
+ msg.setconf_req.h.name = BT_SET_CONFIGURATION;
+ msg.setconf_req.h.length = sizeof(msg.setconf_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.codec.transport = u->transport;
+ if (u->transport == BT_CAPABILITIES_TRANSPORT_A2DP) {
+ memcpy(&msg.setconf_req.codec, &u->a2dp.sbc_capabilities,
+ sizeof(u->a2dp.sbc_capabilities));
+ msg.setconf_req.h.length += msg.setconf_req.codec.length
+ - sizeof(msg.setconf_req.codec);
+ }
msg.setconf_req.access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE;
e = bt_audioservice_send(u->audioservice_fd, &msg.setconf_req.h);
@@ -424,17 +472,12 @@ static int bt_setconf(struct userdata *u) {
return e;
}
- e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp_hdr.msg_h, BT_SETCONFIGURATION_RSP);
+ e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp, BT_SET_CONFIGURATION, sizeof(msg.setconf_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;
@@ -456,14 +499,17 @@ 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];
+ bt_audio_msg_header_t rsp;
+ struct bt_start_stream_req start_req;
+ struct bt_start_stream_rsp start_rsp;
+ struct bt_new_stream_ind streamfd_ind;
+ uint8_t buf[BT_SUGGESTED_BUFFER_SIZE];
} msg;
- memset(msg.buf, 0, BT_AUDIO_IPC_PACKET_SIZE);
- msg.start_req.h.msg_type = BT_STREAMSTART_REQ;
+ memset(msg.buf, 0, BT_SUGGESTED_BUFFER_SIZE);
+ msg.start_req.h.type = BT_REQUEST;
+ msg.start_req.h.name = BT_START_STREAM;
+ msg.start_req.h.length = sizeof(msg.start_req);
e = bt_audioservice_send(u->audioservice_fd, &msg.start_req.h);
if (e < 0) {
@@ -471,18 +517,13 @@ static int bt_getstreamfd(struct userdata *u) {
return e;
}
- e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp_hdr.msg_h, BT_STREAMSTART_RSP);
+ e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp, BT_START_STREAM, sizeof(msg.start_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);
+ e = bt_audioservice_expect(u->audioservice_fd, &msg.rsp, BT_NEW_STREAM, sizeof(msg.streamfd_ind));
if (e < 0) {
pa_log_error("Failed to expect BT_STREAMFD_IND");
return e;
diff --git a/src/modules/bluetooth/module-bluetooth-discover.c b/src/modules/bluetooth/module-bluetooth-discover.c
index a33ca648..2fe09370 100644
--- a/src/modules/bluetooth/module-bluetooth-discover.c
+++ b/src/modules/bluetooth/module-bluetooth-discover.c
@@ -44,7 +44,7 @@ PA_MODULE_USAGE("");
struct module {
char *profile;
- pa_module *pa_m;
+ uint32_t index;
PA_LLIST_FIELDS(struct module);
};
@@ -78,7 +78,7 @@ static struct module *module_new(const char *profile, pa_module *pa_m) {
m = pa_xnew(struct module, 1);
m->profile = pa_xstrdup(profile);
- m->pa_m = pa_m;
+ m->index = pa_m->index;
PA_LLIST_INIT(struct module, m);
return m;
@@ -94,7 +94,7 @@ static void module_free(struct module *m) {
static struct module* module_find(struct device *d, const char *profile) {
struct module *m;
- for (m = d->module_list; d; d = d->next)
+ for (m = d->module_list; m; m = m->next)
if (pa_streq(m->profile, profile))
return m;
@@ -346,7 +346,7 @@ static void load_module_for_device(struct userdata *u, struct device *d, const c
pa_m = pa_module_load(u->module->core, "module-bluetooth-device", args);
pa_xfree(args);
- if (!m) {
+ if (!pa_m) {
pa_log_debug("Failed to load module for device %s", d->object_path);
return;
}
@@ -364,7 +364,7 @@ static void unload_module_for_device(struct userdata *u, struct device *d, const
if (!(m = module_find(d, profile)))
return;
- pa_module_unload_request(m->pa_m, TRUE);
+ pa_module_unload_request_by_index(u->module->core, m->index, TRUE);
PA_LLIST_REMOVE(struct module, d->module_list, m);
module_free(m);
@@ -485,8 +485,23 @@ void pa__done(pa_module* m) {
device_free(i);
}
- if (u->conn)
+ 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);
}
diff --git a/src/modules/bluetooth/sbc.c b/src/modules/bluetooth/sbc.c
index 02a6143d..651981fa 100644
--- a/src/modules/bluetooth/sbc.c
+++ b/src/modules/bluetooth/sbc.c
@@ -2,7 +2,7 @@
*
* Bluetooth low-complexity, subband codec (SBC) library
*
- * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch>
* Copyright (C) 2005-2008 Brad Midgley <bmidgley@xmission.com>
*
@@ -40,6 +40,7 @@
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
+#include <limits.h>
#include "sbc_math.h"
#include "sbc_tables.h"
@@ -68,7 +69,7 @@ struct sbc_frame {
uint8_t subband_mode;
uint8_t subbands;
uint8_t bitpool;
- uint8_t codesize;
+ uint16_t codesize;
uint8_t length;
/* bit number x set means joint stereo has been used in subband x */
@@ -93,7 +94,11 @@ struct sbc_decoder_state {
struct sbc_encoder_state {
int subbands;
int position[2];
- int32_t X[2][160];
+ int16_t X[2][256];
+ void (*sbc_analyze_4b_4s)(int16_t *pcm, int16_t *x,
+ int32_t *out, int out_stride);
+ void (*sbc_analyze_4b_8s)(int16_t *pcm, int16_t *x,
+ int32_t *out, int out_stride);
};
/*
@@ -145,7 +150,7 @@ static uint8_t sbc_crc8(const uint8_t *data, size_t len)
octet = data[i];
for (i = 0; i < len % 8; i++) {
- unsigned char bit = ((octet ^ crc) & 0x80) >> 7;
+ char bit = ((octet ^ crc) & 0x80) >> 7;
crc = ((crc & 0x7f) << 1) ^ (bit ? 0x1d : 0);
@@ -563,7 +568,7 @@ static inline void sbc_synthesize_four(struct sbc_decoder_state *state,
k = (i + 4) & 0xf;
/* Store in output, Q0 */
- frame->pcm_sample[ch][blk * 4 + i] = SCALE4_STAGED2(
+ frame->pcm_sample[ch][blk * 4 + i] = SCALE4_STAGED1(
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],
@@ -609,7 +614,7 @@ static inline void sbc_synthesize_eight(struct sbc_decoder_state *state,
k = (i + 8) & 0xf;
/* Store in output */
- frame->pcm_sample[ch][blk * 8 + i] = SCALE8_STAGED2( // Q0
+ frame->pcm_sample[ch][blk * 8 + i] = SCALE8_STAGED1( // 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],
@@ -648,242 +653,144 @@ static int sbc_synthesize_audio(struct sbc_decoder_state *state,
}
}
-static void sbc_encoder_init(struct sbc_encoder_state *state,
- const struct sbc_frame *frame)
+static inline void _sbc_analyze_four(const int16_t *in, int32_t *out)
{
- memset(&state->X, 0, sizeof(state->X));
- state->subbands = frame->subbands;
- state->position[0] = state->position[1] = 9 * frame->subbands;
-}
+ FIXED_A t1[4];
+ FIXED_T t2[4];
+ int i = 0, hop = 0;
+
+ /* rounding coefficient */
+ t1[0] = t1[1] = t1[2] = t1[3] =
+ (FIXED_A) 1 << (SBC_PROTO_FIXED4_SCALE - 1);
+
+ /* low pass polyphase filter */
+ for (hop = 0; hop < 40; hop += 8) {
+ t1[0] += (FIXED_A) in[hop] * _sbc_proto_fixed4[hop];
+ t1[1] += (FIXED_A) in[hop + 1] * _sbc_proto_fixed4[hop + 1];
+ t1[2] += (FIXED_A) in[hop + 2] * _sbc_proto_fixed4[hop + 2];
+ t1[1] += (FIXED_A) in[hop + 3] * _sbc_proto_fixed4[hop + 3];
+ t1[0] += (FIXED_A) in[hop + 4] * _sbc_proto_fixed4[hop + 4];
+ t1[3] += (FIXED_A) in[hop + 5] * _sbc_proto_fixed4[hop + 5];
+ t1[3] += (FIXED_A) in[hop + 7] * _sbc_proto_fixed4[hop + 7];
+ }
-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]);
+ /* scaling */
+ t2[0] = t1[0] >> SBC_PROTO_FIXED4_SCALE;
+ t2[1] = t1[1] >> SBC_PROTO_FIXED4_SCALE;
+ t2[2] = t1[2] >> SBC_PROTO_FIXED4_SCALE;
+ t2[3] = t1[3] >> SBC_PROTO_FIXED4_SCALE;
+
+ /* do the cos transform */
+ for (i = 0, hop = 0; i < 4; hop += 8, i++) {
+ out[i] = ((FIXED_A) t2[0] * cos_table_fixed_4[0 + hop] +
+ (FIXED_A) t2[1] * cos_table_fixed_4[1 + hop] +
+ (FIXED_A) t2[2] * cos_table_fixed_4[2 + hop] +
+ (FIXED_A) t2[3] * cos_table_fixed_4[5 + hop]) >>
+ (SBC_COS_TABLE_FIXED4_SCALE - SCALE_OUT_BITS);
+ }
}
-static inline void sbc_analyze_four(struct sbc_encoder_state *state,
- struct sbc_frame *frame, int ch, int blk)
+static void sbc_analyze_4b_4s(int16_t *pcm, int16_t *x,
+ int32_t *out, int out_stride)
{
- 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]);
+ int i;
+
+ /* Input 4 x 4 Audio Samples */
+ for (i = 0; i < 16; i += 4) {
+ x[64 + i] = x[0 + i] = pcm[15 - i];
+ x[65 + i] = x[1 + i] = pcm[14 - i];
+ x[66 + i] = x[2 + i] = pcm[13 - i];
+ x[67 + i] = x[3 + i] = pcm[12 - i];
+ }
- state->position[ch] -= 4;
- if (state->position[ch] < 0)
- state->position[ch] = 36;
+ /* Analyze four blocks */
+ _sbc_analyze_four(x + 12, out);
+ out += out_stride;
+ _sbc_analyze_four(x + 8, out);
+ out += out_stride;
+ _sbc_analyze_four(x + 4, out);
+ out += out_stride;
+ _sbc_analyze_four(x, out);
}
-static inline void _sbc_analyze_eight(const int32_t *in, int32_t *out)
+static inline void _sbc_analyze_eight(const int16_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]);
+ FIXED_A t1[8];
+ FIXED_T t2[8];
+ int i, hop;
+
+ /* rounding coefficient */
+ t1[0] = t1[1] = t1[2] = t1[3] = t1[4] = t1[5] = t1[6] = t1[7] =
+ (FIXED_A) 1 << (SBC_PROTO_FIXED8_SCALE-1);
+
+ /* low pass polyphase filter */
+ for (hop = 0; hop < 80; hop += 16) {
+ t1[0] += (FIXED_A) in[hop] * _sbc_proto_fixed8[hop];
+ t1[1] += (FIXED_A) in[hop + 1] * _sbc_proto_fixed8[hop + 1];
+ t1[2] += (FIXED_A) in[hop + 2] * _sbc_proto_fixed8[hop + 2];
+ t1[3] += (FIXED_A) in[hop + 3] * _sbc_proto_fixed8[hop + 3];
+ t1[4] += (FIXED_A) in[hop + 4] * _sbc_proto_fixed8[hop + 4];
+ t1[3] += (FIXED_A) in[hop + 5] * _sbc_proto_fixed8[hop + 5];
+ t1[2] += (FIXED_A) in[hop + 6] * _sbc_proto_fixed8[hop + 6];
+ t1[1] += (FIXED_A) in[hop + 7] * _sbc_proto_fixed8[hop + 7];
+ t1[0] += (FIXED_A) in[hop + 8] * _sbc_proto_fixed8[hop + 8];
+ t1[5] += (FIXED_A) in[hop + 9] * _sbc_proto_fixed8[hop + 9];
+ t1[6] += (FIXED_A) in[hop + 10] * _sbc_proto_fixed8[hop + 10];
+ t1[7] += (FIXED_A) in[hop + 11] * _sbc_proto_fixed8[hop + 11];
+ t1[7] += (FIXED_A) in[hop + 13] * _sbc_proto_fixed8[hop + 13];
+ t1[6] += (FIXED_A) in[hop + 14] * _sbc_proto_fixed8[hop + 14];
+ t1[5] += (FIXED_A) in[hop + 15] * _sbc_proto_fixed8[hop + 15];
+ }
+
+ /* scaling */
+ t2[0] = t1[0] >> SBC_PROTO_FIXED8_SCALE;
+ t2[1] = t1[1] >> SBC_PROTO_FIXED8_SCALE;
+ t2[2] = t1[2] >> SBC_PROTO_FIXED8_SCALE;
+ t2[3] = t1[3] >> SBC_PROTO_FIXED8_SCALE;
+ t2[4] = t1[4] >> SBC_PROTO_FIXED8_SCALE;
+ t2[5] = t1[5] >> SBC_PROTO_FIXED8_SCALE;
+ t2[6] = t1[6] >> SBC_PROTO_FIXED8_SCALE;
+ t2[7] = t1[7] >> SBC_PROTO_FIXED8_SCALE;
+
+ /* do the cos transform */
+ for (i = 0, hop = 0; i < 8; hop += 16, i++) {
+ out[i] = ((FIXED_A) t2[0] * cos_table_fixed_8[0 + hop] +
+ (FIXED_A) t2[1] * cos_table_fixed_8[1 + hop] +
+ (FIXED_A) t2[2] * cos_table_fixed_8[2 + hop] +
+ (FIXED_A) t2[3] * cos_table_fixed_8[3 + hop] +
+ (FIXED_A) t2[4] * cos_table_fixed_8[4 + hop] +
+ (FIXED_A) t2[5] * cos_table_fixed_8[9 + hop] +
+ (FIXED_A) t2[6] * cos_table_fixed_8[10 + hop] +
+ (FIXED_A) t2[7] * cos_table_fixed_8[11 + hop]) >>
+ (SBC_COS_TABLE_FIXED8_SCALE - SCALE_OUT_BITS);
+ }
}
-static inline void sbc_analyze_eight(struct sbc_encoder_state *state,
- struct sbc_frame *frame, int ch,
- int blk)
+static void sbc_analyze_4b_8s(int16_t *pcm, int16_t *x,
+ int32_t *out, int out_stride)
{
- 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;
+ int i;
+
+ /* Input 4 x 8 Audio Samples */
+ for (i = 0; i < 32; i += 8) {
+ x[128 + i] = x[0 + i] = pcm[31 - i];
+ x[129 + i] = x[1 + i] = pcm[30 - i];
+ x[130 + i] = x[2 + i] = pcm[29 - i];
+ x[131 + i] = x[3 + i] = pcm[28 - i];
+ x[132 + i] = x[4 + i] = pcm[27 - i];
+ x[133 + i] = x[5 + i] = pcm[26 - i];
+ x[134 + i] = x[6 + i] = pcm[25 - i];
+ x[135 + i] = x[7 + i] = pcm[24 - i];
+ }
+
+ /* Analyze four blocks */
+ _sbc_analyze_eight(x + 24, out);
+ out += out_stride;
+ _sbc_analyze_eight(x + 16, out);
+ out += out_stride;
+ _sbc_analyze_eight(x + 8, out);
+ out += out_stride;
+ _sbc_analyze_eight(x, out);
}
static int sbc_analyze_audio(struct sbc_encoder_state *state,
@@ -894,14 +801,32 @@ static int sbc_analyze_audio(struct sbc_encoder_state *state,
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);
+ for (blk = 0; blk < frame->blocks; blk += 4) {
+ state->sbc_analyze_4b_4s(
+ &frame->pcm_sample[ch][blk * 4],
+ &state->X[ch][state->position[ch]],
+ frame->sb_sample_f[blk][ch],
+ frame->sb_sample_f[blk + 1][ch] -
+ frame->sb_sample_f[blk][ch]);
+ state->position[ch] -= 16;
+ if (state->position[ch] < 0)
+ state->position[ch] = 64 - 16;
+ }
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);
+ for (blk = 0; blk < frame->blocks; blk += 4) {
+ state->sbc_analyze_4b_8s(
+ &frame->pcm_sample[ch][blk * 8],
+ &state->X[ch][state->position[ch]],
+ frame->sb_sample_f[blk][ch],
+ frame->sb_sample_f[blk + 1][ch] -
+ frame->sb_sample_f[blk][ch]);
+ state->position[ch] -= 32;
+ if (state->position[ch] < 0)
+ state->position[ch] = 128 - 32;
+ }
return frame->blocks * 8;
default:
@@ -909,6 +834,26 @@ static int sbc_analyze_audio(struct sbc_encoder_state *state,
}
}
+/* Supplementary bitstream writing macros for 'sbc_pack_frame' */
+
+#define PUT_BITS(v, n)\
+ bits_cache = (v) | (bits_cache << (n));\
+ bits_count += (n);\
+ if (bits_count >= 16) {\
+ bits_count -= 8;\
+ *data_ptr++ = (uint8_t) (bits_cache >> bits_count);\
+ bits_count -= 8;\
+ *data_ptr++ = (uint8_t) (bits_cache >> bits_count);\
+ }\
+
+#define FLUSH_BITS()\
+ while (bits_count >= 8) {\
+ bits_count -= 8;\
+ *data_ptr++ = (uint8_t) (bits_cache >> bits_count);\
+ }\
+ if (bits_count > 0)\
+ *data_ptr++ = (uint8_t) (bits_cache << (8 - bits_count));\
+
/*
* 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
@@ -926,16 +871,21 @@ static int sbc_analyze_audio(struct sbc_encoder_state *state,
static int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len)
{
- int produced;
+ /* Bitstream writer starts from the fourth byte */
+ uint8_t *data_ptr = data + 4;
+ uint32_t bits_cache = 0;
+ uint32_t bits_count = 0;
+
/* 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;
+ uint32_t audio_sample;
- int ch, sb, blk, bit; /* channel, subband, block and bit counters */
+ int ch, sb, blk; /* channel, subband, block and bit counters */
int bits[2][8]; /* bits distribution */
- int levels[2][8]; /* levels are derived from that */
+ uint32_t levels[2][8]; /* levels are derived from that */
+ uint32_t sb_sample_delta[2][8];
u_int32_t scalefactor[2][8]; /* derived from frame->scale_factor */
@@ -973,8 +923,6 @@ static int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len)
/* Can't fill in crc yet */
- produced = 32;
-
crc_header[0] = data[1];
crc_header[1] = data[2];
crc_pos = 16;
@@ -982,7 +930,7 @@ static int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len)
for (ch = 0; ch < frame->channels; ch++) {
for (sb = 0; sb < frame->subbands; sb++) {
frame->scale_factor[ch][sb] = 0;
- scalefactor[ch][sb] = 2;
+ scalefactor[ch][sb] = 2 << SCALE_OUT_BITS;
for (blk = 0; blk < frame->blocks; blk++) {
while (scalefactor[ch][sb] < fabs(frame->sb_sample_f[blk][ch][sb])) {
frame->scale_factor[ch][sb]++;
@@ -999,22 +947,23 @@ static int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len)
u_int32_t scalefactor_j[2];
uint8_t scale_factor_j[2];
+ uint8_t joint = 0;
frame->joint = 0;
for (sb = 0; sb < frame->subbands - 1; sb++) {
scale_factor_j[0] = 0;
- scalefactor_j[0] = 2;
+ scalefactor_j[0] = 2 << SCALE_OUT_BITS;
scale_factor_j[1] = 0;
- scalefactor_j[1] = 2;
+ scalefactor_j[1] = 2 << SCALE_OUT_BITS;
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;
+ ASR(frame->sb_sample_f[blk][0][sb], 1) +
+ ASR(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;
+ ASR(frame->sb_sample_f[blk][0][sb], 1) -
+ ASR(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])) {
@@ -1028,14 +977,15 @@ static int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len)
}
/* decide whether to join this subband */
- if ((scalefactor[0][sb] + scalefactor[1][sb]) >
- (scalefactor_j[0] + scalefactor_j[1]) ) {
+ if ((frame->scale_factor[0][sb] +
+ frame->scale_factor[1][sb]) >
+ (scale_factor_j[0] +
+ scale_factor_j[1])) {
/* use joint stereo for this subband */
+ joint |= 1 << (frame->subbands - 1 - sb);
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];
@@ -1045,24 +995,16 @@ static int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len)
}
}
- 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;
+ PUT_BITS(joint, frame->subbands);
+ crc_header[crc_pos >> 3] = joint;
crc_pos += frame->subbands;
}
for (ch = 0; ch < frame->channels; ch++) {
for (sb = 0; sb < frame->subbands; sb++) {
- data[produced >> 3] <<= 4;
+ PUT_BITS(frame->scale_factor[ch][sb] & 0x0F, 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;
}
}
@@ -1076,37 +1018,47 @@ static int sbc_pack_frame(uint8_t *data, struct sbc_frame *frame, size_t len)
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 (sb = 0; sb < frame->subbands; sb++) {
+ levels[ch][sb] = ((1 << bits[ch][sb]) - 1) <<
+ (32 - (frame->scale_factor[ch][sb] +
+ SCALE_OUT_BITS + 2));
+ sb_sample_delta[ch][sb] = (uint32_t) 1 <<
+ (frame->scale_factor[ch][sb] +
+ SCALE_OUT_BITS + 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++;
- }
- }
+
+ if (bits[ch][sb] == 0)
+ continue;
+
+ audio_sample = ((uint64_t) levels[ch][sb] *
+ (sb_sample_delta[ch][sb] +
+ frame->sb_sample_f[blk][ch][sb])) >> 32;
+
+ PUT_BITS(audio_sample, bits[ch][sb]);
}
}
}
- /* align the last byte */
- if (produced % 8) {
- data[produced >> 3] <<= 8 - (produced % 8);
- }
+ FLUSH_BITS();
+
+ return data_ptr - data;
+}
+
+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] = 12 * frame->subbands;
- return (produced + 7) >> 3;
+ /* Default implementation for analyze function */
+ state->sbc_analyze_4b_4s = sbc_analyze_4b_4s;
+ state->sbc_analyze_4b_8s = sbc_analyze_4b_8s;
}
struct sbc_priv {
@@ -1190,6 +1142,9 @@ int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,
if (written)
*written = 0;
+ if (framelen <= 0)
+ return framelen;
+
samples = sbc_synthesize_audio(&priv->dec_state, &priv->frame);
ptr = output;
@@ -1202,13 +1157,7 @@ int sbc_decode(sbc_t *sbc, void *input, int input_len, void *output,
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 {
@@ -1269,13 +1218,7 @@ int sbc_encode(sbc_t *sbc, void *input, int input_len, void *output,
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;
@@ -1374,9 +1317,9 @@ int sbc_get_frame_duration(sbc_t *sbc)
return (1000000 * blocks * subbands) / frequency;
}
-int sbc_get_codesize(sbc_t *sbc)
+uint16_t sbc_get_codesize(sbc_t *sbc)
{
- uint8_t subbands, channels, blocks;
+ uint16_t subbands, channels, blocks;
struct sbc_priv *priv;
priv = sbc->priv;
diff --git a/src/modules/bluetooth/sbc.h b/src/modules/bluetooth/sbc.h
index ab47e329..8ac59309 100644
--- a/src/modules/bluetooth/sbc.h
+++ b/src/modules/bluetooth/sbc.h
@@ -2,7 +2,7 @@
*
* Bluetooth low-complexity, subband codec (SBC) library
*
- * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch>
* Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com>
*
@@ -87,7 +87,7 @@ 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);
+uint16_t sbc_get_codesize(sbc_t *sbc);
void sbc_finish(sbc_t *sbc);
#ifdef __cplusplus
diff --git a/src/modules/bluetooth/sbc_math.h b/src/modules/bluetooth/sbc_math.h
index b3d87a62..6ca4f526 100644
--- a/src/modules/bluetooth/sbc_math.h
+++ b/src/modules/bluetooth/sbc_math.h
@@ -2,7 +2,7 @@
*
* Bluetooth low-complexity, subband codec (SBC) library
*
- * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch>
* Copyright (C) 2005-2008 Brad Midgley <bmidgley@xmission.com>
*
@@ -29,31 +29,21 @@
#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_OUT_BITS 15
+
#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)
diff --git a/src/modules/bluetooth/sbc_tables.h b/src/modules/bluetooth/sbc_tables.h
index 7ac4e68b..f1dfe6c0 100644
--- a/src/modules/bluetooth/sbc_tables.h
+++ b/src/modules/bluetooth/sbc_tables.h
@@ -2,7 +2,7 @@
*
* Bluetooth low-complexity, subband codec (SBC) library
*
- * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2004-2009 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2004-2005 Henryk Ploetz <henryk@ploetzli.ch>
* Copyright (C) 2005-2006 Brad Midgley <bmidgley@xmission.com>
*
@@ -39,40 +39,12 @@ static const int sbc_offset8[4][8] = {
{ -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),
@@ -115,11 +87,6 @@ static const int32_t sbc_proto_8_80m1[] = {
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) },
@@ -165,3 +132,216 @@ static const int32_t synmatrix8[16][8] = {
{ SN8(0xf9592678), SN8(0x018f8b84), SN8(0x07d8a5f0), SN8(0x0471ced0),
SN8(0xfb8e3130), SN8(0xf8275a10), SN8(0xfe70747c), SN8(0x06a6d988) }
};
+
+/* Uncomment the following line to enable high precision build of SBC encoder */
+
+/* #define SBC_HIGH_PRECISION */
+
+#ifdef SBC_HIGH_PRECISION
+#define FIXED_A int64_t /* data type for fixed point accumulator */
+#define FIXED_T int32_t /* data type for fixed point constants */
+#define SBC_FIXED_EXTRA_BITS 16
+#else
+#define FIXED_A int32_t /* data type for fixed point accumulator */
+#define FIXED_T int16_t /* data type for fixed point constants */
+#define SBC_FIXED_EXTRA_BITS 0
+#endif
+
+/* A2DP specification: Section 12.8 Tables
+ *
+ * Original values are premultiplied by 2 for better precision (that is the
+ * maximum which is possible without overflows)
+ *
+ * Note: in each block of 8 numbers sign was changed for elements 2 and 7
+ * in order to compensate the same change applied to cos_table_fixed_4
+ */
+#define SBC_PROTO_FIXED4_SCALE \
+ ((sizeof(FIXED_T) * CHAR_BIT - 1) - SBC_FIXED_EXTRA_BITS + 1)
+#define F(x) (FIXED_A) ((x * 2) * \
+ ((FIXED_A) 1 << (sizeof(FIXED_T) * CHAR_BIT - 1)) + 0.5)
+static const FIXED_T _sbc_proto_fixed4[40] = {
+ F(0.00000000E+00), F(5.36548976E-04),
+ -F(1.49188357E-03), F(2.73370904E-03),
+ F(3.83720193E-03), F(3.89205149E-03),
+ F(1.86581691E-03), F(3.06012286E-03),
+
+ F(1.09137620E-02), F(2.04385087E-02),
+ -F(2.88757392E-02), F(3.21939290E-02),
+ F(2.58767811E-02), F(6.13245186E-03),
+ -F(2.88217274E-02), F(7.76463494E-02),
+
+ F(1.35593274E-01), F(1.94987841E-01),
+ -F(2.46636662E-01), F(2.81828203E-01),
+ F(2.94315332E-01), F(2.81828203E-01),
+ F(2.46636662E-01), -F(1.94987841E-01),
+
+ -F(1.35593274E-01), -F(7.76463494E-02),
+ F(2.88217274E-02), F(6.13245186E-03),
+ F(2.58767811E-02), F(3.21939290E-02),
+ F(2.88757392E-02), -F(2.04385087E-02),
+
+ -F(1.09137620E-02), -F(3.06012286E-03),
+ -F(1.86581691E-03), F(3.89205149E-03),
+ F(3.83720193E-03), F(2.73370904E-03),
+ F(1.49188357E-03), -F(5.36548976E-04),
+};
+#undef F
+
+/*
+ * To produce this cosine matrix in Octave:
+ *
+ * b = zeros(4, 8);
+ * for i = 0:3
+ * for j = 0:7 b(i+1, j+1) = cos((i + 0.5) * (j - 2) * (pi/4))
+ * endfor
+ * endfor;
+ * printf("%.10f, ", b');
+ *
+ * Note: in each block of 8 numbers sign was changed for elements 2 and 7
+ *
+ * Change of sign for element 2 allows to replace constant 1.0 (not
+ * representable in Q15 format) with -1.0 (fine with Q15).
+ * Changed sign for element 7 allows to have more similar constants
+ * and simplify subband filter function code.
+ */
+#define SBC_COS_TABLE_FIXED4_SCALE \
+ ((sizeof(FIXED_T) * CHAR_BIT - 1) + SBC_FIXED_EXTRA_BITS)
+#define F(x) (FIXED_A) ((x) * \
+ ((FIXED_A) 1 << (sizeof(FIXED_T) * CHAR_BIT - 1)) + 0.5)
+static const FIXED_T cos_table_fixed_4[32] = {
+ F(0.7071067812), F(0.9238795325), -F(1.0000000000), F(0.9238795325),
+ F(0.7071067812), F(0.3826834324), F(0.0000000000), F(0.3826834324),
+
+ -F(0.7071067812), F(0.3826834324), -F(1.0000000000), F(0.3826834324),
+ -F(0.7071067812), -F(0.9238795325), -F(0.0000000000), -F(0.9238795325),
+
+ -F(0.7071067812), -F(0.3826834324), -F(1.0000000000), -F(0.3826834324),
+ -F(0.7071067812), F(0.9238795325), F(0.0000000000), F(0.9238795325),
+
+ F(0.7071067812), -F(0.9238795325), -F(1.0000000000), -F(0.9238795325),
+ F(0.7071067812), -F(0.3826834324), -F(0.0000000000), -F(0.3826834324),
+};
+#undef F
+
+/* A2DP specification: Section 12.8 Tables
+ *
+ * Original values are premultiplied by 4 for better precision (that is the
+ * maximum which is possible without overflows)
+ *
+ * Note: in each block of 16 numbers sign was changed for elements 4, 13, 14, 15
+ * in order to compensate the same change applied to cos_table_fixed_8
+ */
+#define SBC_PROTO_FIXED8_SCALE \
+ ((sizeof(FIXED_T) * CHAR_BIT - 1) - SBC_FIXED_EXTRA_BITS + 2)
+#define F(x) (FIXED_A) ((x * 4) * \
+ ((FIXED_A) 1 << (sizeof(FIXED_T) * CHAR_BIT - 1)) + 0.5)
+static const FIXED_T _sbc_proto_fixed8[80] = {
+ F(0.00000000E+00), F(1.56575398E-04),
+ F(3.43256425E-04), F(5.54620202E-04),
+ -F(8.23919506E-04), F(1.13992507E-03),
+ F(1.47640169E-03), F(1.78371725E-03),
+ F(2.01182542E-03), F(2.10371989E-03),
+ F(1.99454554E-03), F(1.61656283E-03),
+ F(9.02154502E-04), F(1.78805361E-04),
+ F(1.64973098E-03), F(3.49717454E-03),
+
+ F(5.65949473E-03), F(8.02941163E-03),
+ F(1.04584443E-02), F(1.27472335E-02),
+ -F(1.46525263E-02), F(1.59045603E-02),
+ F(1.62208471E-02), F(1.53184106E-02),
+ F(1.29371806E-02), F(8.85757540E-03),
+ F(2.92408442E-03), -F(4.91578024E-03),
+ -F(1.46404076E-02), F(2.61098752E-02),
+ F(3.90751381E-02), F(5.31873032E-02),
+
+ F(6.79989431E-02), F(8.29847578E-02),
+ F(9.75753918E-02), F(1.11196689E-01),
+ -F(1.23264548E-01), F(1.33264415E-01),
+ F(1.40753505E-01), F(1.45389847E-01),
+ F(1.46955068E-01), F(1.45389847E-01),
+ F(1.40753505E-01), F(1.33264415E-01),
+ F(1.23264548E-01), -F(1.11196689E-01),
+ -F(9.75753918E-02), -F(8.29847578E-02),
+
+ -F(6.79989431E-02), -F(5.31873032E-02),
+ -F(3.90751381E-02), -F(2.61098752E-02),
+ F(1.46404076E-02), -F(4.91578024E-03),
+ F(2.92408442E-03), F(8.85757540E-03),
+ F(1.29371806E-02), F(1.53184106E-02),
+ F(1.62208471E-02), F(1.59045603E-02),
+ F(1.46525263E-02), -F(1.27472335E-02),
+ -F(1.04584443E-02), -F(8.02941163E-03),
+
+ -F(5.65949473E-03), -F(3.49717454E-03),
+ -F(1.64973098E-03), -F(1.78805361E-04),
+ -F(9.02154502E-04), F(1.61656283E-03),
+ F(1.99454554E-03), F(2.10371989E-03),
+ F(2.01182542E-03), F(1.78371725E-03),
+ F(1.47640169E-03), F(1.13992507E-03),
+ F(8.23919506E-04), -F(5.54620202E-04),
+ -F(3.43256425E-04), -F(1.56575398E-04),
+};
+#undef F
+
+/*
+ * To produce this cosine matrix in Octave:
+ *
+ * b = zeros(8, 16);
+ * for i = 0:7
+ * for j = 0:15 b(i+1, j+1) = cos((i + 0.5) * (j - 4) * (pi/8))
+ * endfor endfor;
+ * printf("%.10f, ", b');
+ *
+ * Note: in each block of 16 numbers sign was changed for elements 4, 13, 14, 15
+ *
+ * Change of sign for element 4 allows to replace constant 1.0 (not
+ * representable in Q15 format) with -1.0 (fine with Q15).
+ * Changed signs for elements 13, 14, 15 allow to have more similar constants
+ * and simplify subband filter function code.
+ */
+#define SBC_COS_TABLE_FIXED8_SCALE \
+ ((sizeof(FIXED_T) * CHAR_BIT - 1) + SBC_FIXED_EXTRA_BITS)
+#define F(x) (FIXED_A) ((x) * \
+ ((FIXED_A) 1 << (sizeof(FIXED_T) * CHAR_BIT - 1)) + 0.5)
+static const FIXED_T cos_table_fixed_8[128] = {
+ F(0.7071067812), F(0.8314696123), F(0.9238795325), F(0.9807852804),
+ -F(1.0000000000), F(0.9807852804), F(0.9238795325), F(0.8314696123),
+ F(0.7071067812), F(0.5555702330), F(0.3826834324), F(0.1950903220),
+ F(0.0000000000), F(0.1950903220), F(0.3826834324), F(0.5555702330),
+
+ -F(0.7071067812), -F(0.1950903220), F(0.3826834324), F(0.8314696123),
+ -F(1.0000000000), F(0.8314696123), F(0.3826834324), -F(0.1950903220),
+ -F(0.7071067812), -F(0.9807852804), -F(0.9238795325), -F(0.5555702330),
+ -F(0.0000000000), -F(0.5555702330), -F(0.9238795325), -F(0.9807852804),
+
+ -F(0.7071067812), -F(0.9807852804), -F(0.3826834324), F(0.5555702330),
+ -F(1.0000000000), F(0.5555702330), -F(0.3826834324), -F(0.9807852804),
+ -F(0.7071067812), F(0.1950903220), F(0.9238795325), F(0.8314696123),
+ F(0.0000000000), F(0.8314696123), F(0.9238795325), F(0.1950903220),
+
+ F(0.7071067812), -F(0.5555702330), -F(0.9238795325), F(0.1950903220),
+ -F(1.0000000000), F(0.1950903220), -F(0.9238795325), -F(0.5555702330),
+ F(0.7071067812), F(0.8314696123), -F(0.3826834324), -F(0.9807852804),
+ -F(0.0000000000), -F(0.9807852804), -F(0.3826834324), F(0.8314696123),
+
+ F(0.7071067812), F(0.5555702330), -F(0.9238795325), -F(0.1950903220),
+ -F(1.0000000000), -F(0.1950903220), -F(0.9238795325), F(0.5555702330),
+ F(0.7071067812), -F(0.8314696123), -F(0.3826834324), F(0.9807852804),
+ F(0.0000000000), F(0.9807852804), -F(0.3826834324), -F(0.8314696123),
+
+ -F(0.7071067812), F(0.9807852804), -F(0.3826834324), -F(0.5555702330),
+ -F(1.0000000000), -F(0.5555702330), -F(0.3826834324), F(0.9807852804),
+ -F(0.7071067812), -F(0.1950903220), F(0.9238795325), -F(0.8314696123),
+ -F(0.0000000000), -F(0.8314696123), F(0.9238795325), -F(0.1950903220),
+
+ -F(0.7071067812), F(0.1950903220), F(0.3826834324), -F(0.8314696123),
+ -F(1.0000000000), -F(0.8314696123), F(0.3826834324), F(0.1950903220),
+ -F(0.7071067812), F(0.9807852804), -F(0.9238795325), F(0.5555702330),
+ -F(0.0000000000), F(0.5555702330), -F(0.9238795325), F(0.9807852804),
+
+ F(0.7071067812), -F(0.8314696123), F(0.9238795325), -F(0.9807852804),
+ -F(1.0000000000), -F(0.9807852804), F(0.9238795325), -F(0.8314696123),
+ F(0.7071067812), -F(0.5555702330), F(0.3826834324), -F(0.1950903220),
+ -F(0.0000000000), -F(0.1950903220), F(0.3826834324), -F(0.5555702330),
+};
+#undef F
diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c
index 0e15da3c..95a8c972 100644
--- a/src/modules/module-alsa-sink.c
+++ b/src/modules/module-alsa-sink.c
@@ -241,7 +241,7 @@ 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 = 0, process_usec = 0;
size_t left_to_play;
@@ -261,7 +261,7 @@ 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", (int) n)) == 0)
continue;
@@ -279,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 (;;) {
@@ -299,7 +308,7 @@ static int mmap_write(struct userdata *u, pa_usec_t *sleep_usec) {
/* 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;
@@ -357,7 +366,7 @@ 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 = 0, process_usec = 0;
size_t left_to_play;
@@ -374,7 +383,7 @@ 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", (int) n)) == 0)
continue;
@@ -392,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;
@@ -796,7 +814,7 @@ static int sink_get_volume_cb(pa_sink *s) {
VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol));
#endif
- r.values[i] = pa_sw_volume_from_dB((double) alsa_vol / 100.0);
+ 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)
@@ -818,7 +836,7 @@ static int sink_get_volume_cb(pa_sink *s) {
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 / 100.0));
+ pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0));
} else {
@@ -875,6 +893,7 @@ static int sink_set_volume_cb(pa_sink *s) {
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)
@@ -883,7 +902,7 @@ static int sink_set_volume_cb(pa_sink *s) {
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 / 100.0);
+ 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);
@@ -906,6 +925,7 @@ static int sink_set_volume_cb(pa_sink *s) {
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_all(u->mixer_elem, alsa_vol, 1)) < 0)
@@ -914,7 +934,7 @@ static int sink_set_volume_cb(pa_sink *s) {
if ((err = snd_mixer_selem_get_playback_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0)
goto fail;
- pa_cvolume_set(&r, s->volume.channels, pa_sw_volume_from_dB((double) 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));
} else {
alsa_vol = to_alsa_volume(u, vol);
@@ -1082,6 +1102,7 @@ finish:
static void thread_func(void *userdata) {
struct userdata *u = userdata;
+ unsigned short revents = 0;
pa_assert(u);
@@ -1108,9 +1129,9 @@ static void thread_func(void *userdata) {
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;
@@ -1178,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;
@@ -1189,7 +1209,7 @@ static void thread_func(void *userdata) {
goto fail;
}
- if (revents & (POLLERR|POLLNVAL|POLLHUP|POLLPRI)) {
+ if (revents & (POLLIN|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) {
if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0)
goto fail;
@@ -1199,7 +1219,8 @@ static void thread_func(void *userdata) {
if (revents && u->use_tsched)
pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : "");
- }
+ } else
+ revents = 0;
}
fail:
@@ -1388,7 +1409,7 @@ int pa__init(pa_module*m) {
}
if (found)
- if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM")))
+ if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM", TRUE)))
found = FALSE;
if (!found) {
diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c
index 2827ecfe..b6c6ed1a 100644
--- a/src/modules/module-alsa-source.c
+++ b/src/modules/module-alsa-source.c
@@ -238,7 +238,7 @@ 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 = 0, process_usec = 0;
size_t left_to_record;
@@ -255,7 +255,7 @@ 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", (int) n)) == 0)
continue;
@@ -266,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;
@@ -282,7 +291,7 @@ static int mmap_read(struct userdata *u, pa_usec_t *sleep_usec) {
/* 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;
@@ -336,7 +345,7 @@ 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 = 0, process_usec = 0;
size_t left_to_record;
@@ -353,7 +362,7 @@ 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", (int) n)) == 0)
continue;
@@ -364,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;
@@ -742,7 +760,7 @@ static int source_get_volume_cb(pa_source *s) {
VALGRIND_MAKE_MEM_DEFINED(&alsa_vol, sizeof(alsa_vol));
#endif
- r.values[i] = pa_sw_volume_from_dB((double) alsa_vol / 100.0);
+ 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)
@@ -764,7 +782,7 @@ static int source_get_volume_cb(pa_source *s) {
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 / 100.0));
+ pa_cvolume_set(&r, s->sample_spec.channels, pa_sw_volume_from_dB((double) (alsa_vol - u->hw_dB_max) / 100.0));
} else {
@@ -821,6 +839,7 @@ static int source_set_volume_cb(pa_source *s) {
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)
@@ -829,7 +848,7 @@ static int source_set_volume_cb(pa_source *s) {
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 / 100.0);
+ 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);
@@ -852,6 +871,7 @@ static int source_set_volume_cb(pa_source *s) {
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)
@@ -860,7 +880,7 @@ static int source_set_volume_cb(pa_source *s) {
if ((err = snd_mixer_selem_get_capture_dB(u->mixer_elem, SND_MIXER_SCHN_MONO, &alsa_vol)) < 0)
goto fail;
- pa_cvolume_set(&r, s->volume.channels, pa_sw_volume_from_dB((double) 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));
} else {
alsa_vol = to_alsa_volume(u, vol);
@@ -948,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);
@@ -970,9 +991,9 @@ static void thread_func(void *userdata) {
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;
@@ -1014,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;
@@ -1025,7 +1045,7 @@ static void thread_func(void *userdata) {
goto fail;
}
- if (revents & (POLLERR|POLLNVAL|POLLHUP|POLLPRI)) {
+ if (revents & (POLLOUT|POLLERR|POLLNVAL|POLLHUP|POLLPRI)) {
if (pa_alsa_recover_from_poll(u->pcm_handle, revents) < 0)
goto fail;
@@ -1034,7 +1054,8 @@ static void thread_func(void *userdata) {
if (revents && u->use_tsched)
pa_log_debug("Wakeup from ALSA!%s%s", (revents & POLLIN) ? " INPUT" : "", (revents & POLLOUT) ? " OUTPUT" : "");
- }
+ } else
+ revents = 0;
}
fail:
@@ -1215,7 +1236,7 @@ int pa__init(pa_module*m) {
}
if (found)
- if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic")))
+ if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic", FALSE)))
found = FALSE;
if (!found) {
diff --git a/src/modules/module-always-sink.c b/src/modules/module-always-sink.c
index 9d60c29e..cd3f3112 100644
--- a/src/modules/module-always-sink.c
+++ b/src/modules/module-always-sink.c
@@ -50,7 +50,7 @@ static const char* const valid_modargs[] = {
struct userdata {
pa_hook_slot *put_slot, *unlink_slot;
- pa_module* null_module;
+ uint32_t null_module;
pa_bool_t ignore;
char *sink_name;
};
@@ -59,10 +59,11 @@ static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata*
pa_sink *target;
uint32_t idx;
char *t;
+ pa_module *m;
pa_assert(c);
pa_assert(u);
- pa_assert(!u->null_module);
+ pa_assert(u->null_module == PA_INVALID_INDEX);
/* Loop through all sinks and check to see if we have *any*
* sinks. Ignore the sink passed in (if it's not null) */
@@ -78,12 +79,13 @@ static void load_null_sink_if_needed(pa_core *c, pa_sink *sink, struct userdata*
u->ignore = TRUE;
t = pa_sprintf_malloc("sink_name=%s", u->sink_name);
- u->null_module = pa_module_load(c, "module-null-sink", t);
+ m = pa_module_load(c, "module-null-sink", t);
+ u->null_module = m ? m->index : PA_INVALID_INDEX;
pa_xfree(t);
u->ignore = FALSE;
- if (!u->null_module)
+ if (!m)
pa_log_warn("Unable to load module-null-sink");
}
@@ -99,17 +101,17 @@ static pa_hook_result_t put_hook_callback(pa_core *c, pa_sink *sink, void* userd
return PA_HOOK_OK;
/* Auto-loaded null-sink not active, so ignoring newly detected sink. */
- if (!u->null_module)
+ if (u->null_module == PA_INVALID_INDEX)
return PA_HOOK_OK;
/* This is us detecting ourselves on load in a different way... just ignore this too. */
- if (sink->module == u->null_module)
+ if (sink->module && sink->module->index == u->null_module)
return PA_HOOK_OK;
pa_log_info("A new sink has been discovered. Unloading null-sink.");
- pa_module_unload_request(u->null_module, TRUE);
- u->null_module = NULL;
+ pa_module_unload_request_by_index(c, u->null_module, TRUE);
+ u->null_module = PA_INVALID_INDEX;
return PA_HOOK_OK;
}
@@ -122,9 +124,9 @@ static pa_hook_result_t unlink_hook_callback(pa_core *c, pa_sink *sink, void* us
pa_assert(u);
/* First check to see if it's our own null-sink that's been removed... */
- if (u->null_module && sink->module == u->null_module) {
+ if (u->null_module != PA_INVALID_INDEX && sink->module && sink->module->index == u->null_module) {
pa_log_debug("Autoloaded null-sink removed");
- u->null_module = NULL;
+ u->null_module = PA_INVALID_INDEX;
return PA_HOOK_OK;
}
@@ -148,7 +150,7 @@ int pa__init(pa_module*m) {
u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME));
u->put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_PUT], PA_HOOK_LATE, (pa_hook_cb_t) put_hook_callback, u);
u->unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], PA_HOOK_EARLY, (pa_hook_cb_t) unlink_hook_callback, u);
- u->null_module = NULL;
+ u->null_module = PA_INVALID_INDEX;
u->ignore = FALSE;
pa_modargs_free(ma);
@@ -170,8 +172,8 @@ void pa__done(pa_module*m) {
pa_hook_slot_free(u->put_slot);
if (u->unlink_slot)
pa_hook_slot_free(u->unlink_slot);
- if (u->null_module)
- pa_module_unload_request(u->null_module, TRUE);
+ if (u->null_module != PA_INVALID_INDEX)
+ pa_module_unload_request_by_index(m->core, u->null_module, TRUE);
pa_xfree(u->sink_name);
pa_xfree(u);
diff --git a/src/modules/module-device-restore.c b/src/modules/module-device-restore.c
index 86a78810..c0cb0dc5 100644
--- a/src/modules/module-device-restore.c
+++ b/src/modules/module-device-restore.c
@@ -332,7 +332,7 @@ int pa__init(pa_module*m) {
if (!fname)
goto fail;
- if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT, 0600, NULL))) {
+ if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) {
pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno));
pa_xfree(fname);
goto fail;
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 c76a366c..8c1ab329 100644
--- a/src/modules/module-hal-detect.c
+++ b/src/modules/module-hal-detect.c
@@ -511,7 +511,7 @@ static void device_removed_cb(LibHalContext* context, const char *udi) {
pa_log_debug("Device removed: %s", udi);
if ((d = pa_hashmap_remove(u->devices, udi))) {
- pa_module_unload_by_index(u->core, d->index, TRUE);
+ pa_module_unload_request_by_index(u->core, d->index, TRUE);
hal_device_free(d);
}
}
diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c
index 9127af01..a27ed712 100644
--- a/src/modules/module-ladspa-sink.c
+++ b/src/modules/module-ladspa-sink.c
@@ -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;
@@ -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-pipe-sink.c b/src/modules/module-pipe-sink.c
index ae230b2c..2b55c823 100644
--- a/src/modules/module-pipe-sink.c
+++ b/src/modules/module-pipe-sink.c
@@ -101,8 +101,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse
size_t n = 0;
int l;
-#ifdef TIOCINQ
- if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0)
+#ifdef FIONREAD
+ if (ioctl(u->fd, FIONREAD, &l) >= 0 && l > 0)
n = (size_t) l;
#endif
diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c
index 25151d95..e6437a05 100644
--- a/src/modules/module-pipe-source.c
+++ b/src/modules/module-pipe-source.c
@@ -31,6 +31,7 @@
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
+#include <sys/ioctl.h>
#include <sys/poll.h>
#include <pulse/xmalloc.h>
@@ -89,6 +90,34 @@ static const char* const valid_modargs[] = {
NULL
};
+static int source_process_msg(
+ pa_msgobject *o,
+ int code,
+ void *data,
+ int64_t offset,
+ pa_memchunk *chunk) {
+
+ struct userdata *u = PA_SOURCE(o)->userdata;
+
+ switch (code) {
+
+ case PA_SOURCE_MESSAGE_GET_LATENCY: {
+ size_t n = 0;
+ int l;
+
+#ifdef FIONREAD
+ if (ioctl(u->fd, FIONREAD, &l) >= 0 && l > 0)
+ n = (size_t) l;
+#endif
+
+ *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->source->sample_spec);
+ return 0;
+ }
+ }
+
+ return pa_source_process_msg(o, code, data, offset, chunk);
+}
+
static void thread_func(void *userdata) {
struct userdata *u = userdata;
int read_type = 0;
@@ -243,6 +272,7 @@ int pa__init(pa_module*m) {
goto fail;
}
+ u->source->parent.process_msg = source_process_msg;
u->source->userdata = u;
pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
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-stream-restore.c b/src/modules/module-stream-restore.c
index fe79291e..fdf69a20 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) {
@@ -741,7 +741,7 @@ int pa__init(pa_module*m) {
if (!fname)
goto fail;
- if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT, 0600, NULL))) {
+ if (!(u->gdbm_file = gdbm_open(fname, 0, GDBM_WRCREAT|GDBM_NOLOCK, 0600, NULL))) {
pa_log("Failed to open volume database '%s': %s", fname, gdbm_strerror(gdbm_errno));
pa_xfree(fname);
goto fail;
diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c
index 6cc28ec5..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);
diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
index 4bbb11a5..a46d6e59 100644
--- a/src/modules/module-tunnel.c
+++ b/src/modules/module-tunnel.c
@@ -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((uint64_t) 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;
diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c
index c8087abb..9a867cb5 100644
--- a/src/modules/module-zeroconf-discover.c
+++ b/src/modules/module-zeroconf-discover.c
@@ -286,7 +286,7 @@ static void browser_cb(
struct tunnel *t2;
if ((t2 = pa_hashmap_get(u->tunnels, t))) {
- pa_module_unload_by_index(u->core, t2->module_index, TRUE);
+ pa_module_unload_request_by_index(u->core, t2->module_index, TRUE);
pa_hashmap_remove(u->tunnels, t2);
tunnel_free(t2);
}
@@ -427,7 +427,7 @@ void pa__done(pa_module*m) {
struct tunnel *t;
while ((t = pa_hashmap_steal_first(u->tunnels))) {
- pa_module_unload_by_index(u->core, t->module_index, TRUE);
+ pa_module_unload_request_by_index(u->core, t->module_index, TRUE);
tunnel_free(t);
}
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/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-send.c b/src/modules/rtp/module-rtp-send.c
index 280067a5..9c0f07f1 100644
--- a/src/modules/rtp/module-rtp-send.c
+++ b/src/modules/rtp/module-rtp-send.c
@@ -296,6 +296,11 @@ int pa__init(pa_module*m) {
pa_log("IP_MULTICAST_TTL failed: %s", pa_cstrerror(errno));
goto fail;
}
+
+ if (setsockopt(sap_fd, IPPROTO_IP, IP_MULTICAST_TTL, &_ttl, sizeof(_ttl)) < 0) {
+ pa_log("IP_MULTICAST_TTL (sap) failed: %s", pa_cstrerror(errno));
+ goto fail;
+ }
}
/* If the socket queue is full, let's drop packets */
@@ -316,7 +321,7 @@ int pa__init(pa_module*m) {
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) {
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