diff options
Diffstat (limited to 'src/modules')
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 |