mirror of
				https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
				synced 2025-10-29 05:40:23 -04:00 
			
		
		
		
	Merge commit 'coling/airtunes-0.9.13'
This commit is contained in:
		
						commit
						8e3e88df8b
					
				
					 12 changed files with 2753 additions and 2 deletions
				
			
		
							
								
								
									
										41
									
								
								configure.ac
									
										
									
									
									
								
							
							
						
						
									
										41
									
								
								configure.ac
									
										
									
									
									
								
							|  | @ -1000,6 +1000,41 @@ AC_SUBST(POLKIT_LIBS) | ||||||
| AC_SUBST(HAVE_POLKIT) | AC_SUBST(HAVE_POLKIT) | ||||||
| AM_CONDITIONAL([HAVE_POLKIT], [test "x$HAVE_POLKIT" = x1]) | 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 ### | ### Build and Install man pages ### | ||||||
| AC_ARG_ENABLE(manpages, | AC_ARG_ENABLE(manpages, | ||||||
|         AS_HELP_STRING([--disable-manpages],[Disable building and installation of man pages]), |         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 |    ENABLE_POLKIT=yes | ||||||
| fi | fi | ||||||
| 
 | 
 | ||||||
|  | ENABLE_OPENSSL=no | ||||||
|  | if test "x${HAVE_OPENSSL}" = "x1" ; then | ||||||
|  |    ENABLE_OPENSSL=yes | ||||||
|  | fi | ||||||
|  | 
 | ||||||
| ENABLE_PER_USER_ESOUND_SOCKET=no | ENABLE_PER_USER_ESOUND_SOCKET=no | ||||||
| if test "x$per_user_esound_socket" = "x1" ; then | if test "x$per_user_esound_socket" = "x1" ; then | ||||||
|    ENABLE_PER_USER_ESOUND_SOCKET=yes |    ENABLE_PER_USER_ESOUND_SOCKET=yes | ||||||
|  | @ -1232,6 +1272,7 @@ echo " | ||||||
|     Enable TCP Wrappers:           ${ENABLE_TCPWRAP} |     Enable TCP Wrappers:           ${ENABLE_TCPWRAP} | ||||||
|     Enable libsamplerate:          ${ENABLE_LIBSAMPLERATE} |     Enable libsamplerate:          ${ENABLE_LIBSAMPLERATE} | ||||||
|     Enable PolicyKit:              ${ENABLE_POLKIT} |     Enable PolicyKit:              ${ENABLE_POLKIT} | ||||||
|  |     Enable OpenSSL (for Airtunes): ${ENABLE_OPENSSL} | ||||||
|     System User:                   ${PA_SYSTEM_USER} |     System User:                   ${PA_SYSTEM_USER} | ||||||
|     System Group:                  ${PA_SYSTEM_GROUP} |     System Group:                  ${PA_SYSTEM_GROUP} | ||||||
|     Realtime Group:                ${PA_REALTIME_GROUP} |     Realtime Group:                ${PA_REALTIME_GROUP} | ||||||
|  |  | ||||||
|  | @ -62,6 +62,10 @@ AM_CFLAGS += -DPA_MACHINE_ID=\"$(localstatedir)/lib/dbus/machine-id\" | ||||||
| # This cool debug trap works on i386/gcc only | # This cool debug trap works on i386/gcc only | ||||||
| AM_CFLAGS += '-DDEBUG_TRAP=__asm__("int $$3")' | 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_LIBADD = $(PTHREAD_LIBS) $(INTLLIBS) | ||||||
| AM_LDADD = $(PTHREAD_LIBS) $(INTLLIBS) | AM_LDADD = $(PTHREAD_LIBS) $(INTLLIBS) | ||||||
| 
 | 
 | ||||||
|  | @ -89,6 +93,7 @@ PA_THREAD_OBJS = \ | ||||||
| 		pulsecore/semaphore-posix.c pulsecore/semaphore.h | 		pulsecore/semaphore-posix.c pulsecore/semaphore.h | ||||||
| endif | endif | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
| ################################### | ################################### | ||||||
| #          Extra files            # | #          Extra files            # | ||||||
| ################################### | ################################### | ||||||
|  | @ -1004,9 +1009,21 @@ libsocket_util_la_SOURCES = \ | ||||||
| libsocket_util_la_LDFLAGS = -avoid-version | libsocket_util_la_LDFLAGS = -avoid-version | ||||||
| libsocket_util_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) libpulsecore.la | 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_client.c modules/rtp/rtsp_client.h \ | ||||||
|  | 		modules/rtp/headerlist.c modules/rtp/headerlist.h | ||||||
| librtp_la_LDFLAGS = -avoid-version | 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 | ||||||
|  | 
 | ||||||
|  | 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 | # X11 | ||||||
| 
 | 
 | ||||||
|  | @ -1178,6 +1195,17 @@ pulselibexec_PROGRAMS += \ | ||||||
| 		proximity-helper | 		proximity-helper | ||||||
| endif | 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 | # These are generated by a M4 script | ||||||
| 
 | 
 | ||||||
| SYMDEF_FILES = \ | SYMDEF_FILES = \ | ||||||
|  | @ -1234,6 +1262,8 @@ SYMDEF_FILES = \ | ||||||
| 		modules/bluetooth/module-bluetooth-proximity-symdef.h \ | 		modules/bluetooth/module-bluetooth-proximity-symdef.h \ | ||||||
| 		modules/bluetooth/module-bluetooth-discover-symdef.h \ | 		modules/bluetooth/module-bluetooth-discover-symdef.h \ | ||||||
| 		modules/bluetooth/module-bluetooth-device-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/gconf/module-gconf-symdef.h \ | ||||||
| 		modules/module-position-event-sounds-symdef.h \ | 		modules/module-position-event-sounds-symdef.h \ | ||||||
| 		modules/module-console-kit-symdef.h \ | 		modules/module-console-kit-symdef.h \ | ||||||
|  | @ -1603,6 +1633,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_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) | 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 libraop.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         # | #        Some minor stuff         # | ||||||
| ################################### | ################################### | ||||||
|  |  | ||||||
							
								
								
									
										380
									
								
								src/modules/module-raop-discover.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										380
									
								
								src/modules/module-raop-discover.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,380 @@ | ||||||
|  | /***
 | ||||||
|  |   This file is part of PulseAudio. | ||||||
|  | 
 | ||||||
|  |   Copyright 2004-2006 Lennart Poettering | ||||||
|  |   Copyright 2008 Colin Guthrie | ||||||
|  | 
 | ||||||
|  |   PulseAudio is free software; you can redistribute it and/or modify | ||||||
|  |   it under the terms of the GNU Lesser General Public License as | ||||||
|  |   published by the Free Software Foundation; either version 2 of the | ||||||
|  |   License, or (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |   PulseAudio is distributed in the hope that it will be useful, but | ||||||
|  |   WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||||
|  |   General Public License for more details. | ||||||
|  | 
 | ||||||
|  |   You should have received a copy of the GNU Lesser General Public | ||||||
|  |   License along with PulseAudio; if not, write to the Free Software | ||||||
|  |   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||||||
|  |   USA. | ||||||
|  | ***/ | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_CONFIG_H | ||||||
|  | #include <config.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | 
 | ||||||
|  | #include <avahi-client/client.h> | ||||||
|  | #include <avahi-client/lookup.h> | ||||||
|  | #include <avahi-common/alternative.h> | ||||||
|  | #include <avahi-common/error.h> | ||||||
|  | #include <avahi-common/domain.h> | ||||||
|  | #include <avahi-common/malloc.h> | ||||||
|  | 
 | ||||||
|  | #include <pulse/xmalloc.h> | ||||||
|  | #include <pulse/util.h> | ||||||
|  | 
 | ||||||
|  | #include <pulsecore/sink.h> | ||||||
|  | #include <pulsecore/source.h> | ||||||
|  | #include <pulsecore/native-common.h> | ||||||
|  | #include <pulsecore/core-util.h> | ||||||
|  | #include <pulsecore/log.h> | ||||||
|  | #include <pulsecore/core-subscribe.h> | ||||||
|  | #include <pulsecore/hashmap.h> | ||||||
|  | #include <pulsecore/modargs.h> | ||||||
|  | #include <pulsecore/namereg.h> | ||||||
|  | #include <pulsecore/avahi-wrap.h> | ||||||
|  | 
 | ||||||
|  | #include "module-raop-discover-symdef.h" | ||||||
|  | 
 | ||||||
|  | PA_MODULE_AUTHOR("Colin Guthrie"); | ||||||
|  | PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery of Airtunes"); | ||||||
|  | PA_MODULE_VERSION(PACKAGE_VERSION); | ||||||
|  | PA_MODULE_LOAD_ONCE(TRUE); | ||||||
|  | 
 | ||||||
|  | #define SERVICE_TYPE_SINK "_raop._tcp" | ||||||
|  | 
 | ||||||
