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 --- 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 ++++ 4 files changed, 767 insertions(+) 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 (limited to 'src/modules/raop') 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 -- cgit