From 8c1c565c86ddeb0c2b3b89d53aed0052eca1ebbf Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 1 May 2008 23:35:24 +0000 Subject: 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 00000000..9ea17ae3 --- /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 00000000..276d0e35 --- /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 -- cgit From 48477067ee20678a9c741da4e75dbbcdd6b01efe Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 1 May 2008 23:40:19 +0000 Subject: 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 00000000..3fd1ba0f --- /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 00000000..504f1445 --- /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 -- cgit From fef102e35ae4adff8ab000c628cb659c337af51d Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 1 May 2008 23:43:34 +0000 Subject: 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 00000000..ec9f2212 --- /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 00000000..199c6352 --- /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 -- cgit From 6570620cc3717eb82acd19788538fda3786c7b99 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 1 May 2008 23:51:45 +0000 Subject: 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 f2771980..831e4565 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 00000000..ad10d78f --- /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); +} -- cgit From 91edf9eaca3a0da83484c9c3787beff2cc7a5945 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Fri, 2 May 2008 09:47:09 +0000 Subject: 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 9ea17ae3..8bdc7251 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) -- cgit From ce9a41ef06e0b6c619b985415ccac6f0fddd68b8 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Fri, 2 May 2008 09:49:28 +0000 Subject: 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 3fd1ba0f..61688713 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 504f1445..7b3df8f3 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); -- cgit From 405cf720dc5190f14eee6e2eaad51aa52ff18c62 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 4 May 2008 00:43:31 +0000 Subject: 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 | 125 +++++++++++++++++++++++++------------------------ src/modules/rtp/rtsp.h | 9 ++-- 2 files changed, 71 insertions(+), 63 deletions(-) diff --git a/src/modules/rtp/rtsp.c b/src/modules/rtp/rtsp.c index 61688713..3556230b 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(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)); + pa_assert(c->sc == sc); - if ((c->fd = socket(sa.sin_family, SOCK_STREAM, 0)) < 0) { - pa_log("socket(): %s", pa_cstrerror(errno)); - return 0; - } + pa_socket_client_unref(c->sc); + c->sc = NULL; - /* 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 (!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); } +} - if (0 != getsockname(c->fd, (struct sockaddr*)&name, &namelen)) { - pa_close(c->fd); - c->fd = -1; +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); + + if (!(c->sc = pa_socket_client_new_string(mainloop, hostname, port))) { + pa_log("failed to connect to server '%s:%d'", hostname, port); 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 7b3df8f3..8d86f7ba 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); -- cgit From 27ed970adf66ea27d3db47c2b0e138d3d7e0f0b3 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 4 May 2008 01:01:52 +0000 Subject: 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 3556230b..9f4d5e45 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); } -- cgit From a08d733fd149d3d927583bad0dc69104d08b0ceb Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 4 May 2008 01:26:29 +0000 Subject: 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 9f4d5e45..d8440724 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 8d86f7ba..181d0854 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. -- cgit From a0d3582fb1bddbb8fb6a7da98bbfeb05b517088e Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:14:33 +0000 Subject: 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 ec9f2212..043ef5a8 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;) { -- cgit From d423605bd9f5fb18b44fde5424b075c977de25ad Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:17:17 +0000 Subject: 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 d8440724..55d91018 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 181d0854..6458f851 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); -- cgit From 20478a4544e8ef622434c5af5dcb4c66269a7dd9 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:20:35 +0000 Subject: 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 831e4565..5bd6388b 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 00000000..18e596b0 --- /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 00000000..499b1248 --- /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 -- cgit From 66cf1d1f66c90b5f92d67a00ea2c1f6404453d97 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:25:37 +0000 Subject: 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 ad10d78f..f2ddf1cf 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(); -- cgit From 8fb58e3a9082bafc3aa7b874d2bba9258b29cb38 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 00:28:04 +0000 Subject: 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 18e596b0..fbcbe4bb 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" -- cgit From 22e299ad3e16d1a2636653a7be9d625ecdc23802 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 6 May 2008 18:39:09 +0000 Subject: 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 fbcbe4bb..8f6f2594 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 499b1248..99c75fdb 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 55d91018..4f2411ab 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) { -- cgit From 6510d97315b9bdf7b1afc204c3dca0a2b0a3a528 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 7 May 2008 00:35:10 +0000 Subject: 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 4f2411ab..44cd80b9 100644 --- a/src/modules/rtp/rtsp.c +++ b/src/modules/rtp/rtsp.c @@ -46,84 +46,35 @@ #include #include #include +#include #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; -}; + pa_ioline *ioline; -/* - * 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; - } + pa_rtsp_state state; + uint8_t waiting; - *line = 0; - return count; -} + pa_headerlist* headers; + 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; +}; static int pa_rtsp_exec(pa_rtsp_context* c, const char* cmd, const char* content_type, const char* content, @@ -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; - 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; + char delimiters[] = ";"; - 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); + 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); - /* - if (do_read(u) < 0 || do_write(u) < 0) { +static void line_callback(pa_ioline *line, const char *s, void *userdata) { + char *delimpos; + char *s2, *s2p; - if (u->io) { - pa_iochannel_free(u->io); - u->io = NULL; + pa_rtsp_context *c = userdata; + pa_assert(line); + pa_assert(c); + pa_assert(s); + + 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) { -- cgit From e596f42f39232e4e0d36c3764474f73a7ff48fbb Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 7 May 2008 01:10:31 +0000 Subject: 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 8f6f2594..2dd2de94 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 99c75fdb..1dcf779f 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); -- cgit From 41e31ab204ca48ea749c416eb270ebfa2f74b086 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 7 May 2008 01:23:16 +0000 Subject: 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 | 507 ------------------------------------------ src/modules/rtp/rtsp.h | 74 ------ src/modules/rtp/rtsp_client.c | 507 ++++++++++++++++++++++++++++++++++++++++++ src/modules/rtp/rtsp_client.h | 74 ++++++ 6 files changed, 586 insertions(+), 586 deletions(-) delete mode 100644 src/modules/rtp/rtsp.c delete mode 100644 src/modules/rtp/rtsp.h create mode 100644 src/modules/rtp/rtsp_client.c create mode 100644 src/modules/rtp/rtsp_client.h diff --git a/src/Makefile.am b/src/Makefile.am index 5bd6388b..91979207 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 2dd2de94..7bfce930 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.c deleted file mode 100644 index 44cd80b9..00000000 --- a/src/modules/rtp/rtsp.c +++ /dev/null @@ -1,507 +0,0 @@ -/* $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 - -#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; - - pa_rtsp_state state; - uint8_t waiting; - - pa_headerlist* headers; - char *last_header; - pa_strbuf *header_buffer; - pa_headerlist* response_headers; - - char *localip; - char *url; - uint32_t port; - uint32_t cseq; - char *session; - char *transport; -}; - -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_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_context* pa_rtsp_context_new(const char* useragent) { - pa_rtsp_context *c; - - c = pa_xnew0(pa_rtsp_context, 1); - c->headers = pa_headerlist_new(); - - if (useragent) - c->useragent = useragent; - else - c->useragent = "PulseAudio RTSP Client"; - - return c; -} - - -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_xfree(c->last_header); - if (c->header_buffer) - pa_strbuf_free(c->header_buffer); - if (c->response_headers) - pa_headerlist_free(c->response_headers); - pa_headerlist_free(c->headers); - } - pa_xfree(c); -} - - -static void headers_read(pa_rtsp_context *c) { - char* token; - char delimiters[] = ";"; - - pa_assert(c); - 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(c->response_headers, "Session")); - c->transport = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Transport")); - - if (!c->session || !c->transport) { - pa_headerlist_free(c->response_headers); - c->response_headers = NULL; - pa_log("Invalid SETUP response."); - return; - } - - /* Now parse out the server port component of the response. */ - while ((token = pa_split(c->transport, delimiters, &token_state))) { - if ((pc = strstr(token, "="))) { - if (0 == strncmp(token, "server_port", 11)) { - pa_atou(pc+1, &c->port); - pa_xfree(token); - break; - } - } - pa_xfree(token); - } - if (0 == c->port) { - /* Error no server_port in response */ - pa_headerlist_free(c->response_headers); - c->response_headers = NULL; - pa_log("Invalid SETUP response (no port number)."); - return; - } - } - - /* Call our callback */ - if (c->callback) - c->callback(c, c->state, c->response_headers, c->userdata); - - pa_headerlist_free(c->response_headers); - c->response_headers = NULL; -} - - -static void line_callback(pa_ioline *line, const char *s, void *userdata) { - char *delimpos; - char *s2, *s2p; - - pa_rtsp_context *c = userdata; - pa_assert(line); - pa_assert(c); - pa_assert(s); - - s2 = pa_xstrdup(s); - /* Trim trailing carriage returns */ - s2p = s2 + strlen(s2) - 1; - while (s2p >= s2 && '\r' == *s2p) { - *s2p = '\0'; - s2p -= 1; - } - if (c->waiting && 0 == strcmp("RTSP/1.0 200 OK", s2)) { - c->waiting = 0; - pa_assert(!c->response_headers); - c->response_headers = pa_headerlist_new(); - goto exit; - } - if (c->waiting) { - pa_log_warn("Unexpected response: %s", s2); - goto exit;; - } - if (!strlen(s2)) { - /* End of headers */ - /* We will have a header left from our looping itteration, so add it in :) */ - if (c->last_header) { - /* This is not a continuation header so let's dump it into our proplist */ - pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); - pa_xfree(c->last_header); - c->last_header = NULL; - c->header_buffer= NULL; - } - - pa_log_debug("Full response received. Dispatching"); - headers_read(c); - c->waiting = 1; - goto exit; - } - - /* Read and parse a header (we know it's not empty) */ - /* TODO: Move header reading into the headerlist. */ - - /* If the first character is a space, it's a continuation header */ - if (c->last_header && ' ' == s2[0]) { - pa_assert(c->header_buffer); - - /* Add this line to the buffer (sans the space. */ - pa_strbuf_puts(c->header_buffer, &(s2[1])); - goto exit; - } - - if (c->last_header) { - /* This is not a continuation header so let's dump the full - header/value into our proplist */ - pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); - pa_xfree(c->last_header); - c->last_header = NULL; - c->header_buffer = NULL; - } - - delimpos = strstr(s2, ":"); - if (!delimpos) { - pa_log_warn("Unexpected response when expecting header: %s", s); - goto exit; - } - - pa_assert(!c->header_buffer); - pa_assert(!c->last_header); - - c->header_buffer = pa_strbuf_new(); - if (strlen(delimpos) > 1) { - /* Cut our line off so we can copy the header name out */ - *delimpos++ = '\0'; - - /* Trim the front of any spaces */ - while (' ' == *delimpos) - ++delimpos; - - pa_strbuf_puts(c->header_buffer, delimpos); - } else { - /* Cut our line off so we can copy the header name out */ - *delimpos = '\0'; - } - - /* Save the header name */ - c->last_header = pa_xstrdup(s2); - exit: - pa_xfree(s2); -} - - -static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { - pa_rtsp_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; - - c->ioline = pa_ioline_new(io); - pa_ioline_set_callback(c->ioline, line_callback, c); - - /* Get the local IP address for use externally */ - if (0 == getsockname(pa_iochannel_get_recv_fd(io), &sa.sa, &sa_len)) { - char buf[INET6_ADDRSTRLEN]; - const char *res = NULL; - - if (AF_INET == sa.sa.sa_family) { - 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); - } - 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) { - pa_assert(c); - pa_assert(mainloop); - pa_assert(hostname); - pa_assert(port > 0); - - if (!(c->sc = pa_socket_client_new_string(mainloop, hostname, port))) { - pa_log("failed to connect to server '%s:%d'", hostname, port); - return -1; - } - - 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); - - if (c->io) - pa_iochannel_free(c->io); - c->io = NULL; -} - - -const char* pa_rtsp_localip(pa_rtsp_context* c) { - pa_assert(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); - - 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; - - 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* headers; - int rv; - - pa_assert(c); - - headers = pa_headerlist_new(); - pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); - - c->state = STATE_SETUP; - rv = pa_rtsp_exec(c, "SETUP", NULL, NULL, 1, headers); - pa_headerlist_free(headers); - return rv; -} - - -int pa_rtsp_record(pa_rtsp_context* c) { - pa_headerlist* headers; - int rv; - - pa_assert(c); - if (!c->session) { - /* No seesion in progres */ - return -1; - } - - headers = pa_headerlist_new(); - pa_headerlist_puts(headers, "Range", "npt=0-"); - pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); - - c->state = STATE_RECORD; - rv = pa_rtsp_exec(c, "RECORD", NULL, NULL, 1, headers); - pa_headerlist_free(headers); - return rv; -} - - -int pa_rtsp_teardown(pa_rtsp_context *c) { - pa_assert(c); - - c->state = STATE_TEARDOWN; - return pa_rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); -} - - -int pa_rtsp_setparameter(pa_rtsp_context *c, const char* param) { - pa_assert(c); - if (!param) - return -1; - - c->state = STATE_SET_PARAMETER; - return pa_rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, 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"); - - 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 deleted file mode 100644 index 6458f851..00000000 --- a/src/modules/rtp/rtsp.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef foortsphfoo -#define foortsphfoo - -/* $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 -#include -#include -#include - -#include -#include -#include -#include - -#include "headerlist.h" - -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); -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 diff --git a/src/modules/rtp/rtsp_client.c b/src/modules/rtp/rtsp_client.c new file mode 100644 index 00000000..c22f801e --- /dev/null +++ b/src/modules/rtp/rtsp_client.c @@ -0,0 +1,507 @@ +/* $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 + +#include "rtsp_client.h" + +struct pa_rtsp_client { + pa_socket_client *sc; + pa_iochannel *io; + pa_ioline *ioline; + + pa_rtsp_cb_t callback; + + void *userdata; + const char *useragent; + + pa_rtsp_state state; + uint8_t waiting; + + pa_headerlist* headers; + char *last_header; + pa_strbuf *header_buffer; + pa_headerlist* response_headers; + + char *localip; + char *url; + uint32_t port; + uint32_t cseq; + char *session; + char *transport; +}; + +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) { + 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 *c; + + c = pa_xnew0(pa_rtsp_client, 1); + c->headers = pa_headerlist_new(); + + if (useragent) + c->useragent = useragent; + else + c->useragent = "PulseAudio RTSP Client"; + + return c; +} + + +void pa_rtsp_client_free(pa_rtsp_client* c) { + if (c) { + if (c->sc) + pa_socket_client_unref(c->sc); + + pa_xfree(c->url); + pa_xfree(c->localip); + pa_xfree(c->session); + pa_xfree(c->transport); + pa_xfree(c->last_header); + if (c->header_buffer) + pa_strbuf_free(c->header_buffer); + if (c->response_headers) + pa_headerlist_free(c->response_headers); + pa_headerlist_free(c->headers); + } + pa_xfree(c); +} + + +static void headers_read(pa_rtsp_client *c) { + char* token; + char delimiters[] = ";"; + + pa_assert(c); + pa_assert(c->response_headers); + + /* Deal with a SETUP response */ + if (STATE_SETUP == c->state) { + const char* token_state = NULL; + const char* pc = NULL; + c->session = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Session")); + c->transport = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Transport")); + + if (!c->session || !c->transport) { + pa_headerlist_free(c->response_headers); + c->response_headers = NULL; + pa_log("Invalid SETUP response."); + return; + } + + /* Now parse out the server port component of the response. */ + while ((token = pa_split(c->transport, delimiters, &token_state))) { + if ((pc = strstr(token, "="))) { + if (0 == strncmp(token, "server_port", 11)) { + pa_atou(pc+1, &c->port); + pa_xfree(token); + break; + } + } + pa_xfree(token); + } + if (0 == c->port) { + /* Error no server_port in response */ + pa_headerlist_free(c->response_headers); + c->response_headers = NULL; + pa_log("Invalid SETUP response (no port number)."); + return; + } + } + + /* Call our callback */ + if (c->callback) + c->callback(c, c->state, c->response_headers, c->userdata); + + pa_headerlist_free(c->response_headers); + c->response_headers = NULL; +} + + +static void line_callback(pa_ioline *line, const char *s, void *userdata) { + char *delimpos; + char *s2, *s2p; + + pa_rtsp_client *c = userdata; + pa_assert(line); + pa_assert(c); + pa_assert(s); + + s2 = pa_xstrdup(s); + /* Trim trailing carriage returns */ + s2p = s2 + strlen(s2) - 1; + while (s2p >= s2 && '\r' == *s2p) { + *s2p = '\0'; + s2p -= 1; + } + if (c->waiting && 0 == strcmp("RTSP/1.0 200 OK", s2)) { + c->waiting = 0; + pa_assert(!c->response_headers); + c->response_headers = pa_headerlist_new(); + goto exit; + } + if (c->waiting) { + pa_log_warn("Unexpected response: %s", s2); + goto exit;; + } + if (!strlen(s2)) { + /* End of headers */ + /* We will have a header left from our looping itteration, so add it in :) */ + if (c->last_header) { + /* This is not a continuation header so let's dump it into our proplist */ + pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); + pa_xfree(c->last_header); + c->last_header = NULL; + c->header_buffer= NULL; + } + + pa_log_debug("Full response received. Dispatching"); + headers_read(c); + c->waiting = 1; + goto exit; + } + + /* Read and parse a header (we know it's not empty) */ + /* TODO: Move header reading into the headerlist. */ + + /* If the first character is a space, it's a continuation header */ + if (c->last_header && ' ' == s2[0]) { + pa_assert(c->header_buffer); + + /* Add this line to the buffer (sans the space. */ + pa_strbuf_puts(c->header_buffer, &(s2[1])); + goto exit; + } + + if (c->last_header) { + /* This is not a continuation header so let's dump the full + header/value into our proplist */ + pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); + pa_xfree(c->last_header); + c->last_header = NULL; + c->header_buffer = NULL; + } + + delimpos = strstr(s2, ":"); + if (!delimpos) { + pa_log_warn("Unexpected response when expecting header: %s", s); + goto exit; + } + + pa_assert(!c->header_buffer); + pa_assert(!c->last_header); + + c->header_buffer = pa_strbuf_new(); + if (strlen(delimpos) > 1) { + /* Cut our line off so we can copy the header name out */ + *delimpos++ = '\0'; + + /* Trim the front of any spaces */ + while (' ' == *delimpos) + ++delimpos; + + pa_strbuf_puts(c->header_buffer, delimpos); + } else { + /* Cut our line off so we can copy the header name out */ + *delimpos = '\0'; + } + + /* Save the header name */ + c->last_header = pa_xstrdup(s2); + exit: + pa_xfree(s2); +} + + +static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { + pa_rtsp_client *c = userdata; + union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; + } sa; + socklen_t sa_len = sizeof(sa); + + pa_assert(sc); + pa_assert(c); + pa_assert(c->sc == sc); + + pa_socket_client_unref(c->sc); + c->sc = NULL; + + if (!io) { + pa_log("Connection failed: %s", pa_cstrerror(errno)); + return; + } + pa_assert(!c->io); + c->io = io; + + c->ioline = pa_ioline_new(io); + pa_ioline_set_callback(c->ioline, line_callback, c); + + /* Get the local IP address for use externally */ + if (0 == getsockname(pa_iochannel_get_recv_fd(io), &sa.sa, &sa_len)) { + char buf[INET6_ADDRSTRLEN]; + const char *res = NULL; + + if (AF_INET == sa.sa.sa_family) { + 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); + } + 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) { + pa_assert(c); + pa_assert(mainloop); + pa_assert(hostname); + pa_assert(port > 0); + + if (!(c->sc = pa_socket_client_new_string(mainloop, hostname, port))) { + pa_log("failed to connect to server '%s:%d'", hostname, port); + return -1; + } + + pa_socket_client_set_callback(c->sc, on_connection, c); + c->state = STATE_CONNECT; + return 0; +} + +void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata) { + pa_assert(c); + + c->callback = callback; + c->userdata = userdata; +} + +void pa_rtsp_disconnect(pa_rtsp_client *c) { + pa_assert(c); + + if (c->io) + pa_iochannel_free(c->io); + c->io = NULL; +} + + +const char* pa_rtsp_localip(pa_rtsp_client* c) { + pa_assert(c); + + return c->localip; +} + +uint32_t pa_rtsp_serverport(pa_rtsp_client* c) { + pa_assert(c); + + return c->port; +} + +void pa_rtsp_set_url(pa_rtsp_client* c, const char* url) { + pa_assert(c); + + c->url = pa_xstrdup(url); +} + +void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value) +{ + pa_assert(c); + pa_assert(key); + pa_assert(value); + + pa_headerlist_puts(c->headers, key, value); +} + +void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key) +{ + pa_assert(c); + pa_assert(key); + + pa_headerlist_remove(c->headers, key); +} + +int pa_rtsp_announce(pa_rtsp_client *c, const char* sdp) { + pa_assert(c); + if (!sdp) + return -1; + + c->state = STATE_ANNOUNCE; + return pa_rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL); +} + + +int pa_rtsp_setup(pa_rtsp_client* c) { + pa_headerlist* headers; + int rv; + + pa_assert(c); + + headers = pa_headerlist_new(); + pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); + + c->state = STATE_SETUP; + rv = pa_rtsp_exec(c, "SETUP", NULL, NULL, 1, headers); + pa_headerlist_free(headers); + return rv; +} + + +int pa_rtsp_record(pa_rtsp_client* c) { + pa_headerlist* headers; + int rv; + + pa_assert(c); + if (!c->session) { + /* No seesion in progres */ + return -1; + } + + headers = pa_headerlist_new(); + pa_headerlist_puts(headers, "Range", "npt=0-"); + pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); + + c->state = STATE_RECORD; + rv = pa_rtsp_exec(c, "RECORD", NULL, NULL, 1, headers); + pa_headerlist_free(headers); + return rv; +} + + +int pa_rtsp_teardown(pa_rtsp_client *c) { + pa_assert(c); + + c->state = STATE_TEARDOWN; + return pa_rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); +} + + +int pa_rtsp_setparameter(pa_rtsp_client *c, const char* param) { + pa_assert(c); + if (!param) + return -1; + + c->state = STATE_SET_PARAMETER; + return pa_rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL); +} + + +int pa_rtsp_flush(pa_rtsp_client *c) { + pa_headerlist* headers; + int rv; + + pa_assert(c); + + headers = pa_headerlist_new(); + pa_headerlist_puts(headers, "RTP-Info", "seq=0;rtptime=0"); + + 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_client.h b/src/modules/rtp/rtsp_client.h new file mode 100644 index 00000000..0f1daabd --- /dev/null +++ b/src/modules/rtp/rtsp_client.h @@ -0,0 +1,74 @@ +#ifndef foortspclienthfoo +#define foortspclienthfoo + +/* $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 +#include +#include +#include + +#include +#include +#include +#include + +#include "headerlist.h" + +typedef struct pa_rtsp_client pa_rtsp_client; +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_client *c, pa_rtsp_state state, pa_headerlist* hl, void *userdata); + +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_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_client* c); + +const char* pa_rtsp_localip(pa_rtsp_client* c); +uint32_t pa_rtsp_serverport(pa_rtsp_client* c); +void pa_rtsp_set_url(pa_rtsp_client* c, const char* url); +void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value); +void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key); + +int pa_rtsp_announce(pa_rtsp_client* c, const char* sdp); + +int pa_rtsp_setup(pa_rtsp_client* c); +int pa_rtsp_record(pa_rtsp_client* c); +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); + +#endif -- cgit From 1fb046536a687e7c5eef9a440f66d111cd0e8cb4 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sat, 10 May 2008 23:01:37 +0000 Subject: 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 7bfce930..0df80e11 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); - - c->buffer = pa_xrealloc(c->buffer, (count + header_size + 16)); + pa_assert(c->fd > 0); + pa_assert(raw); + pa_assert(raw->memblock); + pa_assert(raw->length > 0); + + /* 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 1dcf779f..68a1cdb0 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 -- cgit From f97c5debcc9564d368e6d79606df7b3ce6269d58 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 12:18:36 +0000 Subject: 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 0df80e11..bad747bb 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); } -- cgit From 264a1c2ffc3b38fd88421c7d7ad9ebbab6b7e1bb Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 12:20:14 +0000 Subject: 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 91979207..5a9b9024 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 -- cgit From d51f5944b7248ec759ab71b0e811ec0f7c655e22 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 12:21:32 +0000 Subject: 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 | 390 ++++++++++++++++++++++++++++------------- 1 file changed, 267 insertions(+), 123 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index f2ddf1cf..3d08cb86 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; + + 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; - char *server_name; + 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,31 +122,65 @@ 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_SET_STATE: + + 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_smoother_pause(u->smoother, pa_rtclock_usec()); + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; + case PA_SINK_MESSAGE_GET_LATENCY: { - size_t n = 0; - //int l; - -#ifdef TIOCINQ - /* - if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0) - n = (size_t) l; - */ -#endif + pa_usec_t w, r; - n += u->memchunk.length; + 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) = pa_bytes_to_usec(n, &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; + } } return pa_sink_process_msg(o, code, data, offset, chunk); @@ -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); - - /* 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); + 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) { + pa_usec_t usec; + int64_t n; + + for (;;) { + ssize_t l; + void *p; + + 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); + + 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); + + p = pa_memblock_acquire(u->encoded_memchunk.memblock); + l = pa_write(u->fd, (uint8_t*) p + u->encoded_memchunk.index, u->encoded_memchunk.length, &write_type); + pa_memblock_release(u->encoded_memchunk.memblock); + + pa_assert(l != 0); + + if (l < 0) { + + if (errno == EINTR) + continue; + else if (errno == EAGAIN) { + + /* OK, we filled all socket buffers up + * now. */ + goto filled_up; + + } else { + pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + u->offset += l; + + u->encoded_memchunk.index += l; + u->encoded_memchunk.length -= l; + + 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; + } + } - 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); + filled_up: - pa_assert(l != 0); + /* 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 (l < 0) { + n = u->offset; - if (errno == EINTR) - continue; - else if (errno != EAGAIN) { - pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); - goto fail; +#ifdef SIOCOUTQ + { + int l; + if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0) + n -= l; } +#endif - } else { - - u->memchunk.index += l; - u->memchunk.length -= l; + usec = pa_bytes_to_usec(n, &u->sink->sample_spec); - if (u->memchunk.length <= 0) { - pa_memblock_unref(u->memchunk.memblock); - pa_memchunk_reset(&u->memchunk); - } + if (usec > u->latency) + usec -= u->latency; + else + usec = 0; - pollfd->revents = 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; - } - - pa_make_fd_cloexec(u->fd); - pa_make_fd_nonblock(u->fd); + u->read_data = u->write_data = NULL; + u->read_index = u->write_index = u->read_length = u->write_length = 0; - if (fstat(u->fd, &st) < 0) { - pa_log("fstat('%s'): %s", u->filename, pa_cstrerror(errno)); - goto fail; - } + /*u->state = STATE_AUTH;*/ + u->latency = 0; - 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); } -- cgit From 4dd318519fbec1811a16dca05aca859da74b60c2 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 13:32:09 +0000 Subject: 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 c22f801e..5665c9f6 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 */ -- cgit From 5eecfa2e3f3abcacc9df2776cba798598e5fb6ee Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 13:35:01 +0000 Subject: 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 3d08cb86..f6f93a46 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 bad747bb..b4cbd2ba 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; } -- cgit From 899492c31581f5591cd9437052dda15ad02ec0ac Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 14:18:48 +0000 Subject: 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 b4cbd2ba..75881c6e 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 68a1cdb0..1ec56ca9 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 5665c9f6..22f0f0c1 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 0f1daabd..3c5280c2 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); -- cgit From ec9a618768790055fef00a46866b4e0e1fa33048 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 14:19:41 +0000 Subject: 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 f6f93a46..090f04fa 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); -- cgit From e00127fe245cc2065f74617dada3b474b88907af Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 14:57:30 +0000 Subject: 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 090f04fa..0a7ce171 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 75881c6e..92be6cd7 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 1ec56ca9..b2817e59 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); -- cgit From d195d06da7009db985c0a5827b096bc39dd994bf Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 15:06:14 +0000 Subject: 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 0a7ce171..3d0eaeff 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 92be6cd7..15810170 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 b2817e59..303fdaa4 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); -- cgit From 4b7b7b15d73a5f2a98229b12406b4397563d2983 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 15:12:20 +0000 Subject: 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 22f0f0c1..193248eb 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); -- cgit From cb8c5a925fc22819626cbe4525b1d334db75d071 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 15:34:37 +0000 Subject: 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 043ef5a8..980b018e 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 8bdc7251..de8710b7 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 15810170..fc42340c 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); -- cgit From be73d378f5fd5ea1aedcc75b0c7c1e1a82a27047 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 15:43:56 +0000 Subject: 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 3d0eaeff..79c517aa 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); } -- cgit From eca94fee59ba68603f1c34f5a4abe55dd7d9d638 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 16:38:33 +0000 Subject: 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 fc42340c..a9b9ab1d 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); } -- cgit From 92166846913ebb5e86f36352e20c9ca4f4bf23ae Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 16:40:26 +0000 Subject: 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 193248eb..24839729 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; } -- cgit From 3767cdb6d16c5817eb489129585fb353e3ad6afa Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 11 May 2008 17:02:19 +0000 Subject: 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 a9b9ab1d..e5a373d0 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 24839729..f9fe9bfe 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; } -- cgit From 6c1dd6e54b4b5b4213467d156abc9f260c63aaa3 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 21:04:45 +0000 Subject: 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 79c517aa..e17198cd 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; -- cgit From 6dc5e0797738d6afe2c3c99750abd2fb26fed9a9 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 21:05:53 +0000 Subject: 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 e5a373d0..cdf9150d 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); -- cgit From 8108121fa76e51c36772a592f74b075dcaf7c4cb Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 21:10:08 +0000 Subject: 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 e17198cd..42092e06 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. -- cgit From b93e9e80ecbdc90c0bf6f90c5bd4aafcdb4d2488 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 23:02:30 +0000 Subject: 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 cdf9150d..7ba1be74 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); -- cgit From 13bc07587564977a64222f5a4075b7fa63ac5548 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 26 May 2008 23:43:51 +0000 Subject: 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 | 79 +++++++++++++++++++++++++++++++----------- src/modules/rtp/raop_client.c | 16 +-------- 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/src/modules/module-raop-sink.c b/src/modules/module-raop-sink.c index 42092e06..f78abbae 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); - - /* 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); + if (u->encoded_memchunk.memblock) + pa_memblock_unref(u->encoded_memchunk.memblock); + if (PA_SINK_OPENED(u->sink->thread_info.state)) { + size_t rl; + + /* We render real data */ + if (u->raw_memchunk.length <= 0) { + if (u->raw_memchunk.memblock) + pa_memblock_unref(u->raw_memchunk.memblock); + pa_memchunk_reset(&u->raw_memchunk); + + /* Grab unencoded data */ + pa_sink_render(u->sink, u->block_size, &u->raw_memchunk); + } + pa_assert(u->raw_memchunk.length > 0); + + /* Encode it */ + rl = u->raw_memchunk.length; + u->encoding_overhead += u->next_encoding_overhead; + pa_raop_client_encode_sample(u->raop, &u->raw_memchunk, &u->encoded_memchunk); + u->next_encoding_overhead = (u->encoded_memchunk.length - (rl - u->raw_memchunk.length)); + u->encoding_ratio = u->encoded_memchunk.length / (rl - u->raw_memchunk.length); + } else { + /* We render some silence into our memchunk */ + 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 7ba1be74..4085a494 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); -- cgit From 7f0cf0c9adc5176ba41d549754ea25ed6f12bdce Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 3 Jun 2008 23:07:48 +0000 Subject: 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 f78abbae..96c98a6f 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 -- cgit From 651da7d095f78e930fb758442905d5769cfda1c5 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 9 Jun 2008 21:59:00 +0000 Subject: 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 199c6352..347a9971 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); -- cgit From 5f527dc47944bbd97a49e8d89427d09850b28e5d Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 9 Jun 2008 21:59:41 +0000 Subject: 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 4085a494..4714d273 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 f9fe9bfe..70040428 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 3c5280c2..55540180 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 -- cgit From 19dcb529ad3baa8e8e506ddfa703e952044e0f60 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Mon, 9 Jun 2008 22:01:23 +0000 Subject: 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 70040428..c0ca49fa 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" -- cgit From d86fc75e0cbc9d102dc000d2781f9dfddc89fbbf Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 10 Jun 2008 23:49:35 +0000 Subject: 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 c0ca49fa..88f77818 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); + + pa_xfree(c->session); + c->session = NULL; - if (!(c->sc = pa_socket_client_new_string(mainloop, hostname, port))) { - pa_log("failed to connect to server '%s:%d'", hostname, port); + 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 55540180..dcc9209c 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); -- cgit From c49be7891fac98056010cf553042946740061590 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 10 Jun 2008 23:55:58 +0000 Subject: 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 | 73 +++++++++++++++++++++++++++++++------------ src/modules/rtp/raop_client.h | 3 ++ 2 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c index 4714d273..48deff0d 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,7 +375,43 @@ 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)"); + + if (pa_raop_connect(c)) { + pa_raop_client_free(c); + return NULL; + } + return c; +} + + +void pa_raop_client_free(pa_raop_client* c) +{ + pa_assert(c); + + if (c->rtsp) + pa_rtsp_client_free(c->rtsp); + pa_xfree(c->host); + pa_xfree(c); +} + + +int pa_raop_connect(pa_raop_client* c) +{ + char *sci; + struct { + uint32_t a; + uint32_t b; + uint32_t c; + } rand_data; + + pa_assert(c); + + if (c->rtsp) { + pa_log_debug("Connection already in progress"); + return 0; + } + + c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, 5000, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); /* Initialise the AES encryption system */ pa_random(c->aes_iv, sizeof(c->aes_iv)); @@ -386,23 +424,18 @@ pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) 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); - if (pa_rtsp_connect(c->rtsp, c->core->mainloop, host, 5000)) { - pa_rtsp_client_free(c->rtsp); - return NULL; - } - return c; + return pa_rtsp_connect(c->rtsp); } -void pa_raop_client_free(pa_raop_client* c) +int pa_raop_flush(pa_raop_client* c) { pa_assert(c); - if (c->rtsp) - pa_rtsp_client_free(c->rtsp); - pa_xfree(c->host); - pa_xfree(c); + pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); + return 0; } diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h index 303fdaa4..882dae17 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); -- cgit From 15e8420a251ec4912d2df9d29dfb728bdc97f33c Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 11 Jun 2008 00:02:10 +0000 Subject: 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 96c98a6f..51c2368a 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; -- cgit From d997420791cb8defbda65acdf08fe18072d01d7b Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 11 Jun 2008 22:43:27 +0000 Subject: 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 51c2368a..545266d5 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="); -- cgit From 729bbaf88485207c8637f3c30fffed60a1f73ca1 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 11 Jun 2008 22:44:09 +0000 Subject: 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 5a9b9024..4ffdfd93 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 00000000..7f623617 --- /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); +} -- cgit From 0ff75aea053cb8a7af958aa72d99b27cc90becf6 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Wed, 11 Jun 2008 23:01:07 +0000 Subject: 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 7f623617..7f89bb5c 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 545266d5..39374a36 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 -- cgit From 36f2aad5f0892e6738a8d59111c3c9017152db7e Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Tue, 24 Jun 2008 23:57:37 +0100 Subject: 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 7f89bb5c..baa64eec 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); } -- cgit From e543e04ca725ef1c240762529e7dafec31749683 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 3 Jul 2008 23:47:34 +0100 Subject: 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 48deff0d..792eceec 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 882dae17..3d5ef167 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); -- cgit From ded09d1f9b00ac793ccc26caea0f8706e88bd521 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Thu, 3 Jul 2008 23:49:01 +0100 Subject: 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 39374a36..50ef985e 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); -- cgit From 19d28319733f92cc518d0d45a15f15b704f603d8 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 3 Aug 2008 20:52:35 +0100 Subject: 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 baa64eec..38436a38 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 50ef985e..b90d4e23 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."); -- cgit From c3d8bb5b34c45f4dda594cc1d8107cac468fa232 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 3 Aug 2008 20:56:21 +0100 Subject: 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 980b018e..8918def8 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 347a9971..dac0e707 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 de8710b7..0fef835b 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 276d0e35..4b9c6433 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 792eceec..4627545e 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 3d5ef167..ec3136a7 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 88f77818..9eb3d964 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 dcc9209c..88fb3839 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. -- cgit From 8715121755ad1aa9c083dd70781a63ced13359c2 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Sun, 3 Aug 2008 22:46:21 +0100 Subject: Modularise the RAOP stuff that requires OpenSSL and make it optional at compile time --- configure.ac | 41 +++ src/Makefile.am | 35 ++- src/modules/raop/base64.c | 126 +++++++++ src/modules/raop/base64.h | 34 +++ src/modules/raop/raop_client.c | 561 +++++++++++++++++++++++++++++++++++++++++ src/modules/raop/raop_client.h | 46 ++++ src/modules/rtp/base64.c | 126 --------- src/modules/rtp/base64.h | 34 --- src/modules/rtp/raop_client.c | 561 ----------------------------------------- src/modules/rtp/raop_client.h | 46 ---- 10 files changed, 835 insertions(+), 775 deletions(-) create mode 100644 src/modules/raop/base64.c create mode 100644 src/modules/raop/base64.h create mode 100644 src/modules/raop/raop_client.c create mode 100644 src/modules/raop/raop_client.h delete mode 100644 src/modules/rtp/base64.c delete mode 100644 src/modules/rtp/base64.h delete mode 100644 src/modules/rtp/raop_client.c delete mode 100644 src/modules/rtp/raop_client.h diff --git a/configure.ac b/configure.ac index 2b91a006..2a040763 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 4ffdfd93..f216b71b 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/raop/base64.c b/src/modules/raop/base64.c new file mode 100644 index 00000000..8918def8 --- /dev/null +++ b/src/modules/raop/base64.c @@ -0,0 +1,126 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* + This file was originally inspired by a file developed by + Kungliga Tekniska H�gskolan +*/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include + +#include "base64.h" + +static const char base64_chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static int pos(char c) +{ + if (c >= 'A' && c <= 'Z') return c - 'A' + 0; + if (c >= 'a' && c <= 'z') return c - 'a' + 26; + if (c >= '0' && c <= '9') return c - '0' + 52; + if (c == '+') return 62; + if (c == '/') return 63; +} + +int pa_base64_encode(const void *data, int size, char **str) +{ + char *s, *p; + int i; + int c; + const unsigned char *q; + + p = s = pa_xnew(char, size * 4 / 3 + 4); + q = (const unsigned char *) data; + i = 0; + for (i = 0; i < size;) { + c = q[i++]; + c *= 256; + if (i < size) + c += q[i]; + i++; + c *= 256; + if (i < size) + c += q[i]; + i++; + p[0] = base64_chars[(c & 0x00fc0000) >> 18]; + p[1] = base64_chars[(c & 0x0003f000) >> 12]; + p[2] = base64_chars[(c & 0x00000fc0) >> 6]; + p[3] = base64_chars[(c & 0x0000003f) >> 0]; + if (i > size) + p[3] = '='; + if (i > size + 1) + p[2] = '='; + p += 4; + } + *p = 0; + *str = s; + return strlen(s); +} + +#define DECODE_ERROR 0xffffffff + +static unsigned int token_decode(const char *token) +{ + int i; + unsigned int val = 0; + int marker = 0; + if (strlen(token) < 4) + return DECODE_ERROR; + for (i = 0; i < 4; i++) { + val *= 64; + if (token[i] == '=') + marker++; + else if (marker > 0) + return DECODE_ERROR; + else + val += pos(token[i]); + } + if (marker > 2) + return DECODE_ERROR; + return (marker << 24) | val; +} + +int pa_base64_decode(const char *str, void *data) +{ + const char *p; + unsigned char *q; + + q = data; + for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) { + unsigned int val = token_decode(p); + unsigned int marker = (val >> 24) & 0xff; + if (val == DECODE_ERROR) + return -1; + *q++ = (val >> 16) & 0xff; + if (marker < 2) + *q++ = (val >> 8) & 0xff; + if (marker < 1) + *q++ = val & 0xff; + } + return q - (unsigned char *) data; +} diff --git a/src/modules/raop/base64.h b/src/modules/raop/base64.h new file mode 100644 index 00000000..dac0e707 --- /dev/null +++ b/src/modules/raop/base64.h @@ -0,0 +1,34 @@ +#ifndef foobase64hfoo +#define foobase64hfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + Copyright Kungliga Tekniska Høgskolan + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +/* + This file was originally inspired by a file developed by + Kungliga Tekniska Høgskolan +*/ + +int pa_base64_encode(const void *data, int size, char **str); +int pa_base64_decode(const char *str, void *data); + +#endif diff --git a/src/modules/raop/raop_client.c b/src/modules/raop/raop_client.c new file mode 100644 index 00000000..4627545e --- /dev/null +++ b/src/modules/raop/raop_client.c @@ -0,0 +1,561 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include +#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_client.h" +#include "base64.h" + +#define AES_CHUNKSIZE 16 + +#define JACK_STATUS_DISCONNECTED 0 +#define JACK_STATUS_CONNECTED 1 + +#define JACK_TYPE_ANALOG 0 +#define JACK_TYPE_DIGITAL 1 + +#define VOLUME_DEF -30 +#define VOLUME_MIN -144 +#define VOLUME_MAX 0 + + +struct pa_raop_client { + pa_core *core; + char *host; + char *sid; + pa_rtsp_client *rtsp; + + uint8_t jack_type; + uint8_t jack_status; + + /* Encryption Related bits */ + AES_KEY aes; + uint8_t aes_iv[AES_CHUNKSIZE]; /* initialization vector for aes-cbc */ + uint8_t aes_nv[AES_CHUNKSIZE]; /* next vector for aes-cbc */ + uint8_t aes_key[AES_CHUNKSIZE]; /* key for aes-cbc */ + + pa_socket_client *sc; + int fd; + + uint16_t seq; + uint32_t rtptime; + + pa_raop_client_cb_t callback; + void* userdata; + pa_raop_client_closed_cb_t closed_callback; + void* closed_userdata; +}; + +/** + * Function to write bits into a buffer. + * @param buffer Handle to the buffer. It will be incremented if new data requires it. + * @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB) + * @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks + * @param data The data to write + * @param data_bit_len The number of bits from data to write + */ +static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uint8_t data, uint8_t data_bit_len) { + int bits_left, bit_overflow; + uint8_t bit_data; + + if (!data_bit_len) + return; + + /* If bit pos is zero, we will definatly use at least one bit from the current byte so size increments. */ + if (!*bit_pos) + *size += 1; + + /* Calc the number of bits left in the current byte of buffer */ + bits_left = 7 - *bit_pos + 1; + /* Calc the overflow of bits in relation to how much space we have left... */ + bit_overflow = bits_left - data_bit_len; + if (bit_overflow >= 0) { + /* We can fit the new data in our current byte */ + /* As we write from MSB->LSB we need to left shift by the overflow amount */ + bit_data = data << bit_overflow; + if (*bit_pos) + **buffer |= bit_data; + else + **buffer = bit_data; + /* If our data fits exactly into the current byte, we need to increment our pointer */ + if (0 == bit_overflow) { + /* Do not increment size as it will be incremeneted on next call as bit_pos is zero */ + *buffer += 1; + *bit_pos = 0; + } else { + *bit_pos += data_bit_len; + } + } else { + /* bit_overflow is negative, there for we will need a new byte from our buffer */ + /* Firstly fill up what's left in the current byte */ + bit_data = data >> -bit_overflow; + **buffer |= bit_data; + /* Increment our buffer pointer and size counter*/ + *buffer += 1; + *size += 1; + **buffer = data << (8 + bit_overflow); + *bit_pos = -bit_overflow; + } +} + +static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { + const char n[] = + "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" + "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" + "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" + "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" + "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" + "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; + const char e[] = "AQAB"; + uint8_t modules[256]; + uint8_t exponent[8]; + int size; + RSA *rsa; + + rsa = RSA_new(); + size = pa_base64_decode(n, modules); + rsa->n = BN_bin2bn(modules, size, NULL); + size = pa_base64_decode(e, exponent); + rsa->e = BN_bin2bn(exponent, size, NULL); + + size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); + RSA_free(rsa); + return size; +} + +static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) +{ + uint8_t *buf; + int i=0, j; + + pa_assert(c); + + memcpy(c->aes_nv, c->aes_iv, AES_CHUNKSIZE); + while (i+AES_CHUNKSIZE <= size) { + buf = data + i; + for (j=0; jaes_nv[j]; + + AES_encrypt(buf, buf, &c->aes); + memcpy(c->aes_nv, buf, AES_CHUNKSIZE); + i += AES_CHUNKSIZE; + } + return i; +} + +static inline void rtrimchar(char *str, char rc) +{ + char *sp = str + strlen(str) - 1; + while (sp >= str && *sp == rc) { + *sp = '\0'; + sp -= 1; + } +} + +static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { + pa_raop_client *c = userdata; + + pa_assert(sc); + pa_assert(c); + pa_assert(c->sc == sc); + pa_assert(c->fd < 0); + pa_assert(c->callback); + + pa_socket_client_unref(c->sc); + c->sc = NULL; + + if (!io) { + pa_log("Connection failed: %s", pa_cstrerror(errno)); + return; + } + + c->fd = pa_iochannel_get_send_fd(io); + + pa_iochannel_set_noclose(io, TRUE); + pa_iochannel_socket_set_sndbuf(io, 1024); + pa_iochannel_free(io); + + pa_make_tcp_socket_low_delay(c->fd); + + pa_log_debug("Connection established"); + c->callback(c->fd, c->userdata); +} + +static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) +{ + pa_raop_client* c = userdata; + pa_assert(c); + pa_assert(rtsp); + pa_assert(rtsp == c->rtsp); + + switch (state) { + case STATE_CONNECT: { + int i; + uint8_t rsakey[512]; + char *key, *iv, *sac, *sdp; + uint16_t rand_data; + const char *ip; + char *url; + + pa_log_debug("RAOP: CONNECTED"); + ip = pa_rtsp_localip(c->rtsp); + /* First of all set the url properly */ + url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid); + pa_rtsp_set_url(c->rtsp, url); + pa_xfree(url); + + /* Now encrypt our aes_public key to send to the device */ + i = rsa_encrypt(c->aes_key, AES_CHUNKSIZE, rsakey); + pa_base64_encode(rsakey, i, &key); + rtrimchar(key, '='); + pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv); + rtrimchar(iv, '='); + + pa_random(&rand_data, sizeof(rand_data)); + pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac); + rtrimchar(sac, '='); + pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); + sdp = pa_sprintf_malloc( + "v=0\r\n" + "o=iTunes %s 0 IN IP4 %s\r\n" + "s=iTunes\r\n" + "c=IN IP4 %s\r\n" + "t=0 0\r\n" + "m=audio 0 RTP/AVP 96\r\n" + "a=rtpmap:96 AppleLossless\r\n" + "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n" + "a=rsaaeskey:%s\r\n" + "a=aesiv:%s\r\n", + c->sid, ip, c->host, key, iv); + pa_rtsp_announce(c->rtsp, sdp); + pa_xfree(key); + pa_xfree(iv); + pa_xfree(sac); + pa_xfree(sdp); + break; + } + + case STATE_ANNOUNCE: + pa_log_debug("RAOP: ANNOUNCED"); + pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); + pa_rtsp_setup(c->rtsp); + break; + + case STATE_SETUP: { + char *aj = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); + pa_log_debug("RAOP: SETUP"); + if (aj) { + char *token, *pc; + char delimiters[] = ";"; + const char* token_state = NULL; + c->jack_type = JACK_TYPE_ANALOG; + c->jack_status = JACK_STATUS_DISCONNECTED; + + while ((token = pa_split(aj, delimiters, &token_state))) { + if ((pc = strstr(token, "="))) { + *pc = 0; + if (!strcmp(token, "type") && !strcmp(pc+1, "digital")) { + c->jack_type = JACK_TYPE_DIGITAL; + } + } else { + if (!strcmp(token,"connected")) + c->jack_status = JACK_STATUS_CONNECTED; + } + pa_xfree(token); + } + pa_xfree(aj); + } else { + pa_log_warn("Audio Jack Status missing"); + } + pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); + break; + } + + case STATE_RECORD: { + uint32_t port = pa_rtsp_serverport(c->rtsp); + pa_log_debug("RAOP: RECORDED"); + + if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, c->host, port))) { + pa_log("failed to connect to server '%s:%d'", c->host, port); + return; + } + pa_socket_client_set_callback(c->sc, on_connection, c); + break; + } + + case STATE_FLUSH: + pa_log_debug("RAOP: FLUSHED"); + break; + + case STATE_TEARDOWN: + case STATE_SET_PARAMETER: + pa_log_debug("RAOP: SET_PARAMETER"); + break; + case STATE_DISCONNECTED: + pa_assert(c->closed_callback); + pa_assert(c->rtsp); + + pa_log_debug("RTSP control channel closed"); + pa_rtsp_client_free(c->rtsp); + c->rtsp = NULL; + if (c->fd > 0) { + /* We do not close the fd, we leave it to the closed callback to do that */ + c->fd = -1; + } + if (c->sc) { + pa_socket_client_unref(c->sc); + c->sc = NULL; + } + pa_xfree(c->sid); + c->sid = NULL; + c->closed_callback(c->closed_userdata); + break; + } +} + +pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) +{ + pa_raop_client* c = pa_xnew0(pa_raop_client, 1); + + pa_assert(core); + pa_assert(host); + + c->core = core; + c->fd = -1; + c->host = pa_xstrdup(host); + + if (pa_raop_connect(c)) { + pa_raop_client_free(c); + return NULL; + } + return c; +} + + +void pa_raop_client_free(pa_raop_client* c) +{ + pa_assert(c); + + if (c->rtsp) + pa_rtsp_client_free(c->rtsp); + pa_xfree(c->host); + pa_xfree(c); +} + + +int pa_raop_connect(pa_raop_client* c) +{ + char *sci; + struct { + uint32_t a; + uint32_t b; + uint32_t c; + } rand_data; + + pa_assert(c); + + if (c->rtsp) { + pa_log_debug("Connection already in progress"); + return 0; + } + + c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, 5000, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); + + /* Initialise the AES encryption system */ + pa_random(c->aes_iv, sizeof(c->aes_iv)); + pa_random(c->aes_key, sizeof(c->aes_key)); + memcpy(c->aes_nv, c->aes_iv, sizeof(c->aes_nv)); + AES_set_encrypt_key(c->aes_key, 128, &c->aes); + + /* Generate random instance id */ + pa_random(&rand_data, sizeof(rand_data)); + c->sid = pa_sprintf_malloc("%u", rand_data.a); + sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c); + pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); + pa_xfree(sci); + pa_rtsp_set_callback(c->rtsp, rtsp_cb, c); + return pa_rtsp_connect(c->rtsp); +} + + +int pa_raop_flush(pa_raop_client* c) +{ + pa_assert(c); + + pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); + return 0; +} + + +int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume) +{ + int rv; + double db; + char *param; + + pa_assert(c); + + db = pa_sw_volume_to_dB(volume); + if (db < VOLUME_MIN) + db = VOLUME_MIN; + else if (db > VOLUME_MAX) + db = VOLUME_MAX; + + param = pa_sprintf_malloc("volume: %0.6f\r\n", db); + + /* We just hit and hope, cannot wait for the callback */ + rv = pa_rtsp_setparameter(c->rtsp, param); + pa_xfree(param); + return rv; +} + + +int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) +{ + uint16_t len; + size_t bufmax; + uint8_t *bp, bpos; + uint8_t *ibp, *maxibp; + int size; + uint8_t *b, *p; + uint32_t bsize; + size_t length; + static uint8_t header[] = { + 0x24, 0x00, 0x00, 0x00, + 0xF0, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + }; + int header_size = sizeof(header); + + pa_assert(c); + pa_assert(c->fd > 0); + pa_assert(raw); + pa_assert(raw->memblock); + pa_assert(raw->length > 0); + pa_assert(encoded); + + /* We have to send 4 byte chunks */ + bsize = (int)(raw->length / 4); + length = bsize * 4; + + /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ + bufmax = length + header_size + 16; + pa_memchunk_reset(encoded); + encoded->memblock = pa_memblock_new(c->core->mempool, bufmax); + b = pa_memblock_acquire(encoded->memblock); + memcpy(b, header, header_size); + + /* Now write the actual samples */ + bp = b + header_size; + size = bpos = 0; + bit_writer(&bp,&bpos,&size,1,3); /* channel=1, stereo */ + bit_writer(&bp,&bpos,&size,0,4); /* unknown */ + bit_writer(&bp,&bpos,&size,0,8); /* unknown */ + bit_writer(&bp,&bpos,&size,0,4); /* unknown */ + bit_writer(&bp,&bpos,&size,1,1); /* hassize */ + bit_writer(&bp,&bpos,&size,0,2); /* unused */ + bit_writer(&bp,&bpos,&size,1,1); /* is-not-compressed */ + + /* size of data, integer, big endian */ + bit_writer(&bp,&bpos,&size,(bsize>>24)&0xff,8); + bit_writer(&bp,&bpos,&size,(bsize>>16)&0xff,8); + bit_writer(&bp,&bpos,&size,(bsize>>8)&0xff,8); + bit_writer(&bp,&bpos,&size,(bsize)&0xff,8); + + ibp = p = pa_memblock_acquire(raw->memblock); + maxibp = p + raw->length - 4; + while (ibp <= maxibp) { + /* Byte swap stereo data */ + bit_writer(&bp,&bpos,&size,*(ibp+1),8); + bit_writer(&bp,&bpos,&size,*(ibp+0),8); + bit_writer(&bp,&bpos,&size,*(ibp+3),8); + bit_writer(&bp,&bpos,&size,*(ibp+2),8); + ibp += 4; + raw->index += 4; + raw->length -= 4; + } + pa_memblock_release(raw->memblock); + encoded->length = header_size + size; + + /* store the lenght (endian swapped: make this better) */ + len = size + header_size - 4; + *(b + 2) = len >> 8; + *(b + 3) = len & 0xff; + + /* encrypt our data */ + aes_encrypt(c, (b + header_size), size); + + /* We're done with the chunk */ + pa_memblock_release(encoded->memblock); + + return 0; +} + + +void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata) +{ + pa_assert(c); + + c->callback = callback; + c->userdata = userdata; +} + +void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata) +{ + pa_assert(c); + + c->closed_callback = callback; + c->closed_userdata = userdata; +} diff --git a/src/modules/raop/raop_client.h b/src/modules/raop/raop_client.h new file mode 100644 index 00000000..ec3136a7 --- /dev/null +++ b/src/modules/raop/raop_client.h @@ -0,0 +1,46 @@ +#ifndef fooraopclientfoo +#define fooraopclientfoo + +/*** + This file is part of PulseAudio. + + Copyright 2008 Colin Guthrie + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#include +#include +#include + +typedef struct pa_raop_client pa_raop_client; + +pa_raop_client* pa_raop_client_new(pa_core *core, const char* host); +void pa_raop_client_free(pa_raop_client* c); + +int pa_raop_connect(pa_raop_client* c); +int pa_raop_flush(pa_raop_client* c); + +int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume); +int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded); + +typedef void (*pa_raop_client_cb_t)(int fd, void *userdata); +void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata); + +typedef void (*pa_raop_client_closed_cb_t)(void *userdata); +void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata); + +#endif diff --git a/src/modules/rtp/base64.c b/src/modules/rtp/base64.c deleted file mode 100644 index 8918def8..00000000 --- a/src/modules/rtp/base64.c +++ /dev/null @@ -1,126 +0,0 @@ -/*** - 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 - -#include "base64.h" - -static const char base64_chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -static int pos(char c) -{ - if (c >= 'A' && c <= 'Z') return c - 'A' + 0; - if (c >= 'a' && c <= 'z') return c - 'a' + 26; - if (c >= '0' && c <= '9') return c - '0' + 52; - if (c == '+') return 62; - if (c == '/') return 63; -} - -int pa_base64_encode(const void *data, int size, char **str) -{ - char *s, *p; - int i; - int c; - const unsigned char *q; - - p = s = pa_xnew(char, size * 4 / 3 + 4); - q = (const unsigned char *) data; - i = 0; - for (i = 0; i < size;) { - c = q[i++]; - c *= 256; - if (i < size) - c += q[i]; - i++; - c *= 256; - if (i < size) - c += q[i]; - i++; - p[0] = base64_chars[(c & 0x00fc0000) >> 18]; - p[1] = base64_chars[(c & 0x0003f000) >> 12]; - p[2] = base64_chars[(c & 0x00000fc0) >> 6]; - p[3] = base64_chars[(c & 0x0000003f) >> 0]; - if (i > size) - p[3] = '='; - if (i > size + 1) - p[2] = '='; - p += 4; - } - *p = 0; - *str = s; - return strlen(s); -} - -#define DECODE_ERROR 0xffffffff - -static unsigned int token_decode(const char *token) -{ - int i; - unsigned int val = 0; - int marker = 0; - if (strlen(token) < 4) - return DECODE_ERROR; - for (i = 0; i < 4; i++) { - val *= 64; - if (token[i] == '=') - marker++; - else if (marker > 0) - return DECODE_ERROR; - else - val += pos(token[i]); - } - if (marker > 2) - return DECODE_ERROR; - return (marker << 24) | val; -} - -int pa_base64_decode(const char *str, void *data) -{ - const char *p; - unsigned char *q; - - q = data; - for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) { - unsigned int val = token_decode(p); - unsigned int marker = (val >> 24) & 0xff; - if (val == DECODE_ERROR) - return -1; - *q++ = (val >> 16) & 0xff; - if (marker < 2) - *q++ = (val >> 8) & 0xff; - if (marker < 1) - *q++ = val & 0xff; - } - return q - (unsigned char *) data; -} diff --git a/src/modules/rtp/base64.h b/src/modules/rtp/base64.h deleted file mode 100644 index dac0e707..00000000 --- a/src/modules/rtp/base64.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef foobase64hfoo -#define foobase64hfoo - -/*** - This file is part of PulseAudio. - - Copyright 2008 Colin Guthrie - Copyright Kungliga Tekniska Høgskolan - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -/* - This file was originally inspired by a file developed by - Kungliga Tekniska Høgskolan -*/ - -int pa_base64_encode(const void *data, int size, char **str); -int pa_base64_decode(const char *str, void *data); - -#endif diff --git a/src/modules/rtp/raop_client.c b/src/modules/rtp/raop_client.c deleted file mode 100644 index 4627545e..00000000 --- a/src/modules/rtp/raop_client.c +++ /dev/null @@ -1,561 +0,0 @@ -/*** - 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_client.h" -#include "base64.h" - -#define AES_CHUNKSIZE 16 - -#define JACK_STATUS_DISCONNECTED 0 -#define JACK_STATUS_CONNECTED 1 - -#define JACK_TYPE_ANALOG 0 -#define JACK_TYPE_DIGITAL 1 - -#define VOLUME_DEF -30 -#define VOLUME_MIN -144 -#define VOLUME_MAX 0 - - -struct pa_raop_client { - pa_core *core; - char *host; - char *sid; - pa_rtsp_client *rtsp; - - uint8_t jack_type; - uint8_t jack_status; - - /* Encryption Related bits */ - AES_KEY aes; - uint8_t aes_iv[AES_CHUNKSIZE]; /* initialization vector for aes-cbc */ - uint8_t aes_nv[AES_CHUNKSIZE]; /* next vector for aes-cbc */ - uint8_t aes_key[AES_CHUNKSIZE]; /* key for aes-cbc */ - - pa_socket_client *sc; - int fd; - - uint16_t seq; - uint32_t rtptime; - - pa_raop_client_cb_t callback; - void* userdata; - pa_raop_client_closed_cb_t closed_callback; - void* closed_userdata; -}; - -/** - * Function to write bits into a buffer. - * @param buffer Handle to the buffer. It will be incremented if new data requires it. - * @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB) - * @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks - * @param data The data to write - * @param data_bit_len The number of bits from data to write - */ -static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uint8_t data, uint8_t data_bit_len) { - int bits_left, bit_overflow; - uint8_t bit_data; - - if (!data_bit_len) - return; - - /* If bit pos is zero, we will definatly use at least one bit from the current byte so size increments. */ - if (!*bit_pos) - *size += 1; - - /* Calc the number of bits left in the current byte of buffer */ - bits_left = 7 - *bit_pos + 1; - /* Calc the overflow of bits in relation to how much space we have left... */ - bit_overflow = bits_left - data_bit_len; - if (bit_overflow >= 0) { - /* We can fit the new data in our current byte */ - /* As we write from MSB->LSB we need to left shift by the overflow amount */ - bit_data = data << bit_overflow; - if (*bit_pos) - **buffer |= bit_data; - else - **buffer = bit_data; - /* If our data fits exactly into the current byte, we need to increment our pointer */ - if (0 == bit_overflow) { - /* Do not increment size as it will be incremeneted on next call as bit_pos is zero */ - *buffer += 1; - *bit_pos = 0; - } else { - *bit_pos += data_bit_len; - } - } else { - /* bit_overflow is negative, there for we will need a new byte from our buffer */ - /* Firstly fill up what's left in the current byte */ - bit_data = data >> -bit_overflow; - **buffer |= bit_data; - /* Increment our buffer pointer and size counter*/ - *buffer += 1; - *size += 1; - **buffer = data << (8 + bit_overflow); - *bit_pos = -bit_overflow; - } -} - -static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { - const char n[] = - "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" - "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" - "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" - "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" - "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" - "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; - const char e[] = "AQAB"; - uint8_t modules[256]; - uint8_t exponent[8]; - int size; - RSA *rsa; - - rsa = RSA_new(); - size = pa_base64_decode(n, modules); - rsa->n = BN_bin2bn(modules, size, NULL); - size = pa_base64_decode(e, exponent); - rsa->e = BN_bin2bn(exponent, size, NULL); - - size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); - RSA_free(rsa); - return size; -} - -static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) -{ - uint8_t *buf; - int i=0, j; - - pa_assert(c); - - memcpy(c->aes_nv, c->aes_iv, AES_CHUNKSIZE); - while (i+AES_CHUNKSIZE <= size) { - buf = data + i; - for (j=0; jaes_nv[j]; - - AES_encrypt(buf, buf, &c->aes); - memcpy(c->aes_nv, buf, AES_CHUNKSIZE); - i += AES_CHUNKSIZE; - } - return i; -} - -static inline void rtrimchar(char *str, char rc) -{ - char *sp = str + strlen(str) - 1; - while (sp >= str && *sp == rc) { - *sp = '\0'; - sp -= 1; - } -} - -static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { - pa_raop_client *c = userdata; - - pa_assert(sc); - pa_assert(c); - pa_assert(c->sc == sc); - pa_assert(c->fd < 0); - pa_assert(c->callback); - - pa_socket_client_unref(c->sc); - c->sc = NULL; - - if (!io) { - pa_log("Connection failed: %s", pa_cstrerror(errno)); - return; - } - - c->fd = pa_iochannel_get_send_fd(io); - - pa_iochannel_set_noclose(io, TRUE); - pa_iochannel_socket_set_sndbuf(io, 1024); - pa_iochannel_free(io); - - pa_make_tcp_socket_low_delay(c->fd); - - pa_log_debug("Connection established"); - c->callback(c->fd, c->userdata); -} - -static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) -{ - pa_raop_client* c = userdata; - pa_assert(c); - pa_assert(rtsp); - pa_assert(rtsp == c->rtsp); - - switch (state) { - case STATE_CONNECT: { - int i; - uint8_t rsakey[512]; - char *key, *iv, *sac, *sdp; - uint16_t rand_data; - const char *ip; - char *url; - - pa_log_debug("RAOP: CONNECTED"); - ip = pa_rtsp_localip(c->rtsp); - /* First of all set the url properly */ - url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid); - pa_rtsp_set_url(c->rtsp, url); - pa_xfree(url); - - /* Now encrypt our aes_public key to send to the device */ - i = rsa_encrypt(c->aes_key, AES_CHUNKSIZE, rsakey); - pa_base64_encode(rsakey, i, &key); - rtrimchar(key, '='); - pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv); - rtrimchar(iv, '='); - - pa_random(&rand_data, sizeof(rand_data)); - pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac); - rtrimchar(sac, '='); - pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); - sdp = pa_sprintf_malloc( - "v=0\r\n" - "o=iTunes %s 0 IN IP4 %s\r\n" - "s=iTunes\r\n" - "c=IN IP4 %s\r\n" - "t=0 0\r\n" - "m=audio 0 RTP/AVP 96\r\n" - "a=rtpmap:96 AppleLossless\r\n" - "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n" - "a=rsaaeskey:%s\r\n" - "a=aesiv:%s\r\n", - c->sid, ip, c->host, key, iv); - pa_rtsp_announce(c->rtsp, sdp); - pa_xfree(key); - pa_xfree(iv); - pa_xfree(sac); - pa_xfree(sdp); - break; - } - - case STATE_ANNOUNCE: - pa_log_debug("RAOP: ANNOUNCED"); - pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); - pa_rtsp_setup(c->rtsp); - break; - - case STATE_SETUP: { - char *aj = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); - pa_log_debug("RAOP: SETUP"); - if (aj) { - char *token, *pc; - char delimiters[] = ";"; - const char* token_state = NULL; - c->jack_type = JACK_TYPE_ANALOG; - c->jack_status = JACK_STATUS_DISCONNECTED; - - while ((token = pa_split(aj, delimiters, &token_state))) { - if ((pc = strstr(token, "="))) { - *pc = 0; - if (!strcmp(token, "type") && !strcmp(pc+1, "digital")) { - c->jack_type = JACK_TYPE_DIGITAL; - } - } else { - if (!strcmp(token,"connected")) - c->jack_status = JACK_STATUS_CONNECTED; - } - pa_xfree(token); - } - pa_xfree(aj); - } else { - pa_log_warn("Audio Jack Status missing"); - } - pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); - break; - } - - case STATE_RECORD: { - uint32_t port = pa_rtsp_serverport(c->rtsp); - pa_log_debug("RAOP: RECORDED"); - - if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, c->host, port))) { - pa_log("failed to connect to server '%s:%d'", c->host, port); - return; - } - pa_socket_client_set_callback(c->sc, on_connection, c); - break; - } - - case STATE_FLUSH: - pa_log_debug("RAOP: FLUSHED"); - break; - - case STATE_TEARDOWN: - case STATE_SET_PARAMETER: - pa_log_debug("RAOP: SET_PARAMETER"); - break; - case STATE_DISCONNECTED: - pa_assert(c->closed_callback); - pa_assert(c->rtsp); - - pa_log_debug("RTSP control channel closed"); - pa_rtsp_client_free(c->rtsp); - c->rtsp = NULL; - if (c->fd > 0) { - /* We do not close the fd, we leave it to the closed callback to do that */ - c->fd = -1; - } - if (c->sc) { - pa_socket_client_unref(c->sc); - c->sc = NULL; - } - pa_xfree(c->sid); - c->sid = NULL; - c->closed_callback(c->closed_userdata); - break; - } -} - -pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) -{ - pa_raop_client* c = pa_xnew0(pa_raop_client, 1); - - pa_assert(core); - pa_assert(host); - - c->core = core; - c->fd = -1; - c->host = pa_xstrdup(host); - - if (pa_raop_connect(c)) { - pa_raop_client_free(c); - return NULL; - } - return c; -} - - -void pa_raop_client_free(pa_raop_client* c) -{ - pa_assert(c); - - if (c->rtsp) - pa_rtsp_client_free(c->rtsp); - pa_xfree(c->host); - pa_xfree(c); -} - - -int pa_raop_connect(pa_raop_client* c) -{ - char *sci; - struct { - uint32_t a; - uint32_t b; - uint32_t c; - } rand_data; - - pa_assert(c); - - if (c->rtsp) { - pa_log_debug("Connection already in progress"); - return 0; - } - - c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, 5000, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); - - /* Initialise the AES encryption system */ - pa_random(c->aes_iv, sizeof(c->aes_iv)); - pa_random(c->aes_key, sizeof(c->aes_key)); - memcpy(c->aes_nv, c->aes_iv, sizeof(c->aes_nv)); - AES_set_encrypt_key(c->aes_key, 128, &c->aes); - - /* Generate random instance id */ - pa_random(&rand_data, sizeof(rand_data)); - c->sid = pa_sprintf_malloc("%u", rand_data.a); - sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c); - pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); - pa_xfree(sci); - pa_rtsp_set_callback(c->rtsp, rtsp_cb, c); - return pa_rtsp_connect(c->rtsp); -} - - -int pa_raop_flush(pa_raop_client* c) -{ - pa_assert(c); - - pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); - return 0; -} - - -int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume) -{ - int rv; - double db; - char *param; - - pa_assert(c); - - db = pa_sw_volume_to_dB(volume); - if (db < VOLUME_MIN) - db = VOLUME_MIN; - else if (db > VOLUME_MAX) - db = VOLUME_MAX; - - param = pa_sprintf_malloc("volume: %0.6f\r\n", db); - - /* We just hit and hope, cannot wait for the callback */ - rv = pa_rtsp_setparameter(c->rtsp, param); - pa_xfree(param); - return rv; -} - - -int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) -{ - uint16_t len; - size_t bufmax; - uint8_t *bp, bpos; - uint8_t *ibp, *maxibp; - int size; - uint8_t *b, *p; - uint32_t bsize; - size_t length; - static uint8_t header[] = { - 0x24, 0x00, 0x00, 0x00, - 0xF0, 0xFF, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, - }; - int header_size = sizeof(header); - - pa_assert(c); - pa_assert(c->fd > 0); - pa_assert(raw); - pa_assert(raw->memblock); - pa_assert(raw->length > 0); - pa_assert(encoded); - - /* We have to send 4 byte chunks */ - bsize = (int)(raw->length / 4); - length = bsize * 4; - - /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ - bufmax = length + header_size + 16; - pa_memchunk_reset(encoded); - encoded->memblock = pa_memblock_new(c->core->mempool, bufmax); - b = pa_memblock_acquire(encoded->memblock); - memcpy(b, header, header_size); - - /* Now write the actual samples */ - bp = b + header_size; - size = bpos = 0; - bit_writer(&bp,&bpos,&size,1,3); /* channel=1, stereo */ - bit_writer(&bp,&bpos,&size,0,4); /* unknown */ - bit_writer(&bp,&bpos,&size,0,8); /* unknown */ - bit_writer(&bp,&bpos,&size,0,4); /* unknown */ - bit_writer(&bp,&bpos,&size,1,1); /* hassize */ - bit_writer(&bp,&bpos,&size,0,2); /* unused */ - bit_writer(&bp,&bpos,&size,1,1); /* is-not-compressed */ - - /* size of data, integer, big endian */ - bit_writer(&bp,&bpos,&size,(bsize>>24)&0xff,8); - bit_writer(&bp,&bpos,&size,(bsize>>16)&0xff,8); - bit_writer(&bp,&bpos,&size,(bsize>>8)&0xff,8); - bit_writer(&bp,&bpos,&size,(bsize)&0xff,8); - - ibp = p = pa_memblock_acquire(raw->memblock); - maxibp = p + raw->length - 4; - while (ibp <= maxibp) { - /* Byte swap stereo data */ - bit_writer(&bp,&bpos,&size,*(ibp+1),8); - bit_writer(&bp,&bpos,&size,*(ibp+0),8); - bit_writer(&bp,&bpos,&size,*(ibp+3),8); - bit_writer(&bp,&bpos,&size,*(ibp+2),8); - ibp += 4; - raw->index += 4; - raw->length -= 4; - } - pa_memblock_release(raw->memblock); - encoded->length = header_size + size; - - /* store the lenght (endian swapped: make this better) */ - len = size + header_size - 4; - *(b + 2) = len >> 8; - *(b + 3) = len & 0xff; - - /* encrypt our data */ - aes_encrypt(c, (b + header_size), size); - - /* We're done with the chunk */ - pa_memblock_release(encoded->memblock); - - return 0; -} - - -void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata) -{ - pa_assert(c); - - c->callback = callback; - c->userdata = userdata; -} - -void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata) -{ - pa_assert(c); - - c->closed_callback = callback; - c->closed_userdata = userdata; -} diff --git a/src/modules/rtp/raop_client.h b/src/modules/rtp/raop_client.h deleted file mode 100644 index ec3136a7..00000000 --- a/src/modules/rtp/raop_client.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef fooraopclientfoo -#define fooraopclientfoo - -/*** - This file is part of PulseAudio. - - Copyright 2008 Colin Guthrie - - PulseAudio is free software; you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published - by the Free Software Foundation; either version 2 of the License, - or (at your option) any later version. - - PulseAudio is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with PulseAudio; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 - USA. -***/ - -#include -#include -#include - -typedef struct pa_raop_client pa_raop_client; - -pa_raop_client* pa_raop_client_new(pa_core *core, const char* host); -void pa_raop_client_free(pa_raop_client* c); - -int pa_raop_connect(pa_raop_client* c); -int pa_raop_flush(pa_raop_client* c); - -int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume); -int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded); - -typedef void (*pa_raop_client_cb_t)(int fd, void *userdata); -void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata); - -typedef void (*pa_raop_client_closed_cb_t)(void *userdata); -void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata); - -#endif -- cgit From 59eb64987fdf5dde71638f5f77e705e490ec7133 Mon Sep 17 00:00:00 2001 From: Colin Guthrie Date: Fri, 22 Aug 2008 09:51:41 +0100 Subject: 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 38436a38..3706d921 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 b90d4e23..62f0a73c 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; } -- cgit