|  | static const char* const valid_modargs[] = { | ||||||
|  |     NULL | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct tunnel { | ||||||
|  |     AvahiIfIndex interface; | ||||||
|  |     AvahiProtocol protocol; | ||||||
|  |     char *name, *type, *domain; | ||||||
|  |     uint32_t module_index; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | struct userdata { | ||||||
|  |     pa_core *core; | ||||||
|  |     pa_module *module; | ||||||
|  |     AvahiPoll *avahi_poll; | ||||||
|  |     AvahiClient *client; | ||||||
|  |     AvahiServiceBrowser *sink_browser; | ||||||
|  | 
 | ||||||
|  |     pa_hashmap *tunnels; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static unsigned tunnel_hash(const void *p) { | ||||||
|  |     const struct tunnel *t = p; | ||||||
|  | 
 | ||||||
|  |     return | ||||||
|  |         (unsigned) t->interface + | ||||||
|  |         (unsigned) t->protocol + | ||||||
|  |         pa_idxset_string_hash_func(t->name) + | ||||||
|  |         pa_idxset_string_hash_func(t->type) + | ||||||
|  |         pa_idxset_string_hash_func(t->domain); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int tunnel_compare(const void *a, const void *b) { | ||||||
|  |     const struct tunnel *ta = a, *tb = b; | ||||||
|  |     int r; | ||||||
|  | 
 | ||||||
|  |     if (ta->interface != tb->interface) | ||||||
|  |         return 1; | ||||||
|  |     if (ta->protocol != tb->protocol) | ||||||
|  |         return 1; | ||||||
|  |     if ((r = strcmp(ta->name, tb->name))) | ||||||
|  |         return r; | ||||||
|  |     if ((r = strcmp(ta->type, tb->type))) | ||||||
|  |         return r; | ||||||
|  |     if ((r = strcmp(ta->domain, tb->domain))) | ||||||
|  |         return r; | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static struct tunnel *tunnel_new( | ||||||
|  |         AvahiIfIndex interface, AvahiProtocol protocol, | ||||||
|  |         const char *name, const char *type, const char *domain) { | ||||||
|  | 
 | ||||||
|  |     struct tunnel *t; | ||||||
|  |     t = pa_xnew(struct tunnel, 1); | ||||||
|  |     t->interface = interface; | ||||||
|  |     t->protocol = protocol; | ||||||
|  |     t->name = pa_xstrdup(name); | ||||||
|  |     t->type = pa_xstrdup(type); | ||||||
|  |     t->domain = pa_xstrdup(domain); | ||||||
|  |     t->module_index = PA_IDXSET_INVALID; | ||||||
|  |     return t; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void tunnel_free(struct tunnel *t) { | ||||||
|  |     pa_assert(t); | ||||||
|  |     pa_xfree(t->name); | ||||||
|  |     pa_xfree(t->type); | ||||||
|  |     pa_xfree(t->domain); | ||||||
|  |     pa_xfree(t); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void resolver_cb( | ||||||
|  |         AvahiServiceResolver *r, | ||||||
|  |         AvahiIfIndex interface, AvahiProtocol protocol, | ||||||
|  |         AvahiResolverEvent event, | ||||||
|  |         const char *name, const char *type, const char *domain, | ||||||
|  |         const char *host_name, const AvahiAddress *a, uint16_t port, | ||||||
|  |         AvahiStringList *txt, | ||||||
|  |         AvahiLookupResultFlags flags, | ||||||
|  |         void *userdata) { | ||||||
|  | 
 | ||||||
|  |     struct userdata *u = userdata; | ||||||
|  |     struct tunnel *tnl; | ||||||
|  | 
 | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     tnl = tunnel_new(interface, protocol, name, type, domain); | ||||||
|  | 
 | ||||||
|  |     if (event != AVAHI_RESOLVER_FOUND) | ||||||
|  |         pa_log("Resolving of '%s' failed: %s", name, avahi_strerror(avahi_client_errno(u->client))); | ||||||
|  |     else { | ||||||
|  |         char *device = NULL, *dname, *vname, *args; | ||||||
|  |         char at[AVAHI_ADDRESS_STR_MAX]; | ||||||
|  |         AvahiStringList *l; | ||||||
|  |         pa_module *m; | ||||||
|  | 
 | ||||||
|  |         for (l = txt; l; l = l->next) { | ||||||
|  |             char *key, *value; | ||||||
|  |             pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0); | ||||||
|  | 
 | ||||||
|  |             pa_log_debug("Found key: '%s' with value: '%s'", key, value); | ||||||
|  |             if (strcmp(key, "device") == 0) { | ||||||
|  |                 pa_xfree(device); | ||||||
|  |                 device = value; | ||||||
|  |                 value = NULL; | ||||||
|  |             } | ||||||
|  |             avahi_free(key); | ||||||
|  |             avahi_free(value); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (device) | ||||||
|  |             dname = pa_sprintf_malloc("airtunes.%s.%s", host_name, device); | ||||||
|  |         else | ||||||
|  |             dname = pa_sprintf_malloc("airtunes.%s", host_name); | ||||||
|  | 
 | ||||||
|  |         if (!(vname = pa_namereg_make_valid_name(dname))) { | ||||||
|  |             pa_log("Cannot construct valid device name from '%s'.", dname); | ||||||
|  |             avahi_free(device); | ||||||
|  |             pa_xfree(dname); | ||||||
|  |             goto finish; | ||||||
|  |         } | ||||||
|  |         pa_xfree(dname); | ||||||
|  | 
 | ||||||
|  |         /*
 | ||||||
|  |          TODO: allow this syntax of server name in things.... | ||||||
|  |         args = pa_sprintf_malloc("server=[%s]:%u " | ||||||
|  |                                  "sink_name=%s", | ||||||
|  |                                  avahi_address_snprint(at, sizeof(at), a), port, | ||||||
|  |                                  vname);*/ | ||||||
|  |         args = pa_sprintf_malloc("server=%s " | ||||||
|  |                                  "sink_name=%s", | ||||||
|  |                                  avahi_address_snprint(at, sizeof(at), a), | ||||||
|  |                                  vname); | ||||||
|  | 
 | ||||||
|  |         pa_log_debug("Loading module-raop-sink with arguments '%s'", args); | ||||||
|  | 
 | ||||||
|  |         if ((m = pa_module_load(u->core, "module-raop-sink", args))) { | ||||||
|  |             tnl->module_index = m->index; | ||||||
|  |             pa_hashmap_put(u->tunnels, tnl, tnl); | ||||||
|  |             tnl = NULL; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         pa_xfree(vname); | ||||||
|  |         pa_xfree(args); | ||||||
|  |         avahi_free(device); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | finish: | ||||||
|  | 
 | ||||||
|  |     avahi_service_resolver_free(r); | ||||||
|  | 
 | ||||||
|  |     if (tnl) | ||||||
|  |         tunnel_free(tnl); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void browser_cb( | ||||||
|  |         AvahiServiceBrowser *b, | ||||||
|  |         AvahiIfIndex interface, AvahiProtocol protocol, | ||||||
|  |         AvahiBrowserEvent event, | ||||||
|  |         const char *name, const char *type, const char *domain, | ||||||
|  |         AvahiLookupResultFlags flags, | ||||||
|  |         void *userdata) { | ||||||
|  | 
 | ||||||
|  |     struct userdata *u = userdata; | ||||||
|  |     struct tunnel *t; | ||||||
|  | 
 | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     if (flags & AVAHI_LOOKUP_RESULT_LOCAL) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     t = tunnel_new(interface, protocol, name, type, domain); | ||||||
|  | 
 | ||||||
|  |     if (event == AVAHI_BROWSER_NEW) { | ||||||
|  | 
 | ||||||
|  |         if (!pa_hashmap_get(u->tunnels, t)) | ||||||
|  |             if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u))) | ||||||
|  |                 pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client))); | ||||||
|  | 
 | ||||||
|  |         /* We ignore the returned resolver object here, since the we don't
 | ||||||
|  |          * need to attach any special data to it, and we can still destory | ||||||
|  |          * it from the callback */ | ||||||
|  | 
 | ||||||
|  |     } else if (event == AVAHI_BROWSER_REMOVE) { | ||||||
|  |         struct tunnel *t2; | ||||||
|  | 
 | ||||||
|  |         if ((t2 = pa_hashmap_get(u->tunnels, t))) { | ||||||
|  |             pa_module_unload_by_index(u->core, t2->module_index, TRUE); | ||||||
|  |             pa_hashmap_remove(u->tunnels, t2); | ||||||
|  |             tunnel_free(t2); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     tunnel_free(t); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) { | ||||||
|  |     struct userdata *u = userdata; | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     u->client = c; | ||||||
|  | 
 | ||||||
|  |     switch (state) { | ||||||
|  |         case AVAHI_CLIENT_S_REGISTERING: | ||||||
|  |         case AVAHI_CLIENT_S_RUNNING: | ||||||
|  |         case AVAHI_CLIENT_S_COLLISION: | ||||||
|  | 
 | ||||||
|  |             if (!u->sink_browser) { | ||||||
|  | 
 | ||||||
|  |                 if (!(u->sink_browser = avahi_service_browser_new( | ||||||
|  |                               c, | ||||||
|  |                               AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, | ||||||
|  |                               SERVICE_TYPE_SINK, | ||||||
|  |                               NULL, | ||||||
|  |                               0, | ||||||
|  |                               browser_cb, u))) { | ||||||
|  | 
 | ||||||
|  |                     pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c))); | ||||||
|  |                     pa_module_unload_request(u->module, TRUE); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case AVAHI_CLIENT_FAILURE: | ||||||
|  |             if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { | ||||||
|  |                 int error; | ||||||
|  | 
 | ||||||
|  |                 pa_log_debug("Avahi daemon disconnected."); | ||||||
|  | 
 | ||||||
|  |                 if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { | ||||||
|  |                     pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); | ||||||
|  |                     pa_module_unload_request(u->module, TRUE); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             /* Fall through */ | ||||||
|  | 
 | ||||||
|  |         case AVAHI_CLIENT_CONNECTING: | ||||||
|  | 
 | ||||||
|  |             if (u->sink_browser) { | ||||||
|  |                 avahi_service_browser_free(u->sink_browser); | ||||||
|  |                 u->sink_browser = NULL; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         default: ; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int pa__init(pa_module*m) { | ||||||
|  | 
 | ||||||
|  |     struct userdata *u; | ||||||
|  |     pa_modargs *ma = NULL; | ||||||
|  |     int error; | ||||||
|  | 
 | ||||||
|  |     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { | ||||||
|  |         pa_log("Failed to parse module arguments."); | ||||||
|  |         goto fail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     m->userdata = u = pa_xnew(struct userdata, 1); | ||||||
|  |     u->core = m->core; | ||||||
|  |     u->module = m; | ||||||
|  |     u->sink_browser = NULL; | ||||||
|  | 
 | ||||||
|  |     u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare); | ||||||
|  | 
 | ||||||
|  |     u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); | ||||||
|  | 
 | ||||||
|  |     if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { | ||||||
|  |         pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error)); | ||||||
|  |         goto fail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_modargs_free(ma); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | 
 | ||||||
|  | fail: | ||||||
|  |     pa__done(m); | ||||||
|  | 
 | ||||||
|  |     if (ma) | ||||||
|  |         pa_modargs_free(ma); | ||||||
|  | 
 | ||||||
|  |     return -1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pa__done(pa_module*m) { | ||||||
|  |     struct userdata*u; | ||||||
|  |     pa_assert(m); | ||||||
|  | 
 | ||||||
|  |     if (!(u = m->userdata)) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     if (u->client) | ||||||
|  |         avahi_client_free(u->client); | ||||||
|  | 
 | ||||||
|  |     if (u->avahi_poll) | ||||||
|  |         pa_avahi_poll_free(u->avahi_poll); | ||||||
|  | 
 | ||||||
|  |     if (u->tunnels) { | ||||||
|  |         struct tunnel *t; | ||||||
|  | 
 | ||||||
|  |         while ((t = pa_hashmap_steal_first(u->tunnels))) { | ||||||
|  |             pa_module_unload_by_index(u->core, t->module_index, TRUE); | ||||||
|  |             tunnel_free(t); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         pa_hashmap_free(u->tunnels, NULL, NULL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_xfree(u); | ||||||
|  | } | ||||||
							
								
								
									
										675
									
								
								src/modules/module-raop-sink.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										675
									
								
								src/modules/module-raop-sink.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,675 @@ | ||||||
|  | /***
 | ||||||
|  |   This file is part of PulseAudio. | ||||||
|  | 
 | ||||||
|  |   Copyright 2004-2006 Lennart Poettering | ||||||
|  |   Copyright 2008 Colin Guthrie | ||||||
|  | 
 | ||||||
|  |   PulseAudio is free software; you can redistribute it and/or modify | ||||||
|  |   it under the terms of the GNU Lesser General Public License as published | ||||||
|  |   by the Free Software Foundation; either version 2 of the License, | ||||||
|  |   or (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |   PulseAudio is distributed in the hope that it will be useful, but | ||||||
|  |   WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||||
|  |   General Public License for more details. | ||||||
|  | 
 | ||||||
|  |   You should have received a copy of the GNU Lesser General Public License | ||||||
|  |   along with PulseAudio; if not, write to the Free Software | ||||||
|  |   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||||||
|  |   USA. | ||||||
|  | ***/ | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_CONFIG_H | ||||||
|  | #include <config.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <sys/stat.h> | ||||||
|  | #include <stdio.h> | ||||||
|  | #include <errno.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <fcntl.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | #include <limits.h> | ||||||
|  | #include <poll.h> | ||||||
|  | #include <sys/socket.h> | ||||||
|  | #include <netinet/in.h> | ||||||
|  | #include <netinet/tcp.h> | ||||||
|  | #include <sys/ioctl.h> | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_LINUX_SOCKIOS_H | ||||||
|  | #include <linux/sockios.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <pulse/xmalloc.h> | ||||||
|  | #include <pulse/timeval.h> | ||||||
|  | 
 | ||||||
|  | #include <pulsecore/core-error.h> | ||||||
|  | #include <pulsecore/iochannel.h> | ||||||
|  | #include <pulsecore/sink.h> | ||||||
|  | #include <pulsecore/module.h> | ||||||
|  | #include <pulsecore/core-util.h> | ||||||
|  | #include <pulsecore/modargs.h> | ||||||
|  | #include <pulsecore/log.h> | ||||||
|  | #include <pulsecore/socket-client.h> | ||||||
|  | #include <pulsecore/authkey.h> | ||||||
|  | #include <pulsecore/thread-mq.h> | ||||||
|  | #include <pulsecore/thread.h> | ||||||
|  | #include <pulsecore/time-smoother.h> | ||||||
|  | #include <pulsecore/rtclock.h> | ||||||
|  | #include <pulsecore/socket-util.h> | ||||||
|  | 
 | ||||||
|  | #include "module-raop-sink-symdef.h" | ||||||
|  | #include "rtp.h" | ||||||
|  | #include "sdp.h" | ||||||
|  | #include "sap.h" | ||||||
|  | #include "raop_client.h" | ||||||
|  | 
 | ||||||
|  | PA_MODULE_AUTHOR("Colin Guthrie"); | ||||||
|  | PA_MODULE_DESCRIPTION("RAOP Sink (Apple Airtunes)"); | ||||||
|  | PA_MODULE_VERSION(PACKAGE_VERSION); | ||||||
|  | PA_MODULE_LOAD_ONCE(FALSE); | ||||||
|  | PA_MODULE_USAGE( | ||||||
|  |         "sink_name=<name for the sink> " | ||||||
|  |         "server=<address>  " | ||||||
|  |         "format=<sample format> " | ||||||
|  |         "channels=<number of channels> " | ||||||
|  |         "rate=<sample rate>"); | ||||||
|  | 
 | ||||||
|  | #define DEFAULT_SINK_NAME "airtunes" | ||||||
|  | 
 | ||||||
|  | struct userdata { | ||||||
|  |     pa_core *core; | ||||||
|  |     pa_module *module; | ||||||
|  |     pa_sink *sink; | ||||||
|  | 
 | ||||||
|  |     pa_thread_mq thread_mq; | ||||||
|  |     pa_rtpoll *rtpoll; | ||||||
|  |     pa_rtpoll_item *rtpoll_item; | ||||||
|  |     pa_thread *thread; | ||||||
|  | 
 | ||||||
|  |     pa_memchunk raw_memchunk; | ||||||
|  |     pa_memchunk encoded_memchunk; | ||||||
|  | 
 | ||||||
|  |     void *write_data; | ||||||
|  |     size_t write_length, write_index; | ||||||
|  | 
 | ||||||
|  |     void *read_data; | ||||||
|  |     size_t read_length, read_index; | ||||||
|  | 
 | ||||||
|  |     pa_usec_t latency; | ||||||
|  | 
 | ||||||
|  |     pa_volume_t volume; | ||||||
|  |     pa_bool_t muted; | ||||||
|  | 
 | ||||||
|  |     /*esd_format_t format;*/ | ||||||
|  |     int32_t rate; | ||||||
|  | 
 | ||||||
|  |     pa_smoother *smoother; | ||||||
|  |     int fd; | ||||||
|  | 
 | ||||||
|  |     int64_t offset; | ||||||
|  |     int64_t encoding_overhead; | ||||||
|  |     int32_t next_encoding_overhead; | ||||||
|  |     double encoding_ratio; | ||||||
|  | 
 | ||||||
|  |     pa_raop_client *raop; | ||||||
|  | 
 | ||||||
|  |     size_t block_size; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static const char* const valid_modargs[] = { | ||||||
|  |     "server", | ||||||
|  |     "rate", | ||||||
|  |     "format", | ||||||
|  |     "channels", | ||||||
|  |     "sink_name", | ||||||
|  |     NULL | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | enum { | ||||||
|  |     SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX, | ||||||
|  |     SINK_MESSAGE_RIP_SOCKET | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static void on_connection(PA_GCC_UNUSED int fd, void*userdata) { | ||||||
|  |     struct userdata *u = userdata; | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     pa_assert(u->fd < 0); | ||||||
|  |     u->fd = fd; | ||||||
|  | 
 | ||||||
|  |     /* Set the initial volume */ | ||||||
|  |     pa_raop_client_set_volume(u->raop, u->volume); | ||||||
|  | 
 | ||||||
|  |     pa_log_debug("Connection authenticated, handing fd to IO thread..."); | ||||||
|  | 
 | ||||||
|  |     pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void on_close(void*userdata) { | ||||||
|  |     struct userdata *u = userdata; | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     pa_log_debug("Connection closed, informing IO thread..."); | ||||||
|  | 
 | ||||||
|  |     pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_RIP_SOCKET, NULL, 0, NULL, NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { | ||||||
|  |     struct userdata *u = PA_SINK(o)->userdata; | ||||||
|  | 
 | ||||||
|  |     switch (code) { | ||||||
|  | 
 | ||||||
|  |         case PA_SINK_MESSAGE_SET_STATE: | ||||||
|  | 
 | ||||||
|  |             switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { | ||||||
|  | 
 | ||||||
|  |                 case PA_SINK_SUSPENDED: | ||||||
|  |                     pa_assert(PA_SINK_IS_OPENED(u->sink->thread_info.state)); | ||||||
|  | 
 | ||||||
|  |                     pa_smoother_pause(u->smoother, pa_rtclock_usec()); | ||||||
|  | 
 | ||||||
|  |                     /* Issue a FLUSH if we are connected */ | ||||||
|  |                     if (u->fd >= 0) { | ||||||
|  |                         pa_raop_flush(u->raop); | ||||||
|  |                     } | ||||||
|  |                     break; | ||||||
|  | 
 | ||||||
|  |                 case PA_SINK_IDLE: | ||||||
|  |                 case PA_SINK_RUNNING: | ||||||
|  | 
 | ||||||
|  |                     if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { | ||||||
|  |                         pa_smoother_resume(u->smoother, pa_rtclock_usec()); | ||||||
|  | 
 | ||||||
|  |                         /* The connection can be closed when idle, so check to
 | ||||||
|  |                            see if we need to reestablish it */ | ||||||
|  |                         if (u->fd < 0) | ||||||
|  |                             pa_raop_connect(u->raop); | ||||||
|  |                         else | ||||||
|  |                             pa_raop_flush(u->raop); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     break; | ||||||
|  | 
 | ||||||
|  |                 case PA_SINK_UNLINKED: | ||||||
|  |                 case PA_SINK_INIT: | ||||||
|  |                     ; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case PA_SINK_MESSAGE_GET_LATENCY: { | ||||||
|  |             pa_usec_t w, r; | ||||||
|  | 
 | ||||||
|  |             r = pa_smoother_get(u->smoother, pa_rtclock_usec()); | ||||||
|  |             w = pa_bytes_to_usec((u->offset - u->encoding_overhead + (u->encoded_memchunk.length / u->encoding_ratio)), &u->sink->sample_spec); | ||||||
|  | 
 | ||||||
|  |             *((pa_usec_t*) data) = w > r ? w - r : 0; | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         case SINK_MESSAGE_PASS_SOCKET: { | ||||||
|  |             struct pollfd *pollfd; | ||||||
|  | 
 | ||||||
|  |             pa_assert(!u->rtpoll_item); | ||||||
|  | 
 | ||||||
|  |             u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); | ||||||
|  |             pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); | ||||||
|  |             pollfd->fd = u->fd; | ||||||
|  |             pollfd->events = POLLOUT; | ||||||
|  |             /*pollfd->events = */pollfd->revents = 0; | ||||||
|  | 
 | ||||||
|  |             if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { | ||||||
|  |                 /* Our stream has been suspended so we just flush it.... */ | ||||||
|  |                 pa_raop_flush(u->raop); | ||||||
|  |             } | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         case SINK_MESSAGE_RIP_SOCKET: { | ||||||
|  |             pa_assert(u->fd >= 0); | ||||||
|  | 
 | ||||||
|  |             pa_close(u->fd); | ||||||
|  |             u->fd = -1; | ||||||
|  | 
 | ||||||
|  |             if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { | ||||||
|  | 
 | ||||||
|  |                 pa_log_debug("RTSP control connection closed, but we're suspended so let's not worry about it... we'll open it again later"); | ||||||
|  | 
 | ||||||
|  |                 if (u->rtpoll_item) | ||||||
|  |                     pa_rtpoll_item_free(u->rtpoll_item); | ||||||
|  |                 u->rtpoll_item = NULL; | ||||||
|  |             } else { | ||||||
|  |                 /* Quesiton: is this valid here: or should we do some sort of:
 | ||||||
|  |                    return pa_sink_process_msg(PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL); | ||||||
|  |                    ?? */ | ||||||
|  |                 pa_module_unload_request(u->module, TRUE); | ||||||
|  |             } | ||||||
|  |             return 0; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return pa_sink_process_msg(o, code, data, offset, chunk); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sink_get_volume_cb(pa_sink *s) { | ||||||
|  |     struct userdata *u = s->userdata; | ||||||
|  |     int i; | ||||||
|  | 
 | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     for (i = 0; i < s->sample_spec.channels; i++) { | ||||||
|  |         s->volume.values[i] = u->volume; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sink_set_volume_cb(pa_sink *s) { | ||||||
|  |     struct userdata *u = s->userdata; | ||||||
|  |     int rv; | ||||||
|  | 
 | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     /* If we're muted, we fake it */ | ||||||
|  |     if (u->muted) | ||||||
|  |         return 0; | ||||||
|  | 
 | ||||||
|  |     pa_assert(s->sample_spec.channels > 0); | ||||||
|  | 
 | ||||||
|  |     /* Avoid pointless volume sets */ | ||||||
|  |     if (u->volume == s->volume.values[0]) | ||||||
|  |         return 0; | ||||||
|  | 
 | ||||||
|  |     rv = pa_raop_client_set_volume(u->raop, s->volume.values[0]); | ||||||
|  |     if (0 == rv) | ||||||
|  |         u->volume = s->volume.values[0]; | ||||||
|  | 
 | ||||||
|  |     return rv; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sink_get_mute_cb(pa_sink *s) { | ||||||
|  |     struct userdata *u = s->userdata; | ||||||
|  | 
 | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     s->muted = u->muted; | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int sink_set_mute_cb(pa_sink *s) { | ||||||
|  |     struct userdata *u = s->userdata; | ||||||
|  |     int rv; | ||||||
|  | 
 | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     rv = pa_raop_client_set_volume(u->raop, (s->muted ? PA_VOLUME_MUTED : u->volume)); | ||||||
|  |     u->muted = s->muted; | ||||||
|  |     return rv; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void thread_func(void *userdata) { | ||||||
|  |     struct userdata *u = userdata; | ||||||
|  |     int write_type = 0; | ||||||
|  |     pa_memchunk silence; | ||||||
|  |     uint32_t silence_overhead = 0; | ||||||
|  |     double silence_ratio = 0; | ||||||
|  | 
 | ||||||
|  |     pa_assert(u); | ||||||
|  | 
 | ||||||
|  |     pa_log_debug("Thread starting up"); | ||||||
|  | 
 | ||||||
|  |     pa_thread_mq_install(&u->thread_mq); | ||||||
|  |     pa_rtpoll_install(u->rtpoll); | ||||||
|  | 
 | ||||||
|  |     pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); | ||||||
|  | 
 | ||||||
|  |     /* Create a chunk of memory that is our encoded silence sample. */ | ||||||
|  |     pa_memchunk_reset(&silence); | ||||||
|  | 
 | ||||||
|  |     for (;;) { | ||||||
|  |         int ret; | ||||||
|  | 
 | ||||||
|  |         if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) | ||||||
|  |             if (u->sink->thread_info.rewind_requested) | ||||||
|  |                 pa_sink_process_rewind(u->sink, 0); | ||||||
|  | 
 | ||||||
|  |         if (u->rtpoll_item) { | ||||||
|  |             struct pollfd *pollfd; | ||||||
|  |             pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); | ||||||
|  | 
 | ||||||
|  |             /* Render some data and write it to the fifo */ | ||||||
|  |             if (/*PA_SINK_IS_OPENED(u->sink->thread_info.state) && */pollfd->revents) { | ||||||
|  |                 pa_usec_t usec; | ||||||
|  |                 int64_t n; | ||||||
|  |                 void *p; | ||||||
|  | 
 | ||||||
|  |                 if (!silence.memblock) { | ||||||
|  |                     pa_memchunk silence_tmp; | ||||||
|  | 
 | ||||||
|  |                     pa_memchunk_reset(&silence_tmp); | ||||||
|  |                     silence_tmp.memblock = pa_memblock_new(u->core->mempool, 4096); | ||||||
|  |                     silence_tmp.length = 4096; | ||||||
|  |                     p = pa_memblock_acquire(silence_tmp.memblock); | ||||||
|  |                       memset(p, 0, 4096); | ||||||
|  |                     pa_memblock_release(silence_tmp.memblock); | ||||||
|  |                     pa_raop_client_encode_sample(u->raop, &silence_tmp, &silence); | ||||||
|  |                     pa_assert(0 == silence_tmp.length); | ||||||
|  |                     silence_overhead = silence_tmp.length - 4096; | ||||||
|  |                     silence_ratio = silence_tmp.length / 4096; | ||||||
|  |                     pa_memblock_unref(silence_tmp.memblock); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 for (;;) { | ||||||
|  |                     ssize_t l; | ||||||
|  | 
 | ||||||
|  |                     if (u->encoded_memchunk.length <= 0) { | ||||||
|  |                         if (u->encoded_memchunk.memblock) | ||||||
|  |                             pa_memblock_unref(u->encoded_memchunk.memblock); | ||||||
|  |                         if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) { | ||||||
|  |                             size_t rl; | ||||||
|  | 
 | ||||||
|  |                             /* We render real data */ | ||||||
|  |                             if (u->raw_memchunk.length <= 0) { | ||||||
|  |                                 if (u->raw_memchunk.memblock) | ||||||
|  |                                     pa_memblock_unref(u->raw_memchunk.memblock); | ||||||
|  |                                 pa_memchunk_reset(&u->raw_memchunk); | ||||||
|  | 
 | ||||||
|  |                                 /* Grab unencoded data */ | ||||||
|  |                                 pa_sink_render(u->sink, u->block_size, &u->raw_memchunk); | ||||||
|  |                             } | ||||||
|  |                             pa_assert(u->raw_memchunk.length > 0); | ||||||
|  | 
 | ||||||
|  |                             /* Encode it */ | ||||||
|  |                             rl = u->raw_memchunk.length; | ||||||
|  |                             u->encoding_overhead += u->next_encoding_overhead; | ||||||
|  |                             pa_raop_client_encode_sample(u->raop, &u->raw_memchunk, &u->encoded_memchunk); | ||||||
|  |                             u->next_encoding_overhead = (u->encoded_memchunk.length - (rl - u->raw_memchunk.length)); | ||||||
|  |                             u->encoding_ratio = u->encoded_memchunk.length / (rl - u->raw_memchunk.length); | ||||||
|  |                         } else { | ||||||
|  |                             /* We render some silence into our memchunk */ | ||||||
|  |                             memcpy(&u->encoded_memchunk, &silence, sizeof(pa_memchunk)); | ||||||
|  |                             pa_memblock_ref(silence.memblock); | ||||||
|  | 
 | ||||||
|  |                             /* Calculate/store some values to be used with the smoother */ | ||||||
|  |                             u->next_encoding_overhead = silence_overhead; | ||||||
|  |                             u->encoding_ratio = silence_ratio; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     pa_assert(u->encoded_memchunk.length > 0); | ||||||
|  | 
 | ||||||
|  |                     p = pa_memblock_acquire(u->encoded_memchunk.memblock); | ||||||
|  |                     l = pa_write(u->fd, (uint8_t*) p + u->encoded_memchunk.index, u->encoded_memchunk.length, &write_type); | ||||||
|  |                     pa_memblock_release(u->encoded_memchunk.memblock); | ||||||
|  | 
 | ||||||
|  |                     pa_assert(l != 0); | ||||||
|  | 
 | ||||||
|  |                     if (l < 0) { | ||||||
|  | 
 | ||||||
|  |                         if (errno == EINTR) | ||||||
|  |                             continue; | ||||||
|  |                         else if (errno == EAGAIN) { | ||||||
|  | 
 | ||||||
|  |                             /* OK, we filled all socket buffers up
 | ||||||
|  |                              * now. */ | ||||||
|  |                             goto filled_up; | ||||||
|  | 
 | ||||||
|  |                         } else { | ||||||
|  |                             pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); | ||||||
|  |                             goto fail; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                     } else { | ||||||
|  |                         u->offset += l; | ||||||
|  | 
 | ||||||
|  |                         u->encoded_memchunk.index += l; | ||||||
|  |                         u->encoded_memchunk.length -= l; | ||||||
|  | 
 | ||||||
|  |                         pollfd->revents = 0; | ||||||
|  | 
 | ||||||
|  |                         if (u->encoded_memchunk.length > 0) { | ||||||
|  |                             /* we've completely written the encoded data, so update our overhead */ | ||||||
|  |                             u->encoding_overhead += u->next_encoding_overhead; | ||||||
|  | 
 | ||||||
|  |                             /* OK, we wrote less that we asked for,
 | ||||||
|  |                              * hence we can assume that the socket | ||||||
|  |                              * buffers are full now */ | ||||||
|  |                             goto filled_up; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |             filled_up: | ||||||
|  | 
 | ||||||
|  |                 /* At this spot we know that the socket buffers are
 | ||||||
|  |                  * fully filled up. This is the best time to estimate | ||||||
|  |                  * the playback position of the server */ | ||||||
|  | 
 | ||||||
|  |                 n = u->offset - u->encoding_overhead; | ||||||
|  | 
 | ||||||
|  | #ifdef SIOCOUTQ | ||||||
|  |                 { | ||||||
|  |                     int l; | ||||||
|  |                     if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0) | ||||||
|  |                         n -= (l / u->encoding_ratio); | ||||||
|  |                 } | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  |                 usec = pa_bytes_to_usec(n, &u->sink->sample_spec); | ||||||
|  | 
 | ||||||
|  |                 if (usec > u->latency) | ||||||
|  |                     usec -= u->latency; | ||||||
|  |                 else | ||||||
|  |                     usec = 0; | ||||||
|  | 
 | ||||||
|  |                 pa_smoother_put(u->smoother, pa_rtclock_usec(), usec); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             /* Hmm, nothing to do. Let's sleep */ | ||||||
|  |             pollfd->events = POLLOUT; /*PA_SINK_IS_OPENED(u->sink->thread_info.state)  ? POLLOUT : 0;*/ | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) | ||||||
|  |             goto fail; | ||||||
|  | 
 | ||||||
|  |         if (ret == 0) | ||||||
|  |             goto finish; | ||||||
|  | 
 | ||||||
|  |         if (u->rtpoll_item) { | ||||||
|  |             struct pollfd* pollfd; | ||||||
|  | 
 | ||||||
|  |             pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); | ||||||
|  | 
 | ||||||
|  |             if (pollfd->revents & ~POLLOUT) { | ||||||
|  |                 if (u->sink->thread_info.state != PA_SINK_SUSPENDED) { | ||||||
|  |                     pa_log("FIFO shutdown."); | ||||||
|  |                     goto fail; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 /* We expect this to happen on occasion if we are not sending data.
 | ||||||
|  |                    It's perfectly natural and normal and natural */ | ||||||
|  |                 if (u->rtpoll_item) | ||||||
|  |                     pa_rtpoll_item_free(u->rtpoll_item); | ||||||
|  |                 u->rtpoll_item = NULL; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | fail: | ||||||
|  |     /* If this was no regular exit from the loop we have to continue
 | ||||||
|  |      * processing messages until we received PA_MESSAGE_SHUTDOWN */ | ||||||
|  |     pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); | ||||||
|  |     pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); | ||||||
|  | 
 | ||||||
|  | finish: | ||||||
|  |     if (silence.memblock) | ||||||
|  |         pa_memblock_unref(silence.memblock); | ||||||
|  |     pa_log_debug("Thread shutting down"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int pa__init(pa_module*m) { | ||||||
|  |     struct userdata *u = NULL; | ||||||
|  |     pa_sample_spec ss; | ||||||
|  |     pa_modargs *ma = NULL; | ||||||
|  |     const char *server; | ||||||
|  |     pa_sink_new_data data; | ||||||
|  | 
 | ||||||
|  |     pa_assert(m); | ||||||
|  | 
 | ||||||
|  |     if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { | ||||||
|  |         pa_log("failed to parse module arguments"); | ||||||
|  |         goto fail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     ss = m->core->default_sample_spec; | ||||||
|  |     if (pa_modargs_get_sample_spec(ma, &ss) < 0) { | ||||||
|  |         pa_log("invalid sample format specification"); | ||||||
|  |         goto fail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ((/*ss.format != PA_SAMPLE_U8 &&*/ ss.format != PA_SAMPLE_S16NE) || | ||||||
|  |         (ss.channels > 2)) { | ||||||
|  |         pa_log("sample type support is limited to mono/stereo and U8 or S16NE sample data"); | ||||||
|  |         goto fail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     u = pa_xnew0(struct userdata, 1); | ||||||
|  |     u->core = m->core; | ||||||
|  |     u->module = m; | ||||||
|  |     m->userdata = u; | ||||||
|  |     u->fd = -1; | ||||||
|  |     u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE, 10); | ||||||
|  |     pa_memchunk_reset(&u->raw_memchunk); | ||||||
|  |     pa_memchunk_reset(&u->encoded_memchunk); | ||||||
|  |     u->offset = 0; | ||||||
|  |     u->encoding_overhead = 0; | ||||||
|  |     u->next_encoding_overhead = 0; | ||||||
|  |     u->encoding_ratio = 1.0; | ||||||
|  | 
 | ||||||
|  |     u->volume = roundf(0.7 * PA_VOLUME_NORM); | ||||||
|  |     u->muted = FALSE; | ||||||
|  | 
 | ||||||
|  |     u->rtpoll = pa_rtpoll_new(); | ||||||
|  |     pa_thread_mq_init(&u->thread_mq, m->core->mainloop, u->rtpoll); | ||||||
|  |     u->rtpoll_item = NULL; | ||||||
|  | 
 | ||||||
|  |     /*u->format =
 | ||||||
|  |         (ss.format == PA_SAMPLE_U8 ? ESD_BITS8 : ESD_BITS16) | | ||||||
|  |         (ss.channels == 2 ? ESD_STEREO : ESD_MONO);*/ | ||||||
|  |     u->rate = ss.rate; | ||||||
|  |     u->block_size = pa_usec_to_bytes(PA_USEC_PER_SEC/20, &ss); | ||||||
|  | 
 | ||||||
|  |     u->read_data = u->write_data = NULL; | ||||||
|  |     u->read_index = u->write_index = u->read_length = u->write_length = 0; | ||||||
|  | 
 | ||||||
|  |     /*u->state = STATE_AUTH;*/ | ||||||
|  |     u->latency = 0; | ||||||
|  | 
 | ||||||
|  |     if (!(server = pa_modargs_get_value(ma, "server", NULL))) { | ||||||
|  |         pa_log("No server argument given."); | ||||||
|  |         goto fail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_sink_new_data_init(&data); | ||||||
|  |     data.driver = __FILE__; | ||||||
|  |     data.module = m; | ||||||
|  |     pa_sink_new_data_set_name(&data, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME)); | ||||||
|  |     pa_sink_new_data_set_sample_spec(&data, &ss); | ||||||
|  |     pa_proplist_sets(data.proplist, PA_PROP_DEVICE_STRING, server); | ||||||
|  |     pa_proplist_setf(data.proplist, PA_PROP_DEVICE_DESCRIPTION, "Airtunes sink '%s'", server); | ||||||
|  | 
 | ||||||
|  |     u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_NETWORK); | ||||||
|  |     pa_sink_new_data_done(&data); | ||||||
|  | 
 | ||||||
|  |     if (!u->sink) { | ||||||
|  |         pa_log("Failed to create sink."); | ||||||
|  |         goto fail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     u->sink->parent.process_msg = sink_process_msg; | ||||||
|  |     u->sink->userdata = u; | ||||||
|  |     u->sink->get_volume = sink_get_volume_cb; | ||||||
|  |     u->sink->set_volume = sink_set_volume_cb; | ||||||
|  |     u->sink->get_mute = sink_get_mute_cb; | ||||||
|  |     u->sink->set_mute = sink_set_mute_cb; | ||||||
|  |     u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK|PA_SINK_HW_VOLUME_CTRL; | ||||||
|  | 
 | ||||||
|  |     pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); | ||||||
|  |     pa_sink_set_rtpoll(u->sink, u->rtpoll); | ||||||
|  | 
 | ||||||
|  |     if (!(u->raop = pa_raop_client_new(u->core, server))) { | ||||||
|  |         pa_log("Failed to connect to server."); | ||||||
|  |         goto fail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_raop_client_set_callback(u->raop, on_connection, u); | ||||||
|  |     pa_raop_client_set_closed_callback(u->raop, on_close, u); | ||||||
|  | 
 | ||||||
|  |     if (!(u->thread = pa_thread_new(thread_func, u))) { | ||||||
|  |         pa_log("Failed to create thread."); | ||||||
|  |         goto fail; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_sink_put(u->sink); | ||||||
|  | 
 | ||||||
|  |     pa_modargs_free(ma); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | 
 | ||||||
|  | fail: | ||||||
|  |     if (ma) | ||||||
|  |         pa_modargs_free(ma); | ||||||
|  | 
 | ||||||
|  |     pa__done(m); | ||||||
|  | 
 | ||||||
|  |     return -1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pa__done(pa_module*m) { | ||||||
|  |     struct userdata *u; | ||||||
|  |     pa_assert(m); | ||||||
|  | 
 | ||||||
|  |     if (!(u = m->userdata)) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     if (u->sink) | ||||||
|  |         pa_sink_unlink(u->sink); | ||||||
|  | 
 | ||||||
|  |     if (u->thread) { | ||||||
|  |         pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); | ||||||
|  |         pa_thread_free(u->thread); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_thread_mq_done(&u->thread_mq); | ||||||
|  | 
 | ||||||
|  |     if (u->sink) | ||||||
|  |         pa_sink_unref(u->sink); | ||||||
|  | 
 | ||||||
|  |     if (u->rtpoll_item) | ||||||
|  |         pa_rtpoll_item_free(u->rtpoll_item); | ||||||
|  | 
 | ||||||
|  |     if (u->rtpoll) | ||||||
|  |         pa_rtpoll_free(u->rtpoll); | ||||||
|  | 
 | ||||||
|  |     if (u->raw_memchunk.memblock) | ||||||
|  |         pa_memblock_unref(u->raw_memchunk.memblock); | ||||||
|  | 
 | ||||||
|  |     if (u->encoded_memchunk.memblock) | ||||||
|  |         pa_memblock_unref(u->encoded_memchunk.memblock); | ||||||
|  | 
 | ||||||
|  |     if (u->raop) | ||||||
|  |         pa_raop_client_free(u->raop); | ||||||
|  | 
 | ||||||
|  |     pa_xfree(u->read_data); | ||||||
|  |     pa_xfree(u->write_data); | ||||||
|  | 
 | ||||||
|  |     if (u->smoother) | ||||||
|  |         pa_smoother_free(u->smoother); | ||||||
|  | 
 | ||||||
|  |     if (u->fd >= 0) | ||||||
|  |         pa_close(u->fd); | ||||||
|  | 
 | ||||||
|  |     pa_xfree(u); | ||||||
|  | } | ||||||
							
								
								
									
										126
									
								
								src/modules/raop/base64.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/modules/raop/base64.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<EFBFBD>gskolan | ||||||
|  | */ | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_CONFIG_H | ||||||
|  | #include <config.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | 
 | ||||||
|  | #include <pulse/xmalloc.h> | ||||||
|  | 
 | ||||||
|  | #include "base64.h" | ||||||
|  | 
 | ||||||
|  | static const char base64_chars[] = | ||||||
|  |     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; | ||||||
|  | 
 | ||||||
|  | static int pos(char c) | ||||||
|  | { | ||||||
|  |     if (c >= 'A' && c <= 'Z') return c - 'A' + 0; | ||||||
|  |     if (c >= 'a' && c <= 'z') return c - 'a' + 26; | ||||||
|  |     if (c >= '0' && c <= '9') return c - '0' + 52; | ||||||
|  |     if (c == '+') return 62; | ||||||
|  |     if (c == '/') return 63; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int pa_base64_encode(const void *data, int size, char **str) | ||||||
|  | { | ||||||
|  |     char *s, *p; | ||||||
|  |     int i; | ||||||
|  |     int c; | ||||||
|  |     const unsigned char *q; | ||||||
|  | 
 | ||||||
|  |     p = s = pa_xnew(char, size * 4 / 3 + 4); | ||||||
|  |     q = (const unsigned char *) data; | ||||||
|  |     i = 0; | ||||||
|  |     for (i = 0; i < size;) { | ||||||
|  |         c = q[i++]; | ||||||
|  |         c *= 256; | ||||||
|  |         if (i < size) | ||||||
|  |             c += q[i]; | ||||||
|  |         i++; | ||||||
|  |         c *= 256; | ||||||
|  |         if (i < size) | ||||||
|  |             c += q[i]; | ||||||
|  |         i++; | ||||||
|  |         p[0] = base64_chars[(c & 0x00fc0000) >> 18]; | ||||||
|  |         p[1] = base64_chars[(c & 0x0003f000) >> 12]; | ||||||
|  |         p[2] = base64_chars[(c & 0x00000fc0) >> 6]; | ||||||
|  |         p[3] = base64_chars[(c & 0x0000003f) >> 0]; | ||||||
|  |         if (i > size) | ||||||
|  |             p[3] = '='; | ||||||
|  |         if (i > size + 1) | ||||||
|  |             p[2] = '='; | ||||||
|  |         p += 4; | ||||||
|  |     } | ||||||
|  |     *p = 0; | ||||||
|  |     *str = s; | ||||||
|  |     return strlen(s); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #define DECODE_ERROR 0xffffffff | ||||||
|  | 
 | ||||||
|  | static unsigned int token_decode(const char *token) | ||||||
|  | { | ||||||
|  |     int i; | ||||||
|  |     unsigned int val = 0; | ||||||
|  |     int marker = 0; | ||||||
|  |     if (strlen(token) < 4) | ||||||
|  |         return DECODE_ERROR; | ||||||
|  |     for (i = 0; i < 4; i++) { | ||||||
|  |         val *= 64; | ||||||
|  |         if (token[i] == '=') | ||||||
|  |             marker++; | ||||||
|  |         else if (marker > 0) | ||||||
|  |             return DECODE_ERROR; | ||||||
|  |         else | ||||||
|  |             val += pos(token[i]); | ||||||
|  |     } | ||||||
|  |     if (marker > 2) | ||||||
|  |         return DECODE_ERROR; | ||||||
|  |     return (marker << 24) | val; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int pa_base64_decode(const char *str, void *data) | ||||||
|  | { | ||||||
|  |     const char *p; | ||||||
|  |     unsigned char *q; | ||||||
|  | 
 | ||||||
|  |     q = data; | ||||||
|  |     for (p = str; *p && (*p == '=' || strchr(base64_chars, *p)); p += 4) { | ||||||
|  |         unsigned int val = token_decode(p); | ||||||
|  |         unsigned int marker = (val >> 24) & 0xff; | ||||||
|  |         if (val == DECODE_ERROR) | ||||||
|  |             return -1; | ||||||
|  |         *q++ = (val >> 16) & 0xff; | ||||||
|  |         if (marker < 2) | ||||||
|  |             *q++ = (val >> 8) & 0xff; | ||||||
|  |         if (marker < 1) | ||||||
|  |             *q++ = val & 0xff; | ||||||
|  |     } | ||||||
|  |     return q - (unsigned char *) data; | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								src/modules/raop/base64.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/modules/raop/base64.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||||
							
								
								
									
										561
									
								
								src/modules/raop/raop_client.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										561
									
								
								src/modules/raop/raop_client.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,561 @@ | ||||||
|  | /***
 | ||||||
|  |   This file is part of PulseAudio. | ||||||
|  | 
 | ||||||
|  |   Copyright 2008 Colin Guthrie | ||||||
|  | 
 | ||||||
|  |   PulseAudio is free software; you can redistribute it and/or modify | ||||||
|  |   it under the terms of the GNU Lesser General Public License as published | ||||||
|  |   by the Free Software Foundation; either version 2 of the License, | ||||||
|  |   or (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |   PulseAudio is distributed in the hope that it will be useful, but | ||||||
|  |   WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||||
|  |   General Public License for more details. | ||||||
|  | 
 | ||||||
|  |   You should have received a copy of the GNU Lesser General Public License | ||||||
|  |   along with PulseAudio; if not, write to the Free Software | ||||||
|  |   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||||||
|  |   USA. | ||||||
|  | ***/ | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_CONFIG_H | ||||||
|  | #include <config.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <fcntl.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <errno.h> | ||||||
|  | #include <arpa/inet.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | #include <sys/ioctl.h> | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_SYS_FILIO_H | ||||||
|  | #include <sys/filio.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | /* TODO: Replace OpenSSL with NSS */ | ||||||
|  | #include <openssl/err.h> | ||||||
|  | #include <openssl/rand.h> | ||||||
|  | #include <openssl/aes.h> | ||||||
|  | #include <openssl/rsa.h> | ||||||
|  | #include <openssl/engine.h> | ||||||
|  | 
 | ||||||
|  | #include <pulse/xmalloc.h> | ||||||
|  | 
 | ||||||
|  | #include <pulsecore/core-error.h> | ||||||
|  | #include <pulsecore/core-util.h> | ||||||
|  | #include <pulsecore/socket-util.h> | ||||||
|  | #include <pulsecore/log.h> | ||||||
|  | #include <pulsecore/macro.h> | ||||||
|  | #include <pulsecore/strbuf.h> | ||||||
|  | #include <pulsecore/random.h> | ||||||
|  | #include <pulsecore/poll.h> | ||||||
|  | 
 | ||||||
|  | #include "raop_client.h" | ||||||
|  | #include "rtsp_client.h" | ||||||
|  | #include "base64.h" | ||||||
|  | 
 | ||||||
|  | #define AES_CHUNKSIZE 16 | ||||||
|  | 
 | ||||||
|  | #define JACK_STATUS_DISCONNECTED 0 | ||||||
|  | #define JACK_STATUS_CONNECTED 1 | ||||||
|  | 
 | ||||||
|  | #define JACK_TYPE_ANALOG 0 | ||||||
|  | #define JACK_TYPE_DIGITAL 1 | ||||||
|  | 
 | ||||||
|  | #define VOLUME_DEF -30 | ||||||
|  | #define VOLUME_MIN -144 | ||||||
|  | #define VOLUME_MAX 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | struct pa_raop_client { | ||||||
|  |     pa_core *core; | ||||||
|  |     char *host; | ||||||
|  |     char *sid; | ||||||
|  |     pa_rtsp_client *rtsp; | ||||||
|  | 
 | ||||||
|  |     uint8_t jack_type; | ||||||
|  |     uint8_t jack_status; | ||||||
|  | 
 | ||||||
|  |     /* Encryption Related bits */ | ||||||
|  |     AES_KEY aes; | ||||||
|  |     uint8_t aes_iv[AES_CHUNKSIZE]; /* initialization vector for aes-cbc */ | ||||||
|  |     uint8_t aes_nv[AES_CHUNKSIZE]; /* next vector for aes-cbc */ | ||||||
|  |     uint8_t aes_key[AES_CHUNKSIZE]; /* key for aes-cbc */ | ||||||
|  | 
 | ||||||
|  |     pa_socket_client *sc; | ||||||
|  |     int fd; | ||||||
|  | 
 | ||||||
|  |     uint16_t seq; | ||||||
|  |     uint32_t rtptime; | ||||||
|  | 
 | ||||||
|  |     pa_raop_client_cb_t callback; | ||||||
|  |     void* userdata; | ||||||
|  |     pa_raop_client_closed_cb_t closed_callback; | ||||||
|  |     void* closed_userdata; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * Function to write bits into a buffer. | ||||||
|  |  * @param buffer Handle to the buffer. It will be incremented if new data requires it. | ||||||
|  |  * @param bit_pos A pointer to a position buffer to keep track the current write location (0 for MSB, 7 for LSB) | ||||||
|  |  * @param size A pointer to the byte size currently written. This allows the calling function to do simple buffer overflow checks | ||||||
|  |  * @param data The data to write | ||||||
|  |  * @param data_bit_len The number of bits from data to write | ||||||
|  |  */ | ||||||
|  | static inline void bit_writer(uint8_t **buffer, uint8_t *bit_pos, int *size, uint8_t data, uint8_t data_bit_len) { | ||||||
|  |     int bits_left, bit_overflow; | ||||||
|  |     uint8_t bit_data; | ||||||
|  | 
 | ||||||
|  |     if (!data_bit_len) | ||||||
|  |         return; | ||||||
|  | 
 | ||||||
|  |     /* If bit pos is zero, we will definatly use at least one bit from the current byte so size increments. */ | ||||||
|  |     if (!*bit_pos) | ||||||
|  |         *size += 1; | ||||||
|  | 
 | ||||||
|  |     /* Calc the number of bits left in the current byte of buffer */ | ||||||
|  |     bits_left = 7 - *bit_pos  + 1; | ||||||
|  |     /* Calc the overflow of bits in relation to how much space we have left... */ | ||||||
|  |     bit_overflow = bits_left - data_bit_len; | ||||||
|  |     if (bit_overflow >= 0) { | ||||||
|  |         /* We can fit the new data in our current byte */ | ||||||
|  |         /* As we write from MSB->LSB we need to left shift by the overflow amount */ | ||||||
|  |         bit_data = data << bit_overflow; | ||||||
|  |         if (*bit_pos) | ||||||
|  |             **buffer |= bit_data; | ||||||
|  |         else | ||||||
|  |             **buffer = bit_data; | ||||||
|  |         /* If our data fits exactly into the current byte, we need to increment our pointer */ | ||||||
|  |         if (0 == bit_overflow) { | ||||||
|  |             /* Do not increment size as it will be incremeneted on next call as bit_pos is zero */ | ||||||
|  |             *buffer += 1; | ||||||
|  |             *bit_pos = 0; | ||||||
|  |         } else { | ||||||
|  |             *bit_pos += data_bit_len; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         /* bit_overflow is negative, there for we will need a new byte from our buffer */ | ||||||
|  |         /* Firstly fill up what's left in the current byte */ | ||||||
|  |         bit_data = data >> -bit_overflow; | ||||||
|  |         **buffer |= bit_data; | ||||||
|  |         /* Increment our buffer pointer and size counter*/ | ||||||
|  |         *buffer += 1; | ||||||
|  |         *size += 1; | ||||||
|  |         **buffer = data << (8 + bit_overflow); | ||||||
|  |         *bit_pos = -bit_overflow; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int rsa_encrypt(uint8_t *text, int len, uint8_t *res) { | ||||||
|  |     const char n[] = | ||||||
|  |         "59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUtwC" | ||||||
|  |         "5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDR" | ||||||
|  |         "KSKv6kDqnw4UwPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuB" | ||||||
|  |         "OitnZ/bDzPHrTOZz0Dew0uowxf/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJ" | ||||||
|  |         "Q+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/UAaHqn9JdsBWLUEpVviYnh" | ||||||
|  |         "imNVvYFZeCXg/IdTQ+x4IRdiXNv5hEew=="; | ||||||
|  |     const char e[] = "AQAB"; | ||||||
|  |     uint8_t modules[256]; | ||||||
|  |     uint8_t exponent[8]; | ||||||
|  |     int size; | ||||||
|  |     RSA *rsa; | ||||||
|  | 
 | ||||||
|  |     rsa = RSA_new(); | ||||||
|  |     size = pa_base64_decode(n, modules); | ||||||
|  |     rsa->n = BN_bin2bn(modules, size, NULL); | ||||||
|  |     size = pa_base64_decode(e, exponent); | ||||||
|  |     rsa->e = BN_bin2bn(exponent, size, NULL); | ||||||
|  | 
 | ||||||
|  |     size = RSA_public_encrypt(len, text, res, rsa, RSA_PKCS1_OAEP_PADDING); | ||||||
|  |     RSA_free(rsa); | ||||||
|  |     return size; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int aes_encrypt(pa_raop_client* c, uint8_t *data, int size) | ||||||
|  | { | ||||||
|  |     uint8_t *buf; | ||||||
|  |     int i=0, j; | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     memcpy(c->aes_nv, c->aes_iv, AES_CHUNKSIZE); | ||||||
|  |     while (i+AES_CHUNKSIZE <= size) { | ||||||
|  |         buf = data + i; | ||||||
|  |         for (j=0; j<AES_CHUNKSIZE; ++j) | ||||||
|  |             buf[j] ^= c->aes_nv[j]; | ||||||
|  | 
 | ||||||
|  |         AES_encrypt(buf, buf, &c->aes); | ||||||
|  |         memcpy(c->aes_nv, buf, AES_CHUNKSIZE); | ||||||
|  |         i += AES_CHUNKSIZE; | ||||||
|  |     } | ||||||
|  |     return i; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static inline void rtrimchar(char *str, char rc) | ||||||
|  | { | ||||||
|  |     char *sp = str + strlen(str) - 1; | ||||||
|  |     while (sp >= str && *sp == rc) { | ||||||
|  |         *sp = '\0'; | ||||||
|  |         sp -= 1; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { | ||||||
|  |     pa_raop_client *c = userdata; | ||||||
|  | 
 | ||||||
|  |     pa_assert(sc); | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(c->sc == sc); | ||||||
|  |     pa_assert(c->fd < 0); | ||||||
|  |     pa_assert(c->callback); | ||||||
|  | 
 | ||||||
|  |     pa_socket_client_unref(c->sc); | ||||||
|  |     c->sc = NULL; | ||||||
|  | 
 | ||||||
|  |     if (!io) { | ||||||
|  |         pa_log("Connection failed: %s", pa_cstrerror(errno)); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     c->fd = pa_iochannel_get_send_fd(io); | ||||||
|  | 
 | ||||||
|  |     pa_iochannel_set_noclose(io, TRUE); | ||||||
|  |     pa_iochannel_socket_set_sndbuf(io, 1024); | ||||||
|  |     pa_iochannel_free(io); | ||||||
|  | 
 | ||||||
|  |     pa_make_tcp_socket_low_delay(c->fd); | ||||||
|  | 
 | ||||||
|  |     pa_log_debug("Connection established"); | ||||||
|  |     c->callback(c->fd, c->userdata); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void rtsp_cb(pa_rtsp_client *rtsp, pa_rtsp_state state, pa_headerlist* headers, void *userdata) | ||||||
|  | { | ||||||
|  |     pa_raop_client* c = userdata; | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(rtsp); | ||||||
|  |     pa_assert(rtsp == c->rtsp); | ||||||
|  | 
 | ||||||
|  |     switch (state) { | ||||||
|  |         case STATE_CONNECT: { | ||||||
|  |             int i; | ||||||
|  |             uint8_t rsakey[512]; | ||||||
|  |             char *key, *iv, *sac, *sdp; | ||||||
|  |             uint16_t rand_data; | ||||||
|  |             const char *ip; | ||||||
|  |             char *url; | ||||||
|  | 
 | ||||||
|  |             pa_log_debug("RAOP: CONNECTED"); | ||||||
|  |             ip = pa_rtsp_localip(c->rtsp); | ||||||
|  |             /* First of all set the url properly */ | ||||||
|  |             url = pa_sprintf_malloc("rtsp://%s/%s", ip, c->sid); | ||||||
|  |             pa_rtsp_set_url(c->rtsp, url); | ||||||
|  |             pa_xfree(url); | ||||||
|  | 
 | ||||||
|  |             /* Now encrypt our aes_public key to send to the device */ | ||||||
|  |             i = rsa_encrypt(c->aes_key, AES_CHUNKSIZE, rsakey); | ||||||
|  |             pa_base64_encode(rsakey, i, &key); | ||||||
|  |             rtrimchar(key, '='); | ||||||
|  |             pa_base64_encode(c->aes_iv, AES_CHUNKSIZE, &iv); | ||||||
|  |             rtrimchar(iv, '='); | ||||||
|  | 
 | ||||||
|  |             pa_random(&rand_data, sizeof(rand_data)); | ||||||
|  |             pa_base64_encode(&rand_data, AES_CHUNKSIZE, &sac); | ||||||
|  |             rtrimchar(sac, '='); | ||||||
|  |             pa_rtsp_add_header(c->rtsp, "Apple-Challenge", sac); | ||||||
|  |             sdp = pa_sprintf_malloc( | ||||||
|  |                 "v=0\r\n" | ||||||
|  |                 "o=iTunes %s 0 IN IP4 %s\r\n" | ||||||
|  |                 "s=iTunes\r\n" | ||||||
|  |                 "c=IN IP4 %s\r\n" | ||||||
|  |                 "t=0 0\r\n" | ||||||
|  |                 "m=audio 0 RTP/AVP 96\r\n" | ||||||
|  |                 "a=rtpmap:96 AppleLossless\r\n" | ||||||
|  |                 "a=fmtp:96 4096 0 16 40 10 14 2 255 0 0 44100\r\n" | ||||||
|  |                 "a=rsaaeskey:%s\r\n" | ||||||
|  |                 "a=aesiv:%s\r\n", | ||||||
|  |                 c->sid, ip, c->host, key, iv); | ||||||
|  |             pa_rtsp_announce(c->rtsp, sdp); | ||||||
|  |             pa_xfree(key); | ||||||
|  |             pa_xfree(iv); | ||||||
|  |             pa_xfree(sac); | ||||||
|  |             pa_xfree(sdp); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         case STATE_ANNOUNCE: | ||||||
|  |             pa_log_debug("RAOP: ANNOUNCED"); | ||||||
|  |             pa_rtsp_remove_header(c->rtsp, "Apple-Challenge"); | ||||||
|  |             pa_rtsp_setup(c->rtsp); | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case STATE_SETUP: { | ||||||
|  |             char *aj = pa_xstrdup(pa_headerlist_gets(headers, "Audio-Jack-Status")); | ||||||
|  |             pa_log_debug("RAOP: SETUP"); | ||||||
|  |             if (aj) { | ||||||
|  |                 char *token, *pc; | ||||||
|  |                 char delimiters[] = ";"; | ||||||
|  |                 const char* token_state = NULL; | ||||||
|  |                 c->jack_type = JACK_TYPE_ANALOG; | ||||||
|  |                 c->jack_status = JACK_STATUS_DISCONNECTED; | ||||||
|  | 
 | ||||||
|  |                 while ((token = pa_split(aj, delimiters, &token_state))) { | ||||||
|  |                     if ((pc = strstr(token, "="))) { | ||||||
|  |                       *pc = 0; | ||||||
|  |                       if (!strcmp(token, "type") && !strcmp(pc+1, "digital")) { | ||||||
|  |                           c->jack_type = JACK_TYPE_DIGITAL; | ||||||
|  |                       } | ||||||
|  |                     } else { | ||||||
|  |                         if (!strcmp(token,"connected")) | ||||||
|  |                             c->jack_status = JACK_STATUS_CONNECTED; | ||||||
|  |                     } | ||||||
|  |                     pa_xfree(token); | ||||||
|  |                 } | ||||||
|  |                 pa_xfree(aj); | ||||||
|  |             } else { | ||||||
|  |                 pa_log_warn("Audio Jack Status missing"); | ||||||
|  |             } | ||||||
|  |             pa_rtsp_record(c->rtsp, &c->seq, &c->rtptime); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         case STATE_RECORD: { | ||||||
|  |             uint32_t port = pa_rtsp_serverport(c->rtsp); | ||||||
|  |             pa_log_debug("RAOP: RECORDED"); | ||||||
|  | 
 | ||||||
|  |             if (!(c->sc = pa_socket_client_new_string(c->core->mainloop, c->host, port))) { | ||||||
|  |                 pa_log("failed to connect to server '%s:%d'", c->host, port); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             pa_socket_client_set_callback(c->sc, on_connection, c); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         case STATE_FLUSH: | ||||||
|  |             pa_log_debug("RAOP: FLUSHED"); | ||||||
|  |             break; | ||||||
|  | 
 | ||||||
|  |         case STATE_TEARDOWN: | ||||||
|  |         case STATE_SET_PARAMETER: | ||||||
|  |             pa_log_debug("RAOP: SET_PARAMETER"); | ||||||
|  |             break; | ||||||
|  |         case STATE_DISCONNECTED: | ||||||
|  |             pa_assert(c->closed_callback); | ||||||
|  |             pa_assert(c->rtsp); | ||||||
|  | 
 | ||||||
|  |             pa_log_debug("RTSP control channel closed"); | ||||||
|  |             pa_rtsp_client_free(c->rtsp); | ||||||
|  |             c->rtsp = NULL; | ||||||
|  |             if (c->fd > 0) { | ||||||
|  |                 /* We do not close the fd, we leave it to the closed callback to do that */ | ||||||
|  |                 c->fd = -1; | ||||||
|  |             } | ||||||
|  |             if (c->sc) { | ||||||
|  |                 pa_socket_client_unref(c->sc); | ||||||
|  |                 c->sc = NULL; | ||||||
|  |             } | ||||||
|  |             pa_xfree(c->sid); | ||||||
|  |             c->sid = NULL; | ||||||
|  |             c->closed_callback(c->closed_userdata); | ||||||
|  |             break; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pa_raop_client* pa_raop_client_new(pa_core *core, const char* host) | ||||||
|  | { | ||||||
|  |     pa_raop_client* c = pa_xnew0(pa_raop_client, 1); | ||||||
|  | 
 | ||||||
|  |     pa_assert(core); | ||||||
|  |     pa_assert(host); | ||||||
|  | 
 | ||||||
|  |     c->core = core; | ||||||
|  |     c->fd = -1; | ||||||
|  |     c->host = pa_xstrdup(host); | ||||||
|  | 
 | ||||||
|  |     if (pa_raop_connect(c)) { | ||||||
|  |         pa_raop_client_free(c); | ||||||
|  |         return NULL; | ||||||
|  |     } | ||||||
|  |     return c; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | void pa_raop_client_free(pa_raop_client* c) | ||||||
|  | { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     if (c->rtsp) | ||||||
|  |         pa_rtsp_client_free(c->rtsp); | ||||||
|  |     pa_xfree(c->host); | ||||||
|  |     pa_xfree(c); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_raop_connect(pa_raop_client* c) | ||||||
|  | { | ||||||
|  |     char *sci; | ||||||
|  |     struct { | ||||||
|  |         uint32_t a; | ||||||
|  |         uint32_t b; | ||||||
|  |         uint32_t c; | ||||||
|  |     } rand_data; | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     if (c->rtsp) { | ||||||
|  |         pa_log_debug("Connection already in progress"); | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     c->rtsp = pa_rtsp_client_new(c->core->mainloop, c->host, 5000, "iTunes/4.6 (Macintosh; U; PPC Mac OS X 10.3)"); | ||||||
|  | 
 | ||||||
|  |     /* Initialise the AES encryption system */ | ||||||
|  |     pa_random(c->aes_iv, sizeof(c->aes_iv)); | ||||||
|  |     pa_random(c->aes_key, sizeof(c->aes_key)); | ||||||
|  |     memcpy(c->aes_nv, c->aes_iv, sizeof(c->aes_nv)); | ||||||
|  |     AES_set_encrypt_key(c->aes_key, 128, &c->aes); | ||||||
|  | 
 | ||||||
|  |     /* Generate random instance id */ | ||||||
|  |     pa_random(&rand_data, sizeof(rand_data)); | ||||||
|  |     c->sid = pa_sprintf_malloc("%u", rand_data.a); | ||||||
|  |     sci = pa_sprintf_malloc("%08x%08x",rand_data.b, rand_data.c); | ||||||
|  |     pa_rtsp_add_header(c->rtsp, "Client-Instance", sci); | ||||||
|  |     pa_xfree(sci); | ||||||
|  |     pa_rtsp_set_callback(c->rtsp, rtsp_cb, c); | ||||||
|  |     return pa_rtsp_connect(c->rtsp); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_raop_flush(pa_raop_client* c) | ||||||
|  | { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     pa_rtsp_flush(c->rtsp, c->seq, c->rtptime); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume) | ||||||
|  | { | ||||||
|  |     int rv; | ||||||
|  |     double db; | ||||||
|  |     char *param; | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     db = pa_sw_volume_to_dB(volume); | ||||||
|  |     if (db < VOLUME_MIN) | ||||||
|  |         db = VOLUME_MIN; | ||||||
|  |     else if (db > VOLUME_MAX) | ||||||
|  |         db = VOLUME_MAX; | ||||||
|  | 
 | ||||||
|  |     param = pa_sprintf_malloc("volume: %0.6f\r\n",  db); | ||||||
|  | 
 | ||||||
|  |     /* We just hit and hope, cannot wait for the callback */ | ||||||
|  |     rv = pa_rtsp_setparameter(c->rtsp, param); | ||||||
|  |     pa_xfree(param); | ||||||
|  |     return rv; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded) | ||||||
|  | { | ||||||
|  |     uint16_t len; | ||||||
|  |     size_t bufmax; | ||||||
|  |     uint8_t *bp, bpos; | ||||||
|  |     uint8_t *ibp, *maxibp; | ||||||
|  |     int size; | ||||||
|  |     uint8_t *b, *p; | ||||||
|  |     uint32_t bsize; | ||||||
|  |     size_t length; | ||||||
|  |     static uint8_t header[] = { | ||||||
|  |         0x24, 0x00, 0x00, 0x00, | ||||||
|  |         0xF0, 0xFF, 0x00, 0x00, | ||||||
|  |         0x00, 0x00, 0x00, 0x00, | ||||||
|  |         0x00, 0x00, 0x00, 0x00, | ||||||
|  |     }; | ||||||
|  |     int header_size = sizeof(header); | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(c->fd > 0); | ||||||
|  |     pa_assert(raw); | ||||||
|  |     pa_assert(raw->memblock); | ||||||
|  |     pa_assert(raw->length > 0); | ||||||
|  |     pa_assert(encoded); | ||||||
|  | 
 | ||||||
|  |     /* We have to send 4 byte chunks */ | ||||||
|  |     bsize = (int)(raw->length / 4); | ||||||
|  |     length = bsize * 4; | ||||||
|  | 
 | ||||||
|  |     /* Leave 16 bytes extra to allow for the ALAC header which is about 55 bits */ | ||||||
|  |     bufmax = length + header_size + 16; | ||||||
|  |     pa_memchunk_reset(encoded); | ||||||
|  |     encoded->memblock = pa_memblock_new(c->core->mempool, bufmax); | ||||||
|  |     b = pa_memblock_acquire(encoded->memblock); | ||||||
|  |     memcpy(b, header, header_size); | ||||||
|  | 
 | ||||||
|  |     /* Now write the actual samples */ | ||||||
|  |     bp = b + header_size; | ||||||
|  |     size = bpos = 0; | ||||||
|  |     bit_writer(&bp,&bpos,&size,1,3); /* channel=1, stereo */ | ||||||
|  |     bit_writer(&bp,&bpos,&size,0,4); /* unknown */ | ||||||
|  |     bit_writer(&bp,&bpos,&size,0,8); /* unknown */ | ||||||
|  |     bit_writer(&bp,&bpos,&size,0,4); /* unknown */ | ||||||
|  |     bit_writer(&bp,&bpos,&size,1,1); /* hassize */ | ||||||
|  |     bit_writer(&bp,&bpos,&size,0,2); /* unused */ | ||||||
|  |     bit_writer(&bp,&bpos,&size,1,1); /* is-not-compressed */ | ||||||
|  | 
 | ||||||
|  |     /* size of data, integer, big endian */ | ||||||
|  |     bit_writer(&bp,&bpos,&size,(bsize>>24)&0xff,8); | ||||||
|  |     bit_writer(&bp,&bpos,&size,(bsize>>16)&0xff,8); | ||||||
|  |     bit_writer(&bp,&bpos,&size,(bsize>>8)&0xff,8); | ||||||
|  |     bit_writer(&bp,&bpos,&size,(bsize)&0xff,8); | ||||||
|  | 
 | ||||||
|  |     ibp = p = pa_memblock_acquire(raw->memblock); | ||||||
|  |     maxibp = p + raw->length - 4; | ||||||
|  |     while (ibp <= maxibp) { | ||||||
|  |         /* Byte swap stereo data */ | ||||||
|  |         bit_writer(&bp,&bpos,&size,*(ibp+1),8); | ||||||
|  |         bit_writer(&bp,&bpos,&size,*(ibp+0),8); | ||||||
|  |         bit_writer(&bp,&bpos,&size,*(ibp+3),8); | ||||||
|  |         bit_writer(&bp,&bpos,&size,*(ibp+2),8); | ||||||
|  |         ibp += 4; | ||||||
|  |         raw->index += 4; | ||||||
|  |         raw->length -= 4; | ||||||
|  |     } | ||||||
|  |     pa_memblock_release(raw->memblock); | ||||||
|  |     encoded->length = header_size + size; | ||||||
|  | 
 | ||||||
|  |     /* store the lenght (endian swapped: make this better) */ | ||||||
|  |     len = size + header_size - 4; | ||||||
|  |     *(b + 2) = len >> 8; | ||||||
|  |     *(b + 3) = len & 0xff; | ||||||
|  | 
 | ||||||
|  |     /* encrypt our data */ | ||||||
|  |     aes_encrypt(c, (b + header_size), size); | ||||||
|  | 
 | ||||||
|  |     /* We're done with the chunk */ | ||||||
|  |     pa_memblock_release(encoded->memblock); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata) | ||||||
|  | { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     c->callback = callback; | ||||||
|  |     c->userdata = userdata; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata) | ||||||
|  | { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     c->closed_callback = callback; | ||||||
|  |     c->closed_userdata = userdata; | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								src/modules/raop/raop_client.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/modules/raop/raop_client.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | ||||||
|  | #ifndef fooraopclientfoo | ||||||
|  | #define fooraopclientfoo | ||||||
|  | 
 | ||||||
|  | /***
 | ||||||
|  |   This file is part of PulseAudio. | ||||||
|  | 
 | ||||||
|  |   Copyright 2008 Colin Guthrie | ||||||
|  | 
 | ||||||
|  |   PulseAudio is free software; you can redistribute it and/or modify | ||||||
|  |   it under the terms of the GNU Lesser General Public License as published | ||||||
|  |   by the Free Software Foundation; either version 2 of the License, | ||||||
|  |   or (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |   PulseAudio is distributed in the hope that it will be useful, but | ||||||
|  |   WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||||
|  |   General Public License for more details. | ||||||
|  | 
 | ||||||
|  |   You should have received a copy of the GNU Lesser General Public License | ||||||
|  |   along with PulseAudio; if not, write to the Free Software | ||||||
|  |   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||||||
|  |   USA. | ||||||
|  | ***/ | ||||||
|  | 
 | ||||||
|  | #include <pulse/mainloop-api.h> | ||||||
|  | #include <pulsecore/iochannel.h> | ||||||
|  | #include <pulsecore/core.h> | ||||||
|  | 
 | ||||||
|  | typedef struct pa_raop_client pa_raop_client; | ||||||
|  | 
 | ||||||
|  | pa_raop_client* pa_raop_client_new(pa_core *core, const char* host); | ||||||
|  | void pa_raop_client_free(pa_raop_client* c); | ||||||
|  | 
 | ||||||
|  | int pa_raop_connect(pa_raop_client* c); | ||||||
|  | int pa_raop_flush(pa_raop_client* c); | ||||||
|  | 
 | ||||||
|  | int pa_raop_client_set_volume(pa_raop_client* c, pa_volume_t volume); | ||||||
|  | int pa_raop_client_encode_sample(pa_raop_client* c, pa_memchunk* raw, pa_memchunk* encoded); | ||||||
|  | 
 | ||||||
|  | typedef void (*pa_raop_client_cb_t)(int fd, void *userdata); | ||||||
|  | void pa_raop_client_set_callback(pa_raop_client* c, pa_raop_client_cb_t callback, void *userdata); | ||||||
|  | 
 | ||||||
|  | typedef void (*pa_raop_client_closed_cb_t)(void *userdata); | ||||||
|  | void pa_raop_client_set_closed_callback(pa_raop_client* c, pa_raop_client_closed_cb_t callback, void *userdata); | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
							
								
								
									
										186
									
								
								src/modules/rtp/headerlist.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/modules/rtp/headerlist.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,186 @@ | ||||||
|  | /***
 | ||||||
|  |   This file is part of PulseAudio. | ||||||
|  | 
 | ||||||
|  |   Copyright 2008 Colin Guthrie | ||||||
|  |   Copyright 2007 Lennart Poettering | ||||||
|  | 
 | ||||||
|  |   PulseAudio is free software; you can redistribute it and/or modify | ||||||
|  |   it under the terms of the GNU Lesser General Public License as | ||||||
|  |   published by the Free Software Foundation; either version 2.1 of the | ||||||
|  |   License, or (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |   PulseAudio is distributed in the hope that it will be useful, but | ||||||
|  |   WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||||
|  |   Lesser General Public License for more details. | ||||||
|  | 
 | ||||||
|  |   You should have received a copy of the GNU Lesser General Public | ||||||
|  |   License along with PulseAudio; if not, write to the Free Software | ||||||
|  |   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||||||
|  |   USA. | ||||||
|  | ***/ | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_CONFIG_H | ||||||
|  | #include <config.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <string.h> | ||||||
|  | 
 | ||||||
|  | #include <pulse/xmalloc.h> | ||||||
|  | 
 | ||||||
|  | #include <pulsecore/hashmap.h> | ||||||
|  | #include <pulsecore/strbuf.h> | ||||||
|  | #include <pulsecore/core-util.h> | ||||||
|  | 
 | ||||||
|  | #include "headerlist.h" | ||||||
|  | 
 | ||||||
|  | struct header { | ||||||
|  |     char *key; | ||||||
|  |     void *value; | ||||||
|  |     size_t nbytes; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #define MAKE_HASHMAP(p) ((pa_hashmap*) (p)) | ||||||
|  | #define MAKE_HEADERLIST(p) ((pa_headerlist*) (p)) | ||||||
|  | 
 | ||||||
|  | static void header_free(struct header *hdr) { | ||||||
|  |     pa_assert(hdr); | ||||||
|  | 
 | ||||||
|  |     pa_xfree(hdr->key); | ||||||
|  |     pa_xfree(hdr->value); | ||||||
|  |     pa_xfree(hdr); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pa_headerlist* pa_headerlist_new(void) { | ||||||
|  |     return MAKE_HEADERLIST(pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pa_headerlist_free(pa_headerlist* p) { | ||||||
|  |     struct header *hdr; | ||||||
|  | 
 | ||||||
|  |     while ((hdr = pa_hashmap_steal_first(MAKE_HASHMAP(p)))) | ||||||
|  |         header_free(hdr); | ||||||
|  | 
 | ||||||
|  |     pa_hashmap_free(MAKE_HASHMAP(p), NULL, NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int pa_headerlist_puts(pa_headerlist *p, const char *key, const char *value) { | ||||||
|  |     struct header *hdr; | ||||||
|  |     pa_bool_t add = FALSE; | ||||||
|  | 
 | ||||||
|  |     pa_assert(p); | ||||||
|  |     pa_assert(key); | ||||||
|  | 
 | ||||||
|  |     if (!(hdr = pa_hashmap_get(MAKE_HASHMAP(p), key))) { | ||||||
|  |         hdr = pa_xnew(struct header, 1); | ||||||
|  |         hdr->key = pa_xstrdup(key); | ||||||
|  |         add = TRUE; | ||||||
|  |     } else | ||||||
|  |         pa_xfree(hdr->value); | ||||||
|  | 
 | ||||||
|  |     hdr->value = pa_xstrdup(value); | ||||||
|  |     hdr->nbytes = strlen(value)+1; | ||||||
|  | 
 | ||||||
|  |     if (add) | ||||||
|  |         pa_hashmap_put(MAKE_HASHMAP(p), hdr->key, hdr); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int pa_headerlist_putsappend(pa_headerlist *p, const char *key, const char *value) { | ||||||
|  |     struct header *hdr; | ||||||
|  |     pa_bool_t add = FALSE; | ||||||
|  | 
 | ||||||
|  |     pa_assert(p); | ||||||
|  |     pa_assert(key); | ||||||
|  | 
 | ||||||
|  |     if (!(hdr = pa_hashmap_get(MAKE_HASHMAP(p), key))) { | ||||||
|  |         hdr = pa_xnew(struct header, 1); | ||||||
|  |         hdr->key = pa_xstrdup(key); | ||||||
|  |         hdr->value = pa_xstrdup(value); | ||||||
|  |         add = TRUE; | ||||||
|  |     } else { | ||||||
|  |         void *newval = pa_sprintf_malloc("%s%s", (char*)hdr->value, value); | ||||||
|  |         pa_xfree(hdr->value); | ||||||
|  |         hdr->value = newval; | ||||||
|  |     } | ||||||
|  |     hdr->nbytes = strlen(hdr->value)+1; | ||||||
|  | 
 | ||||||
|  |     if (add) | ||||||
|  |         pa_hashmap_put(MAKE_HASHMAP(p), hdr->key, hdr); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const char *pa_headerlist_gets(pa_headerlist *p, const char *key) { | ||||||
|  |     struct header *hdr; | ||||||
|  | 
 | ||||||
|  |     pa_assert(p); | ||||||
|  |     pa_assert(key); | ||||||
|  | 
 | ||||||
|  |     if (!(hdr = pa_hashmap_get(MAKE_HASHMAP(p), key))) | ||||||
|  |         return NULL; | ||||||
|  | 
 | ||||||
|  |     if (hdr->nbytes <= 0) | ||||||
|  |         return NULL; | ||||||
|  | 
 | ||||||
|  |     if (((char*) hdr->value)[hdr->nbytes-1] != 0) | ||||||
|  |         return NULL; | ||||||
|  | 
 | ||||||
|  |     if (strlen((char*) hdr->value) != hdr->nbytes-1) | ||||||
|  |         return NULL; | ||||||
|  | 
 | ||||||
|  |     return (char*) hdr->value; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int pa_headerlist_remove(pa_headerlist *p, const char *key) { | ||||||
|  |     struct header *hdr; | ||||||
|  | 
 | ||||||
|  |     pa_assert(p); | ||||||
|  |     pa_assert(key); | ||||||
|  | 
 | ||||||
|  |     if (!(hdr = pa_hashmap_remove(MAKE_HASHMAP(p), key))) | ||||||
|  |         return -1; | ||||||
|  | 
 | ||||||
|  |     header_free(hdr); | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const char *pa_headerlist_iterate(pa_headerlist *p, void **state) { | ||||||
|  |     struct header *hdr; | ||||||
|  | 
 | ||||||
|  |     if (!(hdr = pa_hashmap_iterate(MAKE_HASHMAP(p), state, NULL))) | ||||||
|  |         return NULL; | ||||||
|  | 
 | ||||||
|  |     return hdr->key; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | char *pa_headerlist_to_string(pa_headerlist *p) { | ||||||
|  |     const char *key; | ||||||
|  |     void *state = NULL; | ||||||
|  |     pa_strbuf *buf; | ||||||
|  | 
 | ||||||
|  |     pa_assert(p); | ||||||
|  | 
 | ||||||
|  |     buf = pa_strbuf_new(); | ||||||
|  | 
 | ||||||
|  |     while ((key = pa_headerlist_iterate(p, &state))) { | ||||||
|  | 
 | ||||||
|  |         const char *v; | ||||||
|  | 
 | ||||||
|  |         if ((v = pa_headerlist_gets(p, key))) | ||||||
|  |             pa_strbuf_printf(buf, "%s: %s\r\n", key, v); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return pa_strbuf_tostring_free(buf); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int pa_headerlist_contains(pa_headerlist *p, const char *key) { | ||||||
|  |     pa_assert(p); | ||||||
|  |     pa_assert(key); | ||||||
|  | 
 | ||||||
|  |     if (!(pa_hashmap_get(MAKE_HASHMAP(p), key))) | ||||||
|  |         return 0; | ||||||
|  | 
 | ||||||
|  |     return 1; | ||||||
|  | } | ||||||
							
								
								
									
										46
									
								
								src/modules/rtp/headerlist.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/modules/rtp/headerlist.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,46 @@ | ||||||
|  | #ifndef foopulseheaderlisthfoo | ||||||
|  | #define foopulseheaderlisthfoo | ||||||
|  | 
 | ||||||
|  | /***
 | ||||||
|  |   This file is part of PulseAudio. | ||||||
|  | 
 | ||||||
|  |   Copyright 2008 Colin Guthrie | ||||||
|  |   Copyright 2007 Lennart Poettering | ||||||
|  | 
 | ||||||
|  |   PulseAudio is free software; you can redistribute it and/or modify | ||||||
|  |   it under the terms of the GNU Lesser General Public License as | ||||||
|  |   published by the Free Software Foundation; either version 2.1 of the | ||||||
|  |   License, or (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |   PulseAudio is distributed in the hope that it will be useful, but | ||||||
|  |   WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||||
|  |   Lesser General Public License for more details. | ||||||
|  | 
 | ||||||
|  |   You should have received a copy of the GNU Lesser General Public | ||||||
|  |   License along with PulseAudio; if not, write to the Free Software | ||||||
|  |   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||||||
|  |   USA. | ||||||
|  | ***/ | ||||||
|  | 
 | ||||||
|  | #include <pulsecore/macro.h> | ||||||
|  | 
 | ||||||
|  | typedef struct pa_headerlist pa_headerlist; | ||||||
|  | 
 | ||||||
|  | pa_headerlist* pa_headerlist_new(void); | ||||||
|  | void pa_headerlist_free(pa_headerlist* p); | ||||||
|  | 
 | ||||||
|  | int pa_headerlist_puts(pa_headerlist *p, const char *key, const char *value); | ||||||
|  | int pa_headerlist_putsappend(pa_headerlist *p, const char *key, const char *value); | ||||||
|  | 
 | ||||||
|  | const char *pa_headerlist_gets(pa_headerlist *p, const char *key); | ||||||
|  | 
 | ||||||
|  | int pa_headerlist_remove(pa_headerlist *p, const char *key); | ||||||
|  | 
 | ||||||
|  | const char *pa_headerlist_iterate(pa_headerlist *p, void **state); | ||||||
|  | 
 | ||||||
|  | char *pa_headerlist_to_string(pa_headerlist *p); | ||||||
|  | 
 | ||||||
|  | int pa_headerlist_contains(pa_headerlist *p, const char *key); | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
							
								
								
									
										542
									
								
								src/modules/rtp/rtsp_client.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										542
									
								
								src/modules/rtp/rtsp_client.c
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,542 @@ | ||||||
|  | /***
 | ||||||
|  |   This file is part of PulseAudio. | ||||||
|  | 
 | ||||||
|  |   Copyright 2008 Colin Guthrie | ||||||
|  | 
 | ||||||
|  |   PulseAudio is free software; you can redistribute it and/or modify | ||||||
|  |   it under the terms of the GNU Lesser General Public License as published | ||||||
|  |   by the Free Software Foundation; either version 2 of the License, | ||||||
|  |   or (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |   PulseAudio is distributed in the hope that it will be useful, but | ||||||
|  |   WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||||
|  |   General Public License for more details. | ||||||
|  | 
 | ||||||
|  |   You should have received a copy of the GNU Lesser General Public License | ||||||
|  |   along with PulseAudio; if not, write to the Free Software | ||||||
|  |   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||||||
|  |   USA. | ||||||
|  | ***/ | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_CONFIG_H | ||||||
|  | #include <config.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <fcntl.h> | ||||||
|  | #include <stdlib.h> | ||||||
|  | #include <string.h> | ||||||
|  | #include <errno.h> | ||||||
|  | #include <arpa/inet.h> | ||||||
|  | #include <unistd.h> | ||||||
|  | #include <sys/ioctl.h> | ||||||
|  | 
 | ||||||
|  | #ifdef HAVE_SYS_FILIO_H | ||||||
|  | #include <sys/filio.h> | ||||||
|  | #endif | ||||||
|  | 
 | ||||||
|  | #include <pulse/xmalloc.h> | ||||||
|  | 
 | ||||||
|  | #include <pulsecore/core-error.h> | ||||||
|  | #include <pulsecore/core-util.h> | ||||||
|  | #include <pulsecore/socket-util.h> | ||||||
|  | #include <pulsecore/log.h> | ||||||
|  | #include <pulsecore/macro.h> | ||||||
|  | #include <pulsecore/strbuf.h> | ||||||
|  | #include <pulsecore/poll.h> | ||||||
|  | #include <pulsecore/ioline.h> | ||||||
|  | 
 | ||||||
|  | #include "rtsp_client.h" | ||||||
|  | 
 | ||||||
|  | struct pa_rtsp_client { | ||||||
|  |     pa_mainloop_api *mainloop; | ||||||
|  |     char *hostname; | ||||||
|  |     uint16_t port; | ||||||
|  | 
 | ||||||
|  |     pa_socket_client *sc; | ||||||
|  |     pa_iochannel *io; | ||||||
|  |     pa_ioline *ioline; | ||||||
|  | 
 | ||||||
|  |     pa_rtsp_cb_t callback; | ||||||
|  | 
 | ||||||
|  |     void *userdata; | ||||||
|  |     const char *useragent; | ||||||
|  | 
 | ||||||
|  |     pa_rtsp_state state; | ||||||
|  |     uint8_t waiting; | ||||||
|  | 
 | ||||||
|  |     pa_headerlist* headers; | ||||||
|  |     char *last_header; | ||||||
|  |     pa_strbuf *header_buffer; | ||||||
|  |     pa_headerlist* response_headers; | ||||||
|  | 
 | ||||||
|  |     char *localip; | ||||||
|  |     char *url; | ||||||
|  |     uint16_t rtp_port; | ||||||
|  |     uint32_t cseq; | ||||||
|  |     char *session; | ||||||
|  |     char *transport; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char* hostname, uint16_t port, const char* useragent) { | ||||||
|  |     pa_rtsp_client *c; | ||||||
|  | 
 | ||||||
|  |     pa_assert(mainloop); | ||||||
|  |     pa_assert(hostname); | ||||||
|  |     pa_assert(port > 0); | ||||||
|  | 
 | ||||||
|  |     c = pa_xnew0(pa_rtsp_client, 1); | ||||||
|  |     c->mainloop = mainloop; | ||||||
|  |     c->hostname = pa_xstrdup(hostname); | ||||||
|  |     c->port = port; | ||||||
|  |     c->headers = pa_headerlist_new(); | ||||||
|  | 
 | ||||||
|  |     if (useragent) | ||||||
|  |         c->useragent = useragent; | ||||||
|  |     else | ||||||
|  |         c->useragent = "PulseAudio RTSP Client"; | ||||||
|  | 
 | ||||||
|  |     return c; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | void pa_rtsp_client_free(pa_rtsp_client* c) { | ||||||
|  |     if (c) { | ||||||
|  |         if (c->sc) | ||||||
|  |             pa_socket_client_unref(c->sc); | ||||||
|  |         if (c->ioline) | ||||||
|  |             pa_ioline_close(c->ioline); | ||||||
|  |         else if (c->io) | ||||||
|  |             pa_iochannel_free(c->io); | ||||||
|  | 
 | ||||||
|  |         pa_xfree(c->hostname); | ||||||
|  |         pa_xfree(c->url); | ||||||
|  |         pa_xfree(c->localip); | ||||||
|  |         pa_xfree(c->session); | ||||||
|  |         pa_xfree(c->transport); | ||||||
|  |         pa_xfree(c->last_header); | ||||||
|  |         if (c->header_buffer) | ||||||
|  |             pa_strbuf_free(c->header_buffer); | ||||||
|  |         if (c->response_headers) | ||||||
|  |             pa_headerlist_free(c->response_headers); | ||||||
|  |         pa_headerlist_free(c->headers); | ||||||
|  |     } | ||||||
|  |     pa_xfree(c); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | static void headers_read(pa_rtsp_client *c) { | ||||||
|  |     char* token; | ||||||
|  |     char delimiters[] = ";"; | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(c->response_headers); | ||||||
|  |     pa_assert(c->callback); | ||||||
|  | 
 | ||||||
|  |     /* Deal with a SETUP response */ | ||||||
|  |     if (STATE_SETUP == c->state) { | ||||||
|  |         const char* token_state = NULL; | ||||||
|  |         const char* pc = NULL; | ||||||
|  |         c->session = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Session")); | ||||||
|  |         c->transport = pa_xstrdup(pa_headerlist_gets(c->response_headers, "Transport")); | ||||||
|  | 
 | ||||||
|  |         if (!c->session || !c->transport) { | ||||||
|  |             pa_headerlist_free(c->response_headers); | ||||||
|  |             c->response_headers = NULL; | ||||||
|  |             pa_log("Invalid SETUP response."); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         /* Now parse out the server port component of the response. */ | ||||||
|  |         while ((token = pa_split(c->transport, delimiters, &token_state))) { | ||||||
|  |             if ((pc = strstr(token, "="))) { | ||||||
|  |                 if (0 == strncmp(token, "server_port", 11)) { | ||||||
|  |                     pa_atou(pc+1, (uint32_t*)(&c->rtp_port)); | ||||||
|  |                     pa_xfree(token); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             pa_xfree(token); | ||||||
|  |         } | ||||||
|  |         if (0 == c->rtp_port) { | ||||||
|  |             /* Error no server_port in response */ | ||||||
|  |             pa_headerlist_free(c->response_headers); | ||||||
|  |             c->response_headers = NULL; | ||||||
|  |             pa_log("Invalid SETUP response (no port number)."); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* Call our callback */ | ||||||
|  |     c->callback(c, c->state, c->response_headers, c->userdata); | ||||||
|  | 
 | ||||||
|  |     pa_headerlist_free(c->response_headers); | ||||||
|  |     c->response_headers = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | static void line_callback(pa_ioline *line, const char *s, void *userdata) { | ||||||
|  |     char *delimpos; | ||||||
|  |     char *s2, *s2p; | ||||||
|  | 
 | ||||||
|  |     pa_rtsp_client *c = userdata; | ||||||
|  |     pa_assert(line); | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(c->callback); | ||||||
|  | 
 | ||||||
|  |     if (!s) { | ||||||
|  |         /* Keep the ioline/iochannel open as they will be freed automatically */ | ||||||
|  |         c->ioline = NULL; | ||||||
|  |         c->io = NULL; | ||||||
|  |         c->callback(c, STATE_DISCONNECTED, NULL, c->userdata); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     s2 = pa_xstrdup(s); | ||||||
|  |     /* Trim trailing carriage returns */ | ||||||
|  |     s2p = s2 + strlen(s2) - 1; | ||||||
|  |     while (s2p >= s2 && '\r' == *s2p) { | ||||||
|  |         *s2p = '\0'; | ||||||
|  |         s2p -= 1; | ||||||
|  |     } | ||||||
|  |     if (c->waiting && 0 == strcmp("RTSP/1.0 200 OK", s2)) { | ||||||
|  |         c->waiting = 0; | ||||||
|  |         pa_assert(!c->response_headers); | ||||||
|  |         c->response_headers = pa_headerlist_new(); | ||||||
|  |         goto exit; | ||||||
|  |     } | ||||||
|  |     if (c->waiting) { | ||||||
|  |         pa_log_warn("Unexpected response: %s", s2); | ||||||
|  |         goto exit;; | ||||||
|  |     } | ||||||
|  |     if (!strlen(s2)) { | ||||||
|  |         /* End of headers */ | ||||||
|  |         /* We will have a header left from our looping itteration, so add it in :) */ | ||||||
|  |         if (c->last_header) { | ||||||
|  |             /* This is not a continuation header so let's dump it into our proplist */ | ||||||
|  |             pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); | ||||||
|  |             pa_xfree(c->last_header); | ||||||
|  |             c->last_header = NULL; | ||||||
|  |             c->header_buffer= NULL; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         pa_log_debug("Full response received. Dispatching"); | ||||||
|  |         headers_read(c); | ||||||
|  |         c->waiting = 1; | ||||||
|  |         goto exit; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* Read and parse a header (we know it's not empty) */ | ||||||
|  |     /* TODO: Move header reading into the headerlist. */ | ||||||
|  | 
 | ||||||
|  |     /* If the first character is a space, it's a continuation header */ | ||||||
|  |     if (c->last_header && ' ' == s2[0]) { | ||||||
|  |         pa_assert(c->header_buffer); | ||||||
|  | 
 | ||||||
|  |         /* Add this line to the buffer (sans the space. */ | ||||||
|  |         pa_strbuf_puts(c->header_buffer, &(s2[1])); | ||||||
|  |         goto exit; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (c->last_header) { | ||||||
|  |         /* This is not a continuation header so let's dump the full
 | ||||||
|  |           header/value into our proplist */ | ||||||
|  |         pa_headerlist_puts(c->response_headers, c->last_header, pa_strbuf_tostring_free(c->header_buffer)); | ||||||
|  |         pa_xfree(c->last_header); | ||||||
|  |         c->last_header = NULL; | ||||||
|  |         c->header_buffer = NULL; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     delimpos = strstr(s2, ":"); | ||||||
|  |     if (!delimpos) { | ||||||
|  |         pa_log_warn("Unexpected response when expecting header: %s", s); | ||||||
|  |         goto exit; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_assert(!c->header_buffer); | ||||||
|  |     pa_assert(!c->last_header); | ||||||
|  | 
 | ||||||
|  |     c->header_buffer = pa_strbuf_new(); | ||||||
|  |     if (strlen(delimpos) > 1) { | ||||||
|  |         /* Cut our line off so we can copy the header name out */ | ||||||
|  |         *delimpos++ = '\0'; | ||||||
|  | 
 | ||||||
|  |         /* Trim the front of any spaces */ | ||||||
|  |         while (' ' == *delimpos) | ||||||
|  |             ++delimpos; | ||||||
|  | 
 | ||||||
|  |         pa_strbuf_puts(c->header_buffer, delimpos); | ||||||
|  |     } else { | ||||||
|  |         /* Cut our line off so we can copy the header name out */ | ||||||
|  |         *delimpos = '\0'; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* Save the header name */ | ||||||
|  |     c->last_header = pa_xstrdup(s2); | ||||||
|  |   exit: | ||||||
|  |     pa_xfree(s2); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { | ||||||
|  |     pa_rtsp_client *c = userdata; | ||||||
|  |     union { | ||||||
|  |         struct sockaddr sa; | ||||||
|  |         struct sockaddr_in in; | ||||||
|  |         struct sockaddr_in6 in6; | ||||||
|  |     } sa; | ||||||
|  |     socklen_t sa_len = sizeof(sa); | ||||||
|  | 
 | ||||||
|  |     pa_assert(sc); | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(STATE_CONNECT == c->state); | ||||||
|  |     pa_assert(c->sc == sc); | ||||||
|  |     pa_socket_client_unref(c->sc); | ||||||
|  |     c->sc = NULL; | ||||||
|  | 
 | ||||||
|  |     if (!io) { | ||||||
|  |         pa_log("Connection failed: %s", pa_cstrerror(errno)); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |     pa_assert(!c->io); | ||||||
|  |     c->io = io; | ||||||
|  | 
 | ||||||
|  |     c->ioline = pa_ioline_new(io); | ||||||
|  |     pa_ioline_set_callback(c->ioline, line_callback, c); | ||||||
|  | 
 | ||||||
|  |     /* Get the local IP address for use externally */ | ||||||
|  |     if (0 == getsockname(pa_iochannel_get_recv_fd(io), &sa.sa, &sa_len)) { | ||||||
|  |         char buf[INET6_ADDRSTRLEN]; | ||||||
|  |         const char *res = NULL; | ||||||
|  | 
 | ||||||
|  |         if (AF_INET == sa.sa.sa_family) { | ||||||
|  |             if ((res = inet_ntop(sa.sa.sa_family, &sa.in.sin_addr, buf, sizeof(buf)))) { | ||||||
|  |                 c->localip = pa_xstrdup(res); | ||||||
|  |             } | ||||||
|  |         } else if (AF_INET6 == sa.sa.sa_family) { | ||||||
|  |             if ((res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)))) { | ||||||
|  |                 c->localip = pa_sprintf_malloc("[%s]", res); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     pa_log_debug("Established RTSP connection from local ip %s", c->localip); | ||||||
|  | 
 | ||||||
|  |     if (c->callback) | ||||||
|  |         c->callback(c, c->state, NULL, c->userdata); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_connect(pa_rtsp_client *c) { | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(!c->sc); | ||||||
|  | 
 | ||||||
|  |     pa_xfree(c->session); | ||||||
|  |     c->session = NULL; | ||||||
|  | 
 | ||||||
|  |     if (!(c->sc = pa_socket_client_new_string(c->mainloop, c->hostname, c->port))) { | ||||||
|  |         pa_log("failed to connect to server '%s:%d'", c->hostname, c->port); | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_socket_client_set_callback(c->sc, on_connection, c); | ||||||
|  |     c->waiting = 1; | ||||||
|  |     c->state = STATE_CONNECT; | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata) { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     c->callback = callback; | ||||||
|  |     c->userdata = userdata; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pa_rtsp_disconnect(pa_rtsp_client *c) { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     if (c->io) | ||||||
|  |         pa_iochannel_free(c->io); | ||||||
|  |     c->io = NULL; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const char* pa_rtsp_localip(pa_rtsp_client* c) { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     return c->localip; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | uint32_t pa_rtsp_serverport(pa_rtsp_client* c) { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     return c->rtp_port; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pa_rtsp_set_url(pa_rtsp_client* c, const char* url) { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     c->url = pa_xstrdup(url); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value) | ||||||
|  | { | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(key); | ||||||
|  |     pa_assert(value); | ||||||
|  | 
 | ||||||
|  |     pa_headerlist_puts(c->headers, key, value); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key) | ||||||
|  | { | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(key); | ||||||
|  | 
 | ||||||
|  |     pa_headerlist_remove(c->headers, key); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int rtsp_exec(pa_rtsp_client* c, const char* cmd, | ||||||
|  |                         const char* content_type, const char* content, | ||||||
|  |                         int expect_response, | ||||||
|  |                         pa_headerlist* headers) { | ||||||
|  |     pa_strbuf* buf; | ||||||
|  |     char* hdrs; | ||||||
|  |     ssize_t l; | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  |     pa_assert(c->url); | ||||||
|  | 
 | ||||||
|  |     if (!cmd) | ||||||
|  |         return -1; | ||||||
|  | 
 | ||||||
|  |     pa_log_debug("Sending command: %s", cmd); | ||||||
|  | 
 | ||||||
|  |     buf = pa_strbuf_new(); | ||||||
|  |     pa_strbuf_printf(buf, "%s %s RTSP/1.0\r\nCSeq: %d\r\n", cmd, c->url, ++c->cseq); | ||||||
|  |     if (c->session) | ||||||
|  |         pa_strbuf_printf(buf, "Session: %s\r\n", c->session); | ||||||
|  | 
 | ||||||
|  |     /* Add the headers */ | ||||||
|  |     if (headers) { | ||||||
|  |         hdrs = pa_headerlist_to_string(headers); | ||||||
|  |         pa_strbuf_puts(buf, hdrs); | ||||||
|  |         pa_xfree(hdrs); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (content_type && content) { | ||||||
|  |         pa_strbuf_printf(buf, "Content-Type: %s\r\nContent-Length: %d\r\n", | ||||||
|  |           content_type, (int)strlen(content)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_strbuf_printf(buf, "User-Agent: %s\r\n", c->useragent); | ||||||
|  | 
 | ||||||
|  |     if (c->headers) { | ||||||
|  |         hdrs = pa_headerlist_to_string(c->headers); | ||||||
|  |         pa_strbuf_puts(buf, hdrs); | ||||||
|  |         pa_xfree(hdrs); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     pa_strbuf_puts(buf, "\r\n"); | ||||||
|  | 
 | ||||||
|  |     if (content_type && content) { | ||||||
|  |         pa_strbuf_puts(buf, content); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* Our packet is created... now we can send it :) */ | ||||||
|  |     hdrs = pa_strbuf_tostring_free(buf); | ||||||
|  |     /*pa_log_debug("Submitting request:");
 | ||||||
|  |     pa_log_debug(hdrs);*/ | ||||||
|  |     l = pa_iochannel_write(c->io, hdrs, strlen(hdrs)); | ||||||
|  |     pa_xfree(hdrs); | ||||||
|  | 
 | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_announce(pa_rtsp_client *c, const char* sdp) { | ||||||
|  |     pa_assert(c); | ||||||
|  |     if (!sdp) | ||||||
|  |         return -1; | ||||||
|  | 
 | ||||||
|  |     c->state = STATE_ANNOUNCE; | ||||||
|  |     return rtsp_exec(c, "ANNOUNCE", "application/sdp", sdp, 1, NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_setup(pa_rtsp_client* c) { | ||||||
|  |     pa_headerlist* headers; | ||||||
|  |     int rv; | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     headers = pa_headerlist_new(); | ||||||
|  |     pa_headerlist_puts(headers, "Transport", "RTP/AVP/TCP;unicast;interleaved=0-1;mode=record"); | ||||||
|  | 
 | ||||||
|  |     c->state = STATE_SETUP; | ||||||
|  |     rv = rtsp_exec(c, "SETUP", NULL, NULL, 1, headers); | ||||||
|  |     pa_headerlist_free(headers); | ||||||
|  |     return rv; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_record(pa_rtsp_client* c, uint16_t* seq, uint32_t* rtptime) { | ||||||
|  |     pa_headerlist* headers; | ||||||
|  |     int rv; | ||||||
|  |     char *info; | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  |     if (!c->session) { | ||||||
|  |         /* No seesion in progres */ | ||||||
|  |         return -1; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /* Todo: Generate these values randomly as per spec */ | ||||||
|  |     *seq = *rtptime = 0; | ||||||
|  | 
 | ||||||
|  |     headers = pa_headerlist_new(); | ||||||
|  |     pa_headerlist_puts(headers, "Range", "npt=0-"); | ||||||
|  |     info = pa_sprintf_malloc("seq=%u;rtptime=%u", *seq, *rtptime); | ||||||
|  |     pa_headerlist_puts(headers, "RTP-Info", info); | ||||||
|  |     pa_xfree(info); | ||||||
|  | 
 | ||||||
|  |     c->state = STATE_RECORD; | ||||||
|  |     rv = rtsp_exec(c, "RECORD", NULL, NULL, 1, headers); | ||||||
|  |     pa_headerlist_free(headers); | ||||||
|  |     return rv; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_teardown(pa_rtsp_client *c) { | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     c->state = STATE_TEARDOWN; | ||||||
|  |     return rtsp_exec(c, "TEARDOWN", NULL, NULL, 0, NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_setparameter(pa_rtsp_client *c, const char* param) { | ||||||
|  |     pa_assert(c); | ||||||
|  |     if (!param) | ||||||
|  |         return -1; | ||||||
|  | 
 | ||||||
|  |     c->state = STATE_SET_PARAMETER; | ||||||
|  |     return rtsp_exec(c, "SET_PARAMETER", "text/parameters", param, 1, NULL); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_flush(pa_rtsp_client *c, uint16_t seq, uint32_t rtptime) { | ||||||
|  |     pa_headerlist* headers; | ||||||
|  |     int rv; | ||||||
|  |     char *info; | ||||||
|  | 
 | ||||||
|  |     pa_assert(c); | ||||||
|  | 
 | ||||||
|  |     headers = pa_headerlist_new(); | ||||||
|  |     info = pa_sprintf_malloc("seq=%u;rtptime=%u", seq, rtptime); | ||||||
|  |     pa_headerlist_puts(headers, "RTP-Info", info); | ||||||
|  |     pa_xfree(info); | ||||||
|  | 
 | ||||||
|  |     c->state = STATE_FLUSH; | ||||||
|  |     rv = rtsp_exec(c, "FLUSH", NULL, NULL, 1, headers); | ||||||
|  |     pa_headerlist_free(headers); | ||||||
|  |     return rv; | ||||||
|  | } | ||||||
							
								
								
									
										73
									
								
								src/modules/rtp/rtsp_client.h
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/modules/rtp/rtsp_client.h
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,73 @@ | ||||||
|  | #ifndef foortspclienthfoo | ||||||
|  | #define foortspclienthfoo | ||||||
|  | 
 | ||||||
|  | /***
 | ||||||
|  |   This file is part of PulseAudio. | ||||||
|  | 
 | ||||||
|  |   Copyright 2008 Colin Guthrie | ||||||
|  | 
 | ||||||
|  |   PulseAudio is free software; you can redistribute it and/or modify | ||||||
|  |   it under the terms of the GNU Lesser General Public License as published | ||||||
|  |   by the Free Software Foundation; either version 2 of the License, | ||||||
|  |   or (at your option) any later version. | ||||||
|  | 
 | ||||||
|  |   PulseAudio is distributed in the hope that it will be useful, but | ||||||
|  |   WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  |   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||||||
|  |   General Public License for more details. | ||||||
|  | 
 | ||||||
|  |   You should have received a copy of the GNU Lesser General Public License | ||||||
|  |   along with PulseAudio; if not, write to the Free Software | ||||||
|  |   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 | ||||||
|  |   USA. | ||||||
|  | ***/ | ||||||
|  | 
 | ||||||
|  | #include <inttypes.h> | ||||||
|  | #include <sys/socket.h> | ||||||
|  | #include <sys/types.h> | ||||||
|  | #include <netdb.h> | ||||||
|  | 
 | ||||||
|  | #include <pulsecore/memblockq.h> | ||||||
|  | #include <pulsecore/memchunk.h> | ||||||
|  | #include <pulsecore/socket-client.h> | ||||||
|  | #include <pulse/mainloop-api.h> | ||||||
|  | 
 | ||||||
|  | #include "headerlist.h" | ||||||
|  | 
 | ||||||
|  | typedef struct pa_rtsp_client pa_rtsp_client; | ||||||
|  | typedef enum { | ||||||
|  |   STATE_CONNECT, | ||||||
|  |   STATE_ANNOUNCE, | ||||||
|  |   STATE_SETUP, | ||||||
|  |   STATE_RECORD, | ||||||
|  |   STATE_FLUSH, | ||||||
|  |   STATE_TEARDOWN, | ||||||
|  |   STATE_SET_PARAMETER, | ||||||
|  |   STATE_DISCONNECTED | ||||||
|  | } pa_rtsp_state; | ||||||
|  | typedef void (*pa_rtsp_cb_t)(pa_rtsp_client *c, pa_rtsp_state state, pa_headerlist* hl, void *userdata); | ||||||
|  | 
 | ||||||
|  | pa_rtsp_client* pa_rtsp_client_new(pa_mainloop_api *mainloop, const char* hostname, uint16_t port, const char* useragent); | ||||||
|  | void pa_rtsp_client_free(pa_rtsp_client* c); | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_connect(pa_rtsp_client* c); | ||||||
|  | void pa_rtsp_set_callback(pa_rtsp_client *c, pa_rtsp_cb_t callback, void *userdata); | ||||||
|  | 
 | ||||||
|  | void pa_rtsp_disconnect(pa_rtsp_client* c); | ||||||
|  | 
 | ||||||
|  | const char* pa_rtsp_localip(pa_rtsp_client* c); | ||||||
|  | uint32_t pa_rtsp_serverport(pa_rtsp_client* c); | ||||||
|  | void pa_rtsp_set_url(pa_rtsp_client* c, const char* url); | ||||||
|  | void pa_rtsp_add_header(pa_rtsp_client *c, const char* key, const char* value); | ||||||
|  | void pa_rtsp_remove_header(pa_rtsp_client *c, const char* key); | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_announce(pa_rtsp_client* c, const char* sdp); | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_setup(pa_rtsp_client* c); | ||||||
|  | int pa_rtsp_record(pa_rtsp_client* c, uint16_t* seq, uint32_t* rtptime); | ||||||
|  | int pa_rtsp_teardown(pa_rtsp_client* c); | ||||||
|  | 
 | ||||||
|  | int pa_rtsp_setparameter(pa_rtsp_client* c, const char* param); | ||||||
|  | int pa_rtsp_flush(pa_rtsp_client* c, uint16_t seq, uint32_t rtptime); | ||||||
|  | 
 | ||||||
|  | #endif | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Lennart Poettering
						Lennart Poettering