From 8c1c565c86ddeb0c2b3b89d53aed0052eca1ebbf Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 1 May 2008 23:35:24 +0000 Subject: [PATCH 01/56] Add a small lib to interpret and produce headers as used in http style requests. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2332 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/headerlist.c | 190 +++++++++++++++++++++++++++++++++++ src/modules/rtp/headerlist.h | 48 +++++++++ 2 files changed, 238 insertions(+) create mode 100644 src/modules/rtp/headerlist.c create mode 100644 src/modules/rtp/headerlist.h diff --git a/src/modules/rtp/headerlist.c b/src/modules/rtp/headerlist.c new file mode 100644 index 000000000..9ea17ae39 --- /dev/null +++ b/src/modules/rtp/headerlist.c @@ -0,0 +1,190 @@ +/* $Id$ */ + +/*** + 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 +#endif + +#include + +#include + +#include +#include +#include + +#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_strbuf *buf; + + pa_assert(p); + pa_assert(key); + + buf = pa_strbuf_new(); + if (!(hdr = pa_hashmap_get(MAKE_HASHMAP(p), key))) { + hdr = pa_xnew(struct header, 1); + hdr->key = pa_xstrdup(key); + add = TRUE; + } else { + pa_strbuf_puts(buf, hdr->value); + pa_xfree(hdr->value); + } + pa_strbuf_puts(buf, value); + hdr->value = pa_strbuf_tostring_free(buf); + 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 000000000..276d0e350 --- /dev/null +++ b/src/modules/rtp/headerlist.h @@ -0,0 +1,48 @@ +#ifndef foopulseheaderlisthfoo +#define foopulseheaderlisthfoo + +/* $Id$ */ + +/*** + 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 + +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 From 48477067ee20678a9c741da4e75dbbcdd6b01efe Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 1 May 2008 23:40:19 +0000 Subject: [PATCH 02/56] Add a RTSP client impelmentation. I still need to adapt the header reading to move the concatenation code to the headerlist lib git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2333 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp.c | 472 +++++++++++++++++++++++++++++++++++++++++ src/modules/rtp/rtsp.h | 66 ++++++ 2 files changed, 538 insertions(+) create mode 100644 src/modules/rtp/rtsp.c create mode 100644 src/modules/rtp/rtsp.h diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp.c new file mode 100644 index 000000000..3fd1ba0f2 --- /dev/null +++ b/src/modules/rtp/rtsp.c @@ -0,0 +1,472 @@ +/* $Id$ */ + +/*** + 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_FILIO_H +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "rtsp.h" + +/* + * read one line from the file descriptor + * timeout: msec unit, -1 for infinite + * if CR comes then following LF is expected + * returned string in line is always null terminated, maxlen-1 is maximum string length + */ +static int pa_read_line(int fd, char *line, int maxlen, int timeout) +{ + int i, rval; + int count; + char ch; + struct pollfd pfds; + count = 0; + *line = 0; + pfds.events = POLLIN; + pfds.fd = fd; + + for (i=0; i= maxlen-1) + break; + } + + *line = 0; + return count; +} + + +static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, + const char* content_type, const char* content, + int expect_response, + pa_headerlist* headers, pa_headerlist** response_headers) { + pa_strbuf* buf; + char* hdrs; + ssize_t l; + char response[1024]; + int timeout; + char* token; + const char* token_state; + char delimiters[2]; + char* header; + char* delimpos; + + + pa_assert(c); + pa_assert(c->url); + + if (!cmd) + return 0; + + 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); + l = pa_write(c->fd, hdrs, strlen(hdrs), NULL); + pa_xfree(hdrs); + + // Do we expect a response? + if (!expect_response) + return 1; + + timeout = 5000; + if (pa_read_line(c->fd, response, sizeof(response), timeout) <= 0) { + //ERRMSG("%s: request failed\n",__func__); + return 0; + } + + delimiters[0] = ' '; + delimiters[1] = '\0'; + token_state = NULL; + pa_xfree(pa_split(response, delimiters, &token_state)); + token = pa_split(response, delimiters, &token_state); + if (!token || strcmp(token, "200")) { + pa_xfree(token); + //ERRMSG("%s: request failed, error %s\n",__func__,token); + return 0; + } + pa_xfree(token); + + // We want to return the headers? + if (!response_headers) + { + // We have no storage, so just clear out the response. + while (pa_read_line(c->fd, response, sizeof(response), timeout) > 0) { + // Reduce timeout for future requests + timeout = 1000; + } + return 1; + } + + header = NULL; + buf = pa_strbuf_new(); + while (pa_read_line(c->fd, response, sizeof(response), timeout) > 0) { + // Reduce timeout for future requests + timeout = 1000; + + // If the first character is a space, it's a continuation header + if (header && ' ' == response[0]) { + // Add this line to the buffer (sans the space. + pa_strbuf_puts(buf, &(response[1])); + continue; + } + + if (header) { + // This is not a continuation header so let's dump the full header/value into our proplist + pa_headerlist_puts(*response_headers, header, pa_strbuf_tostring_free(buf)); + pa_xfree(header); + //header = NULL; + buf = pa_strbuf_new(); + } + + delimpos = strstr(response, ":"); + if (!delimpos) { + //ERRMSG("%s: Request failed, bad header\n",__func__); + return 0; + } + + 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(buf, delimpos); + } else { + // Cut our line off so we can copy the header name out + *delimpos = '\0'; + } + + // Save the header name + header = pa_xstrdup(response); + } + // We will have a header left from our looping itteration, so add it in :) + if (header) { + // This is not a continuation header so let's dump it into our proplist + pa_headerlist_puts(*response_headers, header, pa_strbuf_tostring(buf)); + } + pa_strbuf_free(buf); + + return 1; +} + + +pa_rtsp_context* pa_rtsp_context_new(const char* useragent) { + pa_rtsp_context *c; + + c = pa_xnew0(pa_rtsp_context, 1); + c->fd = -1; + c->headers = pa_headerlist_new(); + + if (useragent) + c->useragent = useragent; + else + c->useragent = "PulseAudio RTSP Client"; + + return c; +} + + +void pa_rtsp_context_destroy(pa_rtsp_context* c) { + if (c) { + pa_xfree(c->url); + pa_xfree(c->session); + pa_xfree(c->transport); + pa_headerlist_free(c->headers); + } + pa_xfree(c); +} + + +int pa_rtsp_connect(pa_rtsp_context *c, const char* hostname, uint16_t port, const char* sid) { + struct sockaddr_in sa; + struct sockaddr_in name; + socklen_t namelen = sizeof(name); + struct hostent *host = NULL; + int r; + + pa_assert(c); + pa_assert(hostname); + pa_assert(port > 0); + pa_assert(sid); + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + + host = gethostbyname(hostname); + if (!host) { + unsigned int addr = inet_addr(hostname); + if (addr != INADDR_NONE) + host = gethostbyaddr((char*)&addr, 4, AF_INET); + if (!host) + return 0; + } + memcpy(&sa.sin_addr, host->h_addr, sizeof(struct in_addr)); + + if ((c->fd = socket(sa.sin_family, SOCK_STREAM, 0)) < 0) { + pa_log("socket(): %s", pa_cstrerror(errno)); + return 0; + } + + // Q: is FD_CLOEXEC reqd? + pa_make_fd_cloexec(c->fd); + pa_make_tcp_socket_low_delay(c->fd); + + if ((r = connect(c->fd, &sa, sizeof(struct sockaddr_in))) < 0) { +#ifdef OS_IS_WIN32 + if (WSAGetLastError() != EWOULDBLOCK) { + pa_log_debug("connect(): %d", WSAGetLastError()); +#else + if (errno != EINPROGRESS) { + pa_log_debug("connect(): %s (%d)", pa_cstrerror(errno), errno); +#endif + pa_close(c->fd); + c->fd = -1; + return 0; + } + } + + if (0 != getsockname(c->fd, (struct sockaddr*)&name, &namelen)) { + pa_close(c->fd); + c->fd = -1; + return 0; + } + memcpy(&c->local_addr, &name.sin_addr, sizeof(struct in_addr)); + c->url = pa_sprintf_malloc("rtsp://%s/%s", inet_ntoa(name.sin_addr), sid); + + return 1; +} + + +void pa_rtsp_disconnect(pa_rtsp_context *c) { + pa_assert(c); + + if (c->fd < 0) + return; + pa_close(c->fd); + c->fd = -1; +} + + +const char* pa_rtsp_localip(pa_rtsp_context* c) { + pa_assert(c); + + if (c->fd < 0) + return NULL; + return inet_ntoa(c->local_addr); +} + + +int pa_rtsp_announce(pa_rtsp_context *c, const char* sdp) { + pa_assert(c); + if (!sdp) + return 0; + + return pa_rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL, NULL); +} + + +int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers) { + pa_headerlist* headers; + pa_headerlist* rheaders; + char delimiters[2]; + char* token; + const char* token_state; + const char* pc; + + pa_assert(c); + + headers = pa_headerlist_new(); + rheaders = pa_headerlist_new(); + pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); + + if (!pa_rtsp_exec(c, "SETUP", NULL, NULL, 1, headers, &rheaders)) { + pa_headerlist_free(headers); + pa_headerlist_free(rheaders); + return 0; + } + pa_headerlist_free(headers); + + c->session = pa_xstrdup(pa_headerlist_gets(rheaders, "Session")); + c->transport = pa_xstrdup(pa_headerlist_gets(rheaders, "Transport")); + + if (!c->session || !c->transport) { + pa_headerlist_free(rheaders); + return 0; + } + + // Now parse out the server port component of the response. + c->port = 0; + delimiters[0] = ';'; + delimiters[1] = '\0'; + token_state = NULL; + while ((token = pa_split(c->transport, delimiters, &token_state))) { + if ((pc = strstr(token, "="))) { + if (0 == strncmp(token, "server_port", 11)) { + pa_atou(pc+1, &c->port); + pa_xfree(token); + break; + } + } + pa_xfree(token); + } + if (0 == c->port) { + // Error no server_port in response + pa_headerlist_free(rheaders); + return 0; + } + + *response_headers = rheaders; + return 1; +} + + +int pa_rtsp_record(pa_rtsp_context* c) { + pa_headerlist* headers; + int rv; + + pa_assert(c); + if (!c->session) { + // No seesion in progres + return 0; + } + + headers = pa_headerlist_new(); + pa_headerlist_puts(headers, "Range", "npt=0-"); + pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); + + rv = pa_rtsp_exec(c, "RECORD", NULL, NULL, 1, headers, NULL); + pa_headerlist_free(headers); + return rv; +} + + +int pa_rtsp_teardown(pa_rtsp_context *c) { + pa_assert(c); + + return pa_rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL, NULL); +} + + +int pa_rtsp_setparameter(pa_rtsp_context *c, const char* param) { + pa_assert(c); + if (!param) + return 0; + + return pa_rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL, NULL); +} + + +int pa_rtsp_flush(pa_rtsp_context *c) { + pa_headerlist* headers; + int rv; + + pa_assert(c); + + headers = pa_headerlist_new(); + pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); + + rv = pa_rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers, NULL); + pa_headerlist_free(headers); + return rv; +} diff --git a/src/modules/rtp/rtsp.h b/src/modules/rtp/rtsp.h new file mode 100644 index 000000000..504f1445d --- /dev/null +++ b/src/modules/rtp/rtsp.h @@ -0,0 +1,66 @@ +#ifndef foortsphfoo +#define foortsphfoo + +/* $Id: rtp.h 1465 2007-05-29 17:24:48Z lennart $ */ + +/*** + 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 +#include +#include +#include + +#include +#include +#include + +#include "headerlist.h" + +typedef struct pa_rtsp_context { + int fd; + const char* useragent; + pa_headerlist* headers; + char* url; + uint32_t port; + uint32_t cseq; + char* session; + char* transport; + struct in_addr local_addr; +} pa_rtsp_context; + +pa_rtsp_context* pa_rtsp_context_new(const char* useragent); +void pa_rtsp_context_destroy(pa_rtsp_context* c); + +int pa_rtsp_connect(pa_rtsp_context* c, const char* hostname, uint16_t port, const char* sid); +void pa_rtsp_disconnect(pa_rtsp_context* c); + +const char* pa_rtsp_localip(pa_rtsp_context* c); +int pa_rtsp_announce(pa_rtsp_context* c, const char* sdp); + +int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers); +int pa_rtsp_record(pa_rtsp_context* c); +int pa_rtsp_teardown(pa_rtsp_context* c); + +int pa_rtsp_setparameter(pa_rtsp_context* c, const char* param); +int pa_rtsp_flush(pa_rtsp_context* c); + +#endif From fef102e35ae4adff8ab000c628cb659c337af51d Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 1 May 2008 23:43:34 +0000 Subject: [PATCH 03/56] Add a simple base64 library that will be used by the sink git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2334 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/base64.c | 128 +++++++++++++++++++++++++++++++++++++++ src/modules/rtp/base64.h | 35 +++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/modules/rtp/base64.c create mode 100644 src/modules/rtp/base64.h diff --git a/src/modules/rtp/base64.c b/src/modules/rtp/base64.c new file mode 100644 index 000000000..ec9f2212e --- /dev/null +++ b/src/modules/rtp/base64.c @@ -0,0 +1,128 @@ +/* $Id$ */ + +/*** + 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 +#endif + +#include +#include + +#include "base64.h" + +static char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int pos(char c) +{ + char *p; + for (p = base64_chars; *p; p++) + if (*p == c) + return p - base64_chars; + return -1; +} + +int pa_base64_encode(const void *data, int size, char **str) +{ + char *s, *p; + int i; + int c; + const unsigned char *q; + + p = s = (char *) malloc(size * 4 / 3 + 4); + if (p == NULL) + return -1; + 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/rtp/base64.h b/src/modules/rtp/base64.h new file mode 100644 index 000000000..199c63522 --- /dev/null +++ b/src/modules/rtp/base64.h @@ -0,0 +1,35 @@ +#ifndef foobase64hfoo +#define foobase64hfoo + +/* $Id$ */ + +/*** + 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 +*/ + +int pa_base64_encode(const void *data, int size, char **str); +int pa_base64_decode(const char *str, void *data); + +#endif From 6570620cc3717eb82acd19788538fda3786c7b99 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 1 May 2008 23:51:45 +0000 Subject: [PATCH 04/56] Start the raop sink. It's based on pipe sink and isn't anywhere near finished. It does however compile. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2335 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/Makefile.am | 14 +- src/modules/module-raop-sink.c | 417 +++++++++++++++++++++++++++++++++ 2 files changed, 430 insertions(+), 1 deletion(-) create mode 100644 src/modules/module-raop-sink.c diff --git a/src/Makefile.am b/src/Makefile.am index f2771980b..831e45656 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1004,7 +1004,13 @@ libsocket_util_la_SOURCES = \ libsocket_util_la_LDFLAGS = -avoid-version libsocket_util_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) libpulsecore.la -librtp_la_SOURCES = modules/rtp/rtp.c modules/rtp/rtp.h modules/rtp/sdp.c modules/rtp/sdp.h modules/rtp/sap.c modules/rtp/sap.h +librtp_la_SOURCES = \ + modules/rtp/rtp.c modules/rtp/rtp.h \ + modules/rtp/sdp.c modules/rtp/sdp.h \ + modules/rtp/sap.c modules/rtp/sap.h \ + modules/rtp/rtsp.c modules/rtp/rtsp.h \ + modules/rtp/headerlist.c modules/rtp/headerlist.h \ + modules/rtp/base64.c modules/rtp/base64.h librtp_la_LDFLAGS = -avoid-version librtp_la_LIBADD = $(AM_LIBADD) libpulsecore.la @@ -1053,6 +1059,7 @@ modlibexec_LTLIBRARIES += \ module-remap-sink.la \ module-ladspa-sink.la \ module-esound-sink.la \ + module-raop-sink.la \ module-tunnel-sink.la \ module-tunnel-source.la \ module-position-event-sounds.la @@ -1199,6 +1206,7 @@ SYMDEF_FILES = \ modules/module-esound-compat-spawnfd-symdef.h \ modules/module-esound-compat-spawnpid-symdef.h \ modules/module-match-symdef.h \ + modules/module-raop-sink-symdef.h \ modules/module-tunnel-sink-symdef.h \ modules/module-tunnel-source-symdef.h \ modules/module-null-sink-symdef.h \ @@ -1367,6 +1375,10 @@ module_match_la_SOURCES = modules/module-match.c module_match_la_LDFLAGS = -module -avoid-version module_match_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_raop_sink_la_SOURCES = modules/module-raop-sink.c +module_raop_sink_la_LDFLAGS = -module -avoid-version +module_raop_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la librtp.la + module_tunnel_sink_la_SOURCES = modules/module-tunnel.c module_tunnel_sink_la_CFLAGS = -DTUNNEL_SINK=1 $(AM_CFLAGS) module_tunnel_sink_la_LDFLAGS = -module -avoid-version diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c new file mode 100644 index 000000000..ad10d78fb --- /dev/null +++ b/src/modules/module-raop-sink.c @@ -0,0 +1,417 @@ +/* $Id$ */ + +/*** + 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 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rtp.h" +#include "sdp.h" +#include "sap.h" +#include "rtsp.h" +#include "base64.h" + + +#include "module-raop-sink-symdef.h" + +#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 + + +PA_MODULE_AUTHOR("Colin Guthrie"); +PA_MODULE_DESCRIPTION("RAOP Sink (Apple Airport)"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "server=
" + "sink_name= " + "format= " + "channels= " + "rate=" + "channel_map="); + +#define DEFAULT_SINK_NAME "airtunes" +#define AES_CHUNKSIZE 16 + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + char *server_name; + + // 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_rtsp_context *rtsp; + //pa_socket_client *client; + pa_memchunk memchunk; + + pa_rtpoll_item *rtpoll_item; +}; + +static const char* const valid_modargs[] = { + "server", + "rate", + "format", + "channels", + "sink_name", + "channel_map", + NULL +}; + +static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { + char n[] = + "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" + "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" + "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" + "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" + "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" + "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; + 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(struct userdata *u, uint8_t *data, int size) +{ + uint8_t *buf; + int i=0, j; + + pa_assert(u); + + memcpy(u->aes_nv, u->aes_iv, AES_CHUNKSIZE); + while (i+AES_CHUNKSIZE <= size) { + buf = data + i; + for (j=0; jaes_nv[j]; + + AES_encrypt(buf, buf, &u->aes); + memcpy(u->aes_nv, buf, AES_CHUNKSIZE); + i += AES_CHUNKSIZE; + } + return i; +} + +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_GET_LATENCY: { + size_t n = 0; + //int l; + +#ifdef TIOCINQ + /* + if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0) + n = (size_t) l; + */ +#endif + + n += u->memchunk.length; + + *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); + break; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + //int write_type = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + struct pollfd *pollfd; + int ret; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + /* Render some data and write it to the fifo */ + if (u->sink->thread_info.state == PA_SINK_RUNNING && pollfd->revents) { + ssize_t l; + void *p; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, PIPE_BUF, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + p = pa_memblock_acquire(u->memchunk.memblock); + //l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); + // Fake the length of the "write". + l = u->memchunk.length; + pa_memblock_release(u->memchunk.memblock); + + pa_assert(l != 0); + + if (l < 0) { + + if (errno == EINTR) + continue; + else if (errno != EAGAIN) { + pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + + u->memchunk.index += l; + u->memchunk.length -= l; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + pollfd->revents = 0; + } + } + + /* Hmm, nothing to do. Let's sleep */ + pollfd->events = u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0; + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~POLLOUT) { + pa_log("FIFO shutdown."); + goto fail; + } + } + +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: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + struct userdata *u; + //struct stat st; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + char *t; + struct pollfd *pollfd; + + 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_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + + // Initialise the AES encryption system + pa_random_seed(); + pa_random(u->aes_iv, sizeof(u->aes_iv)); + pa_random(u->aes_key, sizeof(u->aes_key)); + memcpy(u->aes_nv, u->aes_iv, sizeof(u->aes_nv)); + AES_set_encrypt_key(u->aes_key, 128, &u->aes); + + pa_memchunk_reset(&u->memchunk); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL)); + + // Open a connection to the server... this is just to connect and test.... + /* + mkfifo(u->filename, 0666); + if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { + pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); + goto fail; + } + + pa_make_fd_cloexec(u->fd); + pa_make_fd_nonblock(u->fd); + + if (fstat(u->fd, &st) < 0) { + pa_log("fstat('%s'): %s", u->filename, pa_cstrerror(errno)); + goto fail; + } + + if (!S_ISFIFO(st.st_mode)) { + pa_log("'%s' is not a FIFO.", u->filename); + goto fail; + } + */ + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Airtunes sink '%s'", u->server_name)); + pa_xfree(t); + + 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 = pollfd->revents = 0; + + 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->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + pa_xfree(u->server_name); + pa_xfree(u); +} From 91edf9eaca3a0da83484c9c3787beff2cc7a5945 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Fri, 2 May 2008 09:47:09 +0000 Subject: [PATCH 05/56] Use pa_sprintf_malloc to do simple concatenation rather than using the higher overhead of pa_strbuf git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2348 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/headerlist.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/modules/rtp/headerlist.c b/src/modules/rtp/headerlist.c index 9ea17ae39..8bdc7251d 100644 --- a/src/modules/rtp/headerlist.c +++ b/src/modules/rtp/headerlist.c @@ -92,22 +92,20 @@ 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) { struct header *hdr; pa_bool_t add = FALSE; - pa_strbuf *buf; pa_assert(p); pa_assert(key); - buf = pa_strbuf_new(); 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 { - pa_strbuf_puts(buf, hdr->value); + void *newval = (void*)pa_sprintf_malloc("%s%s", (char*)hdr->value, value); pa_xfree(hdr->value); + hdr->value = newval; } - pa_strbuf_puts(buf, value); - hdr->value = pa_strbuf_tostring_free(buf); hdr->nbytes = strlen(hdr->value)+1; if (add) From ce9a41ef06e0b6c619b985415ccac6f0fddd68b8 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Fri, 2 May 2008 09:49:28 +0000 Subject: [PATCH 06/56] Use _free rather than _destroy so as not to mix naming conventions. Convert C++ comments to C. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2349 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp.c | 55 +++++++++++++++++++++--------------------- src/modules/rtp/rtsp.h | 2 +- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp.c index 3fd1ba0f2..616887134 100644 --- a/src/modules/rtp/rtsp.c +++ b/src/modules/rtp/rtsp.c @@ -75,12 +75,12 @@ static int pa_read_line(int fd, char *line, int maxlen, int timeout) if (-1 == rval) { if (EAGAIN == errno) return 0; - //ERRMSG("%s:read error: %s\n", __func__, strerror(errno)); + /*ERRMSG("%s:read error: %s\n", __func__, strerror(errno));*/ return -1; } if (0 == rval) { - //INFMSG("%s:disconnected on the other end\n", __func__); + /*INFMSG("%s:disconnected on the other end\n", __func__);*/ return -1; } @@ -131,7 +131,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, if (c->session) pa_strbuf_printf(buf, "Session: %s\r\n", c->session); - // Add the headers + /* Add the headers */ if (headers) { hdrs = pa_headerlist_to_string(headers); pa_strbuf_puts(buf, hdrs); @@ -157,18 +157,18 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, pa_strbuf_puts(buf, content); } - // Our packet is created... now we can send it :) + /* Our packet is created... now we can send it :) */ hdrs = pa_strbuf_tostring_free(buf); l = pa_write(c->fd, hdrs, strlen(hdrs), NULL); pa_xfree(hdrs); - // Do we expect a response? + /* Do we expect a response? */ if (!expect_response) return 1; timeout = 5000; if (pa_read_line(c->fd, response, sizeof(response), timeout) <= 0) { - //ERRMSG("%s: request failed\n",__func__); + /*ERRMSG("%s: request failed\n",__func__);*/ return 0; } @@ -179,69 +179,70 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, token = pa_split(response, delimiters, &token_state); if (!token || strcmp(token, "200")) { pa_xfree(token); - //ERRMSG("%s: request failed, error %s\n",__func__,token); + /*ERRMSG("%s: request failed, error %s\n",__func__,token);*/ return 0; } pa_xfree(token); - // We want to return the headers? + /* We want to return the headers? */ if (!response_headers) { - // We have no storage, so just clear out the response. + /* We have no storage, so just clear out the response. */ while (pa_read_line(c->fd, response, sizeof(response), timeout) > 0) { - // Reduce timeout for future requests + /* Reduce timeout for future requests */ timeout = 1000; } return 1; } + /* TODO: Move header reading into the headerlist. */ header = NULL; buf = pa_strbuf_new(); while (pa_read_line(c->fd, response, sizeof(response), timeout) > 0) { - // Reduce timeout for future requests + /* Reduce timeout for future requests */ timeout = 1000; - // If the first character is a space, it's a continuation header + /* If the first character is a space, it's a continuation header */ if (header && ' ' == response[0]) { - // Add this line to the buffer (sans the space. + /* Add this line to the buffer (sans the space. */ pa_strbuf_puts(buf, &(response[1])); continue; } if (header) { - // This is not a continuation header so let's dump the full header/value into our proplist + /* This is not a continuation header so let's dump the full + header/value into our proplist */ pa_headerlist_puts(*response_headers, header, pa_strbuf_tostring_free(buf)); pa_xfree(header); - //header = NULL; buf = pa_strbuf_new(); } delimpos = strstr(response, ":"); if (!delimpos) { - //ERRMSG("%s: Request failed, bad header\n",__func__); + /*ERRMSG("%s: Request failed, bad header\n",__func__);*/ return 0; } if (strlen(delimpos) > 1) { - // Cut our line off so we can copy the header name out + /* Cut our line off so we can copy the header name out */ *delimpos++ = '\0'; - // Trim the front of any spaces + /* Trim the front of any spaces */ while (' ' == *delimpos) ++delimpos; pa_strbuf_puts(buf, delimpos); } else { - // Cut our line off so we can copy the header name out + /* Cut our line off so we can copy the header name out */ *delimpos = '\0'; } - // Save the header name + /* Save the header name */ header = pa_xstrdup(response); } - // We will have a header left from our looping itteration, so add it in :) + /* We will have a header left from our looping itteration, so add it in :) */ if (header) { - // This is not a continuation header so let's dump it into our proplist + /* This is not a continuation header so let's dump it into our proplist */ pa_headerlist_puts(*response_headers, header, pa_strbuf_tostring(buf)); } pa_strbuf_free(buf); @@ -266,7 +267,7 @@ pa_rtsp_context* pa_rtsp_context_new(const char* useragent) { } -void pa_rtsp_context_destroy(pa_rtsp_context* c) { +void pa_rtsp_context_free(pa_rtsp_context* c) { if (c) { pa_xfree(c->url); pa_xfree(c->session); @@ -308,7 +309,7 @@ int pa_rtsp_connect(pa_rtsp_context *c, const char* hostname, uint16_t port, con return 0; } - // Q: is FD_CLOEXEC reqd? + /* Q: is FD_CLOEXEC reqd? */ pa_make_fd_cloexec(c->fd); pa_make_tcp_socket_low_delay(c->fd); @@ -395,7 +396,7 @@ int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers) { return 0; } - // Now parse out the server port component of the response. + /* Now parse out the server port component of the response. */ c->port = 0; delimiters[0] = ';'; delimiters[1] = '\0'; @@ -411,7 +412,7 @@ int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers) { pa_xfree(token); } if (0 == c->port) { - // Error no server_port in response + /* Error no server_port in response */ pa_headerlist_free(rheaders); return 0; } @@ -427,7 +428,7 @@ int pa_rtsp_record(pa_rtsp_context* c) { pa_assert(c); if (!c->session) { - // No seesion in progres + /* No seesion in progres */ return 0; } diff --git a/src/modules/rtp/rtsp.h b/src/modules/rtp/rtsp.h index 504f1445d..7b3df8f31 100644 --- a/src/modules/rtp/rtsp.h +++ b/src/modules/rtp/rtsp.h @@ -48,7 +48,7 @@ typedef struct pa_rtsp_context { } pa_rtsp_context; pa_rtsp_context* pa_rtsp_context_new(const char* useragent); -void pa_rtsp_context_destroy(pa_rtsp_context* c); +void pa_rtsp_context_free(pa_rtsp_context* c); int pa_rtsp_connect(pa_rtsp_context* c, const char* hostname, uint16_t port, const char* sid); void pa_rtsp_disconnect(pa_rtsp_context* c); From 405cf720dc5190f14eee6e2eaad51aa52ff18c62 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 4 May 2008 00:43:31 +0000 Subject: [PATCH 07/56] Convert to using pa_socket_client rather than using blocking IO. This change requires a reference to the mainloop api be passed during initial connection. In addition, the passing in of the session id during connect has been deprecated. A new function pa_rtsp_set_url has been added to allow the URL to be set by external code. The concept of sid is something specific to raop, not to the rtsp client. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2360 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp.c | 129 +++++++++++++++++++++-------------------- src/modules/rtp/rtsp.h | 9 ++- 2 files changed, 73 insertions(+), 65 deletions(-) diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp.c index 616887134..3556230b2 100644 --- a/src/modules/rtp/rtsp.c +++ b/src/modules/rtp/rtsp.c @@ -55,12 +55,17 @@ * if CR comes then following LF is expected * returned string in line is always null terminated, maxlen-1 is maximum string length */ -static int pa_read_line(int fd, char *line, int maxlen, int timeout) +static int pa_read_line(pa_iochannel* io, char *line, int maxlen, int timeout) { int i, rval; int count; + int fd; char ch; struct pollfd pfds; + + pa_assert(io); + fd = pa_iochannel_get_recv_fd(io); + count = 0; *line = 0; pfds.events = POLLIN; @@ -159,7 +164,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, /* Our packet is created... now we can send it :) */ hdrs = pa_strbuf_tostring_free(buf); - l = pa_write(c->fd, hdrs, strlen(hdrs), NULL); + l = pa_iochannel_write(c->io, hdrs, strlen(hdrs)); pa_xfree(hdrs); /* Do we expect a response? */ @@ -167,7 +172,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, return 1; timeout = 5000; - if (pa_read_line(c->fd, response, sizeof(response), timeout) <= 0) { + if (pa_read_line(c->io, response, sizeof(response), timeout) <= 0) { /*ERRMSG("%s: request failed\n",__func__);*/ return 0; } @@ -188,7 +193,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, if (!response_headers) { /* We have no storage, so just clear out the response. */ - while (pa_read_line(c->fd, response, sizeof(response), timeout) > 0) { + while (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { /* Reduce timeout for future requests */ timeout = 1000; } @@ -198,7 +203,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, /* TODO: Move header reading into the headerlist. */ header = NULL; buf = pa_strbuf_new(); - while (pa_read_line(c->fd, response, sizeof(response), timeout) > 0) { + while (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { /* Reduce timeout for future requests */ timeout = 1000; @@ -255,7 +260,6 @@ pa_rtsp_context* pa_rtsp_context_new(const char* useragent) { pa_rtsp_context *c; c = pa_xnew0(pa_rtsp_context, 1); - c->fd = -1; c->headers = pa_headerlist_new(); if (useragent) @@ -269,7 +273,11 @@ pa_rtsp_context* pa_rtsp_context_new(const char* useragent) { void pa_rtsp_context_free(pa_rtsp_context* c) { if (c) { + if (c->sc) + pa_socket_client_unref(c->sc); + pa_xfree(c->url); + pa_xfree(c->localip); pa_xfree(c->session); pa_xfree(c->transport); pa_headerlist_free(c->headers); @@ -278,63 +286,57 @@ void pa_rtsp_context_free(pa_rtsp_context* c) { } -int pa_rtsp_connect(pa_rtsp_context *c, const char* hostname, uint16_t port, const char* sid) { - struct sockaddr_in sa; - struct sockaddr_in name; - socklen_t namelen = sizeof(name); - struct hostent *host = NULL; - int r; +static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { + pa_rtsp_context *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(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; + + /* 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) { + res = inet_ntop(sa.sa.sa_family, &sa.in.sin_addr, buf, sizeof(buf)); + } else if (AF_INET6 == sa.sa.sa_family) { + res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)); + } + if (res) + c->localip = pa_xstrdup(res); + } +} + +int pa_rtsp_connect(pa_rtsp_context *c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port) { + pa_assert(c); + pa_assert(mainloop); pa_assert(hostname); pa_assert(port > 0); - pa_assert(sid); - memset(&sa, 0, sizeof(sa)); - sa.sin_family = AF_INET; - sa.sin_port = htons(port); - - host = gethostbyname(hostname); - if (!host) { - unsigned int addr = inet_addr(hostname); - if (addr != INADDR_NONE) - host = gethostbyaddr((char*)&addr, 4, AF_INET); - if (!host) - return 0; - } - memcpy(&sa.sin_addr, host->h_addr, sizeof(struct in_addr)); - - if ((c->fd = socket(sa.sin_family, SOCK_STREAM, 0)) < 0) { - pa_log("socket(): %s", pa_cstrerror(errno)); + if (!(c->sc = pa_socket_client_new_string(mainloop, hostname, port))) { + pa_log("failed to connect to server '%s:%d'", hostname, port); return 0; } - /* Q: is FD_CLOEXEC reqd? */ - pa_make_fd_cloexec(c->fd); - pa_make_tcp_socket_low_delay(c->fd); - - if ((r = connect(c->fd, &sa, sizeof(struct sockaddr_in))) < 0) { -#ifdef OS_IS_WIN32 - if (WSAGetLastError() != EWOULDBLOCK) { - pa_log_debug("connect(): %d", WSAGetLastError()); -#else - if (errno != EINPROGRESS) { - pa_log_debug("connect(): %s (%d)", pa_cstrerror(errno), errno); -#endif - pa_close(c->fd); - c->fd = -1; - return 0; - } - } - - if (0 != getsockname(c->fd, (struct sockaddr*)&name, &namelen)) { - pa_close(c->fd); - c->fd = -1; - return 0; - } - memcpy(&c->local_addr, &name.sin_addr, sizeof(struct in_addr)); - c->url = pa_sprintf_malloc("rtsp://%s/%s", inet_ntoa(name.sin_addr), sid); - + pa_socket_client_set_callback(c->sc, on_connection, c); return 1; } @@ -342,22 +344,25 @@ int pa_rtsp_connect(pa_rtsp_context *c, const char* hostname, uint16_t port, con void pa_rtsp_disconnect(pa_rtsp_context *c) { pa_assert(c); - if (c->fd < 0) - return; - pa_close(c->fd); - c->fd = -1; + if (c->io) + pa_iochannel_free(c->io); + c->io = NULL; } const char* pa_rtsp_localip(pa_rtsp_context* c) { pa_assert(c); - if (c->fd < 0) - return NULL; - return inet_ntoa(c->local_addr); + return c->localip; } +void pa_rtsp_set_url(pa_rtsp_context* c, const char* url) { + pa_assert(c); + + c->url = pa_xstrdup(url); +} + int pa_rtsp_announce(pa_rtsp_context *c, const char* sdp) { pa_assert(c); if (!sdp) diff --git a/src/modules/rtp/rtsp.h b/src/modules/rtp/rtsp.h index 7b3df8f31..8d86f7bae 100644 --- a/src/modules/rtp/rtsp.h +++ b/src/modules/rtp/rtsp.h @@ -32,28 +32,31 @@ #include #include #include +#include #include "headerlist.h" typedef struct pa_rtsp_context { - int fd; + pa_socket_client *sc; + pa_iochannel *io; const char* useragent; pa_headerlist* headers; + char* localip; char* url; uint32_t port; uint32_t cseq; char* session; char* transport; - struct in_addr local_addr; } pa_rtsp_context; pa_rtsp_context* pa_rtsp_context_new(const char* useragent); void pa_rtsp_context_free(pa_rtsp_context* c); -int pa_rtsp_connect(pa_rtsp_context* c, const char* hostname, uint16_t port, const char* sid); +int pa_rtsp_connect(pa_rtsp_context* c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port); void pa_rtsp_disconnect(pa_rtsp_context* c); const char* pa_rtsp_localip(pa_rtsp_context* c); +void pa_rtsp_set_url(pa_rtsp_context* c, const char* url); int pa_rtsp_announce(pa_rtsp_context* c, const char* sdp); int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers); From 27ed970adf66ea27d3db47c2b0e138d3d7e0f0b3 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 4 May 2008 01:01:52 +0000 Subject: [PATCH 08/56] Convert the return values to fit with the rest of pulse 0 == success, < 0 == failure git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2362 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp.c index 3556230b2..9f4d5e45a 100644 --- a/src/modules/rtp/rtsp.c +++ b/src/modules/rtp/rtsp.c @@ -129,7 +129,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, pa_assert(c->url); if (!cmd) - return 0; + return -1; buf = pa_strbuf_new(); pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, c->url, ++c->cseq); @@ -169,12 +169,12 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, /* Do we expect a response? */ if (!expect_response) - return 1; + return 0; timeout = 5000; if (pa_read_line(c->io, response, sizeof(response), timeout) <= 0) { /*ERRMSG("%s: request failed\n",__func__);*/ - return 0; + return -1; } delimiters[0] = ' '; @@ -185,7 +185,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, if (!token || strcmp(token, "200")) { pa_xfree(token); /*ERRMSG("%s: request failed, error %s\n",__func__,token);*/ - return 0; + return -1; } pa_xfree(token); @@ -197,7 +197,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, /* Reduce timeout for future requests */ timeout = 1000; } - return 1; + return 0; } /* TODO: Move header reading into the headerlist. */ @@ -225,7 +225,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, delimpos = strstr(response, ":"); if (!delimpos) { /*ERRMSG("%s: Request failed, bad header\n",__func__);*/ - return 0; + return -1; } if (strlen(delimpos) > 1) { @@ -252,7 +252,7 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, } pa_strbuf_free(buf); - return 1; + return 0; } @@ -333,11 +333,11 @@ int pa_rtsp_connect(pa_rtsp_context *c, pa_mainloop_api *mainloop, const char* h if (!(c->sc = pa_socket_client_new_string(mainloop, hostname, port))) { pa_log("failed to connect to server '%s:%d'", hostname, port); - return 0; + return -1; } pa_socket_client_set_callback(c->sc, on_connection, c); - return 1; + return 0; } @@ -366,7 +366,7 @@ void pa_rtsp_set_url(pa_rtsp_context* c, const char* url) { int pa_rtsp_announce(pa_rtsp_context *c, const char* sdp) { pa_assert(c); if (!sdp) - return 0; + return -1; return pa_rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL, NULL); } @@ -386,10 +386,10 @@ int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers) { rheaders = pa_headerlist_new(); pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); - if (!pa_rtsp_exec(c, "SETUP", NULL, NULL, 1, headers, &rheaders)) { + if (pa_rtsp_exec(c, "SETUP", NULL, NULL, 1, headers, &rheaders)) { pa_headerlist_free(headers); pa_headerlist_free(rheaders); - return 0; + return -1; } pa_headerlist_free(headers); @@ -398,7 +398,7 @@ int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers) { if (!c->session || !c->transport) { pa_headerlist_free(rheaders); - return 0; + return -1; } /* Now parse out the server port component of the response. */ @@ -419,11 +419,11 @@ int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers) { if (0 == c->port) { /* Error no server_port in response */ pa_headerlist_free(rheaders); - return 0; + return -1; } *response_headers = rheaders; - return 1; + return 0; } @@ -434,7 +434,7 @@ int pa_rtsp_record(pa_rtsp_context* c) { pa_assert(c); if (!c->session) { /* No seesion in progres */ - return 0; + return -1; } headers = pa_headerlist_new(); @@ -457,7 +457,7 @@ int pa_rtsp_teardown(pa_rtsp_context *c) { int pa_rtsp_setparameter(pa_rtsp_context *c, const char* param) { pa_assert(c); if (!param) - return 0; + return -1; return pa_rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL, NULL); } From a08d733fd149d3d927583bad0dc69104d08b0ceb Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 4 May 2008 01:26:29 +0000 Subject: [PATCH 09/56] Fix svn properties and some minor indentation git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2363 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp.c | 2 +- src/modules/rtp/rtsp.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp.c index 9f4d5e45a..d84407248 100644 --- a/src/modules/rtp/rtsp.c +++ b/src/modules/rtp/rtsp.c @@ -321,7 +321,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)); } if (res) - c->localip = pa_xstrdup(res); + c->localip = pa_xstrdup(res); } } diff --git a/src/modules/rtp/rtsp.h b/src/modules/rtp/rtsp.h index 8d86f7bae..181d08547 100644 --- a/src/modules/rtp/rtsp.h +++ b/src/modules/rtp/rtsp.h @@ -1,7 +1,7 @@ #ifndef foortsphfoo #define foortsphfoo -/* $Id: rtp.h 1465 2007-05-29 17:24:48Z lennart $ */ +/* $Id$ */ /*** This file is part of PulseAudio. From a0d3582fb1bddbb8fb6a7da98bbfeb05b517088e Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:14:33 +0000 Subject: [PATCH 10/56] Trivial change to allocate memory using pulse methods. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2364 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/base64.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/modules/rtp/base64.c b/src/modules/rtp/base64.c index ec9f2212e..043ef5a8a 100644 --- a/src/modules/rtp/base64.c +++ b/src/modules/rtp/base64.c @@ -33,6 +33,8 @@ #include #include +#include + #include "base64.h" static char base64_chars[] = @@ -54,9 +56,7 @@ int pa_base64_encode(const void *data, int size, char **str) int c; const unsigned char *q; - p = s = (char *) malloc(size * 4 / 3 + 4); - if (p == NULL) - return -1; + p = s = pa_xnew(char, size * 4 / 3 + 4); q = (const unsigned char *) data; i = 0; for (i = 0; i < size;) { From d423605bd9f5fb18b44fde5424b075c977de25ad Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:17:17 +0000 Subject: [PATCH 11/56] Move closer to an asynchronous structure (still some parsing code to be converted). Move type definition into .c file to keep it private Add more utility functions to add/remove headers and return the serverport now the structure is private. This commit will break the test application but I will fix that in due course git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2365 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp.c | 345 ++++++++++++++++++++++++----------------- src/modules/rtp/rtsp.h | 31 ++-- 2 files changed, 220 insertions(+), 156 deletions(-) diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp.c index d84407248..55d910186 100644 --- a/src/modules/rtp/rtsp.c +++ b/src/modules/rtp/rtsp.c @@ -49,6 +49,22 @@ #include "rtsp.h" +struct pa_rtsp_context { + pa_socket_client *sc; + pa_iochannel *io; + pa_rtsp_cb_t callback; + void* userdata; + const char* useragent; + pa_headerlist* headers; + char* localip; + char* url; + uint32_t port; + uint32_t cseq; + char* session; + char* transport; + pa_rtsp_state state; +}; + /* * read one line from the file descriptor * timeout: msec unit, -1 for infinite @@ -112,18 +128,10 @@ static int pa_read_line(pa_iochannel* io, char *line, int maxlen, int timeout) static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, const char* content_type, const char* content, int expect_response, - pa_headerlist* headers, pa_headerlist** response_headers) { + pa_headerlist* headers) { pa_strbuf* buf; char* hdrs; ssize_t l; - char response[1024]; - int timeout; - char* token; - const char* token_state; - char delimiters[2]; - char* header; - char* delimpos; - pa_assert(c); pa_assert(c->url); @@ -167,91 +175,6 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, l = pa_iochannel_write(c->io, hdrs, strlen(hdrs)); pa_xfree(hdrs); - /* Do we expect a response? */ - if (!expect_response) - return 0; - - timeout = 5000; - if (pa_read_line(c->io, response, sizeof(response), timeout) <= 0) { - /*ERRMSG("%s: request failed\n",__func__);*/ - return -1; - } - - delimiters[0] = ' '; - delimiters[1] = '\0'; - token_state = NULL; - pa_xfree(pa_split(response, delimiters, &token_state)); - token = pa_split(response, delimiters, &token_state); - if (!token || strcmp(token, "200")) { - pa_xfree(token); - /*ERRMSG("%s: request failed, error %s\n",__func__,token);*/ - return -1; - } - pa_xfree(token); - - /* We want to return the headers? */ - if (!response_headers) - { - /* We have no storage, so just clear out the response. */ - while (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { - /* Reduce timeout for future requests */ - timeout = 1000; - } - return 0; - } - - /* TODO: Move header reading into the headerlist. */ - header = NULL; - buf = pa_strbuf_new(); - while (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { - /* Reduce timeout for future requests */ - timeout = 1000; - - /* If the first character is a space, it's a continuation header */ - if (header && ' ' == response[0]) { - /* Add this line to the buffer (sans the space. */ - pa_strbuf_puts(buf, &(response[1])); - continue; - } - - if (header) { - /* This is not a continuation header so let's dump the full - header/value into our proplist */ - pa_headerlist_puts(*response_headers, header, pa_strbuf_tostring_free(buf)); - pa_xfree(header); - buf = pa_strbuf_new(); - } - - delimpos = strstr(response, ":"); - if (!delimpos) { - /*ERRMSG("%s: Request failed, bad header\n",__func__);*/ - return -1; - } - - 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(buf, delimpos); - } else { - /* Cut our line off so we can copy the header name out */ - *delimpos = '\0'; - } - - /* Save the header name */ - header = pa_xstrdup(response); - } - /* We will have a header left from our looping itteration, so add it in :) */ - if (header) { - /* This is not a continuation header so let's dump it into our proplist */ - pa_headerlist_puts(*response_headers, header, pa_strbuf_tostring(buf)); - } - pa_strbuf_free(buf); - return 0; } @@ -286,6 +209,146 @@ void pa_rtsp_context_free(pa_rtsp_context* c) { } +static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) { + pa_strbuf* buf; + pa_headerlist* response_headers = NULL; + char response[1024]; + int timeout; + char* token; + char* header; + char* delimpos; + char delimiters[] = " "; + pa_rtsp_context *c = userdata; + pa_assert(c); + + /* TODO: convert this to a pa_ioline based reader */ + if (STATE_CONNECT == c->state) { + response_headers = pa_headerlist_new(); + } + timeout = 5000; + /* read in any response headers */ + if (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { + const char* token_state = NULL; + + timeout = 1000; + pa_xfree(pa_split(response, delimiters, &token_state)); + token = pa_split(response, delimiters, &token_state); + if (!token || strcmp(token, "200")) { + pa_xfree(token); + pa_log("Invalid Response"); + /* TODO: Bail out completely */ + return; + } + pa_xfree(token); + + /* We want to return the headers? */ + if (!response_headers) { + /* We have no storage, so just clear out the response. */ + while (pa_read_line(c->io, response, sizeof(response), timeout) > 0); + } else { + /* TODO: Move header reading into the headerlist. */ + header = NULL; + buf = pa_strbuf_new(); + while (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { + /* If the first character is a space, it's a continuation header */ + if (header && ' ' == response[0]) { + /* Add this line to the buffer (sans the space. */ + pa_strbuf_puts(buf, &(response[1])); + continue; + } + + if (header) { + /* This is not a continuation header so let's dump the full + header/value into our proplist */ + pa_headerlist_puts(response_headers, header, pa_strbuf_tostring_free(buf)); + pa_xfree(header); + buf = pa_strbuf_new(); + } + + delimpos = strstr(response, ":"); + if (!delimpos) { + pa_log("Invalid response header"); + return; + } + + 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(buf, delimpos); + } else { + /* Cut our line off so we can copy the header name out */ + *delimpos = '\0'; + } + + /* Save the header name */ + header = pa_xstrdup(response); + } + /* We will have a header left from our looping itteration, so add it in :) */ + if (header) { + /* This is not a continuation header so let's dump it into our proplist */ + pa_headerlist_puts(response_headers, header, pa_strbuf_tostring(buf)); + } + pa_strbuf_free(buf); + } + } + + /* Deal with a CONNECT response */ + if (STATE_CONNECT == c->state) { + const char* token_state = NULL; + const char* pc = NULL; + c->session = pa_xstrdup(pa_headerlist_gets(response_headers, "Session")); + c->transport = pa_xstrdup(pa_headerlist_gets(response_headers, "Transport")); + + if (!c->session || !c->transport) { + pa_headerlist_free(response_headers); + return; + } + + /* Now parse out the server port component of the response. */ + c->port = 0; + delimiters[0] = ';'; + while ((token = pa_split(c->transport, delimiters, &token_state))) { + if ((pc = strstr(token, "="))) { + if (0 == strncmp(token, "server_port", 11)) { + pa_atou(pc+1, &c->port); + pa_xfree(token); + break; + } + } + pa_xfree(token); + } + if (0 == c->port) { + /* Error no server_port in response */ + pa_headerlist_free(response_headers); + return; + } + } + + /* Call our callback */ + if (c->callback) + c->callback(c, c->state, response_headers, c->userdata); + + + if (response_headers) + pa_headerlist_free(response_headers); + + /* + if (do_read(u) < 0 || do_write(u) < 0) { + + if (u->io) { + pa_iochannel_free(u->io); + u->io = NULL; + } + + pa_module_unload_request(u->module); + } + */ +} static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { pa_rtsp_context *c = userdata; @@ -309,6 +372,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata } pa_assert(!c->io); c->io = io; + pa_iochannel_set_callback(c->io, io_callback, c); /* Get the local IP address for use externally */ if (0 == getsockname(pa_iochannel_get_recv_fd(io), &sa.sa, &sa_len)) { @@ -337,9 +401,16 @@ int pa_rtsp_connect(pa_rtsp_context *c, pa_mainloop_api *mainloop, const char* h } pa_socket_client_set_callback(c->sc, on_connection, c); + c->state = STATE_CONNECT; return 0; } +void pa_rtsp_set_callback(pa_rtsp_context *c, pa_rtsp_cb_t callback, void *userdata) { + pa_assert(c); + + c->callback = callback; + c->userdata = userdata; +} void pa_rtsp_disconnect(pa_rtsp_context *c) { pa_assert(c); @@ -356,6 +427,11 @@ const char* pa_rtsp_localip(pa_rtsp_context* c) { return c->localip; } +uint32_t pa_rtsp_serverport(pa_rtsp_context* c) { + pa_assert(c); + + return c->port; +} void pa_rtsp_set_url(pa_rtsp_context* c, const char* url) { pa_assert(c); @@ -363,67 +439,46 @@ void pa_rtsp_set_url(pa_rtsp_context* c, const char* url) { c->url = pa_xstrdup(url); } +void pa_rtsp_add_header(pa_rtsp_context *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_context *c, const char* key) +{ + pa_assert(c); + pa_assert(key); + + pa_headerlist_remove(c->headers, key); +} + int pa_rtsp_announce(pa_rtsp_context *c, const char* sdp) { pa_assert(c); if (!sdp) return -1; - return pa_rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL, NULL); + c->state = STATE_ANNOUNCE; + return pa_rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL); } -int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers) { +int pa_rtsp_setup(pa_rtsp_context* c) { pa_headerlist* headers; - pa_headerlist* rheaders; - char delimiters[2]; - char* token; - const char* token_state; - const char* pc; + int rv; pa_assert(c); headers = pa_headerlist_new(); - rheaders = pa_headerlist_new(); pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); - if (pa_rtsp_exec(c, "SETUP", NULL, NULL, 1, headers, &rheaders)) { - pa_headerlist_free(headers); - pa_headerlist_free(rheaders); - return -1; - } + c->state = STATE_SETUP; + rv = pa_rtsp_exec(c, "SETUP", NULL, NULL, 1, headers); pa_headerlist_free(headers); - - c->session = pa_xstrdup(pa_headerlist_gets(rheaders, "Session")); - c->transport = pa_xstrdup(pa_headerlist_gets(rheaders, "Transport")); - - if (!c->session || !c->transport) { - pa_headerlist_free(rheaders); - return -1; - } - - /* Now parse out the server port component of the response. */ - c->port = 0; - delimiters[0] = ';'; - delimiters[1] = '\0'; - token_state = NULL; - while ((token = pa_split(c->transport, delimiters, &token_state))) { - if ((pc = strstr(token, "="))) { - if (0 == strncmp(token, "server_port", 11)) { - pa_atou(pc+1, &c->port); - pa_xfree(token); - break; - } - } - pa_xfree(token); - } - if (0 == c->port) { - /* Error no server_port in response */ - pa_headerlist_free(rheaders); - return -1; - } - - *response_headers = rheaders; - return 0; + return rv; } @@ -441,7 +496,8 @@ int pa_rtsp_record(pa_rtsp_context* c) { pa_headerlist_puts(headers, "Range", "npt=0-"); pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); - rv = pa_rtsp_exec(c, "RECORD", NULL, NULL, 1, headers, NULL); + c->state = STATE_RECORD; + rv = pa_rtsp_exec(c, "RECORD", NULL, NULL, 1, headers); pa_headerlist_free(headers); return rv; } @@ -450,7 +506,8 @@ int pa_rtsp_record(pa_rtsp_context* c) { int pa_rtsp_teardown(pa_rtsp_context *c) { pa_assert(c); - return pa_rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL, NULL); + c->state = STATE_TEARDOWN; + return pa_rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); } @@ -459,7 +516,8 @@ int pa_rtsp_setparameter(pa_rtsp_context *c, const char* param) { if (!param) return -1; - return pa_rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL, NULL); + c->state = STATE_SET_PARAMETER; + return pa_rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL); } @@ -472,7 +530,8 @@ int pa_rtsp_flush(pa_rtsp_context *c) { headers = pa_headerlist_new(); pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); - rv = pa_rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers, NULL); + c->state = STATE_FLUSH; + rv = pa_rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers); pa_headerlist_free(headers); return rv; } diff --git a/src/modules/rtp/rtsp.h b/src/modules/rtp/rtsp.h index 181d08547..6458f851c 100644 --- a/src/modules/rtp/rtsp.h +++ b/src/modules/rtp/rtsp.h @@ -36,30 +36,35 @@ #include "headerlist.h" -typedef struct pa_rtsp_context { - pa_socket_client *sc; - pa_iochannel *io; - const char* useragent; - pa_headerlist* headers; - char* localip; - char* url; - uint32_t port; - uint32_t cseq; - char* session; - char* transport; -} pa_rtsp_context; +typedef struct pa_rtsp_context pa_rtsp_context; +typedef enum { + STATE_CONNECT, + STATE_ANNOUNCE, + STATE_SETUP, + STATE_RECORD, + STATE_TEARDOWN, + STATE_SET_PARAMETER, + STATE_FLUSH +} pa_rtsp_state; +typedef void (*pa_rtsp_cb_t)(pa_rtsp_context *c, pa_rtsp_state state, pa_headerlist* hl, void *userdata); pa_rtsp_context* pa_rtsp_context_new(const char* useragent); void pa_rtsp_context_free(pa_rtsp_context* c); int pa_rtsp_connect(pa_rtsp_context* c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port); +void pa_rtsp_set_callback(pa_rtsp_context *c, pa_rtsp_cb_t callback, void *userdata); + void pa_rtsp_disconnect(pa_rtsp_context* c); const char* pa_rtsp_localip(pa_rtsp_context* c); +uint32_t pa_rtsp_serverport(pa_rtsp_context* c); void pa_rtsp_set_url(pa_rtsp_context* c, const char* url); +void pa_rtsp_add_header(pa_rtsp_context *c, const char* key, const char* value); +void pa_rtsp_remove_header(pa_rtsp_context *c, const char* key); + int pa_rtsp_announce(pa_rtsp_context* c, const char* sdp); -int pa_rtsp_setup(pa_rtsp_context* c, pa_headerlist** response_headers); +int pa_rtsp_setup(pa_rtsp_context* c); int pa_rtsp_record(pa_rtsp_context* c); int pa_rtsp_teardown(pa_rtsp_context* c); From 20478a4544e8ef622434c5af5dcb4c66269a7dd9 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:20:35 +0000 Subject: [PATCH 12/56] Add a skeleton raop client which builds on the rtsp client. It still requires a socket client and callback system to be added before it will be functional. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2366 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/Makefile.am | 1 + src/modules/rtp/raop_client.c | 308 ++++++++++++++++++++++++++++++++++ src/modules/rtp/raop_client.h | 40 +++++ 3 files changed, 349 insertions(+) create mode 100644 src/modules/rtp/raop_client.c create mode 100644 src/modules/rtp/raop_client.h diff --git a/src/Makefile.am b/src/Makefile.am index 831e45656..5bd6388b8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1009,6 +1009,7 @@ librtp_la_SOURCES = \ modules/rtp/sdp.c modules/rtp/sdp.h \ modules/rtp/sap.c modules/rtp/sap.h \ modules/rtp/rtsp.c modules/rtp/rtsp.h \ + modules/rtp/raop_client.c modules/rtp/raop_client.h \ modules/rtp/headerlist.c modules/rtp/headerlist.h \ modules/rtp/base64.c modules/rtp/base64.h librtp_la_LDFLAGS = -avoid-version diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c new file mode 100644 index 000000000..18e596b0b --- /dev/null +++ b/src/modules/rtp/raop_client.c @@ -0,0 +1,308 @@ +/* $Id$ */ + +/*** + 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 +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_FILIO_H +#include +#endif + +/* TODO: Replace OpenSSL with NSS */ +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "raop_client.h" +#include "rtsp.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_rtsp_context *rtsp; + pa_socket_client *sc; + const char *host; + char *sid; + + 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_iochannel *io; + pa_iochannel_cb_t callback; + void* userdata; +}; + +static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { + char n[] = + "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" + "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" + "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" + "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" + "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" + "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; + 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; jaes_nv[j]; + + AES_encrypt(buf, buf, &c->aes); + memcpy(c->aes_nv, buf, AES_CHUNKSIZE); + i += AES_CHUNKSIZE; + } + return i; +} + +pa_raop_client* pa_raop_client_new(void) +{ + pa_raop_client* c = pa_xnew0(pa_raop_client, 1); + return c; +} + +void pa_raop_client_free(pa_raop_client* c) +{ + pa_assert(c); + pa_xfree(c); +} + +static int remove_char_from_string(char *str, char rc) +{ + int i=0, j=0, len; + int num = 0; + len = strlen(str); + while (irtsp); + + 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; + + 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); + remove_char_from_string(key, '='); + pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv); + remove_char_from_string(iv, '='); + + pa_random(&rand_data, sizeof(rand_data)); + pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac); + remove_char_from_string(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_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")); + 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); + pa_rtsp_record(c->rtsp); + } else { + pa_log("Audio Jack Status missing"); + } + break; + } + + case STATE_RECORD: + /* Connect to the actual stream ;) */ + /* if(raopcl_stream_connect(raopcld)) goto erexit; */ + break; + + case STATE_TEARDOWN: + case STATE_SET_PARAMETER: + case STATE_FLUSH: + break; + } +} + +int pa_raop_client_connect(pa_raop_client* c, pa_mainloop_api *mainloop, const char* host) +{ + char *sci; + struct { + uint32_t a; + uint32_t b; + uint32_t c; + } rand_data; + + pa_assert(c); + pa_assert(host); + + c->host = host; + c->rtsp = pa_rtsp_context_new("iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); + + /* Initialise the AES encryption system */ + pa_random_seed(); + 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_rtsp_set_callback(c->rtsp, rtsp_cb, c); + return pa_rtsp_connect(c->rtsp, mainloop, host, 5000); +} + +void pa_raop_client_disconnect(pa_raop_client* c) +{ + +} + +void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, unsigned int count) +{ + +} diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h new file mode 100644 index 000000000..499b1248e --- /dev/null +++ b/src/modules/rtp/raop_client.h @@ -0,0 +1,40 @@ +#ifndef fooraopclientfoo +#define fooraopclientfoo + +/* $Id$ */ + +/*** + 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 + +typedef struct pa_raop_client pa_raop_client; + +pa_raop_client* pa_raop_client_new(void); +void pa_raop_client_free(pa_raop_client* c); + +int pa_raop_client_connect(pa_raop_client* c, pa_mainloop_api *mainloop, const char* host); + +void pa_raop_client_disconnect(pa_raop_client* c); + +void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, unsigned int count); + +#endif From 66cf1d1f66c90b5f92d67a00ea2c1f6404453d97 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:25:37 +0000 Subject: [PATCH 13/56] Some minor tidyup to remove code now in raop client. Still nowhere near functional. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2367 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 69 +--------------------------------- 1 file changed, 2 insertions(+), 67 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index ad10d78fb..f2ddf1cf0 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -36,11 +36,6 @@ #include #include #include -#include -#include -#include -#include -#include #include @@ -58,8 +53,7 @@ #include "rtp.h" #include "sdp.h" #include "sap.h" -#include "rtsp.h" -#include "base64.h" +#include "raop_client.h" #include "module-raop-sink-symdef.h" @@ -88,7 +82,6 @@ PA_MODULE_USAGE( "channel_map="); #define DEFAULT_SINK_NAME "airtunes" -#define AES_CHUNKSIZE 16 struct userdata { pa_core *core; @@ -101,13 +94,7 @@ struct userdata { char *server_name; - // 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_rtsp_context *rtsp; + pa_raop_client *raop; //pa_socket_client *client; pa_memchunk memchunk; @@ -124,51 +111,6 @@ static const char* const valid_modargs[] = { NULL }; -static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { - char n[] = - "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" - "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" - "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" - "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" - "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" - "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; - 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(struct userdata *u, uint8_t *data, int size) -{ - uint8_t *buf; - int i=0, j; - - pa_assert(u); - - memcpy(u->aes_nv, u->aes_iv, AES_CHUNKSIZE); - while (i+AES_CHUNKSIZE <= size) { - buf = data + i; - for (j=0; jaes_nv[j]; - - AES_encrypt(buf, buf, &u->aes); - memcpy(u->aes_nv, buf, AES_CHUNKSIZE); - i += AES_CHUNKSIZE; - } - return i; -} - 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; @@ -307,13 +249,6 @@ int pa__init(pa_module*m) { u->module = m; m->userdata = u; - // Initialise the AES encryption system - pa_random_seed(); - pa_random(u->aes_iv, sizeof(u->aes_iv)); - pa_random(u->aes_key, sizeof(u->aes_key)); - memcpy(u->aes_nv, u->aes_iv, sizeof(u->aes_nv)); - AES_set_encrypt_key(u->aes_key, 128, &u->aes); - pa_memchunk_reset(&u->memchunk); pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); From 8fb58e3a9082bafc3aa7b874d2bba9258b29cb38 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:28:04 +0000 Subject: [PATCH 14/56] Add a function for packing bits into a byte buffer. This will be needed when encoding the audio data in ALAC format. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2368 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 52 +++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 18e596b0b..fbcbe4bb3 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -92,6 +92,58 @@ struct pa_raop_client { void* 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) { char n[] = "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" From 22e299ad3e16d1a2636653a7be9d625ecdc23802 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 18:39:09 +0000 Subject: [PATCH 15/56] Add a pa_iochannel callback for when the RAOP connection connects. Properly handle the sequence of events that establish a connection. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2369 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 83 ++++++++++++++++++++++++++++++++--- src/modules/rtp/raop_client.h | 3 ++ src/modules/rtp/rtsp.c | 22 ++++++++-- 3 files changed, 97 insertions(+), 11 deletions(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index fbcbe4bb3..8f6f2594e 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -73,10 +73,10 @@ struct pa_raop_client { - pa_rtsp_context *rtsp; - pa_socket_client *sc; + pa_mainloop_api *mainloop; const char *host; char *sid; + pa_rtsp_context *rtsp; uint8_t jack_type; uint8_t jack_status; @@ -87,9 +87,13 @@ struct pa_raop_client { 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; pa_iochannel *io; pa_iochannel_cb_t callback; void* userdata; + + uint8_t *buffer; + /*pa_memchunk memchunk;*/ }; /** @@ -219,6 +223,25 @@ static int remove_char_from_string(char *str, char rc) return num; } +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_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; + pa_iochannel_set_callback(c->io, c->callback, c->userdata); +} + static void rtsp_cb(pa_rtsp_context *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) { pa_raop_client* c = userdata; @@ -235,6 +258,7 @@ static void rtsp_cb(pa_rtsp_context *rtsp, pa_rtsp_state state, pa_headerlist* h 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); @@ -273,12 +297,14 @@ static void rtsp_cb(pa_rtsp_context *rtsp, pa_rtsp_state state, pa_headerlist* h } 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[] = ";"; @@ -299,17 +325,24 @@ static void rtsp_cb(pa_rtsp_context *rtsp, pa_rtsp_state state, pa_headerlist* h pa_xfree(token); } pa_xfree(aj); - pa_rtsp_record(c->rtsp); } else { - pa_log("Audio Jack Status missing"); + pa_log_warn("Audio Jack Status missing"); } + pa_rtsp_record(c->rtsp); break; } - case STATE_RECORD: - /* Connect to the actual stream ;) */ - /* if(raopcl_stream_connect(raopcld)) goto erexit; */ + 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->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_TEARDOWN: case STATE_SET_PARAMETER: @@ -330,6 +363,7 @@ int pa_raop_client_connect(pa_raop_client* c, pa_mainloop_api *mainloop, const c pa_assert(c); pa_assert(host); + c->mainloop = mainloop; c->host = host; c->rtsp = pa_rtsp_context_new("iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); @@ -356,5 +390,40 @@ void pa_raop_client_disconnect(pa_raop_client* c) void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, unsigned int count) { + ssize_t l; + uint16_t len; + static uint8_t header[] = { + 0x24, 0x00, 0x00, 0x00, + 0xF0, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + const int header_size = sizeof(header); + pa_assert(c); + pa_assert(buffer); + pa_assert(count > 0); + + c->buffer = pa_xrealloc(c->buffer, (count + header_size + 16)); + memcpy(c->buffer, header, header_size); + len = count + header_size - 4; + + /* store the lenght (endian swapped: make this better) */ + *(c->buffer + 2) = len >> 8; + *(c->buffer + 3) = len & 0xff; + + memcpy((c->buffer+header_size), buffer, count); + aes_encrypt(c, (c->buffer + header_size), count); + len = header_size + count; + + /* TODO: move this into a memchunk/memblock and write only in callback */ + l = pa_iochannel_write(c->io, c->buffer, len); +} + +void pa_raop_client_set_callback(pa_raop_client* c, pa_iochannel_cb_t callback, void *userdata) +{ + pa_assert(c); + + c->callback = callback; + c->userdata = userdata; } diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h index 499b1248e..99c75fdb5 100644 --- a/src/modules/rtp/raop_client.h +++ b/src/modules/rtp/raop_client.h @@ -25,6 +25,7 @@ ***/ #include +#include typedef struct pa_raop_client pa_raop_client; @@ -37,4 +38,6 @@ void pa_raop_client_disconnect(pa_raop_client* c); void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, unsigned int count); +void pa_raop_client_set_callback(pa_raop_client* c, pa_iochannel_cb_t callback, void *userdata); + #endif diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp.c index 55d910186..4f2411abc 100644 --- a/src/modules/rtp/rtsp.c +++ b/src/modules/rtp/rtsp.c @@ -172,6 +172,8 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, /* 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); @@ -220,15 +222,22 @@ static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) { char delimiters[] = " "; pa_rtsp_context *c = userdata; pa_assert(c); + pa_assert(c->io == io); + + if (!pa_iochannel_is_readable(c->io)) { + if (STATE_SETUP == c->state || STATE_ANNOUNCE == c->state) return; + goto do_callback; + } /* TODO: convert this to a pa_ioline based reader */ - if (STATE_CONNECT == c->state) { + if (STATE_SETUP == c->state || STATE_ANNOUNCE == c->state) { response_headers = pa_headerlist_new(); } timeout = 5000; /* read in any response headers */ if (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { const char* token_state = NULL; + pa_log_debug("Response Line: %s", response); timeout = 1000; pa_xfree(pa_split(response, delimiters, &token_state)); @@ -244,12 +253,15 @@ static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) { /* We want to return the headers? */ if (!response_headers) { /* We have no storage, so just clear out the response. */ - while (pa_read_line(c->io, response, sizeof(response), timeout) > 0); + while (pa_read_line(c->io, response, sizeof(response), timeout) > 0){ + pa_log_debug("Response Line: %s", response); + } } else { /* TODO: Move header reading into the headerlist. */ header = NULL; buf = pa_strbuf_new(); while (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { + pa_log_debug("Response Line: %s", response); /* If the first character is a space, it's a continuation header */ if (header && ' ' == response[0]) { /* Add this line to the buffer (sans the space. */ @@ -297,8 +309,8 @@ static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) { } } - /* Deal with a CONNECT response */ - if (STATE_CONNECT == c->state) { + /* 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(response_headers, "Session")); @@ -330,6 +342,7 @@ static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) { } /* Call our callback */ +do_callback: if (c->callback) c->callback(c, c->state, response_headers, c->userdata); @@ -387,6 +400,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata if (res) c->localip = pa_xstrdup(res); } + pa_log_debug("Established RTSP connection from local ip %s", c->localip); } int pa_rtsp_connect(pa_rtsp_context *c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port) { From 6510d97315b9bdf7b1afc204c3dca0a2b0a3a528 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 7 May 2008 00:35:10 +0000 Subject: [PATCH 16/56] Use a more stateful response parser. This makes things fully asyncronous. Some of the continuation headerlist stuff could be moved to headerlist for neatness, but this is OK for now. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2373 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp.c | 320 ++++++++++++++++++----------------------- 1 file changed, 138 insertions(+), 182 deletions(-) diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp.c index 4f2411abc..44cd80b95 100644 --- a/src/modules/rtp/rtsp.c +++ b/src/modules/rtp/rtsp.c @@ -46,85 +46,36 @@ #include #include #include +#include #include "rtsp.h" struct pa_rtsp_context { pa_socket_client *sc; pa_iochannel *io; + pa_ioline *ioline; + pa_rtsp_cb_t callback; - void* userdata; - const char* useragent; + + void *userdata; + const char *useragent; + + pa_rtsp_state state; + uint8_t waiting; + pa_headerlist* headers; - char* localip; - char* url; + char *last_header; + pa_strbuf *header_buffer; + pa_headerlist* response_headers; + + char *localip; + char *url; uint32_t port; uint32_t cseq; - char* session; - char* transport; - pa_rtsp_state state; + char *session; + char *transport; }; -/* - * read one line from the file descriptor - * timeout: msec unit, -1 for infinite - * if CR comes then following LF is expected - * returned string in line is always null terminated, maxlen-1 is maximum string length - */ -static int pa_read_line(pa_iochannel* io, char *line, int maxlen, int timeout) -{ - int i, rval; - int count; - int fd; - char ch; - struct pollfd pfds; - - pa_assert(io); - fd = pa_iochannel_get_recv_fd(io); - - count = 0; - *line = 0; - pfds.events = POLLIN; - pfds.fd = fd; - - for (i=0; i= maxlen-1) - break; - } - - *line = 0; - return count; -} - - static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, const char* content_type, const char* content, int expect_response, @@ -172,8 +123,8 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, /* Our packet is created... now we can send it :) */ hdrs = pa_strbuf_tostring_free(buf); - pa_log_debug("Submitting request:"); - pa_log_debug(hdrs); + /*pa_log_debug("Submitting request:"); + pa_log_debug(hdrs);*/ l = pa_iochannel_write(c->io, hdrs, strlen(hdrs)); pa_xfree(hdrs); @@ -205,125 +156,39 @@ void pa_rtsp_context_free(pa_rtsp_context* c) { 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 io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) { - pa_strbuf* buf; - pa_headerlist* response_headers = NULL; - char response[1024]; - int timeout; +static void headers_read(pa_rtsp_context *c) { char* token; - char* header; - char* delimpos; - char delimiters[] = " "; - pa_rtsp_context *c = userdata; + char delimiters[] = ";"; + pa_assert(c); - pa_assert(c->io == io); - - if (!pa_iochannel_is_readable(c->io)) { - if (STATE_SETUP == c->state || STATE_ANNOUNCE == c->state) return; - goto do_callback; - } - - /* TODO: convert this to a pa_ioline based reader */ - if (STATE_SETUP == c->state || STATE_ANNOUNCE == c->state) { - response_headers = pa_headerlist_new(); - } - timeout = 5000; - /* read in any response headers */ - if (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { - const char* token_state = NULL; - pa_log_debug("Response Line: %s", response); - - timeout = 1000; - pa_xfree(pa_split(response, delimiters, &token_state)); - token = pa_split(response, delimiters, &token_state); - if (!token || strcmp(token, "200")) { - pa_xfree(token); - pa_log("Invalid Response"); - /* TODO: Bail out completely */ - return; - } - pa_xfree(token); - - /* We want to return the headers? */ - if (!response_headers) { - /* We have no storage, so just clear out the response. */ - while (pa_read_line(c->io, response, sizeof(response), timeout) > 0){ - pa_log_debug("Response Line: %s", response); - } - } else { - /* TODO: Move header reading into the headerlist. */ - header = NULL; - buf = pa_strbuf_new(); - while (pa_read_line(c->io, response, sizeof(response), timeout) > 0) { - pa_log_debug("Response Line: %s", response); - /* If the first character is a space, it's a continuation header */ - if (header && ' ' == response[0]) { - /* Add this line to the buffer (sans the space. */ - pa_strbuf_puts(buf, &(response[1])); - continue; - } - - if (header) { - /* This is not a continuation header so let's dump the full - header/value into our proplist */ - pa_headerlist_puts(response_headers, header, pa_strbuf_tostring_free(buf)); - pa_xfree(header); - buf = pa_strbuf_new(); - } - - delimpos = strstr(response, ":"); - if (!delimpos) { - pa_log("Invalid response header"); - return; - } - - 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(buf, delimpos); - } else { - /* Cut our line off so we can copy the header name out */ - *delimpos = '\0'; - } - - /* Save the header name */ - header = pa_xstrdup(response); - } - /* We will have a header left from our looping itteration, so add it in :) */ - if (header) { - /* This is not a continuation header so let's dump it into our proplist */ - pa_headerlist_puts(response_headers, header, pa_strbuf_tostring(buf)); - } - pa_strbuf_free(buf); - } - } + pa_assert(c->response_headers); /* 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(response_headers, "Session")); - c->transport = pa_xstrdup(pa_headerlist_gets(response_headers, "Transport")); + 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(response_headers); + 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. */ - c->port = 0; - delimiters[0] = ';'; while ((token = pa_split(c->transport, delimiters, &token_state))) { if ((pc = strstr(token, "="))) { if (0 == strncmp(token, "server_port", 11)) { @@ -336,33 +201,117 @@ static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) { } if (0 == c->port) { /* Error no server_port in response */ - pa_headerlist_free(response_headers); + pa_headerlist_free(c->response_headers); + c->response_headers = NULL; + pa_log("Invalid SETUP response (no port number)."); return; } } /* Call our callback */ -do_callback: if (c->callback) - c->callback(c, c->state, response_headers, c->userdata); + c->callback(c, c->state, c->response_headers, c->userdata); + + pa_headerlist_free(c->response_headers); + c->response_headers = NULL; +} - if (response_headers) - pa_headerlist_free(response_headers); +static void line_callback(pa_ioline *line, const char *s, void *userdata) { + char *delimpos; + char *s2, *s2p; - /* - if (do_read(u) < 0 || do_write(u) < 0) { + pa_rtsp_context *c = userdata; + pa_assert(line); + pa_assert(c); + pa_assert(s); - if (u->io) { - pa_iochannel_free(u->io); - u->io = NULL; + 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_module_unload_request(u->module); + 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_context *c = userdata; union { @@ -385,7 +334,9 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata } pa_assert(!c->io); c->io = io; - pa_iochannel_set_callback(c->io, io_callback, c); + + 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)) { @@ -401,6 +352,11 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata c->localip = pa_xstrdup(res); } pa_log_debug("Established RTSP connection from local ip %s", c->localip); + + c->waiting = 1; + c->state = STATE_CONNECT; + if (c->callback) + c->callback(c, c->state, NULL, c->userdata); } int pa_rtsp_connect(pa_rtsp_context *c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port) { From e596f42f39232e4e0d36c3764474f73a7ff48fbb Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 7 May 2008 01:10:31 +0000 Subject: [PATCH 17/56] Wrap the io_callback to ensure that all data is written before asking for more. Fix the length type for send_sample (restrict to 16bit value) git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2374 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 82 ++++++++++++++++++++++++++--------- src/modules/rtp/raop_client.h | 2 +- 2 files changed, 62 insertions(+), 22 deletions(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 8f6f2594e..2dd2de949 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -93,6 +93,8 @@ struct pa_raop_client { void* userdata; uint8_t *buffer; + uint8_t *buffer_index; + uint16_t buffer_count; /*pa_memchunk memchunk;*/ }; @@ -205,22 +207,51 @@ void pa_raop_client_free(pa_raop_client* c) pa_xfree(c); } -static int remove_char_from_string(char *str, char rc) +static inline void rtrimchar(char *str, char rc) { - int i=0, j=0, len; - int num = 0; - len = strlen(str); - while (i= str && *sp == rc) { + *sp = '\0'; + sp -= 1; + } +} + +static int pa_raop_client_process(pa_raop_client* c) +{ + ssize_t l; + + pa_assert(c); + + if (!c->buffer_index || !c->buffer_count) + return 1; + + if (!pa_iochannel_is_writable(c->io)) + return 0; + l = pa_iochannel_write(c->io, c->buffer_index, c->buffer_count); + /*pa_log_debug("Wrote %d bytes (from buffer)", (int)l);*/ + if (l == c->buffer_count) { + c->buffer_index = NULL; + c->buffer_count = 0; + return 1; + } + c->buffer_index += l; + c->buffer_count -= l; + /*pa_log_debug("Sill have %d bytes (in buffer)", c->buffer_count);*/ + + return 0; +} + +static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) +{ + pa_raop_client *c = userdata; + + pa_assert(c); + pa_assert(c->io == io); + pa_assert(c->callback); + + if (pa_raop_client_process(c)) { + c->callback(c->io, c->userdata); + } } static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { @@ -239,7 +270,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata } pa_assert(!c->io); c->io = io; - pa_iochannel_set_callback(c->io, c->callback, c->userdata); + pa_iochannel_set_callback(c->io, io_callback, c); } static void rtsp_cb(pa_rtsp_context *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) @@ -268,13 +299,13 @@ static void rtsp_cb(pa_rtsp_context *rtsp, pa_rtsp_state state, pa_headerlist* h /* 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); - remove_char_from_string(key, '='); + rtrimchar(key, '='); pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv); - remove_char_from_string(iv, '='); + rtrimchar(iv, '='); pa_random(&rand_data, sizeof(rand_data)); pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac); - remove_char_from_string(sac, '='); + rtrimchar(sac, '='); pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); sdp = pa_sprintf_malloc( "v=0\r\n" @@ -388,7 +419,7 @@ void pa_raop_client_disconnect(pa_raop_client* c) } -void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, unsigned int count) +void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, uint16_t count) { ssize_t l; uint16_t len; @@ -406,7 +437,7 @@ void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, unsign c->buffer = pa_xrealloc(c->buffer, (count + header_size + 16)); memcpy(c->buffer, header, header_size); - len = count + header_size - 4; + len = header_size + count - 4; /* store the lenght (endian swapped: make this better) */ *(c->buffer + 2) = len >> 8; @@ -417,9 +448,18 @@ void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, unsign len = header_size + count; /* TODO: move this into a memchunk/memblock and write only in callback */ + /*pa_log_debug("Channel status: %d", pa_iochannel_is_writable(c->io)); + pa_log_debug("Writing %d bytes", len);*/ l = pa_iochannel_write(c->io, c->buffer, len); + /*pa_log_debug("Wrote %d bytes", (int)l);*/ + if (l != len) { + c->buffer_index = c->buffer + l; + c->buffer_count = len - l; + } + /*pa_log_debug("Sill have %d bytes (in buffer)", c->buffer_count);*/ } + void pa_raop_client_set_callback(pa_raop_client* c, pa_iochannel_cb_t callback, void *userdata) { pa_assert(c); diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h index 99c75fdb5..1dcf779f9 100644 --- a/src/modules/rtp/raop_client.h +++ b/src/modules/rtp/raop_client.h @@ -36,7 +36,7 @@ int pa_raop_client_connect(pa_raop_client* c, pa_mainloop_api *mainloop, const c void pa_raop_client_disconnect(pa_raop_client* c); -void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, unsigned int count); +void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, uint16_t count); void pa_raop_client_set_callback(pa_raop_client* c, pa_iochannel_cb_t callback, void *userdata); From 41e31ab204ca48ea749c416eb270ebfa2f74b086 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 7 May 2008 01:23:16 +0000 Subject: [PATCH 18/56] Rename rtsp.{c,h} to rtsp_client.{c,h}. Renate pa_rtsp_context to pa_rtsp_client. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2376 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/Makefile.am | 2 +- src/modules/rtp/raop_client.c | 8 ++-- src/modules/rtp/{rtsp.c => rtsp_client.c} | 48 +++++++++++------------ src/modules/rtp/{rtsp.h => rtsp_client.h} | 40 +++++++++---------- 4 files changed, 49 insertions(+), 49 deletions(-) rename src/modules/rtp/{rtsp.c => rtsp_client.c} (90%) rename src/modules/rtp/{rtsp.h => rtsp_client.h} (51%) diff --git a/src/Makefile.am b/src/Makefile.am index 5bd6388b8..91979207c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1008,7 +1008,7 @@ librtp_la_SOURCES = \ modules/rtp/rtp.c modules/rtp/rtp.h \ modules/rtp/sdp.c modules/rtp/sdp.h \ modules/rtp/sap.c modules/rtp/sap.h \ - modules/rtp/rtsp.c modules/rtp/rtsp.h \ + modules/rtp/rtsp_client.c modules/rtp/rtsp_client.h \ modules/rtp/raop_client.c modules/rtp/raop_client.h \ modules/rtp/headerlist.c modules/rtp/headerlist.h \ modules/rtp/base64.c modules/rtp/base64.h diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 2dd2de949..7bfce9305 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -56,7 +56,7 @@ #include #include "raop_client.h" -#include "rtsp.h" +#include "rtsp_client.h" #include "base64.h" #define AES_CHUNKSIZE 16 @@ -76,7 +76,7 @@ struct pa_raop_client { pa_mainloop_api *mainloop; const char *host; char *sid; - pa_rtsp_context *rtsp; + pa_rtsp_client *rtsp; uint8_t jack_type; uint8_t jack_status; @@ -273,7 +273,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata pa_iochannel_set_callback(c->io, io_callback, c); } -static void rtsp_cb(pa_rtsp_context *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *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); @@ -396,7 +396,7 @@ int pa_raop_client_connect(pa_raop_client* c, pa_mainloop_api *mainloop, const c c->mainloop = mainloop; c->host = host; - c->rtsp = pa_rtsp_context_new("iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); + c->rtsp = pa_rtsp_client_new("iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); /* Initialise the AES encryption system */ pa_random_seed(); diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp_client.c similarity index 90% rename from src/modules/rtp/rtsp.c rename to src/modules/rtp/rtsp_client.c index 44cd80b95..c22f801e7 100644 --- a/src/modules/rtp/rtsp.c +++ b/src/modules/rtp/rtsp_client.c @@ -48,9 +48,9 @@ #include #include -#include "rtsp.h" +#include "rtsp_client.h" -struct pa_rtsp_context { +struct pa_rtsp_client { pa_socket_client *sc; pa_iochannel *io; pa_ioline *ioline; @@ -76,7 +76,7 @@ struct pa_rtsp_context { char *transport; }; -static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, +static int pa_rtsp_exec(pa_rtsp_client* c, const char* cmd, const char* content_type, const char* content, int expect_response, pa_headerlist* headers) { @@ -132,10 +132,10 @@ static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, } -pa_rtsp_context* pa_rtsp_context_new(const char* useragent) { - pa_rtsp_context *c; +pa_rtsp_client* pa_rtsp_client_new(const char* useragent) { + pa_rtsp_client *c; - c = pa_xnew0(pa_rtsp_context, 1); + c = pa_xnew0(pa_rtsp_client, 1); c->headers = pa_headerlist_new(); if (useragent) @@ -147,7 +147,7 @@ pa_rtsp_context* pa_rtsp_context_new(const char* useragent) { } -void pa_rtsp_context_free(pa_rtsp_context* c) { +void pa_rtsp_client_free(pa_rtsp_client* c) { if (c) { if (c->sc) pa_socket_client_unref(c->sc); @@ -167,7 +167,7 @@ void pa_rtsp_context_free(pa_rtsp_context* c) { } -static void headers_read(pa_rtsp_context *c) { +static void headers_read(pa_rtsp_client *c) { char* token; char delimiters[] = ";"; @@ -221,7 +221,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { char *delimpos; char *s2, *s2p; - pa_rtsp_context *c = userdata; + pa_rtsp_client *c = userdata; pa_assert(line); pa_assert(c); pa_assert(s); @@ -313,7 +313,7 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { - pa_rtsp_context *c = userdata; + pa_rtsp_client *c = userdata; union { struct sockaddr sa; struct sockaddr_in in; @@ -359,7 +359,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata c->callback(c, c->state, NULL, c->userdata); } -int pa_rtsp_connect(pa_rtsp_context *c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port) { +int pa_rtsp_connect(pa_rtsp_client *c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port) { pa_assert(c); pa_assert(mainloop); pa_assert(hostname); @@ -375,14 +375,14 @@ int pa_rtsp_connect(pa_rtsp_context *c, pa_mainloop_api *mainloop, const char* h return 0; } -void pa_rtsp_set_callback(pa_rtsp_context *c, pa_rtsp_cb_t callback, void *userdata) { +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_context *c) { +void pa_rtsp_disconnect(pa_rtsp_client *c) { pa_assert(c); if (c->io) @@ -391,25 +391,25 @@ void pa_rtsp_disconnect(pa_rtsp_context *c) { } -const char* pa_rtsp_localip(pa_rtsp_context* c) { +const char* pa_rtsp_localip(pa_rtsp_client* c) { pa_assert(c); return c->localip; } -uint32_t pa_rtsp_serverport(pa_rtsp_context* c) { +uint32_t pa_rtsp_serverport(pa_rtsp_client* c) { pa_assert(c); return c->port; } -void pa_rtsp_set_url(pa_rtsp_context* c, const char* url) { +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_context *c, const char* key, const char* value) +void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value) { pa_assert(c); pa_assert(key); @@ -418,7 +418,7 @@ void pa_rtsp_add_header(pa_rtsp_context *c, const char* key, const char* value) pa_headerlist_puts(c->headers, key, value); } -void pa_rtsp_remove_header(pa_rtsp_context *c, const char* key) +void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key) { pa_assert(c); pa_assert(key); @@ -426,7 +426,7 @@ void pa_rtsp_remove_header(pa_rtsp_context *c, const char* key) pa_headerlist_remove(c->headers, key); } -int pa_rtsp_announce(pa_rtsp_context *c, const char* sdp) { +int pa_rtsp_announce(pa_rtsp_client *c, const char* sdp) { pa_assert(c); if (!sdp) return -1; @@ -436,7 +436,7 @@ int pa_rtsp_announce(pa_rtsp_context *c, const char* sdp) { } -int pa_rtsp_setup(pa_rtsp_context* c) { +int pa_rtsp_setup(pa_rtsp_client* c) { pa_headerlist* headers; int rv; @@ -452,7 +452,7 @@ int pa_rtsp_setup(pa_rtsp_context* c) { } -int pa_rtsp_record(pa_rtsp_context* c) { +int pa_rtsp_record(pa_rtsp_client* c) { pa_headerlist* headers; int rv; @@ -473,7 +473,7 @@ int pa_rtsp_record(pa_rtsp_context* c) { } -int pa_rtsp_teardown(pa_rtsp_context *c) { +int pa_rtsp_teardown(pa_rtsp_client *c) { pa_assert(c); c->state = STATE_TEARDOWN; @@ -481,7 +481,7 @@ int pa_rtsp_teardown(pa_rtsp_context *c) { } -int pa_rtsp_setparameter(pa_rtsp_context *c, const char* param) { +int pa_rtsp_setparameter(pa_rtsp_client *c, const char* param) { pa_assert(c); if (!param) return -1; @@ -491,7 +491,7 @@ int pa_rtsp_setparameter(pa_rtsp_context *c, const char* param) { } -int pa_rtsp_flush(pa_rtsp_context *c) { +int pa_rtsp_flush(pa_rtsp_client *c) { pa_headerlist* headers; int rv; diff --git a/src/modules/rtp/rtsp.h b/src/modules/rtp/rtsp_client.h similarity index 51% rename from src/modules/rtp/rtsp.h rename to src/modules/rtp/rtsp_client.h index 6458f851c..0f1daabd8 100644 --- a/src/modules/rtp/rtsp.h +++ b/src/modules/rtp/rtsp_client.h @@ -1,5 +1,5 @@ -#ifndef foortsphfoo -#define foortsphfoo +#ifndef foortspclienthfoo +#define foortspclienthfoo /* $Id$ */ @@ -36,7 +36,7 @@ #include "headerlist.h" -typedef struct pa_rtsp_context pa_rtsp_context; +typedef struct pa_rtsp_client pa_rtsp_client; typedef enum { STATE_CONNECT, STATE_ANNOUNCE, @@ -46,29 +46,29 @@ typedef enum { STATE_SET_PARAMETER, STATE_FLUSH } pa_rtsp_state; -typedef void (*pa_rtsp_cb_t)(pa_rtsp_context *c, pa_rtsp_state state, pa_headerlist* hl, void *userdata); +typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_headerlist* hl, void *userdata); -pa_rtsp_context* pa_rtsp_context_new(const char* useragent); -void pa_rtsp_context_free(pa_rtsp_context* c); +pa_rtsp_client* pa_rtsp_client_new(const char* useragent); +void pa_rtsp_client_free(pa_rtsp_client* c); -int pa_rtsp_connect(pa_rtsp_context* c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port); -void pa_rtsp_set_callback(pa_rtsp_context *c, pa_rtsp_cb_t callback, void *userdata); +int pa_rtsp_connect(pa_rtsp_client* c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port); +void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata); -void pa_rtsp_disconnect(pa_rtsp_context* c); +void pa_rtsp_disconnect(pa_rtsp_client* c); -const char* pa_rtsp_localip(pa_rtsp_context* c); -uint32_t pa_rtsp_serverport(pa_rtsp_context* c); -void pa_rtsp_set_url(pa_rtsp_context* c, const char* url); -void pa_rtsp_add_header(pa_rtsp_context *c, const char* key, const char* value); -void pa_rtsp_remove_header(pa_rtsp_context *c, const char* key); +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_context* c, const char* sdp); +int pa_rtsp_announce(pa_rtsp_client* c, const char* sdp); -int pa_rtsp_setup(pa_rtsp_context* c); -int pa_rtsp_record(pa_rtsp_context* c); -int pa_rtsp_teardown(pa_rtsp_context* c); +int pa_rtsp_setup(pa_rtsp_client* c); +int pa_rtsp_record(pa_rtsp_client* c); +int pa_rtsp_teardown(pa_rtsp_client* c); -int pa_rtsp_setparameter(pa_rtsp_context* c, const char* param); -int pa_rtsp_flush(pa_rtsp_context* c); +int pa_rtsp_setparameter(pa_rtsp_client* c, const char* param); +int pa_rtsp_flush(pa_rtsp_client* c); #endif From 1fb046536a687e7c5eef9a440f66d111cd0e8cb4 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sat, 10 May 2008 23:01:37 +0000 Subject: [PATCH 19/56] Combine pa_raop_client_new and pa_raop_client_connect (no point in having them separate) Convert the iochannel to an fd and do not call a pa_iochannel_cb_t callback but rather trigger the callback on connection and pass the fd. Change pa_raop_client_send_sample to pa_raop_client_encode_sample and work with memchunks. Fix a subtle size bug in the bit writer that techincally isn't triggered in normal operation. Clean up the _free function to actually free stuff. Do the actual ALAC encoding. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2394 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 180 ++++++++++++++++++---------------- src/modules/rtp/raop_client.h | 12 +-- 2 files changed, 102 insertions(+), 90 deletions(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 7bfce9305..0df80e116 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -88,8 +88,8 @@ struct pa_raop_client { uint8_t aes_key[AES_CHUNKSIZE]; /* key for aes-cbc */ pa_socket_client *sc; - pa_iochannel *io; - pa_iochannel_cb_t callback; + int fd; + pa_raop_client_cb_t callback; void* userdata; uint8_t *buffer; @@ -115,7 +115,7 @@ static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uin /* 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; + *size += 1; /* Calc the number of bits left in the current byte of buffer */ bits_left = 7 - *bit_pos + 1; @@ -195,18 +195,6 @@ static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) return i; } -pa_raop_client* pa_raop_client_new(void) -{ - pa_raop_client* c = pa_xnew0(pa_raop_client, 1); - return c; -} - -void pa_raop_client_free(pa_raop_client* c) -{ - pa_assert(c); - pa_xfree(c); -} - static inline void rtrimchar(char *str, char rc) { char *sp = str + strlen(str) - 1; @@ -216,50 +204,14 @@ static inline void rtrimchar(char *str, char rc) } } -static int pa_raop_client_process(pa_raop_client* c) -{ - ssize_t l; - - pa_assert(c); - - if (!c->buffer_index || !c->buffer_count) - return 1; - - if (!pa_iochannel_is_writable(c->io)) - return 0; - l = pa_iochannel_write(c->io, c->buffer_index, c->buffer_count); - /*pa_log_debug("Wrote %d bytes (from buffer)", (int)l);*/ - if (l == c->buffer_count) { - c->buffer_index = NULL; - c->buffer_count = 0; - return 1; - } - c->buffer_index += l; - c->buffer_count -= l; - /*pa_log_debug("Sill have %d bytes (in buffer)", c->buffer_count);*/ - - return 0; -} - -static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void *userdata) -{ - pa_raop_client *c = userdata; - - pa_assert(c); - pa_assert(c->io == io); - pa_assert(c->callback); - - if (pa_raop_client_process(c)) { - c->callback(c->io, c->userdata); - } -} - 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; @@ -268,9 +220,16 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata pa_log("Connection failed: %s", pa_cstrerror(errno)); return; } - pa_assert(!c->io); - c->io = io; - pa_iochannel_set_callback(c->io, io_callback, c); + + c->fd = pa_iochannel_get_send_fd(io); + + pa_iochannel_set_noclose(io, TRUE); + 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) @@ -382,7 +341,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he } } -int pa_raop_client_connect(pa_raop_client* c, pa_mainloop_api *mainloop, const char* host) +pa_raop_client* pa_raop_client_new(pa_mainloop_api *mainloop, const char* host) { char *sci; struct { @@ -390,11 +349,12 @@ int pa_raop_client_connect(pa_raop_client* c, pa_mainloop_api *mainloop, const c uint32_t b; uint32_t c; } rand_data; + pa_raop_client* c = pa_xnew0(pa_raop_client, 1); - pa_assert(c); pa_assert(host); c->mainloop = mainloop; + c->fd = -1; c->host = host; c->rtsp = pa_rtsp_client_new("iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); @@ -411,18 +371,41 @@ int pa_raop_client_connect(pa_raop_client* c, pa_mainloop_api *mainloop, const c sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c); pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); pa_rtsp_set_callback(c->rtsp, rtsp_cb, c); - return pa_rtsp_connect(c->rtsp, mainloop, host, 5000); + if (pa_rtsp_connect(c->rtsp, mainloop, host, 5000)) { + pa_rtsp_client_free(c->rtsp); + pa_xfree(c->aes_iv); + pa_xfree(c->aes_nv); + pa_xfree(c->aes_key); + return NULL; + } + return c; } -void pa_raop_client_disconnect(pa_raop_client* c) -{ +void pa_raop_client_free(pa_raop_client* c) +{ + pa_assert(c); + + pa_rtsp_client_free(c->rtsp); + pa_xfree(c->aes_iv); + pa_xfree(c->aes_nv); + pa_xfree(c->aes_key); + pa_xfree(c); } -void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, uint16_t count) + +static void noop(PA_GCC_UNUSED void* p) {} + +pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, pa_memchunk* raw) { - ssize_t l; - uint16_t len; + uint16_t len, bufmax; + uint8_t *bp, bpos; + uint8_t *ibp, *maxibp; + int size; + uint8_t *p; + uint16_t bsize; + pa_memchunk rv; + size_t length; static uint8_t header[] = { 0x24, 0x00, 0x00, 0x00, 0xF0, 0xFF, 0x00, 0x00, @@ -432,35 +415,66 @@ void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, uint16 const int header_size = sizeof(header); pa_assert(c); - pa_assert(buffer); - pa_assert(count > 0); + pa_assert(c->fd > 0); + pa_assert(raw); + pa_assert(raw->memblock); + pa_assert(raw->length > 0); - c->buffer = pa_xrealloc(c->buffer, (count + header_size + 16)); + /* 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; + c->buffer = pa_xrealloc(c->buffer, bufmax); memcpy(c->buffer, header, header_size); - len = header_size + count - 4; + pa_memchunk_reset(&rv); + rv.memblock = pa_memblock_new_user(mempool, c->buffer, (header_size + length), noop, 1); + + /* Now write the actual samples */ + bp = c->buffer + 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); + rv.length = header_size + size; /* store the lenght (endian swapped: make this better) */ + len = size + header_size - 4; *(c->buffer + 2) = len >> 8; *(c->buffer + 3) = len & 0xff; - memcpy((c->buffer+header_size), buffer, count); - aes_encrypt(c, (c->buffer + header_size), count); - len = header_size + count; - - /* TODO: move this into a memchunk/memblock and write only in callback */ - /*pa_log_debug("Channel status: %d", pa_iochannel_is_writable(c->io)); - pa_log_debug("Writing %d bytes", len);*/ - l = pa_iochannel_write(c->io, c->buffer, len); - /*pa_log_debug("Wrote %d bytes", (int)l);*/ - if (l != len) { - c->buffer_index = c->buffer + l; - c->buffer_count = len - l; - } - /*pa_log_debug("Sill have %d bytes (in buffer)", c->buffer_count);*/ + /* encrypt our data */ + aes_encrypt(c, (c->buffer + header_size), size); + return rv; } -void pa_raop_client_set_callback(pa_raop_client* c, pa_iochannel_cb_t callback, void *userdata) +void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata) { pa_assert(c); diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h index 1dcf779f9..68a1cdb02 100644 --- a/src/modules/rtp/raop_client.h +++ b/src/modules/rtp/raop_client.h @@ -26,18 +26,16 @@ #include #include +#include typedef struct pa_raop_client pa_raop_client; -pa_raop_client* pa_raop_client_new(void); +pa_raop_client* pa_raop_client_new(pa_mainloop_api *mainloop, const char* host); void pa_raop_client_free(pa_raop_client* c); -int pa_raop_client_connect(pa_raop_client* c, pa_mainloop_api *mainloop, const char* host); +pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, pa_memchunk* raw); -void pa_raop_client_disconnect(pa_raop_client* c); - -void pa_raop_client_send_sample(pa_raop_client* c, const uint8_t* buffer, uint16_t count); - -void pa_raop_client_set_callback(pa_raop_client* c, pa_iochannel_cb_t callback, void *userdata); +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); #endif From f97c5debcc9564d368e6d79606df7b3ce6269d58 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 12:18:36 +0000 Subject: [PATCH 20/56] Properly duplicate the hostname passed in on connect. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2396 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 0df80e116..bad747bb9 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -355,7 +355,7 @@ pa_raop_client* pa_raop_client_new(pa_mainloop_api *mainloop, const char* host) c->mainloop = mainloop; c->fd = -1; - c->host = host; + c->host = pa_xstrdup(host); c->rtsp = pa_rtsp_client_new("iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); /* Initialise the AES encryption system */ @@ -390,6 +390,7 @@ void pa_raop_client_free(pa_raop_client* c) pa_xfree(c->aes_iv); pa_xfree(c->aes_nv); pa_xfree(c->aes_key); + pa_xfree(c->host); pa_xfree(c); } From 264a1c2ffc3b38fd88421c7d7ad9ebbab6b7e1bb Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 12:20:14 +0000 Subject: [PATCH 21/56] Add more libraries to librtp now that it's doing a lot more. This currently hacks in -lssl rather than writing a configure hook to detect it as I want to replace this with nss before official release. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2397 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Makefile.am b/src/Makefile.am index 91979207c..5a9b90246 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1013,7 +1013,7 @@ librtp_la_SOURCES = \ modules/rtp/headerlist.c modules/rtp/headerlist.h \ modules/rtp/base64.c modules/rtp/base64.h librtp_la_LDFLAGS = -avoid-version -librtp_la_LIBADD = $(AM_LIBADD) libpulsecore.la +librtp_la_LIBADD = $(AM_LIBADD) libsocket-util.la libiochannel.la libsocket-client.la libioline.la libpulsecore.la -lssl # X11 From d51f5944b7248ec759ab71b0e811ec0f7c655e22 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 12:21:32 +0000 Subject: [PATCH 22/56] A very rough first version of the sink. I can actually play music to my airport now (woot). Still very rough round the edges and I need to handle disconnects etc. but it's all good progress :) git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2398 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 380 +++++++++++++++++++++++---------- 1 file changed, 262 insertions(+), 118 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index f2ddf1cf0..3d08cb86d 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -3,8 +3,7 @@ /*** This file is part of PulseAudio. - Copyright 2004-2006 Lennart Poettering - Copyright 2008 Colin Guthrie + 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 @@ -34,52 +33,50 @@ #include #include #include -#include #include +#include +#include +#include +#include + +#ifdef HAVE_LINUX_SOCKIOS_H +#include +#endif #include +#include #include +#include #include #include #include #include #include -#include +#include +#include #include -#include -#include +#include +#include +#include +#include +#include "module-raop-sink-symdef.h" #include "rtp.h" #include "sdp.h" #include "sap.h" #include "raop_client.h" - -#include "module-raop-sink-symdef.h" - -#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 - - PA_MODULE_AUTHOR("Colin Guthrie"); -PA_MODULE_DESCRIPTION("RAOP Sink (Apple Airport)"); +PA_MODULE_DESCRIPTION("RAOP Sink (Apple Airtunes)"); PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( - "server=
" "sink_name= " + "server=
cookie= " "format= " "channels= " - "rate=" - "channel_map="); + "rate="); #define DEFAULT_SINK_NAME "airtunes" @@ -88,17 +85,35 @@ struct userdata { pa_module *module; pa_sink *sink; - pa_thread *thread; pa_thread_mq thread_mq; pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; + pa_thread *thread; - char *server_name; + 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; + + /*esd_format_t format;*/ + int32_t rate; + + pa_smoother *smoother; + int fd; + + int64_t offset; + int64_t encoding_overhead; + double encoding_ratio; pa_raop_client *raop; - //pa_socket_client *client; - pa_memchunk memchunk; - pa_rtpoll_item *rtpoll_item; + size_t block_size; }; static const char* const valid_modargs[] = { @@ -107,30 +122,64 @@ static const char* const valid_modargs[] = { "format", "channels", "sink_name", - "channel_map", NULL }; +enum { + SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX +}; + 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_GET_LATENCY: { - size_t n = 0; - //int l; + case PA_SINK_MESSAGE_SET_STATE: -#ifdef TIOCINQ - /* - if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0) - n = (size_t) l; - */ -#endif + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { - n += u->memchunk.length; + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } - *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); 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 = pollfd->revents = 0; + + return 0; } } @@ -139,7 +188,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse static void thread_func(void *userdata) { struct userdata *u = userdata; - //int write_type = 0; + int write_type = 0; pa_assert(u); @@ -148,55 +197,113 @@ static void thread_func(void *userdata) { pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); + pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + for (;;) { - struct pollfd *pollfd; int ret; - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + 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 (u->sink->thread_info.state == PA_SINK_RUNNING && pollfd->revents) { - ssize_t l; - void *p; + /* Render some data and write it to the fifo */ + if (PA_SINK_OPENED(u->sink->thread_info.state) && pollfd->revents) { + pa_usec_t usec; + int64_t n; - if (u->memchunk.length <= 0) - pa_sink_render(u->sink, PIPE_BUF, &u->memchunk); + for (;;) { + ssize_t l; + void *p; - pa_assert(u->memchunk.length > 0); + if (u->raw_memchunk.length <= 0) { + /* Grab unencoded data */ + pa_sink_render(u->sink, u->block_size, &u->raw_memchunk); + } + pa_assert(u->raw_memchunk.length > 0); - p = pa_memblock_acquire(u->memchunk.memblock); - //l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); - // Fake the length of the "write". - l = u->memchunk.length; - pa_memblock_release(u->memchunk.memblock); + if (u->encoded_memchunk.length <= 0) { + /* Encode it */ + size_t rl = u->raw_memchunk.length; + if (u->encoded_memchunk.memblock) + pa_memblock_unref(u->encoded_memchunk.memblock); + u->encoded_memchunk = pa_raop_client_encode_sample(u->raop, u->core->mempool, &u->raw_memchunk); + u->encoding_overhead += (u->encoded_memchunk.length - (rl - u->raw_memchunk.length)); + u->encoding_ratio = u->encoded_memchunk.length / (rl - u->raw_memchunk.length); + } + pa_assert(u->encoded_memchunk.length > 0); - pa_assert(l != 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); - if (l < 0) { + pa_assert(l != 0); - if (errno == EINTR) - continue; - else if (errno != EAGAIN) { - pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); - goto fail; + 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; + + if (u->encoded_memchunk.length <= 0) { + pa_memblock_unref(u->encoded_memchunk.memblock); + pa_memchunk_reset(&u->encoded_memchunk); + } + + pollfd->revents = 0; + + if (u->encoded_memchunk.length > 0) + + /* OK, we wrote less that we asked for, + * hence we can assume that the socket + * buffers are full now */ + goto filled_up; + } } - } else { + filled_up: - u->memchunk.index += l; - u->memchunk.length -= l; + /* 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 */ - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - pa_memchunk_reset(&u->memchunk); + n = u->offset; + +#ifdef SIOCOUTQ + { + int l; + if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0) + n -= l; } +#endif - pollfd->revents = 0; + 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 = u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0; + /* Hmm, nothing to do. Let's sleep */ + pollfd->events = PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0; + } if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) goto fail; @@ -204,11 +311,15 @@ static void thread_func(void *userdata) { if (ret == 0) goto finish; - pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + if (u->rtpoll_item) { + struct pollfd* pollfd; - if (pollfd->revents & ~POLLOUT) { - pa_log("FIFO shutdown."); - goto fail; + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~POLLOUT) { + pa_log("FIFO shutdown."); + goto fail; + } } } @@ -222,25 +333,41 @@ finish: pa_log_debug("Thread shutting down"); } +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; + + 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); +} + int pa__init(pa_module*m) { - struct userdata *u; - //struct stat st; + struct userdata *u = NULL; + const char *p; pa_sample_spec ss; - pa_channel_map map; - pa_modargs *ma; + pa_modargs *ma = NULL; char *t; - struct pollfd *pollfd; pa_assert(m); if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { - pa_log("Failed to parse module arguments."); + pa_log("failed to parse module arguments"); goto fail; } ss = m->core->default_sample_spec; - if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { - pa_log("Invalid sample format specification or channel map"); + 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; } @@ -248,54 +375,58 @@ int pa__init(pa_module*m) { 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); + pa_memchunk_reset(&u->raw_memchunk); + pa_memchunk_reset(&u->encoded_memchunk); + u->offset = 0; + u->encoding_overhead = 0; + u->encoding_ratio = 1.0; - pa_memchunk_reset(&u->memchunk); pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + u->rtpoll_item = NULL; - u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", 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); - // Open a connection to the server... this is just to connect and test.... - /* - mkfifo(u->filename, 0666); - if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { - pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); - goto fail; - } + u->read_data = u->write_data = NULL; + u->read_index = u->write_index = u->read_length = u->write_length = 0; - pa_make_fd_cloexec(u->fd); - pa_make_fd_nonblock(u->fd); + /*u->state = STATE_AUTH;*/ + u->latency = 0; - if (fstat(u->fd, &st) < 0) { - pa_log("fstat('%s'): %s", u->filename, pa_cstrerror(errno)); - goto fail; - } - - if (!S_ISFIFO(st.st_mode)) { - pa_log("'%s' is not a FIFO.", u->filename); - goto fail; - } - */ - if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) { pa_log("Failed to create sink."); goto fail; } u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - u->sink->flags = PA_SINK_LATENCY; + u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK; pa_sink_set_module(u->sink, m); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Airtunes sink '%s'", u->server_name)); + + if (!(p = pa_modargs_get_value(ma, "server", NULL))) { + pa_log("No server argument given."); + goto fail; + } + + if (!(u->raop = pa_raop_client_new(u->core->mainloop, p))) { + pa_log("Failed to connect to server."); + goto fail; + } + + pa_raop_client_set_callback(u->raop, on_connection, u); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Airtunes sink '%s'", p)); pa_xfree(t); - 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 = pollfd->revents = 0; if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); @@ -319,7 +450,6 @@ fail: void pa__done(pa_module*m) { struct userdata *u; - pa_assert(m); if (!(u = m->userdata)) @@ -338,15 +468,29 @@ void pa__done(pa_module*m) { if (u->sink) pa_sink_unref(u->sink); - if (u->memchunk.memblock) - pa_memblock_unref(u->memchunk.memblock); - if (u->rtpoll_item) pa_rtpoll_item_free(u->rtpoll_item); if (u->rtpoll) pa_rtpoll_free(u->rtpoll); - pa_xfree(u->server_name); + 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); } From 4dd318519fbec1811a16dca05aca859da74b60c2 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 13:32:09 +0000 Subject: [PATCH 23/56] Do not assert on NULL values of s. This means the connection was closed. This change somehow kills the mainloop with an assert, so I need to sort that out. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2399 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp_client.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index c22f801e7..5665c9f63 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -224,7 +224,14 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { pa_rtsp_client *c = userdata; pa_assert(line); pa_assert(c); - pa_assert(s); + + if (!s) { + pa_log_warn("Connection closed"); + pa_ioline_unref(c->ioline); + c->ioline = NULL; + pa_rtsp_disconnect(c); + return; + } s2 = pa_xstrdup(s); /* Trim trailing carriage returns */ From 5eecfa2e3f3abcacc9df2776cba798598e5fb6ee Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 13:35:01 +0000 Subject: [PATCH 24/56] Move the ownership of the encoded data memchunk into the raop_client. This does not seem to fix the pool full messages so I'll have to try and suss that out. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2400 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 17 ++++--------- src/modules/rtp/raop_client.c | 45 +++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 3d08cb86d..f6f93a468 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -1,4 +1,4 @@ -/* $Id$ */ +/* $Id: module-esound-sink.c 2043 2007-11-09 18:25:40Z lennart $ */ /*** This file is part of PulseAudio. @@ -109,6 +109,7 @@ struct userdata { int64_t offset; int64_t encoding_overhead; + int32_t next_encoding_overhead; double encoding_ratio; pa_raop_client *raop; @@ -224,10 +225,9 @@ static void thread_func(void *userdata) { if (u->encoded_memchunk.length <= 0) { /* Encode it */ size_t rl = u->raw_memchunk.length; - if (u->encoded_memchunk.memblock) - pa_memblock_unref(u->encoded_memchunk.memblock); + u->encoding_overhead += u->next_encoding_overhead; u->encoded_memchunk = pa_raop_client_encode_sample(u->raop, u->core->mempool, &u->raw_memchunk); - u->encoding_overhead += (u->encoded_memchunk.length - (rl - u->raw_memchunk.length)); + 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); } pa_assert(u->encoded_memchunk.length > 0); @@ -259,11 +259,6 @@ static void thread_func(void *userdata) { u->encoded_memchunk.index += l; u->encoded_memchunk.length -= l; - if (u->encoded_memchunk.length <= 0) { - pa_memblock_unref(u->encoded_memchunk.memblock); - pa_memchunk_reset(&u->encoded_memchunk); - } - pollfd->revents = 0; if (u->encoded_memchunk.length > 0) @@ -381,6 +376,7 @@ int pa__init(pa_module*m) { pa_memchunk_reset(&u->encoded_memchunk); u->offset = 0; u->encoding_overhead = 0; + u->next_encoding_overhead = 0; u->encoding_ratio = 1.0; pa_thread_mq_init(&u->thread_mq, m->core->mainloop); @@ -477,9 +473,6 @@ void pa__done(pa_module*m) { 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); diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index bad747bb9..b4cbd2bab 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -74,7 +74,7 @@ struct pa_raop_client { pa_mainloop_api *mainloop; - const char *host; + char *host; char *sid; pa_rtsp_client *rtsp; @@ -93,9 +93,10 @@ struct pa_raop_client { void* userdata; uint8_t *buffer; + uint32_t buffer_length; uint8_t *buffer_index; uint16_t buffer_count; - /*pa_memchunk memchunk;*/ + pa_memchunk memchunk; }; /** @@ -356,6 +357,7 @@ pa_raop_client* pa_raop_client_new(pa_mainloop_api *mainloop, const char* host) c->mainloop = mainloop; c->fd = -1; c->host = pa_xstrdup(host); + pa_memchunk_reset(&c->memchunk); c->rtsp = pa_rtsp_client_new("iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); /* Initialise the AES encryption system */ @@ -386,6 +388,9 @@ void pa_raop_client_free(pa_raop_client* c) { pa_assert(c); + if (c->memchunk.memblock) + pa_memblock_unref(c->memchunk.memblock); + pa_xfree(c->buffer); pa_rtsp_client_free(c->rtsp); pa_xfree(c->aes_iv); pa_xfree(c->aes_nv); @@ -403,9 +408,8 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, uint8_t *bp, bpos; uint8_t *ibp, *maxibp; int size; - uint8_t *p; + uint8_t *b, *p; uint16_t bsize; - pa_memchunk rv; size_t length; static uint8_t header[] = { 0x24, 0x00, 0x00, 0x00, @@ -427,13 +431,22 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ bufmax = length + header_size + 16; - c->buffer = pa_xrealloc(c->buffer, bufmax); - memcpy(c->buffer, header, header_size); - pa_memchunk_reset(&rv); - rv.memblock = pa_memblock_new_user(mempool, c->buffer, (header_size + length), noop, 1); + if (bufmax > c->buffer_length) { + if (c->memchunk.memblock) + pa_memblock_unref(c->memchunk.memblock); + + c->buffer = pa_xrealloc(c->buffer, bufmax); + c->buffer_length = bufmax; + pa_log_debug("Creating new memblock"); + c->memchunk.memblock = pa_memblock_new_user(mempool, c->buffer, bufmax, noop, 0); + } + c->memchunk.index = 0; + c->memchunk.length = 0; + b = pa_memblock_acquire(c->memchunk.memblock); + memcpy(b, header, header_size); /* Now write the actual samples */ - bp = c->buffer + header_size; + 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 @@ -462,16 +475,20 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, raw->length -= 4; } pa_memblock_release(raw->memblock); - rv.length = header_size + size; + c->memchunk.length = header_size + size; /* store the lenght (endian swapped: make this better) */ len = size + header_size - 4; - *(c->buffer + 2) = len >> 8; - *(c->buffer + 3) = len & 0xff; + *(b + 2) = len >> 8; + *(b + 3) = len & 0xff; /* encrypt our data */ - aes_encrypt(c, (c->buffer + header_size), size); - return rv; + aes_encrypt(c, (b + header_size), size); + + /* We're done with the chunk */ + pa_memblock_release(c->memchunk.memblock); + + return c->memchunk; } From 899492c31581f5591cd9437052dda15ad02ec0ac Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 14:18:48 +0000 Subject: [PATCH 25/56] Add a new callback structure to propigate when the RTSP connection dies git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2402 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 25 ++++++++++++++++++++++++- src/modules/rtp/raop_client.h | 3 +++ src/modules/rtp/rtsp_client.c | 7 ++++--- src/modules/rtp/rtsp_client.h | 3 ++- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index b4cbd2bab..75881c6e3 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -89,8 +89,11 @@ struct pa_raop_client { pa_socket_client *sc; int fd; + pa_raop_client_cb_t callback; void* userdata; + pa_raop_client_closed_cb_t closed_callback; + void* closed_userdata; uint8_t *buffer; uint32_t buffer_length; @@ -339,6 +342,19 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he case STATE_SET_PARAMETER: case STATE_FLUSH: break; + case STATE_DISCONNECTED: + pa_assert(c->closed_callback); + pa_log_debug("RTSP channel closed"); + if (c->fd > 0) { + pa_close(c->fd); + c->fd = -1; + } + if (c->sc) { + pa_socket_client_unref(c->sc); + c->sc = NULL; + } + c->closed_callback(c->closed_userdata); + break; } } @@ -437,7 +453,6 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, c->buffer = pa_xrealloc(c->buffer, bufmax); c->buffer_length = bufmax; - pa_log_debug("Creating new memblock"); c->memchunk.memblock = pa_memblock_new_user(mempool, c->buffer, bufmax, noop, 0); } c->memchunk.index = 0; @@ -499,3 +514,11 @@ void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback 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/rtp/raop_client.h b/src/modules/rtp/raop_client.h index 68a1cdb02..1ec56ca99 100644 --- a/src/modules/rtp/raop_client.h +++ b/src/modules/rtp/raop_client.h @@ -38,4 +38,7 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, 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/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 5665c9f63..22f0f0c11 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -173,6 +173,7 @@ static void headers_read(pa_rtsp_client *c) { pa_assert(c); pa_assert(c->response_headers); + pa_assert(c->callback); /* Deal with a SETUP response */ if (STATE_SETUP == c->state) { @@ -209,8 +210,7 @@ static void headers_read(pa_rtsp_client *c) { } /* Call our callback */ - if (c->callback) - c->callback(c, c->state, c->response_headers, c->userdata); + c->callback(c, c->state, c->response_headers, c->userdata); pa_headerlist_free(c->response_headers); c->response_headers = NULL; @@ -224,12 +224,13 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { pa_rtsp_client *c = userdata; pa_assert(line); pa_assert(c); + pa_assert(c->callback); if (!s) { - pa_log_warn("Connection closed"); pa_ioline_unref(c->ioline); c->ioline = NULL; pa_rtsp_disconnect(c); + c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); return; } diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h index 0f1daabd8..3c5280c24 100644 --- a/src/modules/rtp/rtsp_client.h +++ b/src/modules/rtp/rtsp_client.h @@ -44,7 +44,8 @@ typedef enum { STATE_RECORD, STATE_TEARDOWN, STATE_SET_PARAMETER, - STATE_FLUSH + STATE_FLUSH, + STATE_DISCONNECTED } pa_rtsp_state; typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_headerlist* hl, void *userdata); From ec9a618768790055fef00a46866b4e0e1fa33048 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 14:19:41 +0000 Subject: [PATCH 26/56] Listen to the on_close callback. This still causes asserts in the mainloop, so this is not a complete solution git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2403 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index f6f93a468..090f04fad 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -340,6 +340,14 @@ static void on_connection(PA_GCC_UNUSED int fd, void*userdata) { 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("Control connection closed."); + pa_module_unload_request(u->module); +} + int pa__init(pa_module*m) { struct userdata *u = NULL; const char *p; @@ -420,6 +428,7 @@ int pa__init(pa_module*m) { } pa_raop_client_set_callback(u->raop, on_connection, u); + pa_raop_client_set_closed_callback(u->raop, on_close, u); pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Airtunes sink '%s'", p)); pa_xfree(t); From e00127fe245cc2065f74617dada3b474b88907af Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 14:57:30 +0000 Subject: [PATCH 27/56] Various changes suggested by Lennart. Store the core* rather than just the mainloop as we can reuse the mempool without passing it in as an argument. const'ify and deconst'ify some vars git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2404 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 4 ++-- src/modules/rtp/raop_client.c | 24 ++++++++++++------------ src/modules/rtp/raop_client.h | 6 +++--- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 090f04fad..0a7ce171c 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -226,7 +226,7 @@ static void thread_func(void *userdata) { /* Encode it */ size_t rl = u->raw_memchunk.length; u->encoding_overhead += u->next_encoding_overhead; - u->encoded_memchunk = pa_raop_client_encode_sample(u->raop, u->core->mempool, &u->raw_memchunk); + u->encoded_memchunk = pa_raop_client_encode_sample(u->raop, &u->raw_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); } @@ -422,7 +422,7 @@ int pa__init(pa_module*m) { goto fail; } - if (!(u->raop = pa_raop_client_new(u->core->mainloop, p))) { + if (!(u->raop = pa_raop_client_new(u->core, p))) { pa_log("Failed to connect to server."); goto fail; } diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 75881c6e3..92be6cd77 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -73,7 +73,7 @@ struct pa_raop_client { - pa_mainloop_api *mainloop; + pa_core *core; char *host; char *sid; pa_rtsp_client *rtsp; @@ -155,14 +155,14 @@ static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uin } static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { - char n[] = + 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=="; - char e[] = "AQAB"; + const char e[] = "AQAB"; uint8_t modules[256]; uint8_t exponent[8]; int size; @@ -330,7 +330,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he uint32_t port = pa_rtsp_serverport(c->rtsp); pa_log_debug("RAOP: RECORDED"); - if (!(c->sc = pa_socket_client_new_string(c->mainloop, c->host, port))) { + 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; } @@ -358,7 +358,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he } } -pa_raop_client* pa_raop_client_new(pa_mainloop_api *mainloop, const char* host) +pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) { char *sci; struct { @@ -368,16 +368,16 @@ pa_raop_client* pa_raop_client_new(pa_mainloop_api *mainloop, const char* host) } rand_data; pa_raop_client* c = pa_xnew0(pa_raop_client, 1); + pa_assert(core); pa_assert(host); - c->mainloop = mainloop; + c->core = core; c->fd = -1; c->host = pa_xstrdup(host); pa_memchunk_reset(&c->memchunk); c->rtsp = pa_rtsp_client_new("iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); /* Initialise the AES encryption system */ - pa_random_seed(); 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)); @@ -389,7 +389,7 @@ pa_raop_client* pa_raop_client_new(pa_mainloop_api *mainloop, const char* host) sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c); pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); pa_rtsp_set_callback(c->rtsp, rtsp_cb, c); - if (pa_rtsp_connect(c->rtsp, mainloop, host, 5000)) { + if (pa_rtsp_connect(c->rtsp, c->core->mainloop, host, 5000)) { pa_rtsp_client_free(c->rtsp); pa_xfree(c->aes_iv); pa_xfree(c->aes_nv); @@ -418,14 +418,14 @@ void pa_raop_client_free(pa_raop_client* c) static void noop(PA_GCC_UNUSED void* p) {} -pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, pa_memchunk* raw) +pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw) { uint16_t len, bufmax; uint8_t *bp, bpos; uint8_t *ibp, *maxibp; int size; uint8_t *b, *p; - uint16_t bsize; + uint32_t bsize; size_t length; static uint8_t header[] = { 0x24, 0x00, 0x00, 0x00, @@ -433,7 +433,7 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }; - const int header_size = sizeof(header); + int header_size = sizeof(header); pa_assert(c); pa_assert(c->fd > 0); @@ -453,7 +453,7 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, c->buffer = pa_xrealloc(c->buffer, bufmax); c->buffer_length = bufmax; - c->memchunk.memblock = pa_memblock_new_user(mempool, c->buffer, bufmax, noop, 0); + c->memchunk.memblock = pa_memblock_new_user(c->core->mempool, c->buffer, bufmax, noop, 0); } c->memchunk.index = 0; c->memchunk.length = 0; diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h index 1ec56ca99..b2817e599 100644 --- a/src/modules/rtp/raop_client.h +++ b/src/modules/rtp/raop_client.h @@ -26,14 +26,14 @@ #include #include -#include +#include typedef struct pa_raop_client pa_raop_client; -pa_raop_client* pa_raop_client_new(pa_mainloop_api *mainloop, const char* host); +pa_raop_client* pa_raop_client_new(pa_core *core, const char* host); void pa_raop_client_free(pa_raop_client* c); -pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_mempool* mempool, pa_memchunk* raw); +pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw); 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); From d195d06da7009db985c0a5827b096bc39dd994bf Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 15:06:14 +0000 Subject: [PATCH 28/56] Change suggested by Lennart. Do not return a memchunk, instead pass in the pointer. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2405 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 5 ++++- src/modules/rtp/raop_client.c | 30 ++++++++++++++---------------- src/modules/rtp/raop_client.h | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 0a7ce171c..3d0eaeff8 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -226,7 +226,7 @@ static void thread_func(void *userdata) { /* Encode it */ size_t rl = u->raw_memchunk.length; u->encoding_overhead += u->next_encoding_overhead; - u->encoded_memchunk = pa_raop_client_encode_sample(u->raop, &u->raw_memchunk); + 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); } @@ -482,6 +482,9 @@ void pa__done(pa_module*m) { 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); diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 92be6cd77..15810170d 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -96,10 +96,9 @@ struct pa_raop_client { void* closed_userdata; uint8_t *buffer; - uint32_t buffer_length; + size_t buffer_length; uint8_t *buffer_index; uint16_t buffer_count; - pa_memchunk memchunk; }; /** @@ -374,7 +373,6 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) c->core = core; c->fd = -1; c->host = pa_xstrdup(host); - pa_memchunk_reset(&c->memchunk); c->rtsp = pa_rtsp_client_new("iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); /* Initialise the AES encryption system */ @@ -404,8 +402,6 @@ void pa_raop_client_free(pa_raop_client* c) { pa_assert(c); - if (c->memchunk.memblock) - pa_memblock_unref(c->memchunk.memblock); pa_xfree(c->buffer); pa_rtsp_client_free(c->rtsp); pa_xfree(c->aes_iv); @@ -418,9 +414,10 @@ void pa_raop_client_free(pa_raop_client* c) static void noop(PA_GCC_UNUSED void* p) {} -pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw) +int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) { - uint16_t len, bufmax; + uint16_t len; + size_t bufmax; uint8_t *bp, bpos; uint8_t *ibp, *maxibp; int size; @@ -440,6 +437,7 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw) 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); @@ -448,16 +446,16 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw) /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ bufmax = length + header_size + 16; if (bufmax > c->buffer_length) { - if (c->memchunk.memblock) - pa_memblock_unref(c->memchunk.memblock); + if (encoded->memblock) + pa_memblock_unref(encoded->memblock); c->buffer = pa_xrealloc(c->buffer, bufmax); c->buffer_length = bufmax; - c->memchunk.memblock = pa_memblock_new_user(c->core->mempool, c->buffer, bufmax, noop, 0); + encoded->memblock = pa_memblock_new_user(c->core->mempool, c->buffer, bufmax, noop, 0); } - c->memchunk.index = 0; - c->memchunk.length = 0; - b = pa_memblock_acquire(c->memchunk.memblock); + encoded->index = 0; + encoded->length = 0; + b = pa_memblock_acquire(encoded->memblock); memcpy(b, header, header_size); /* Now write the actual samples */ @@ -490,7 +488,7 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw) raw->length -= 4; } pa_memblock_release(raw->memblock); - c->memchunk.length = header_size + size; + encoded->length = header_size + size; /* store the lenght (endian swapped: make this better) */ len = size + header_size - 4; @@ -501,9 +499,9 @@ pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw) aes_encrypt(c, (b + header_size), size); /* We're done with the chunk */ - pa_memblock_release(c->memchunk.memblock); + pa_memblock_release(encoded->memblock); - return c->memchunk; + return 0; } diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h index b2817e599..303fdaa46 100644 --- a/src/modules/rtp/raop_client.h +++ b/src/modules/rtp/raop_client.h @@ -33,7 +33,7 @@ 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); -pa_memchunk pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw); +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); From 4b7b7b15d73a5f2a98229b12406b4397563d2983 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 15:12:20 +0000 Subject: [PATCH 29/56] Fix up IPv6 address format to enclose it in [] git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2406 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp_client.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 22f0f0c11..193248eb8 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -352,12 +352,14 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata const char *res = NULL; if (AF_INET == sa.sa.sa_family) { - res = inet_ntop(sa.sa.sa_family, &sa.in.sin_addr, buf, sizeof(buf)); + 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) { - res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)); + if ((res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)))) { + c->localip = pa_sprintf_malloc("[%s]", res); + } } - if (res) - c->localip = pa_xstrdup(res); } pa_log_debug("Established RTSP connection from local ip %s", c->localip); From cb8c5a925fc22819626cbe4525b1d334db75d071 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 15:34:37 +0000 Subject: [PATCH 30/56] Some misc fixes. consts, base64 optimisation (not that it will be with us long anyway), and c comments git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2407 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/base64.c | 12 ++++++------ src/modules/rtp/headerlist.c | 2 +- src/modules/rtp/raop_client.c | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/modules/rtp/base64.c b/src/modules/rtp/base64.c index 043ef5a8a..980b018e6 100644 --- a/src/modules/rtp/base64.c +++ b/src/modules/rtp/base64.c @@ -37,16 +37,16 @@ #include "base64.h" -static char base64_chars[] = +static const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; static int pos(char c) { - char *p; - for (p = base64_chars; *p; p++) - if (*p == c) - return p - base64_chars; - return -1; + 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) diff --git a/src/modules/rtp/headerlist.c b/src/modules/rtp/headerlist.c index 8bdc7251d..de8710b7e 100644 --- a/src/modules/rtp/headerlist.c +++ b/src/modules/rtp/headerlist.c @@ -102,7 +102,7 @@ int pa_headerlist_putsappend(pa_headerlist *p, const char *key, const char *valu hdr->value = pa_xstrdup(value); add = TRUE; } else { - void *newval = (void*)pa_sprintf_malloc("%s%s", (char*)hdr->value, value); + void *newval = pa_sprintf_malloc("%s%s", (char*)hdr->value, value); pa_xfree(hdr->value); hdr->value = newval; } diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 15810170d..fc42340c1 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -461,13 +461,13 @@ int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchun /* 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 + 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); From be73d378f5fd5ea1aedcc75b0c7c1e1a82a27047 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 15:43:56 +0000 Subject: [PATCH 31/56] unref the raw data memblock before requesting more data. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2408 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 3d0eaeff8..79c517aa0 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -217,6 +217,10 @@ static void thread_func(void *userdata) { void *p; 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); } From eca94fee59ba68603f1c34f5a4abe55dd7d9d638 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 16:38:33 +0000 Subject: [PATCH 32/56] Don't try to free stack variables. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2409 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index fc42340c1..a9b9ab1d3 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -389,9 +389,6 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) pa_rtsp_set_callback(c->rtsp, rtsp_cb, c); if (pa_rtsp_connect(c->rtsp, c->core->mainloop, host, 5000)) { pa_rtsp_client_free(c->rtsp); - pa_xfree(c->aes_iv); - pa_xfree(c->aes_nv); - pa_xfree(c->aes_key); return NULL; } return c; @@ -404,9 +401,6 @@ void pa_raop_client_free(pa_raop_client* c) pa_xfree(c->buffer); pa_rtsp_client_free(c->rtsp); - pa_xfree(c->aes_iv); - pa_xfree(c->aes_nv); - pa_xfree(c->aes_key); pa_xfree(c->host); pa_xfree(c); } From 92166846913ebb5e86f36352e20c9ca4f4bf23ae Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 16:40:26 +0000 Subject: [PATCH 33/56] Do not prefix internal function rtsp_exec. Change port to be 16 bits Do not free stuff on closure as this happens further up the stack. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2410 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp_client.c | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 193248eb8..248397298 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -70,13 +70,13 @@ struct pa_rtsp_client { char *localip; char *url; - uint32_t port; + uint16_t port; uint32_t cseq; char *session; char *transport; }; -static int pa_rtsp_exec(pa_rtsp_client* c, const char* cmd, +static int rtsp_exec(pa_rtsp_client* c, const char* cmd, const char* content_type, const char* content, int expect_response, pa_headerlist* headers) { @@ -193,7 +193,7 @@ static void headers_read(pa_rtsp_client *c) { while ((token = pa_split(c->transport, delimiters, &token_state))) { if ((pc = strstr(token, "="))) { if (0 == strncmp(token, "server_port", 11)) { - pa_atou(pc+1, &c->port); + pa_atou(pc+1, (uint32_t*)(&c->port)); pa_xfree(token); break; } @@ -227,9 +227,6 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { pa_assert(c->callback); if (!s) { - pa_ioline_unref(c->ioline); - c->ioline = NULL; - pa_rtsp_disconnect(c); c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); return; } @@ -442,7 +439,7 @@ int pa_rtsp_announce(pa_rtsp_client *c, const char* sdp) { return -1; c->state = STATE_ANNOUNCE; - return pa_rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL); + return rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL); } @@ -456,7 +453,7 @@ int pa_rtsp_setup(pa_rtsp_client* c) { pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); c->state = STATE_SETUP; - rv = pa_rtsp_exec(c, "SETUP", NULL, NULL, 1, headers); + rv = rtsp_exec(c, "SETUP", NULL, NULL, 1, headers); pa_headerlist_free(headers); return rv; } @@ -477,7 +474,7 @@ int pa_rtsp_record(pa_rtsp_client* c) { pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); c->state = STATE_RECORD; - rv = pa_rtsp_exec(c, "RECORD", NULL, NULL, 1, headers); + rv = rtsp_exec(c, "RECORD", NULL, NULL, 1, headers); pa_headerlist_free(headers); return rv; } @@ -487,7 +484,7 @@ int pa_rtsp_teardown(pa_rtsp_client *c) { pa_assert(c); c->state = STATE_TEARDOWN; - return pa_rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); + return rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); } @@ -497,7 +494,7 @@ int pa_rtsp_setparameter(pa_rtsp_client *c, const char* param) { return -1; c->state = STATE_SET_PARAMETER; - return pa_rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL); + return rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL); } @@ -511,7 +508,7 @@ int pa_rtsp_flush(pa_rtsp_client *c) { pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); c->state = STATE_FLUSH; - rv = pa_rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers); + rv = rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers); pa_headerlist_free(headers); return rv; } From 3767cdb6d16c5817eb489129585fb353e3ad6afa Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 17:02:19 +0000 Subject: [PATCH 34/56] Do tidy up on disconnection. Only clear IO related stuff if this free() was triggered deliberatly (i.e. not by server side disconnect) git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2411 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 4 +++- src/modules/rtp/rtsp_client.c | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index a9b9ab1d3..e5a373d04 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -344,6 +344,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he case STATE_DISCONNECTED: pa_assert(c->closed_callback); pa_log_debug("RTSP channel closed"); + c->rtsp = NULL; if (c->fd > 0) { pa_close(c->fd); c->fd = -1; @@ -400,7 +401,8 @@ void pa_raop_client_free(pa_raop_client* c) pa_assert(c); pa_xfree(c->buffer); - pa_rtsp_client_free(c->rtsp); + if (c->rtsp) + pa_rtsp_client_free(c->rtsp); pa_xfree(c->host); pa_xfree(c); } diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 248397298..f9fe9bfe9 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -151,6 +151,10 @@ 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->url); pa_xfree(c->localip); @@ -227,6 +231,10 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { pa_assert(c->callback); if (!s) { + /* Keep the ioline/iochannel open as they will be freed automatically */ + c->ioline = NULL; + c->io = NULL; + pa_rtsp_client_free(c); c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); return; } From 6c1dd6e54b4b5b4213467d156abc9f260c63aaa3 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 21:04:45 +0000 Subject: [PATCH 35/56] Move the encoding loop around a bit such that it does not grab the data and keep it for the next loop iteration. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2481 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 79c517aa0..e17198cd2 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -216,17 +216,19 @@ static void thread_func(void *userdata) { ssize_t l; void *p; - 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); - if (u->encoded_memchunk.length <= 0) { + 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); + p = pa_memblock_acquire(u->raw_memchunk.memblock); + pa_memblock_release(u->raw_memchunk.memblock); + } + pa_assert(u->raw_memchunk.length > 0); + /* Encode it */ size_t rl = u->raw_memchunk.length; u->encoding_overhead += u->next_encoding_overhead; From 6dc5e0797738d6afe2c3c99750abd2fb26fed9a9 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 21:05:53 +0000 Subject: [PATCH 36/56] Set the send buffer size to prevent rendering silence in amongst our good data (this should be more sophisticated but that can wait for a glitch-free port) git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2482 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index e5a373d04..cdf9150df 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -227,6 +227,7 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata 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); From 8108121fa76e51c36772a592f74b075dcaf7c4cb Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 21:10:08 +0000 Subject: [PATCH 37/56] Set forgotten keyword property git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2483 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index e17198cd2..42092e061 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -1,4 +1,4 @@ -/* $Id: module-esound-sink.c 2043 2007-11-09 18:25:40Z lennart $ */ +/* $Id$ */ /*** This file is part of PulseAudio. From b93e9e80ecbdc90c0bf6f90c5bd4aafcdb4d2488 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 23:02:30 +0000 Subject: [PATCH 38/56] Keep track of the memblock pointer internally and do not rely on subsequent calls to pass it back in for unref'ing git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2484 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index cdf9150df..7ba1be747 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -96,6 +96,7 @@ struct pa_raop_client { void* closed_userdata; uint8_t *buffer; + pa_memblock *memblock; size_t buffer_length; uint8_t *buffer_index; uint16_t buffer_count; @@ -443,15 +444,14 @@ int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchun /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ bufmax = length + header_size + 16; if (bufmax > c->buffer_length) { - if (encoded->memblock) - pa_memblock_unref(encoded->memblock); - c->buffer = pa_xrealloc(c->buffer, bufmax); c->buffer_length = bufmax; - encoded->memblock = pa_memblock_new_user(c->core->mempool, c->buffer, bufmax, noop, 0); + if (c->memblock) + pa_memblock_unref(c->memblock); + c->memblock = pa_memblock_new_user(c->core->mempool, c->buffer, bufmax, noop, 0); } - encoded->index = 0; - encoded->length = 0; + pa_memchunk_reset(encoded); + encoded->memblock = c->memblock; b = pa_memblock_acquire(encoded->memblock); memcpy(b, header, header_size); From 13bc07587564977a64222f5a4075b7fa63ac5548 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 23:43:51 +0000 Subject: [PATCH 39/56] A few related changes: * Change the encode_sample routine to simply return normal memchunks allocated from the mempool. * unref the memchunks returned from encode_sample when we are done with them. * Create an encoded 'silence' sample and play this at all times to prevent hangup and to 'hog' the airtunes device This now works and can be used as a regular sink albeit with a constant latency of about 8 seconds :s git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2485 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 77 +++++++++++++++++++++++++--------- src/modules/rtp/raop_client.c | 16 +------ 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 42092e061..f78abbaef 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -178,7 +178,8 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse 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 = pollfd->revents = 0; + pollfd->events = POLLOUT; + pollfd->revents = 0; return 0; } @@ -190,6 +191,9 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse static void thread_func(void *userdata) { struct userdata *u = userdata; int write_type = 0; + pa_memchunk silence; + uint32_t silence_overhead; + double silence_ratio; pa_assert(u); @@ -200,6 +204,9 @@ static void thread_func(void *userdata) { 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; @@ -208,33 +215,61 @@ static void thread_func(void *userdata) { pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); /* Render some data and write it to the fifo */ - if (PA_SINK_OPENED(u->sink->thread_info.state) && pollfd->revents) { + if (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; - void *p; if (u->encoded_memchunk.length <= 0) { - if (u->raw_memchunk.length <= 0) { - if (u->raw_memchunk.memblock) - pa_memblock_unref(u->raw_memchunk.memblock); - pa_memchunk_reset(&u->raw_memchunk); + if (u->encoded_memchunk.memblock) + pa_memblock_unref(u->encoded_memchunk.memblock); + if (PA_SINK_OPENED(u->sink->thread_info.state)) { + size_t rl; - /* Grab unencoded data */ - pa_sink_render(u->sink, u->block_size, &u->raw_memchunk); - p = pa_memblock_acquire(u->raw_memchunk.memblock); - pa_memblock_release(u->raw_memchunk.memblock); + /* 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 */ + u->encoding_overhead += u->next_encoding_overhead; + memcpy(&u->encoded_memchunk, &silence, sizeof(pa_memchunk)); + pa_memblock_ref(silence.memblock); + u->next_encoding_overhead = silence_overhead; + u->encoding_ratio = silence_ratio; } - pa_assert(u->raw_memchunk.length > 0); - - /* Encode it */ - size_t 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); } pa_assert(u->encoded_memchunk.length > 0); @@ -303,7 +338,7 @@ static void thread_func(void *userdata) { } /* Hmm, nothing to do. Let's sleep */ - pollfd->events = PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0; + /* pollfd->events = PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0; */ } if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) @@ -331,6 +366,8 @@ fail: 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"); } diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 7ba1be747..4085a4949 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -94,12 +94,6 @@ struct pa_raop_client { void* userdata; pa_raop_client_closed_cb_t closed_callback; void* closed_userdata; - - uint8_t *buffer; - pa_memblock *memblock; - size_t buffer_length; - uint8_t *buffer_index; - uint16_t buffer_count; }; /** @@ -402,7 +396,6 @@ void pa_raop_client_free(pa_raop_client* c) { pa_assert(c); - pa_xfree(c->buffer); if (c->rtsp) pa_rtsp_client_free(c->rtsp); pa_xfree(c->host); @@ -443,15 +436,8 @@ int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchun /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ bufmax = length + header_size + 16; - if (bufmax > c->buffer_length) { - c->buffer = pa_xrealloc(c->buffer, bufmax); - c->buffer_length = bufmax; - if (c->memblock) - pa_memblock_unref(c->memblock); - c->memblock = pa_memblock_new_user(c->core->mempool, c->buffer, bufmax, noop, 0); - } pa_memchunk_reset(encoded); - encoded->memblock = c->memblock; + encoded->memblock = pa_memblock_new(c->core->mempool, bufmax); b = pa_memblock_acquire(encoded->memblock); memcpy(b, header, header_size); From 7f0cf0c9adc5176ba41d549754ea25ed6f12bdce Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 3 Jun 2008 23:07:48 +0000 Subject: [PATCH 40/56] Fix up a couple of values related to encoding overhead. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2497 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index f78abbaef..96c98a6f0 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -317,13 +317,13 @@ static void thread_func(void *userdata) { * fully filled up. This is the best time to estimate * the playback position of the server */ - n = u->offset; + n = u->offset - u->encoding_overhead; #ifdef SIOCOUTQ { int l; if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0) - n -= l; + n -= (l / u->encoding_ratio); } #endif From 651da7d095f78e930fb758442905d5769cfda1c5 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 9 Jun 2008 21:59:00 +0000 Subject: [PATCH 41/56] Minor update to copywrite (I still plan to replace this completely but in the mean time....) git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2499 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/base64.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/rtp/base64.h b/src/modules/rtp/base64.h index 199c63522..347a9971b 100644 --- a/src/modules/rtp/base64.h +++ b/src/modules/rtp/base64.h @@ -7,6 +7,7 @@ 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 @@ -26,7 +27,7 @@ /* This file was originally inspired by a file developed by - Kungliga Tekniska Högskolan + Kungliga Tekniska Høgskolan */ int pa_base64_encode(const void *data, int size, char **str); From 5f527dc47944bbd97a49e8d89427d09850b28e5d Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 9 Jun 2008 21:59:41 +0000 Subject: [PATCH 42/56] Add seq and rtptime params to record/flush with a view to using these for timing and device suspension git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2500 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 7 ++++--- src/modules/rtp/rtsp_client.c | 19 +++++++++++++++---- src/modules/rtp/rtsp_client.h | 4 ++-- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 4085a4949..4714d2732 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -90,6 +90,9 @@ struct pa_raop_client { 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; @@ -317,7 +320,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he } else { pa_log_warn("Audio Jack Status missing"); } - pa_rtsp_record(c->rtsp); + pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); break; } @@ -403,8 +406,6 @@ void pa_raop_client_free(pa_raop_client* c) } -static void noop(PA_GCC_UNUSED void* p) {} - int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) { uint16_t len; diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index f9fe9bfe9..700404281 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -47,6 +47,8 @@ #include #include #include +#include +#include #include "rtsp_client.h" @@ -467,9 +469,10 @@ int pa_rtsp_setup(pa_rtsp_client* c) { } -int pa_rtsp_record(pa_rtsp_client* c) { +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) { @@ -477,9 +480,14 @@ int pa_rtsp_record(pa_rtsp_client* c) { return -1; } + /* Todo: Generate these values randomly as per spec */ + *seq = *rtptime = 0; + headers = pa_headerlist_new(); pa_headerlist_puts(headers, "Range", "npt=0-"); - pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=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); @@ -506,14 +514,17 @@ int pa_rtsp_setparameter(pa_rtsp_client *c, const char* param) { } -int pa_rtsp_flush(pa_rtsp_client *c) { +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(); - pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); + 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); diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h index 3c5280c24..555401803 100644 --- a/src/modules/rtp/rtsp_client.h +++ b/src/modules/rtp/rtsp_client.h @@ -66,10 +66,10 @@ 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); +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); +int pa_rtsp_flush(pa_rtsp_client* c, uint16_t seq, uint32_t rtptime); #endif From 19dcb529ad3baa8e8e506ddfa703e952044e0f60 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 9 Jun 2008 22:01:23 +0000 Subject: [PATCH 43/56] Remove unneeded headers accidentially added in r2500. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2501 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp_client.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 700404281..c0ca49fac 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -47,8 +47,6 @@ #include #include #include -#include -#include #include "rtsp_client.h" From d86fc75e0cbc9d102dc000d2781f9dfddc89fbbf Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 10 Jun 2008 23:49:35 +0000 Subject: [PATCH 44/56] Change the API of the RTSP client a bit. * Store the mainloop, hostname and port internally on construction * This should allow use to easily reconnect if disconnected although this has thus far proved unreliable. The changes look like more than they are due to moving a function around. git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2502 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/rtsp_client.c | 155 ++++++++++++++++++---------------- src/modules/rtp/rtsp_client.h | 6 +- 2 files changed, 87 insertions(+), 74 deletions(-) diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index c0ca49fac..88f778181 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -51,6 +51,10 @@ #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; @@ -70,72 +74,23 @@ struct pa_rtsp_client { char *localip; char *url; - uint16_t port; + uint16_t rtp_port; uint32_t cseq; char *session; char *transport; }; -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; - - 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; -} - - -pa_rtsp_client* pa_rtsp_client_new(const char* useragent) { +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) @@ -156,6 +111,7 @@ void pa_rtsp_client_free(pa_rtsp_client* c) { 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); @@ -197,14 +153,14 @@ static void headers_read(pa_rtsp_client *c) { 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->port)); + pa_atou(pc+1, (uint32_t*)(&c->rtp_port)); pa_xfree(token); break; } } pa_xfree(token); } - if (0 == c->port) { + if (0 == c->rtp_port) { /* Error no server_port in response */ pa_headerlist_free(c->response_headers); c->response_headers = NULL; @@ -234,7 +190,6 @@ static void line_callback(pa_ioline *line, const char *s, void *userdata) { /* Keep the ioline/iochannel open as they will be freed automatically */ c->ioline = NULL; c->io = NULL; - pa_rtsp_client_free(c); c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); return; } @@ -336,8 +291,8 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata 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; @@ -368,24 +323,24 @@ static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata } pa_log_debug("Established RTSP connection from local ip %s", c->localip); - c->waiting = 1; - c->state = STATE_CONNECT; if (c->callback) c->callback(c, c->state, NULL, c->userdata); } -int pa_rtsp_connect(pa_rtsp_client *c, pa_mainloop_api *mainloop, const char* hostname, uint16_t port) { +int pa_rtsp_connect(pa_rtsp_client *c) { pa_assert(c); - pa_assert(mainloop); - pa_assert(hostname); - pa_assert(port > 0); + pa_assert(!c->sc); - if (!(c->sc = pa_socket_client_new_string(mainloop, hostname, port))) { - pa_log("failed to connect to server '%s:%d'", hostname, port); + 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; } @@ -415,7 +370,7 @@ const char* pa_rtsp_localip(pa_rtsp_client* c) { uint32_t pa_rtsp_serverport(pa_rtsp_client* c) { pa_assert(c); - return c->port; + return c->rtp_port; } void pa_rtsp_set_url(pa_rtsp_client* c, const char* url) { @@ -441,6 +396,64 @@ void pa_rtsp_remove_header(pa_rtsp_client *c, const char* 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) diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h index 555401803..dcc9209c8 100644 --- a/src/modules/rtp/rtsp_client.h +++ b/src/modules/rtp/rtsp_client.h @@ -42,17 +42,17 @@ typedef enum { STATE_ANNOUNCE, STATE_SETUP, STATE_RECORD, + STATE_FLUSH, STATE_TEARDOWN, STATE_SET_PARAMETER, - STATE_FLUSH, 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(const char* useragent); +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, pa_mainloop_api *mainloop, const char* hostname, uint16_t port); +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); From c49be7891fac98056010cf553042946740061590 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 10 Jun 2008 23:55:58 +0000 Subject: [PATCH 45/56] Add some new public API functions to connect and flush. This allows us to reconnect upon disconnection but this has thus far proved unreliable. We no longer close the socket. We leave this to the module thread to do the closing. We can also flush the remote buffer now. Refs #69 git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2503 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/rtp/raop_client.c | 81 ++++++++++++++++++++++++----------- src/modules/rtp/raop_client.h | 3 ++ 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 4714d2732..48deff0d1 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -336,22 +336,30 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he break; } + case STATE_FLUSH: + pa_log_debug("RAOP: FLUSHED"); + break; + case STATE_TEARDOWN: case STATE_SET_PARAMETER: - case STATE_FLUSH: break; case STATE_DISCONNECTED: pa_assert(c->closed_callback); - pa_log_debug("RTSP channel closed"); + pa_assert(c->rtsp); + + pa_log_debug("RTSP control channel closed"); + pa_rtsp_client_free(c->rtsp); c->rtsp = NULL; if (c->fd > 0) { - pa_close(c->fd); + /* 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; } @@ -359,12 +367,6 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) { - char *sci; - struct { - uint32_t a; - uint32_t b; - uint32_t c; - } rand_data; pa_raop_client* c = pa_xnew0(pa_raop_client, 1); pa_assert(core); @@ -373,22 +375,9 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) c->core = core; c->fd = -1; c->host = pa_xstrdup(host); - c->rtsp = pa_rtsp_client_new("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_rtsp_set_callback(c->rtsp, rtsp_cb, c); - if (pa_rtsp_connect(c->rtsp, c->core->mainloop, host, 5000)) { - pa_rtsp_client_free(c->rtsp); + if (pa_raop_connect(c)) { + pa_raop_client_free(c); return NULL; } return c; @@ -406,6 +395,50 @@ void pa_raop_client_free(pa_raop_client* 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_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) { uint16_t len; diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h index 303fdaa46..882dae175 100644 --- a/src/modules/rtp/raop_client.h +++ b/src/modules/rtp/raop_client.h @@ -33,6 +33,9 @@ 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_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded); typedef void (*pa_raop_client_cb_t)(int fd, void *userdata); From 15e8420a251ec4912d2df9d29dfb728bdc97f33c Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 11 Jun 2008 00:02:10 +0000 Subject: [PATCH 46/56] Still send silence when we are not doing anything else, but also flush the buffers correctly upon recovery from suspension. Close the RTP socket correctly after passing messages about. When not sending silence, the RTSP socket will be closed after some period of inactivity. I'm not sure why this is. Sending silence keeps things working and with the flushes after suspension we now get a better latency. As this relies on the auto-suspend feature, it's not exactly ideal. Typical latencies are currently about 3s which makes it more or less usuable for listening to music. If the connection is disconnected, it will reconnect but I've found that the second connection is silent. Hopefully the silence will prevent the first connection dropping. Refs #69 git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2504 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 111 ++++++++++++++++++++++++--------- 1 file changed, 82 insertions(+), 29 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 96c98a6f0..51c2368ad 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -127,9 +127,31 @@ static const char* const valid_modargs[] = { }; enum { - SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX + 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; + + 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; @@ -143,14 +165,27 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_assert(PA_SINK_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) + 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: @@ -179,8 +214,34 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); pollfd->fd = u->fd; pollfd->events = POLLOUT; - pollfd->revents = 0; + /*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); + } return 0; } } @@ -215,7 +276,7 @@ static void thread_func(void *userdata) { pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); /* Render some data and write it to the fifo */ - if (pollfd->revents) { + if (/*PA_SINK_OPENED(u->sink->thread_info.state) && */pollfd->revents) { pa_usec_t usec; int64_t n; void *p; @@ -264,9 +325,10 @@ static void thread_func(void *userdata) { u->encoding_ratio = u->encoded_memchunk.length / (rl - u->raw_memchunk.length); } else { /* We render some silence into our memchunk */ - u->encoding_overhead += u->next_encoding_overhead; 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; } @@ -302,12 +364,15 @@ static void thread_func(void *userdata) { pollfd->revents = 0; - if (u->encoded_memchunk.length > 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; + } } } @@ -338,7 +403,7 @@ static void thread_func(void *userdata) { } /* Hmm, nothing to do. Let's sleep */ - /* pollfd->events = PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0; */ + pollfd->events = POLLOUT; /*PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0;*/ } if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) @@ -353,8 +418,16 @@ static void thread_func(void *userdata) { pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); if (pollfd->revents & ~POLLOUT) { - pa_log("FIFO shutdown."); - goto fail; + 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; } } } @@ -371,26 +444,6 @@ finish: pa_log_debug("Thread shutting down"); } -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; - - 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("Control connection closed."); - pa_module_unload_request(u->module); -} - int pa__init(pa_module*m) { struct userdata *u = NULL; const char *p; From d997420791cb8defbda65acdf08fe18072d01d7b Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 11 Jun 2008 22:43:27 +0000 Subject: [PATCH 47/56] Minor correction of help text git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2518 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-sink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 51c2368ad..545266d59 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -73,7 +73,7 @@ PA_MODULE_VERSION(PACKAGE_VERSION); PA_MODULE_LOAD_ONCE(FALSE); PA_MODULE_USAGE( "sink_name= " - "server=
cookie= " + "server=
" "format= " "channels= " "rate="); From 729bbaf88485207c8637f3c30fffed60a1f73ca1 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 11 Jun 2008 22:44:09 +0000 Subject: [PATCH 48/56] Automatic discovery of airtunes devices via Bonjour/Avahi. This also does some minor reordering in the Makefile.am Refs #69 git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2519 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/Makefile.am | 21 +- src/modules/module-raop-discover.c | 380 +++++++++++++++++++++++++++++ 2 files changed, 395 insertions(+), 6 deletions(-) create mode 100644 src/modules/module-raop-discover.c diff --git a/src/Makefile.am b/src/Makefile.am index 5a9b90246..4ffdfd93f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1127,7 +1127,8 @@ endif if HAVE_AVAHI modlibexec_LTLIBRARIES += \ module-zeroconf-publish.la \ - module-zeroconf-discover.la + module-zeroconf-discover.la \ + module-raop-discover.la endif if HAVE_LIRC @@ -1207,7 +1208,6 @@ SYMDEF_FILES = \ modules/module-esound-compat-spawnfd-symdef.h \ modules/module-esound-compat-spawnpid-symdef.h \ modules/module-match-symdef.h \ - modules/module-raop-sink-symdef.h \ modules/module-tunnel-sink-symdef.h \ modules/module-tunnel-source-symdef.h \ modules/module-null-sink-symdef.h \ @@ -1242,6 +1242,8 @@ SYMDEF_FILES = \ modules/bluetooth/module-bluetooth-proximity-symdef.h \ modules/bluetooth/module-bluetooth-discover-symdef.h \ modules/bluetooth/module-bluetooth-device-symdef.h \ + modules/module-raop-sink-symdef.h \ + modules/module-raop-discover-symdef.h \ modules/gconf/module-gconf-symdef.h \ modules/module-position-event-sounds-symdef.h \ modules/module-console-kit-symdef.h @@ -1376,10 +1378,6 @@ module_match_la_SOURCES = modules/module-match.c module_match_la_LDFLAGS = -module -avoid-version module_match_la_LIBADD = $(AM_LIBADD) libpulsecore.la -module_raop_sink_la_SOURCES = modules/module-raop-sink.c -module_raop_sink_la_LDFLAGS = -module -avoid-version -module_raop_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la librtp.la - module_tunnel_sink_la_SOURCES = modules/module-tunnel.c module_tunnel_sink_la_CFLAGS = -DTUNNEL_SINK=1 $(AM_CFLAGS) module_tunnel_sink_la_LDFLAGS = -module -avoid-version @@ -1608,6 +1606,17 @@ module_bluetooth_device_la_LDFLAGS = -module -avoid-version module_bluetooth_device_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore.la libdbus-util.la libbluetooth-ipc.la libbluetooth-sbc.la libsocket-util.la module_bluetooth_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) +# Apple Airtunes/RAOP +module_raop_sink_la_SOURCES = modules/module-raop-sink.c +module_raop_sink_la_LDFLAGS = -module -avoid-version +module_raop_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la librtp.la + +module_raop_discover_la_SOURCES = modules/module-raop-discover.c +module_raop_discover_la_LDFLAGS = -module -avoid-version +module_raop_discover_la_LIBADD = $(AM_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libpulsecore.la +module_raop_discover_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS) + + ################################### # Some minor stuff # ################################### diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c new file mode 100644 index 000000000..7f6236173 --- /dev/null +++ b/src/modules/module-raop-discover.c @@ -0,0 +1,380 @@ +/* $Id$ */ + +/*** + 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 +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, *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 (!pa_namereg_is_valid_name(dname)) { + pa_log("Cannot construct valid device name from credentials of service '%s'.", dname); + avahi_free(device); + pa_xfree(dname); + goto finish; + } + + /* + 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, + dname);*/ + args = pa_sprintf_malloc("server=%s " + "sink_name=%s", + avahi_address_snprint(at, sizeof(at), a), + dname); + + 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(dname); + 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); + 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); + } + } + + 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); + } + } + + /* 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); + tunnel_free(t); + } + + pa_hashmap_free(u->tunnels, NULL, NULL); + } + + pa_xfree(u); +} From 0ff75aea053cb8a7af958aa72d99b27cc90becf6 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 11 Jun 2008 23:01:07 +0000 Subject: [PATCH 49/56] Add Lennart back in to Copyright as I copied these files from his originals and was a bit overzealous in changing things ;) git-svn-id: file:///home/lennart/svn/public/pulseaudio/branches/coling@2520 fefdeb5f-60dc-0310-8127-8f9354f1896f --- src/modules/module-raop-discover.c | 1 + src/modules/module-raop-sink.c | 1 + 2 files changed, 2 insertions(+) diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index 7f6236173..7f89bb5c3 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -3,6 +3,7 @@ /*** 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 diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 545266d59..39374a36b 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -3,6 +3,7 @@ /*** 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 From 36f2aad5f0892e6738a8d59111c3c9017152db7e Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 24 Jun 2008 23:57:37 +0100 Subject: [PATCH 50/56] Use the new pa_namereg_make_valid_name() function. --- src/modules/module-raop-discover.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index 7f89bb5c3..baa64eec4 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -154,7 +154,7 @@ static void resolver_cb( 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, *args; + char *device = NULL, *dname, *vname, *args; char at[AVAHI_ADDRESS_STR_MAX]; AvahiStringList *l; pa_module *m; @@ -178,23 +178,24 @@ static void resolver_cb( else dname = pa_sprintf_malloc("airtunes.%s", host_name); - if (!pa_namereg_is_valid_name(dname)) { - pa_log("Cannot construct valid device name from credentials of service '%s'.", dname); + 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, - dname);*/ + vname);*/ args = pa_sprintf_malloc("server=%s " "sink_name=%s", avahi_address_snprint(at, sizeof(at), a), - dname); + vname); pa_log_debug("Loading module-raop-sink with arguments '%s'", args); @@ -204,7 +205,7 @@ static void resolver_cb( tnl = NULL; } - pa_xfree(dname); + pa_xfree(vname); pa_xfree(args); avahi_free(device); } From e543e04ca725ef1c240762529e7dafec31749683 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 3 Jul 2008 23:47:34 +0100 Subject: [PATCH 51/56] Implement a set volume function to expose this capability to higher layers --- src/modules/rtp/raop_client.c | 24 ++++++++++++++++++++++++ src/modules/rtp/raop_client.h | 1 + 2 files changed, 25 insertions(+) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 48deff0d1..792eceecd 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -342,6 +342,7 @@ static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* he case STATE_TEARDOWN: case STATE_SET_PARAMETER: + pa_log_debug("RAOP: SET_PARAMETER"); break; case STATE_DISCONNECTED: pa_assert(c->closed_callback); @@ -439,6 +440,29 @@ int pa_raop_flush(pa_raop_client* c) } +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; diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h index 882dae175..3d5ef1676 100644 --- a/src/modules/rtp/raop_client.h +++ b/src/modules/rtp/raop_client.h @@ -36,6 +36,7 @@ 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); From ded09d1f9b00ac793ccc26caea0f8706e88bd521 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 3 Jul 2008 23:49:01 +0100 Subject: [PATCH 52/56] Implement hardware volume control. This allows near instant change of volume when controlling the hardware but the stream volume still suffers from a sizable delay. --- src/modules/module-raop-sink.c | 75 ++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 3 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 39374a36b..50ef985e8 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -102,6 +102,9 @@ struct userdata { pa_usec_t latency; + pa_volume_t volume; + pa_bool_t muted; + /*esd_format_t format;*/ int32_t rate; @@ -139,6 +142,9 @@ static void on_connection(PA_GCC_UNUSED int fd, void*userdata) { 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); @@ -250,12 +256,68 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse 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; - double silence_ratio; + uint32_t silence_overhead = 0; + double silence_ratio = 0; pa_assert(u); @@ -484,6 +546,9 @@ int pa__init(pa_module*m) { u->next_encoding_overhead = 0; u->encoding_ratio = 1.0; + u->volume = roundf(0.7 * PA_VOLUME_NORM); + u->muted = FALSE; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); @@ -508,7 +573,11 @@ int pa__init(pa_module*m) { u->sink->parent.process_msg = sink_process_msg; u->sink->userdata = u; - u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK; + 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_module(u->sink, m); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); From 19d28319733f92cc518d0d45a15f15b704f603d8 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 3 Aug 2008 20:52:35 +0100 Subject: [PATCH 53/56] Make module-raop-sink/discover work with 0.9.11 API --- src/modules/module-raop-discover.c | 2 -- src/modules/module-raop-sink.c | 52 +++++++++++++++++------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index baa64eec4..38436a387 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 50ef985e8..b90d4e234 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. @@ -169,7 +167,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { case PA_SINK_SUSPENDED: - pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); pa_smoother_pause(u->smoother, pa_rtclock_usec()); @@ -334,12 +332,16 @@ static void thread_func(void *userdata) { 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_OPENED(u->sink->thread_info.state) && */pollfd->revents) { + if (/*PA_SINK_IS_OPENED(u->sink->thread_info.state) && */pollfd->revents) { pa_usec_t usec; int64_t n; void *p; @@ -366,7 +368,7 @@ static void thread_func(void *userdata) { if (u->encoded_memchunk.length <= 0) { if (u->encoded_memchunk.memblock) pa_memblock_unref(u->encoded_memchunk.memblock); - if (PA_SINK_OPENED(u->sink->thread_info.state)) { + if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { size_t rl; /* We render real data */ @@ -466,7 +468,7 @@ static void thread_func(void *userdata) { } /* Hmm, nothing to do. Let's sleep */ - pollfd->events = POLLOUT; /*PA_SINK_OPENED(u->sink->thread_info.state) ? POLLOUT : 0;*/ + pollfd->events = POLLOUT; /*PA_SINK_IS_OPENED(u->sink->thread_info.state) ? POLLOUT : 0;*/ } if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) @@ -509,10 +511,10 @@ finish: int pa__init(pa_module*m) { struct userdata *u = NULL; - const char *p; pa_sample_spec ss; pa_modargs *ma = NULL; - char *t; + const char *server; + pa_sink_new_data data; pa_assert(m); @@ -538,7 +540,7 @@ int pa__init(pa_module*m) { u->module = m; m->userdata = u; u->fd = -1; - u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE); + 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; @@ -549,9 +551,8 @@ int pa__init(pa_module*m) { u->volume = roundf(0.7 * PA_VOLUME_NORM); u->muted = FALSE; - pa_thread_mq_init(&u->thread_mq, m->core->mainloop); u->rtpoll = pa_rtpoll_new(); - pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); u->rtpoll_item = NULL; /*u->format = @@ -566,7 +567,23 @@ int pa__init(pa_module*m) { /*u->state = STATE_AUTH;*/ u->latency = 0; - if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) { + 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; } @@ -579,25 +596,16 @@ int pa__init(pa_module*m) { 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_module(u->sink, m); pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); pa_sink_set_rtpoll(u->sink, u->rtpoll); - if (!(p = pa_modargs_get_value(ma, "server", NULL))) { - pa_log("No server argument given."); - goto fail; - } - - if (!(u->raop = pa_raop_client_new(u->core, p))) { + 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); - pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Airtunes sink '%s'", p)); - pa_xfree(t); - if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); From c3d8bb5b34c45f4dda594cc1d8107cac468fa232 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 3 Aug 2008 20:56:21 +0100 Subject: [PATCH 54/56] Remove $Id$ lines left over from SVN --- src/modules/rtp/base64.c | 2 -- src/modules/rtp/base64.h | 2 -- src/modules/rtp/headerlist.c | 2 -- src/modules/rtp/headerlist.h | 2 -- src/modules/rtp/raop_client.c | 2 -- src/modules/rtp/raop_client.h | 2 -- src/modules/rtp/rtsp_client.c | 2 -- src/modules/rtp/rtsp_client.h | 2 -- 8 files changed, 16 deletions(-) diff --git a/src/modules/rtp/base64.c b/src/modules/rtp/base64.c index 980b018e6..8918def8d 100644 --- a/src/modules/rtp/base64.c +++ b/src/modules/rtp/base64.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/rtp/base64.h b/src/modules/rtp/base64.h index 347a9971b..dac0e707c 100644 --- a/src/modules/rtp/base64.h +++ b/src/modules/rtp/base64.h @@ -1,8 +1,6 @@ #ifndef foobase64hfoo #define foobase64hfoo -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/rtp/headerlist.c b/src/modules/rtp/headerlist.c index de8710b7e..0fef835bb 100644 --- a/src/modules/rtp/headerlist.c +++ b/src/modules/rtp/headerlist.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/rtp/headerlist.h b/src/modules/rtp/headerlist.h index 276d0e350..4b9c6433c 100644 --- a/src/modules/rtp/headerlist.h +++ b/src/modules/rtp/headerlist.h @@ -1,8 +1,6 @@ #ifndef foopulseheaderlisthfoo #define foopulseheaderlisthfoo -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 792eceecd..4627545e1 100644 --- a/src/modules/rtp/raop_client.c +++ b/src/modules/rtp/raop_client.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h index 3d5ef1676..ec3136a77 100644 --- a/src/modules/rtp/raop_client.h +++ b/src/modules/rtp/raop_client.h @@ -1,8 +1,6 @@ #ifndef fooraopclientfoo #define fooraopclientfoo -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c index 88f778181..9eb3d9648 100644 --- a/src/modules/rtp/rtsp_client.c +++ b/src/modules/rtp/rtsp_client.c @@ -1,5 +1,3 @@ -/* $Id$ */ - /*** This file is part of PulseAudio. diff --git a/src/modules/rtp/rtsp_client.h b/src/modules/rtp/rtsp_client.h index dcc9209c8..88fb3839d 100644 --- a/src/modules/rtp/rtsp_client.h +++ b/src/modules/rtp/rtsp_client.h @@ -1,8 +1,6 @@ #ifndef foortspclienthfoo #define foortspclienthfoo -/* $Id$ */ - /*** This file is part of PulseAudio. From 8715121755ad1aa9c083dd70781a63ced13359c2 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 3 Aug 2008 22:46:21 +0100 Subject: [PATCH 55/56] Modularise the RAOP stuff that requires OpenSSL and make it optional at compile time --- configure.ac | 41 +++++++++++++++++++++++++ src/Makefile.am | 35 ++++++++++++++++----- src/modules/{rtp => raop}/base64.c | 0 src/modules/{rtp => raop}/base64.h | 0 src/modules/{rtp => raop}/raop_client.c | 0 src/modules/{rtp => raop}/raop_client.h | 0 6 files changed, 68 insertions(+), 8 deletions(-) rename src/modules/{rtp => raop}/base64.c (100%) rename src/modules/{rtp => raop}/base64.h (100%) rename src/modules/{rtp => raop}/raop_client.c (100%) rename src/modules/{rtp => raop}/raop_client.h (100%) diff --git a/configure.ac b/configure.ac index 2b91a0064..2a040763b 100644 --- a/configure.ac +++ b/configure.ac @@ -1000,6 +1000,41 @@ AC_SUBST(POLKIT_LIBS) AC_SUBST(HAVE_POLKIT) AM_CONDITIONAL([HAVE_POLKIT], [test "x$HAVE_POLKIT" = x1]) +#### OpenSSL support (optional) #### + +AC_ARG_ENABLE([openssl], + AC_HELP_STRING([--disable-openssl], [Disable OpenSSL support (used for Airtunes/RAOP)]), + [ + case "${enableval}" in + yes) openssl=yes ;; + no) openssl=no ;; + *) AC_MSG_ERROR(bad value ${enableval} for --disable-openssl) ;; + esac + ], + [openssl=auto]) + +if test "x${openssl}" != xno ; then + + PKG_CHECK_MODULES(OPENSSL, [ openssl > 0.9 ], + [ + HAVE_OPENSSL=1 + AC_DEFINE([HAVE_OPENSSL], 1, [Have OpenSSL]) + ], + [ + HAVE_OPENSSL=0 + if test "x$openssl" = xyes ; then + AC_MSG_ERROR([*** OpenSSL support not found]) + fi + ]) +else + HAVE_OPENSSL=0 +fi + +AC_SUBST(OPENSSL_CFLAGS) +AC_SUBST(OPENSSL_LIBS) +AC_SUBST(HAVE_OPENSSL) +AM_CONDITIONAL([HAVE_OPENSSL], [test "x$HAVE_OPENSSL" = x1]) + ### Build and Install man pages ### AC_ARG_ENABLE(manpages, AS_HELP_STRING([--disable-manpages],[Disable building and installation of man pages]), @@ -1201,6 +1236,11 @@ if test "x${HAVE_POLKIT}" = "x1" ; then ENABLE_POLKIT=yes fi +ENABLE_OPENSSL=no +if test "x${HAVE_OPENSSL}" = "x1" ; then + ENABLE_OPENSSL=yes +fi + ENABLE_PER_USER_ESOUND_SOCKET=no if test "x$per_user_esound_socket" = "x1" ; then ENABLE_PER_USER_ESOUND_SOCKET=yes @@ -1232,6 +1272,7 @@ echo " Enable TCP Wrappers: ${ENABLE_TCPWRAP} Enable libsamplerate: ${ENABLE_LIBSAMPLERATE} Enable PolicyKit: ${ENABLE_POLKIT} + Enable OpenSSL (for Airtunes): ${ENABLE_OPENSSL} System User: ${PA_SYSTEM_USER} System Group: ${PA_SYSTEM_GROUP} Realtime Group: ${PA_REALTIME_GROUP} diff --git a/src/Makefile.am b/src/Makefile.am index 4ffdfd93f..f216b71b4 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -62,6 +62,10 @@ AM_CFLAGS += -DPA_MACHINE_ID=\"$(localstatedir)/lib/dbus/machine-id\" # This cool debug trap works on i386/gcc only AM_CFLAGS += '-DDEBUG_TRAP=__asm__("int $$3")' +if HAVE_OPENSSL +AM_CFLAGS += -I$(top_builddir)/src/modules/raop +endif + AM_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS) AM_LDADD = $(PTHREAD_LIBS) $(INTLLIBS) @@ -89,6 +93,7 @@ PA_THREAD_OBJS = \ pulsecore/semaphore-posix.c pulsecore/semaphore.h endif + ################################### # Extra files # ################################### @@ -1009,11 +1014,16 @@ librtp_la_SOURCES = \ modules/rtp/sdp.c modules/rtp/sdp.h \ modules/rtp/sap.c modules/rtp/sap.h \ modules/rtp/rtsp_client.c modules/rtp/rtsp_client.h \ - modules/rtp/raop_client.c modules/rtp/raop_client.h \ - modules/rtp/headerlist.c modules/rtp/headerlist.h \ - modules/rtp/base64.c modules/rtp/base64.h + modules/rtp/headerlist.c modules/rtp/headerlist.h librtp_la_LDFLAGS = -avoid-version -librtp_la_LIBADD = $(AM_LIBADD) libsocket-util.la libiochannel.la libsocket-client.la libioline.la libpulsecore.la -lssl +librtp_la_LIBADD = $(AM_LIBADD) libsocket-util.la libiochannel.la libsocket-client.la libioline.la libpulsecore.la + +libraop_la_SOURCES = \ + modules/raop/raop_client.c modules/raop/raop_client.h \ + modules/raop/base64.c modules/raop/base64.h +libraop_la_CFLAGS = $(AM_CFLAGS) $(OPENSSL_CFLAGS) +libraop_la_LDFLAGS = -avoid-version +libraop_la_LIBADD = $(AM_LIBADD) $(OPENSSL_LIBS) libsocket-util.la libiochannel.la libsocket-client.la libioline.la libpulsecore.la librtp.la # X11 @@ -1060,7 +1070,6 @@ modlibexec_LTLIBRARIES += \ module-remap-sink.la \ module-ladspa-sink.la \ module-esound-sink.la \ - module-raop-sink.la \ module-tunnel-sink.la \ module-tunnel-source.la \ module-position-event-sounds.la @@ -1127,8 +1136,7 @@ endif if HAVE_AVAHI modlibexec_LTLIBRARIES += \ module-zeroconf-publish.la \ - module-zeroconf-discover.la \ - module-raop-discover.la + module-zeroconf-discover.la endif if HAVE_LIRC @@ -1186,6 +1194,17 @@ pulselibexec_PROGRAMS += \ proximity-helper endif +if HAVE_OPENSSL +modlibexec_LTLIBRARIES += \ + libraop.la \ + module-raop-sink.la +if HAVE_AVAHI +modlibexec_LTLIBRARIES += \ + module-raop-discover.la +endif +endif + + # These are generated by a M4 script SYMDEF_FILES = \ @@ -1609,7 +1628,7 @@ module_bluetooth_device_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) # Apple Airtunes/RAOP module_raop_sink_la_SOURCES = modules/module-raop-sink.c module_raop_sink_la_LDFLAGS = -module -avoid-version -module_raop_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la librtp.la +module_raop_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la librtp.la libraop.la module_raop_discover_la_SOURCES = modules/module-raop-discover.c module_raop_discover_la_LDFLAGS = -module -avoid-version diff --git a/src/modules/rtp/base64.c b/src/modules/raop/base64.c similarity index 100% rename from src/modules/rtp/base64.c rename to src/modules/raop/base64.c diff --git a/src/modules/rtp/base64.h b/src/modules/raop/base64.h similarity index 100% rename from src/modules/rtp/base64.h rename to src/modules/raop/base64.h diff --git a/src/modules/rtp/raop_client.c b/src/modules/raop/raop_client.c similarity index 100% rename from src/modules/rtp/raop_client.c rename to src/modules/raop/raop_client.c diff --git a/src/modules/rtp/raop_client.h b/src/modules/raop/raop_client.h similarity index 100% rename from src/modules/rtp/raop_client.h rename to src/modules/raop/raop_client.h From 59eb64987fdf5dde71638f5f77e705e490ec7133 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Fri, 22 Aug 2008 09:51:41 +0100 Subject: [PATCH 56/56] Follow master change r34dd4a and fix shutdown when --disallow-module-loading=1 is passed --- src/modules/module-raop-discover.c | 8 ++++---- src/modules/module-raop-sink.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/modules/module-raop-discover.c b/src/modules/module-raop-discover.c index 38436a387..3706d9211 100644 --- a/src/modules/module-raop-discover.c +++ b/src/modules/module-raop-discover.c @@ -248,7 +248,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); + pa_module_unload_by_index(u->core, t2->module_index, TRUE); pa_hashmap_remove(u->tunnels, t2); tunnel_free(t2); } @@ -281,7 +281,7 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda browser_cb, u))) { pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c))); - pa_module_unload_request(u->module); + pa_module_unload_request(u->module, TRUE); } } @@ -295,7 +295,7 @@ static void client_callback(AvahiClient *c, AvahiClientState state, void *userda 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); + pa_module_unload_request(u->module, TRUE); } } @@ -369,7 +369,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); + pa_module_unload_by_index(u->core, t->module_index, TRUE); tunnel_free(t); } diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index b90d4e234..62f0a73c0 100644 --- a/src/modules/module-raop-sink.c +++ b/src/modules/module-raop-sink.c @@ -245,7 +245,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse /* 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); + pa_module_unload_request(u->module, TRUE); } return 0; }