diff options
Diffstat (limited to 'src')
391 files changed, 109802 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 00000000..a3bb4d0e --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,1536 @@ +# $Id$ +# +# This file is part of PulseAudio. +# +# Copyright 2004-2006 Lennart Poettering +# Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB +# Copyright 2006 Diego Pettenò +# +# 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. + + +################################### +# Extra directories # +################################### + +pulseincludedir=$(includedir)/pulse +pulsecoreincludedir=$(includedir)/pulsecore +pulseconfdir=$(sysconfdir)/pulse +pulselibexecdir=$(libexecdir)/pulse +xdgautostartdir=$(sysconfdir)/xdg/autostart + +################################### +# Defines # +################################### + +PA_BINARY=$(bindir)/pulseaudio$(EXEEXT) +if OS_IS_WIN32 +PA_DEFAULT_CONFIG_DIR=%PULSE_ROOT% +else +PA_DEFAULT_CONFIG_DIR=$(pulseconfdir) +endif + +################################### +# Compiler/linker flags # +################################### + +AM_CFLAGS = -I$(top_srcdir)/src -I$(top_builddir)/src/modules -I$(top_builddir)/src/modules/rtp -I$(top_builddir)/src/modules/gconf +AM_CFLAGS += $(PTHREAD_CFLAGS) -D_POSIX_PTHREAD_SEMANTICS +AM_CFLAGS += $(LTDLINCL) +AM_CFLAGS += $(LIBSAMPLERATE_CFLAGS) $(LIBSNDFILE_CFLAGS) +AM_CFLAGS += -DPA_DLSEARCHPATH=\"$(modlibexecdir)\" +AM_CFLAGS += -DPA_DEFAULT_CONFIG_DIR=\"$(PA_DEFAULT_CONFIG_DIR)\" +AM_CFLAGS += -DPA_BINARY=\"$(PA_BINARY)\" +AM_CFLAGS += -DPA_SYSTEM_RUNTIME_PATH=\"$(PA_SYSTEM_RUNTIME_PATH)\" +AM_CFLAGS += -DAO_REQUIRE_CAS + +# This cool debug trap works on i386/gcc only +AM_CFLAGS += '-DDEBUG_TRAP=__asm__("int $$3")' + +AM_LIBADD = $(PTHREAD_LIBS) +AM_LDADD = $(PTHREAD_LIBS) + +# Only required on some platforms but defined for all to avoid errors +AM_LDFLAGS = -Wl,-no-undefined -ffunction-sections -fdata-sections -Wl,--gc-sections + +if STATIC_BINS +BINLDFLAGS = -static +endif + +if OS_IS_WIN32 +AM_LDFLAGS+=-Wl,--export-all-symbols +WINSOCK_LIBS=-lwsock32 -lws2_32 -lwininet +endif + +if OS_IS_WIN32 +PA_THREAD_OBJS = \ + pulsecore/mutex-win32.c pulsecore/mutex.h \ + pulsecore/thread-win32.c pulsecore/thread.h \ + pulsecore/semaphore-win32.c pulsecore/semaphore.h +else +PA_THREAD_OBJS = \ + pulsecore/mutex-posix.c pulsecore/mutex.h \ + pulsecore/thread-posix.c pulsecore/thread.h \ + pulsecore/semaphore-posix.c pulsecore/semaphore.h +endif + +################################### +# Extra files # +################################### + +EXTRA_DIST = \ + pulse/client.conf.in \ + pulse/version.h.in \ + daemon/daemon.conf.in \ + daemon/default.pa.in \ + daemon/default.pa.win32 \ + depmod.py \ + daemon/esdcompat.in \ + utils/padsp \ + modules/module-defs.h.m4 \ + daemon/pulseaudio-module-xsmp.desktop \ + map-file \ + daemon/PulseAudio.policy + +pulseconf_DATA = \ + default.pa \ + daemon.conf \ + client.conf + +if HAVE_X11 +xdgautostart_DATA = \ + daemon/pulseaudio-module-xsmp.desktop +endif + +BUILT_SOURCES = \ + pulse/version.h + +################################### +# Main daemon # +################################### + +bin_PROGRAMS = pulseaudio + +pulseaudio_SOURCES = \ + daemon/caps.h daemon/caps.c \ + daemon/cmdline.c daemon/cmdline.h \ + daemon/cpulimit.c daemon/cpulimit.h \ + daemon/daemon-conf.c daemon/daemon-conf.h \ + daemon/dumpmodules.c daemon/dumpmodules.h \ + daemon/ltdl-bind-now.c daemon/ltdl-bind-now.h \ + daemon/main.c \ + pulsecore/gccmacro.h + +pulseaudio_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) $(LIBSAMPLERATE_CFLAGS) $(LIBSNDFILE_CFLAGS) $(CAP_CFLAGS) $(LIBOIL_CFLAGS) $(DBUS_CFLAGS) +pulseaudio_CPPFLAGS = $(AM_CPPFLAGS) +pulseaudio_LDADD = $(AM_LDADD) libpulsecore.la $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(CAP_LIBS) $(LIBOIL_LIBS) $(DBUS_LIBS) +# This is needed because automake doesn't properly expand the foreach below +pulseaudio_DEPENDENCIES = libpulsecore.la $(PREOPEN_LIBS) + +if PREOPEN_MODS +PREOPEN_LIBS = $(PREOPEN_MODS) +else +PREOPEN_LIBS = $(modlibexec_LTLIBRARIES) +endif + +if FORCE_PREOPEN +pulseaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -dlpreopen force $(foreach f,$(PREOPEN_LIBS),-dlpreopen $(f)) +else +pulseaudio_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) -dlopen force $(foreach f,$(PREOPEN_LIBS),-dlopen $(f)) +endif + +if HAVE_POLKIT + +policy_DATA = daemon/PulseAudio.policy + +pulseaudio_SOURCES += daemon/polkit.c daemon/polkit.h +pulseaudio_CFLAGS += $(POLKIT_CFLAGS) +pulseaudio_LDADD += $(POLKIT_LIBS) + + +endif + +################################### +# Utility programs # +################################### + +bin_PROGRAMS += \ + pacat \ + pactl \ + paplay \ + pasuspender + +if HAVE_AF_UNIX +bin_PROGRAMS += pacmd +endif + +if HAVE_X11 +bin_PROGRAMS += pax11publish +endif + +if HAVE_AVAHI +bin_PROGRAMS += pabrowse +endif + +bin_SCRIPTS = esdcompat + +pacat_SOURCES = utils/pacat.c +pacat_LDADD = $(AM_LDADD) libpulse.la +pacat_CFLAGS = $(AM_CFLAGS) +pacat_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +paplay_SOURCES = utils/paplay.c +paplay_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS) +paplay_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) +paplay_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +pactl_SOURCES = utils/pactl.c pulsecore/core-util.c pulsecore/core-util.h pulsecore/core-error.c pulsecore/core-error.h pulsecore/log.c pulsecore/log.h pulsecore/once.c pulsecore/once.h $(PA_THREAD_OBJS) +pactl_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS) +pactl_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) +pactl_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +pasuspender_SOURCES = utils/pasuspender.c +pasuspender_LDADD = $(AM_LDADD) libpulse.la $(LIBSNDFILE_LIBS) +pasuspender_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS) +pasuspender_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +pacmd_SOURCES = utils/pacmd.c pulsecore/pid.c pulsecore/pid.h pulsecore/core-util.c pulsecore/core-util.h pulsecore/core-error.c pulsecore/core-error.h pulsecore/log.c pulsecore/log.h pulsecore/once.c pulsecore/once.h $(PA_THREAD_OBJS) +pacmd_CFLAGS = $(AM_CFLAGS) +pacmd_LDADD = $(AM_LDADD) libpulse.la +pacmd_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +pax11publish_SOURCES = utils/pax11publish.c pulsecore/x11prop.c pulsecore/x11prop.h pulse/client-conf.c pulse/client-conf.h pulsecore/authkey.h pulsecore/authkey.c pulsecore/random.h pulsecore/random.c pulsecore/conf-parser.c pulsecore/conf-parser.h pulsecore/core-util.c pulsecore/core-util.h pulsecore/core-error.c pulsecore/core-error.h pulsecore/log.c pulsecore/log.h pulsecore/once.c pulsecore/once.h $(PA_THREAD_OBJS) +pax11publish_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS) +pax11publish_LDADD = $(AM_LDADD) libpulse.la $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) +pax11publish_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +pabrowse_SOURCES = utils/pabrowse.c +pabrowse_LDADD = $(AM_LDADD) libpulse.la libpulse-browse.la +pabrowse_CFLAGS = $(AM_CFLAGS) +pabrowse_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +################################### +# Test programs # +################################### + +check_PROGRAMS = \ + mainloop-test \ + mcalign-test \ + pacat-simple \ + parec-simple \ + strlist-test \ + voltest \ + memblockq-test \ + sync-playback \ + interpol-test \ + channelmap-test \ + thread-mainloop-test \ + utf8-test \ + get-binary-name-test \ + ipacl-test \ + hook-list-test \ + memblock-test \ + thread-test \ + flist-test \ + asyncq-test \ + asyncmsgq-test \ + queue-test \ + rtpoll-test \ + sig2str-test \ + resampler-test \ + smoother-test \ + mix-test \ + remix-test \ + envelope-test \ + proplist-test + +if HAVE_SIGXCPU +check_PROGRAMS += \ + cpulimit-test \ + cpulimit-test2 +endif + +if HAVE_GLIB20 +check_PROGRAMS += \ + mainloop-test-glib +endif + +mainloop_test_SOURCES = tests/mainloop-test.c +mainloop_test_CFLAGS = $(AM_CFLAGS) +mainloop_test_LDADD = $(AM_LDADD) libpulse.la +mainloop_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +thread_mainloop_test_SOURCES = tests/thread-mainloop-test.c +thread_mainloop_test_CFLAGS = $(AM_CFLAGS) +thread_mainloop_test_LDADD = $(AM_LDADD) libpulse.la +thread_mainloop_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +utf8_test_SOURCES = tests/utf8-test.c +utf8_test_CFLAGS = $(AM_CFLAGS) +utf8_test_LDADD = $(AM_LDADD) libpulsecore.la +utf8_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +get_binary_name_test_SOURCES = tests/get-binary-name-test.c +get_binary_name_test_CFLAGS = $(AM_CFLAGS) +get_binary_name_test_LDADD = $(AM_LDADD) libpulse.la +get_binary_name_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +ipacl_test_SOURCES = tests/ipacl-test.c \ + pulsecore/ipacl.c pulsecore/ipacl.h \ + pulsecore/inet_pton.c pulsecore/inet_pton.h +ipacl_test_CFLAGS = $(AM_CFLAGS) +ipacl_test_LDADD = $(AM_LDADD) libpulsecore.la +ipacl_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +hook_list_test_SOURCES = tests/hook-list-test.c +hook_list_test_CFLAGS = $(AM_CFLAGS) +hook_list_test_LDADD = $(AM_LDADD) libpulsecore.la +hook_list_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +memblock_test_SOURCES = tests/memblock-test.c +memblock_test_CFLAGS = $(AM_CFLAGS) +memblock_test_LDADD = $(AM_LDADD) libpulsecore.la +memblock_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +thread_test_SOURCES = tests/thread-test.c +thread_test_CFLAGS = $(AM_CFLAGS) +thread_test_LDADD = $(AM_LDADD) libpulsecore.la +thread_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +flist_test_SOURCES = tests/flist-test.c +flist_test_CFLAGS = $(AM_CFLAGS) +flist_test_LDADD = $(AM_LDADD) libpulsecore.la +flist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +asyncq_test_SOURCES = tests/asyncq-test.c +asyncq_test_CFLAGS = $(AM_CFLAGS) +asyncq_test_LDADD = $(AM_LDADD) libpulsecore.la +asyncq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +asyncmsgq_test_SOURCES = tests/asyncmsgq-test.c +asyncmsgq_test_CFLAGS = $(AM_CFLAGS) +asyncmsgq_test_LDADD = $(AM_LDADD) libpulsecore.la +asyncmsgq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +queue_test_SOURCES = tests/queue-test.c +queue_test_CFLAGS = $(AM_CFLAGS) +queue_test_LDADD = $(AM_LDADD) libpulsecore.la +queue_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +rtpoll_test_SOURCES = tests/rtpoll-test.c +rtpoll_test_CFLAGS = $(AM_CFLAGS) +rtpoll_test_LDADD = $(AM_LDADD) libpulsecore.la +rtpoll_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +mcalign_test_SOURCES = tests/mcalign-test.c +mcalign_test_CFLAGS = $(AM_CFLAGS) +mcalign_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore.la +mcalign_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +pacat_simple_SOURCES = tests/pacat-simple.c +pacat_simple_LDADD = $(AM_LDADD) libpulse.la libpulse-simple.la +pacat_simple_CFLAGS = $(AM_CFLAGS) +pacat_simple_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +parec_simple_SOURCES = tests/parec-simple.c +parec_simple_LDADD = $(AM_LDADD) libpulse.la libpulse-simple.la +parec_simple_CFLAGS = $(AM_CFLAGS) +parec_simple_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +strlist_test_SOURCES = tests/strlist-test.c +strlist_test_CFLAGS = $(AM_CFLAGS) +strlist_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore.la libstrlist.la +strlist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +voltest_SOURCES = tests/voltest.c +voltest_CFLAGS = $(AM_CFLAGS) +voltest_LDADD = $(AM_LDADD) libpulse.la +voltest_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +channelmap_test_SOURCES = tests/channelmap-test.c +channelmap_test_CFLAGS = $(AM_CFLAGS) +channelmap_test_LDADD = $(AM_LDADD) libpulse.la +channelmap_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +cpulimit_test_SOURCES = tests/cpulimit-test.c daemon/cpulimit.c daemon/cpulimit.h +cpulimit_test_CFLAGS = $(AM_CFLAGS) +cpulimit_test_LDADD = $(AM_LDADD) libpulsecore.la +cpulimit_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +cpulimit_test2_SOURCES = tests/cpulimit-test.c daemon/cpulimit.c daemon/cpulimit.h +cpulimit_test2_CFLAGS = $(AM_CFLAGS) -DTEST2 +cpulimit_test2_LDADD = $(AM_LDADD) libpulsecore.la +cpulimit_test2_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +mainloop_test_glib_SOURCES = $(mainloop_test_SOURCES) +mainloop_test_glib_CFLAGS = $(mainloop_test_CFLAGS) $(GLIB20_CFLAGS) -DGLIB_MAIN_LOOP +mainloop_test_glib_LDADD = $(mainloop_test_LDADD) $(GLIB20_LIBS) libpulse-mainloop-glib.la +mainloop_test_glib_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +memblockq_test_SOURCES = tests/memblockq-test.c +memblockq_test_CFLAGS = $(AM_CFLAGS) +memblockq_test_LDADD = $(AM_LDADD) $(WINSOCK_LIBS) libpulsecore.la +memblockq_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +sync_playback_SOURCES = tests/sync-playback.c +sync_playback_LDADD = $(AM_LDADD) libpulse.la +sync_playback_CFLAGS = $(AM_CFLAGS) +sync_playback_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +interpol_test_SOURCES = tests/interpol-test.c +interpol_test_LDADD = $(AM_LDADD) libpulse.la libpulsecore.la +interpol_test_CFLAGS = $(AM_CFLAGS) +interpol_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +sig2str_test_SOURCES = tests/sig2str-test.c +sig2str_test_LDADD = $(AM_LDADD) libpulsecore.la +sig2str_test_CFLAGS = $(AM_CFLAGS) +sig2str_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +resampler_test_SOURCES = tests/resampler-test.c +resampler_test_LDADD = $(AM_LDADD) libpulsecore.la +resampler_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +resampler_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) + +mix_test_SOURCES = tests/mix-test.c +mix_test_LDADD = $(AM_LDADD) libpulsecore.la +mix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +mix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) + +remix_test_SOURCES = tests/remix-test.c +remix_test_LDADD = $(AM_LDADD) libpulsecore.la +remix_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +remix_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) + +smoother_test_SOURCES = tests/smoother-test.c +smoother_test_LDADD = $(AM_LDADD) libpulsecore.la +smoother_test_CFLAGS = $(AM_CFLAGS) +smoother_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +envelope_test_SOURCES = tests/envelope-test.c +envelope_test_LDADD = $(AM_LDADD) libpulsecore.la +envelope_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +envelope_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) + +proplist_test_SOURCES = tests/proplist-test.c +proplist_test_LDADD = $(AM_LDADD) libpulse.la +proplist_test_CFLAGS = $(AM_CFLAGS) $(LIBOIL_CFLAGS) +proplist_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBOIL_LIBS) + +################################### +# Client library # +################################### + +pulseinclude_HEADERS = \ + pulse/cdecl.h \ + pulse/channelmap.h \ + pulse/context.h \ + pulse/def.h \ + pulse/error.h \ + pulse/introspect.h \ + pulse/mainloop.h \ + pulse/mainloop-api.h \ + pulse/mainloop-signal.h \ + pulse/operation.h \ + pulse/pulseaudio.h \ + pulse/sample.h \ + pulse/scache.h \ + pulse/simple.h \ + pulse/stream.h \ + pulse/subscribe.h \ + pulse/thread-mainloop.h \ + pulse/timeval.h \ + pulse/utf8.h \ + pulse/util.h \ + pulse/version.h \ + pulse/volume.h \ + pulse/xmalloc.h \ + pulse/proplist.h + +if HAVE_AVAHI +pulseinclude_HEADERS += \ + pulse/browser.h +endif + +if HAVE_GLIB20 +pulseinclude_HEADERS += \ + pulse/glib-mainloop.h +endif + +lib_LTLIBRARIES = \ + libpulse.la \ + libpulse-simple.la + +if HAVE_AVAHI +lib_LTLIBRARIES += \ + libpulse-browse.la +endif + +if HAVE_GLIB20 +lib_LTLIBRARIES += \ + libpulse-mainloop-glib.la +endif + +# Public interface +libpulse_la_SOURCES = \ + pulse/cdecl.h \ + pulse/channelmap.c pulse/channelmap.h \ + pulse/client-conf.c pulse/client-conf.h \ + pulse/context.c pulse/context.h \ + pulse/def.h \ + pulse/error.c pulse/error.h \ + pulse/internal.h \ + pulse/introspect.c pulse/introspect.h \ + pulse/mainloop.c pulse/mainloop.h \ + pulse/mainloop-api.c pulse/mainloop-api.h \ + pulse/mainloop-signal.c pulse/mainloop-signal.h \ + pulse/operation.c pulse/operation.h \ + pulse/pulseaudio.h \ + pulse/sample.c pulse/sample.h \ + pulse/scache.c pulse/scache.h \ + pulse/stream.c pulse/stream.h \ + pulse/subscribe.c pulse/subscribe.h \ + pulse/thread-mainloop.c pulse/thread-mainloop.h \ + pulse/timeval.c pulse/timeval.h \ + pulse/utf8.c pulse/utf8.h \ + pulse/util.c pulse/util.h \ + pulse/volume.c pulse/volume.h \ + pulse/xmalloc.c pulse/xmalloc.h \ + pulse/proplist.c pulse/proplist.h + +# Internal stuff that is shared with libpulsecore +libpulse_la_SOURCES += \ + pulsecore/authkey.c pulsecore/authkey.h \ + pulsecore/conf-parser.c pulsecore/conf-parser.h \ + pulsecore/core-util.c pulsecore/core-util.h \ + pulsecore/dynarray.c pulsecore/dynarray.h \ + pulsecore/gccmacro.h \ + pulsecore/hashmap.c pulsecore/hashmap.h \ + pulsecore/idxset.c pulsecore/idxset.h \ + pulsecore/inet_ntop.c pulsecore/inet_ntop.h \ + pulsecore/iochannel.c pulsecore/iochannel.h \ + pulsecore/llist.h \ + pulsecore/log.c pulsecore/log.h \ + pulsecore/mcalign.c pulsecore/mcalign.h \ + pulsecore/memblock.c pulsecore/memblock.h \ + pulsecore/memblockq.c pulsecore/memblockq.h \ + pulsecore/memchunk.c pulsecore/memchunk.h \ + pulsecore/native-common.h \ + pulsecore/packet.c pulsecore/packet.h \ + pulsecore/parseaddr.c pulsecore/parseaddr.h \ + pulsecore/pdispatch.c pulsecore/pdispatch.h \ + pulsecore/pipe.c pulsecore/pipe.h \ + pulsecore/poll.c pulsecore/poll.h \ + pulsecore/pstream.c pulsecore/pstream.h \ + pulsecore/pstream-util.c pulsecore/pstream-util.h \ + pulsecore/queue.c pulsecore/queue.h \ + pulsecore/random.c pulsecore/random.h \ + pulsecore/socket-client.c pulsecore/socket-client.h \ + pulsecore/socket-util.c pulsecore/socket-util.h \ + pulsecore/strbuf.c pulsecore/strbuf.h \ + pulsecore/strlist.c pulsecore/strlist.h \ + pulsecore/tagstruct.c pulsecore/tagstruct.h \ + pulsecore/core-error.c pulsecore/core-error.h \ + pulsecore/winsock.h pulsecore/creds.h \ + pulsecore/shm.c pulsecore/shm.h \ + pulsecore/flist.c pulsecore/flist.h \ + pulsecore/object.c pulsecore/object.h \ + pulsecore/msgobject.c pulsecore/msgobject.h \ + pulsecore/once.c pulsecore/once.h \ + $(PA_THREAD_OBJS) + +if OS_IS_WIN32 +libpulse_la_SOURCES += \ + pulsecore/dllmain.c +endif + +if HAVE_X11 +libpulse_la_SOURCES += \ + pulse/client-conf-x11.c pulse/client-conf-x11.h \ + pulsecore/x11prop.c pulsecore/x11prop.h +endif + +libpulse_la_CFLAGS = $(AM_CFLAGS) +libpulse_la_LDFLAGS = -version-info $(LIBPULSE_VERSION_INFO) -Wl,-version-script=$(srcdir)/map-file +libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LIBICONV) + +if HAVE_X11 +libpulse_la_CFLAGS += $(X_CFLAGS) +libpulse_la_LDFLAGS += $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) +endif + +if HAVE_LIBASYNCNS +libpulse_la_CFLAGS += $(LIBASYNCNS_CFLAGS) +libpulse_la_LIBADD += $(LIBASYNCNS_LIBS) +endif + +libpulse_simple_la_SOURCES = pulse/simple.c pulse/simple.h +libpulse_simple_la_CFLAGS = $(AM_CFLAGS) +libpulse_simple_la_LIBADD = $(AM_LIBADD) libpulse.la +libpulse_simple_la_LDFLAGS = -version-info $(LIBPULSE_SIMPLE_VERSION_INFO) -Wl,-version-script=$(srcdir)/map-file + +libpulse_browse_la_SOURCES = pulse/browser.c pulse/browser.h pulsecore/avahi-wrap.c pulsecore/avahi-wrap.h pulsecore/core-util.c pulsecore/core-util.h pulsecore/core-error.c pulsecore/core-error.h pulsecore/log.c pulsecore/log.h pulsecore/once.c pulsecore/once.h $(PA_THREAD_OBJS) +libpulse_browse_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS) +libpulse_browse_la_LIBADD = $(AM_LIBADD) libpulse.la $(AVAHI_LIBS) +libpulse_browse_la_LDFLAGS = -version-info $(LIBPULSE_BROWSE_VERSION_INFO) -Wl,-version-script=$(srcdir)/map-file + +libpulse_mainloop_glib_la_SOURCES = pulse/glib-mainloop.h pulse/glib-mainloop.c +libpulse_mainloop_glib_la_CFLAGS = $(AM_CFLAGS) $(GLIB20_CFLAGS) +libpulse_mainloop_glib_la_LIBADD = $(AM_LIBADD) libpulse.la $(GLIB20_LIBS) +libpulse_mainloop_glib_la_LDFLAGS = -version-info $(LIBPULSE_MAINLOOP_GLIB_VERSION_INFO) -Wl,-version-script=$(srcdir)/map-file + +################################### +# OSS emulation # +################################### + +if HAVE_OSS + +lib_LTLIBRARIES += libpulsedsp.la + +bin_SCRIPTS += utils/padsp + +endif + +libpulsedsp_la_SOURCES = utils/padsp.c +libpulsedsp_la_CFLAGS = $(AM_CFLAGS) +libpulsedsp_la_LIBADD = $(AM_LIBADD) libpulse.la +libpulsedsp_la_LDFLAGS = -avoid-version + +################################### +# Speex Resampler # +################################### + +noinst_LTLIBRARIES = libspeex-resampler-fixed.la libspeex-resampler-float.la libffmpeg-resampler.la + +libspeex_resampler_fixed_la_CPPFLAGS = $(AM_CPPFLAGS) -DRANDOM_PREFIX=paspfx -DOUTSIDE_SPEEX -DFIXED_POINT +libspeex_resampler_fixed_la_SOURCES = pulsecore/speex/resample.c pulsecore/speex/speex_resampler.h pulsecore/speex/arch.h pulsecore/speex/fixed_generic.h pulsecore/speexwrap.h + +libspeex_resampler_float_la_CPPFLAGS = $(AM_CPPFLAGS) -DRANDOM_PREFIX=paspfl -DOUTSIDE_SPEEX -DFLOATING_POINT +libspeex_resampler_float_la_SOURCES = pulsecore/speex/resample.c pulsecore/speex/speex_resampler.h pulsecore/speex/arch.h + +libffmpeg_resampler_la_CPPFLAGS = $(AM_CPPFLAGS) +libffmpeg_resampler_la_SOURCES = pulsecore/ffmpeg/resample2.c pulsecore/ffmpeg/avcodec.h pulsecore/ffmpeg/dsputil.h + +################################### +# Daemon core library # +################################### + +#pulsecoreinclude_HEADERS = +noinst_HEADERS = \ + pulsecore/autoload.h \ + pulsecore/atomic.h \ + pulsecore/cli-command.h \ + pulsecore/cli-text.h \ + pulsecore/client.h \ + pulsecore/core.h \ + pulsecore/core-def.h \ + pulsecore/core-scache.h \ + pulsecore/core-subscribe.h \ + pulsecore/conf-parser.h \ + pulsecore/core-util.h \ + pulsecore/dynarray.h \ + pulsecore/g711.h \ + pulsecore/hashmap.h \ + pulsecore/idxset.h \ + pulsecore/log.h \ + pulsecore/mcalign.h \ + pulsecore/memblock.h \ + pulsecore/memblockq.h \ + pulsecore/memchunk.h \ + pulsecore/modargs.h \ + pulsecore/modinfo.h \ + pulsecore/module.h \ + pulsecore/namereg.h \ + pulsecore/pid.h \ + pulsecore/play-memchunk.h \ + pulsecore/play-memblockq.h \ + pulsecore/props.h \ + pulsecore/queue.h \ + pulsecore/random.h \ + pulsecore/resampler.h \ + pulsecore/sample-util.h \ + pulsecore/sconv.h \ + pulsecore/sink.h \ + pulsecore/sink-input.h \ + pulsecore/sioman.h \ + pulsecore/sound-file.h \ + pulsecore/sound-file-stream.h \ + pulsecore/source.h \ + pulsecore/source-output.h \ + pulsecore/strbuf.h \ + pulsecore/tokenizer.h \ + pulsecore/creds.h \ + pulsecore/shm.h \ + pulsecore/llist.h \ + pulsecore/refcnt.h \ + pulsecore/mutex.h \ + pulsecore/thread.h \ + pulsecore/semaphore.h \ + pulsecore/once.h + +lib_LTLIBRARIES += libpulsecore.la + +# Some public stuff is used even in the core +libpulsecore_la_SOURCES = \ + pulse/channelmap.c pulse/channelmap.h \ + pulse/error.c pulse/error.h \ + pulse/mainloop.c pulse/mainloop.h \ + pulse/mainloop-api.c pulse/mainloop-api.h \ + pulse/mainloop-signal.c pulse/mainloop-signal.h \ + pulse/sample.c pulse/sample.h \ + pulse/timeval.c pulse/timeval.h \ + pulse/utf8.c pulse/utf8.h \ + pulse/util.c pulse/util.h \ + pulse/volume.c pulse/volume.h \ + pulse/xmalloc.c pulse/xmalloc.h + +# Pure core stuff (some are shared in libpulse though). +libpulsecore_la_SOURCES += \ + pulsecore/autoload.c pulsecore/autoload.h \ + pulsecore/cli-command.c pulsecore/cli-command.h \ + pulsecore/cli-text.c pulsecore/cli-text.h \ + pulsecore/client.c pulsecore/client.h \ + pulsecore/conf-parser.c pulsecore/conf-parser.h \ + pulsecore/core.c pulsecore/core.h \ + pulsecore/core-scache.c pulsecore/core-scache.h \ + pulsecore/core-subscribe.c pulsecore/core-subscribe.h \ + pulsecore/core-util.c pulsecore/core-util.h \ + pulsecore/dynarray.c pulsecore/dynarray.h \ + pulsecore/endianmacros.h \ + pulsecore/g711.c pulsecore/g711.h \ + pulsecore/hashmap.c pulsecore/hashmap.h \ + pulsecore/idxset.c pulsecore/idxset.h \ + pulsecore/log.c pulsecore/log.h \ + pulsecore/mcalign.c pulsecore/mcalign.h \ + pulsecore/memblock.c pulsecore/memblock.h \ + pulsecore/memblockq.c pulsecore/memblockq.h \ + pulsecore/memchunk.c pulsecore/memchunk.h \ + pulsecore/modargs.c pulsecore/modargs.h \ + pulsecore/modinfo.c pulsecore/modinfo.h \ + pulsecore/ltdl-helper.c pulsecore/ltdl-helper.h \ + pulsecore/module.c pulsecore/module.h \ + pulsecore/namereg.c pulsecore/namereg.h \ + pulsecore/pid.c pulsecore/pid.h \ + pulsecore/pipe.c pulsecore/pipe.h \ + pulsecore/play-memchunk.c pulsecore/play-memchunk.h \ + pulsecore/play-memblockq.c pulsecore/play-memblockq.h \ + pulsecore/poll.c pulsecore/poll.h \ + pulsecore/props.c pulsecore/props.h \ + pulsecore/queue.c pulsecore/queue.h \ + pulsecore/random.c pulsecore/random.h \ + pulsecore/resampler.c pulsecore/resampler.h \ + pulsecore/sample-util.c pulsecore/sample-util.h \ + pulsecore/sconv.c pulsecore/sconv.h \ + pulsecore/sconv-s16be.c pulsecore/sconv-s16be.h \ + pulsecore/sconv-s16le.c pulsecore/sconv-s16le.h \ + pulsecore/sink.c pulsecore/sink.h \ + pulsecore/sink-input.c pulsecore/sink-input.h \ + pulsecore/sioman.c pulsecore/sioman.h \ + pulsecore/sound-file.c pulsecore/sound-file.h \ + pulsecore/sound-file-stream.c pulsecore/sound-file-stream.h \ + pulsecore/source.c pulsecore/source.h \ + pulsecore/source-output.c pulsecore/source-output.h \ + pulsecore/strbuf.c pulsecore/strbuf.h \ + pulsecore/tokenizer.c pulsecore/tokenizer.h \ + pulsecore/winsock.h \ + pulsecore/core-error.c pulsecore/core-error.h \ + pulsecore/hook-list.c pulsecore/hook-list.h \ + pulsecore/shm.c pulsecore/shm.h \ + pulsecore/flist.c pulsecore/flist.h \ + pulsecore/asyncmsgq.c pulsecore/asyncmsgq.h \ + pulsecore/asyncq.c pulsecore/asyncq.h \ + pulsecore/thread-mq.c pulsecore/thread-mq.h \ + pulsecore/fdsem.c pulsecore/fdsem.h \ + pulsecore/object.c pulsecore/object.h \ + pulsecore/msgobject.c pulsecore/msgobject.h \ + pulsecore/rtsig.c pulsecore/rtsig.h \ + pulsecore/rtpoll.c pulsecore/rtpoll.h \ + pulsecore/rtclock.c pulsecore/rtclock.h \ + pulsecore/macro.h \ + pulsecore/once.c pulsecore/once.h \ + pulsecore/time-smoother.c pulsecore/time-smoother.h \ + pulsecore/start-child.c pulsecore/start-child.h \ + pulsecore/envelope.c pulsecore/envelope.h \ + $(PA_THREAD_OBJS) + +if OS_IS_WIN32 +libpulsecore_la_SOURCES += \ + pulsecore/dllmain.c +endif + +libpulsecore_la_CPPFLAGS = $(AM_CPPFLAGS) $(LIBOIL_CFLAGS) +libpulsecore_la_LDFLAGS = -version-info $(LIBPULSECORE_VERSION_INFO) +libpulsecore_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) $(LIBSAMPLERATE_LIBS) $(LIBSNDFILE_LIBS) $(WINSOCK_LIBS) $(LIBOIL_LIBS) $(LIBICONV) libspeex-resampler-fixed.la libspeex-resampler-float.la libffmpeg-resampler.la + +################################### +# Plug-in support libraries # +################################### + +#pulsecoreinclude_HEADERS += +noinst_HEADERS += \ + pulsecore/socket-util.h \ + pulsecore/iochannel.h \ + pulsecore/socket-server.h \ + pulsecore/ipacl.h \ + pulsecore/socket-client.h \ + pulsecore/parseaddr.h \ + pulsecore/packet.h \ + pulsecore/pstream.h \ + pulsecore/ioline.h \ + pulsecore/cli.h \ + pulsecore/protocol-cli.h \ + pulsecore/tagstruct.h \ + pulsecore/pstream-util.h \ + pulsecore/pdispatch.h \ + pulsecore/authkey.h \ + pulsecore/authkey-prop.h \ + pulsecore/strlist.h \ + pulsecore/protocol-simple.h \ + pulsecore/esound.h \ + pulsecore/protocol-esound.h \ + pulsecore/native-common.h \ + pulsecore/protocol-native.h \ + pulsecore/protocol-http.h + +### Warning! Due to an obscure bug in libtool/automake it is required +### that the libraries in modlibexec_LTLIBRARIES are specified in-order, +### i.e. libraries near the end of the list depend on libraries near +### the head, and not the other way! + +modlibexec_LTLIBRARIES = \ + libsocket-util.la \ + libiochannel.la \ + libsocket-server.la \ + libipacl.la \ + libparseaddr.la \ + libsocket-client.la \ + libpacket.la \ + libpstream.la \ + libioline.la \ + libcli.la \ + libprotocol-cli.la \ + libtagstruct.la \ + libpstream-util.la \ + libpdispatch.la \ + libauthkey.la \ + libauthkey-prop.la \ + libstrlist.la \ + libprotocol-simple.la \ + libprotocol-http.la \ + libprotocol-native.la \ + libprotocol-esound.la + +# We need to emulate sendmsg/recvmsg to support this on Win32 +if !OS_IS_WIN32 +modlibexec_LTLIBRARIES += \ + librtp.la +endif + +if HAVE_X11 +#pulsecoreinclude_HEADERS += +noinst_HEADERS += \ + pulsecore/x11wrap.h \ + pulsecore/x11prop.h + +modlibexec_LTLIBRARIES += \ + libx11wrap.la \ + libx11prop.la +endif + +if HAVE_AVAHI +#pulsecoreinclude_HEADERS += +noinst_HEADERS += \ + pulsecore/avahi-wrap.h + +modlibexec_LTLIBRARIES += \ + libavahi-wrap.la +endif + +libprotocol_simple_la_SOURCES = pulsecore/protocol-simple.c pulsecore/protocol-simple.h +libprotocol_simple_la_LDFLAGS = -avoid-version +libprotocol_simple_la_LIBADD = $(AM_LIBADD) libpulsecore.la libsocket-server.la libiochannel.la + +libsocket_server_la_SOURCES = \ + pulsecore/inet_ntop.c pulsecore/inet_ntop.h \ + pulsecore/inet_pton.c pulsecore/inet_pton.h \ + pulsecore/socket-server.c pulsecore/socket-server.h +libsocket_server_la_LDFLAGS = -avoid-version +libsocket_server_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la libsocket-util.la $(LIBWRAP_LIBS) $(WINSOCK_LIBS) + +libipacl_la_SOURCES = pulsecore/ipacl.h pulsecore/ipacl.c \ + pulsecore/inet_pton.c pulsecore/inet_pton.h +libipacl_la_LDFLAGS = -avoid-version +libipacl_la_LIBADD = $(AM_LIBADD) libpulsecore.la $(WINSOCK_LIBS) + +libsocket_client_la_SOURCES = pulsecore/socket-client.c pulsecore/socket-client.h +libsocket_client_la_LDFLAGS = -avoid-version +libsocket_client_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la libsocket-util.la libparseaddr.la $(LIBASYNCNS_LIBS) $(WINSOCK_LIBS) +libsocket_client_la_CFLAGS = $(AM_CFLAGS) $(LIBASYNCNS_CFLAGS) + +libparseaddr_la_SOURCES = pulsecore/parseaddr.c pulsecore/parseaddr.h +libparseaddr_la_LDFLAGS = -avoid-version +libparseaddr_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +libpstream_la_SOURCES = pulsecore/pstream.c pulsecore/pstream.h +libpstream_la_LDFLAGS = -avoid-version +libpstream_la_LIBADD = $(AM_LIBADD) libpulsecore.la libpacket.la libiochannel.la $(WINSOCK_LIBS) + +libpstream_util_la_SOURCES = pulsecore/pstream-util.c pulsecore/pstream-util.h +libpstream_util_la_LDFLAGS = -avoid-version +libpstream_util_la_LIBADD = $(AM_LIBADD) libpacket.la libpstream.la libtagstruct.la + +libpdispatch_la_SOURCES = pulsecore/pdispatch.c pulsecore/pdispatch.h +libpdispatch_la_LDFLAGS = -avoid-version +libpdispatch_la_LIBADD = $(AM_LIBADD) libtagstruct.la libpulsecore.la + +libiochannel_la_SOURCES = pulsecore/iochannel.c pulsecore/iochannel.h +libiochannel_la_LDFLAGS = -avoid-version +libiochannel_la_LIBADD = $(AM_LIBADD) libsocket-util.la libpulsecore.la $(WINSOCK_LIBS) + +libpacket_la_SOURCES = pulsecore/packet.c pulsecore/packet.h +libpacket_la_LDFLAGS = -avoid-version +libpacket_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +libioline_la_SOURCES = pulsecore/ioline.c pulsecore/ioline.h +libioline_la_LDFLAGS = -avoid-version +libioline_la_LIBADD = $(AM_LIBADD) libiochannel.la libpulsecore.la + +libcli_la_SOURCES = pulsecore/cli.c pulsecore/cli.h +libcli_la_CPPFLAGS = $(AM_CPPFLAGS) +libcli_la_LDFLAGS = -avoid-version +libcli_la_LIBADD = $(AM_LIBADD) libiochannel.la libioline.la libpulsecore.la + +libstrlist_la_SOURCES = pulsecore/strlist.c pulsecore/strlist.h +libstrlist_la_LDFLAGS = -avoid-version +libstrlist_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +libprotocol_cli_la_SOURCES = pulsecore/protocol-cli.c pulsecore/protocol-cli.h +libprotocol_cli_la_LDFLAGS = -avoid-version +libprotocol_cli_la_LIBADD = $(AM_LIBADD) libsocket-server.la libiochannel.la libcli.la libpulsecore.la + +libprotocol_http_la_SOURCES = pulsecore/protocol-http.c pulsecore/protocol-http.h +libprotocol_http_la_LDFLAGS = -avoid-version +libprotocol_http_la_LIBADD = $(AM_LIBADD) libsocket-server.la libioline.la libpulsecore.la libiochannel.la + +libprotocol_native_la_SOURCES = pulsecore/protocol-native.c pulsecore/protocol-native.h pulsecore/native-common.h +libprotocol_native_la_LDFLAGS = -avoid-version +libprotocol_native_la_LIBADD = $(AM_LIBADD) libsocket-server.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libauthkey.la libauthkey-prop.la libstrlist.la libpulsecore.la libiochannel.la libipacl.la + +libtagstruct_la_SOURCES = pulsecore/tagstruct.c pulsecore/tagstruct.h +libtagstruct_la_LDFLAGS = -avoid-version +libtagstruct_la_LIBADD = $(AM_LIBADD) libpulsecore.la $(WINSOCK_LIBS) + +libprotocol_esound_la_SOURCES = pulsecore/protocol-esound.c pulsecore/protocol-esound.h pulsecore/esound.h +libprotocol_esound_la_LDFLAGS = -avoid-version +libprotocol_esound_la_LIBADD = $(AM_LIBADD) libsocket-server.la libiochannel.la libauthkey.la libpulsecore.la libipacl.la + +libauthkey_la_SOURCES = pulsecore/authkey.c pulsecore/authkey.h +libauthkey_la_LDFLAGS = -avoid-version +libauthkey_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +libauthkey_prop_la_SOURCES = pulsecore/authkey-prop.c pulsecore/authkey-prop.h +libauthkey_prop_la_LDFLAGS = -avoid-version +libauthkey_prop_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +libsocket_util_la_SOURCES = \ + pulsecore/inet_ntop.c pulsecore/inet_ntop.h \ + pulsecore/socket-util.c pulsecore/socket-util.h +libsocket_util_la_LDFLAGS = -avoid-version +libsocket_util_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) libpulsecore.la + +librtp_la_SOURCES = modules/rtp/rtp.c modules/rtp/rtp.h modules/rtp/sdp.c modules/rtp/sdp.h modules/rtp/sap.c modules/rtp/sap.h +librtp_la_LDFLAGS = -avoid-version +librtp_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +# X11 + +libx11wrap_la_SOURCES = pulsecore/x11wrap.c pulsecore/x11wrap.h +libx11wrap_la_LDFLAGS = -avoid-version +libx11wrap_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS) +libx11wrap_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libpulsecore.la + +libx11prop_la_SOURCES = pulsecore/x11prop.c pulsecore/x11prop.h +libx11prop_la_LDFLAGS = -avoid-version +libx11prop_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS) +libx11prop_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) + +# Avahi + +libavahi_wrap_la_SOURCES = pulsecore/avahi-wrap.c pulsecore/avahi-wrap.h +libavahi_wrap_la_LDFLAGS = -avoid-version +libavahi_wrap_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS) +libavahi_wrap_la_LIBADD = $(AM_LIBADD) $(AVAHI_CFLAGS) libpulsecore.la + +################################### +# Plug-in libraries # +################################### + +modlibexec_LTLIBRARIES += \ + module-cli.la \ + module-cli-protocol-tcp.la \ + module-simple-protocol-tcp.la \ + module-null-sink.la \ + module-detect.la \ + module-volume-restore.la \ + module-default-device-restore.la \ + module-rescue-streams.la \ + module-suspend-on-idle.la \ + module-http-protocol-tcp.la \ + module-sine.la \ + module-native-protocol-tcp.la \ + module-native-protocol-fd.la \ + module-esound-protocol-tcp.la \ + module-combine.la \ + module-remap-sink.la \ + module-ladspa-sink.la \ + module-esound-sink.la \ + module-tunnel-sink.la \ + module-tunnel-source.la + +# See comment at librtp.la above +if !OS_IS_WIN32 +modlibexec_LTLIBRARIES += \ + module-rtp-send.la \ + module-rtp-recv.la +endif + +if HAVE_AF_UNIX +modlibexec_LTLIBRARIES += \ + module-cli-protocol-unix.la \ + module-simple-protocol-unix.la \ + module-http-protocol-unix.la \ + module-native-protocol-unix.la \ + module-esound-protocol-unix.la +endif + +if HAVE_MKFIFO +modlibexec_LTLIBRARIES += \ + module-pipe-sink.la \ + module-pipe-source.la +endif + +if !OS_IS_WIN32 +modlibexec_LTLIBRARIES += \ + module-esound-compat-spawnfd.la \ + module-esound-compat-spawnpid.la +endif + +if HAVE_REGEX +modlibexec_LTLIBRARIES += \ + module-match.la +endif + +if HAVE_X11 +modlibexec_LTLIBRARIES += \ + module-x11-bell.la \ + module-x11-publish.la \ + module-x11-xsmp.la +endif + +if HAVE_OSS +modlibexec_LTLIBRARIES += \ + liboss-util.la \ + module-oss.la +endif + +if HAVE_ALSA +modlibexec_LTLIBRARIES += \ + libalsa-util.la \ + module-alsa-sink.la \ + module-alsa-source.la +endif + +if HAVE_SOLARIS +modlibexec_LTLIBRARIES += \ + module-solaris.la +endif + +if HAVE_AVAHI +modlibexec_LTLIBRARIES += \ + module-zeroconf-publish.la \ + module-zeroconf-discover.la +endif + +if HAVE_LIRC +modlibexec_LTLIBRARIES += \ + module-lirc.la +endif + +if HAVE_EVDEV +modlibexec_LTLIBRARIES += \ + module-mmkbd-evdev.la +endif + +if HAVE_JACK +modlibexec_LTLIBRARIES += \ + module-jack-sink.la \ + module-jack-source.la +endif + +pulselibexec_PROGRAMS = + +if HAVE_GCONF +modlibexec_LTLIBRARIES += \ + module-gconf.la + +pulselibexec_PROGRAMS += \ + gconf-helper +endif + +#if OS_IS_WIN32 +#modlibexec_LTLIBRARIES += \ +# module-waveout.la +#endif + +if HAVE_HAL +modlibexec_LTLIBRARIES += \ + libdbus-util.la \ + module-hal-detect.la +endif + +if HAVE_BLUEZ +modlibexec_LTLIBRARIES += \ + module-bt-proximity.la + +pulselibexec_PROGRAMS += \ + bt-proximity-helper +endif + +# These are generated by a M4 script + +SYMDEF_FILES = \ + modules/module-cli-symdef.h \ + modules/module-cli-protocol-tcp-symdef.h \ + modules/module-cli-protocol-unix-symdef.h \ + modules/module-pipe-sink-symdef.h \ + modules/module-pipe-source-symdef.h \ + modules/module-simple-protocol-tcp-symdef.h \ + modules/module-simple-protocol-unix-symdef.h \ + modules/module-esound-protocol-tcp-symdef.h \ + modules/module-esound-protocol-unix-symdef.h \ + modules/module-native-protocol-tcp-symdef.h \ + modules/module-native-protocol-unix-symdef.h \ + modules/module-native-protocol-fd-symdef.h \ + modules/module-sine-symdef.h \ + modules/module-combine-symdef.h \ + modules/module-remap-sink-symdef.h \ + modules/module-ladspa-sink-symdef.h \ + modules/module-esound-compat-spawnfd-symdef.h \ + modules/module-esound-compat-spawnpid-symdef.h \ + modules/module-match-symdef.h \ + modules/module-tunnel-sink-symdef.h \ + modules/module-tunnel-source-symdef.h \ + modules/module-null-sink-symdef.h \ + modules/module-esound-sink-symdef.h \ + modules/module-zeroconf-publish-symdef.h \ + modules/module-zeroconf-discover-symdef.h \ + modules/module-lirc-symdef.h \ + modules/module-mmkbd-evdev-symdef.h \ + modules/module-http-protocol-tcp-symdef.h \ + modules/module-http-protocol-unix-symdef.h \ + modules/module-x11-bell-symdef.h \ + modules/module-x11-publish-symdef.h \ + modules/module-x11-xsmp-symdef.h \ + modules/module-oss-symdef.h \ + modules/module-alsa-sink-symdef.h \ + modules/module-alsa-source-symdef.h \ + modules/module-solaris-symdef.h \ + modules/module-waveout-symdef.h \ + modules/module-detect-symdef.h \ + modules/rtp/module-rtp-send-symdef.h \ + modules/rtp/module-rtp-recv-symdef.h \ + modules/module-jack-sink-symdef.h \ + modules/module-jack-source-symdef.h \ + modules/module-volume-restore-symdef.h \ + modules/module-default-device-restore-symdef.h \ + modules/module-rescue-streams-symdef.h \ + modules/module-suspend-on-idle-symdef.h \ + modules/module-hal-detect-symdef.h \ + modules/module-bt-proximity-symdef.h \ + modules/gconf/module-gconf-symdef.h + +EXTRA_DIST += $(SYMDEF_FILES) +BUILT_SOURCES += $(SYMDEF_FILES) + +$(SYMDEF_FILES): modules/module-defs.h.m4 + $(MKDIR_P) modules + $(MKDIR_P) modules/gconf + $(MKDIR_P) modules/rtp + $(M4) -Dfname="$@" $< > $@ + +# Simple protocol + +module_simple_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c +module_simple_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_SIMPLE $(AM_CFLAGS) +module_simple_protocol_tcp_la_LDFLAGS = -module -avoid-version +module_simple_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-simple.la libsocket-server.la + +module_simple_protocol_unix_la_SOURCES = modules/module-protocol-stub.c +module_simple_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_SIMPLE $(AM_CFLAGS) +module_simple_protocol_unix_la_LDFLAGS = -module -avoid-version +module_simple_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-simple.la libsocket-server.la libsocket-util.la + +# CLI protocol + +module_cli_la_SOURCES = modules/module-cli.c +module_cli_la_LDFLAGS = -module -avoid-version +module_cli_la_LIBADD = $(AM_LIBADD) libcli.la libiochannel.la libpulsecore.la + +module_cli_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c +module_cli_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS) +module_cli_protocol_tcp_la_LDFLAGS = -module -avoid-version +module_cli_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-cli.la libsocket-server.la + +module_cli_protocol_unix_la_SOURCES = modules/module-protocol-stub.c +module_cli_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_CLI $(AM_CFLAGS) +module_cli_protocol_unix_la_LDFLAGS = -module -avoid-version +module_cli_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-cli.la libsocket-server.la libsocket-util.la + +# HTTP protocol + +module_http_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c +module_http_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_HTTP $(AM_CFLAGS) +module_http_protocol_tcp_la_LDFLAGS = -module -avoid-version +module_http_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-http.la libsocket-server.la + +module_http_protocol_unix_la_SOURCES = modules/module-protocol-stub.c +module_http_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_HTTP $(AM_CFLAGS) +module_http_protocol_unix_la_LDFLAGS = -module -avoid-version +module_http_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-http.la libsocket-server.la libsocket-util.la + +# Native protocol + +module_native_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c +module_native_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_NATIVE $(AM_CFLAGS) +module_native_protocol_tcp_la_LDFLAGS = -module -avoid-version +module_native_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-native.la libsocket-server.la + +module_native_protocol_unix_la_SOURCES = modules/module-protocol-stub.c +module_native_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_NATIVE $(AM_CFLAGS) +module_native_protocol_unix_la_LDFLAGS = -module -avoid-version +module_native_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-native.la libsocket-server.la libsocket-util.la + +module_native_protocol_fd_la_SOURCES = modules/module-native-protocol-fd.c +module_native_protocol_fd_la_CFLAGS = $(AM_CFLAGS) +module_native_protocol_fd_la_LDFLAGS = -module -avoid-version +module_native_protocol_fd_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-native.la libsocket-server.la libsocket-util.la libiochannel.la + +# EsounD protocol + +module_esound_protocol_tcp_la_SOURCES = modules/module-protocol-stub.c +module_esound_protocol_tcp_la_CFLAGS = -DUSE_TCP_SOCKETS -DUSE_PROTOCOL_ESOUND $(AM_CFLAGS) +module_esound_protocol_tcp_la_LDFLAGS = -module -avoid-version +module_esound_protocol_tcp_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-esound.la libsocket-server.la + +module_esound_protocol_unix_la_SOURCES = modules/module-protocol-stub.c +module_esound_protocol_unix_la_CFLAGS = -DUSE_UNIX_SOCKETS -DUSE_PROTOCOL_ESOUND $(AM_CFLAGS) +module_esound_protocol_unix_la_LDFLAGS = -module -avoid-version +module_esound_protocol_unix_la_LIBADD = $(AM_LIBADD) libpulsecore.la libprotocol-esound.la libsocket-server.la libsocket-util.la + +module_esound_compat_spawnfd_la_SOURCES = modules/module-esound-compat-spawnfd.c +module_esound_compat_spawnfd_la_LDFLAGS = -module -avoid-version +module_esound_compat_spawnfd_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +module_esound_compat_spawnpid_la_SOURCES = modules/module-esound-compat-spawnpid.c +module_esound_compat_spawnpid_la_LDFLAGS = -module -avoid-version +module_esound_compat_spawnpid_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +module_esound_sink_la_SOURCES = modules/module-esound-sink.c +module_esound_sink_la_LDFLAGS = -module -avoid-version +module_esound_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la libsocket-client.la libauthkey.la libsocket-util.la + +# Pipes + +module_pipe_sink_la_SOURCES = modules/module-pipe-sink.c +module_pipe_sink_la_LDFLAGS = -module -avoid-version +module_pipe_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la + +module_pipe_source_la_SOURCES = modules/module-pipe-source.c +module_pipe_source_la_LDFLAGS = -module -avoid-version +module_pipe_source_la_LIBADD = $(AM_LIBADD) libpulsecore.la libiochannel.la + +# Fake sources/sinks + +module_sine_la_SOURCES = modules/module-sine.c +module_sine_la_LDFLAGS = -module -avoid-version +module_sine_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +module_null_sink_la_SOURCES = modules/module-null-sink.c +module_null_sink_la_LDFLAGS = -module -avoid-version +module_null_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +# Couplings + +module_combine_la_SOURCES = modules/module-combine.c +module_combine_la_LDFLAGS = -module -avoid-version +module_combine_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +module_remap_sink_la_SOURCES = modules/module-remap-sink.c +module_remap_sink_la_LDFLAGS = -module -avoid-version +module_remap_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +module_ladspa_sink_la_SOURCES = modules/module-ladspa-sink.c modules/ladspa.h +module_ladspa_sink_la_CFLAGS = -DLADSPA_PATH=\"$(libdir)/ladspa:/usr/local/lib/ladspa:/usr/lib/ladspa\" $(AM_CFLAGS) +module_ladspa_sink_la_LDFLAGS = -module -avoid-version +module_ladspa_sink_la_LIBADD = $(AM_LIBADD) $(LIBLTDL) libpulsecore.la + +module_match_la_SOURCES = modules/module-match.c +module_match_la_LDFLAGS = -module -avoid-version +module_match_la_LIBADD = $(AM_LIBADD) libpulsecore.la + +module_tunnel_sink_la_SOURCES = modules/module-tunnel.c +module_tunnel_sink_la_CFLAGS = -DTUNNEL_SINK=1 $(AM_CFLAGS) +module_tunnel_sink_la_LDFLAGS = -module -avoid-version +module_tunnel_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la libsocket-client.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libauthkey.la libauthkey-prop.la libsocket-util.la libiochannel.la + +module_tunnel_source_la_SOURCES = modules/module-tunnel.c +module_tunnel_source_la_LDFLAGS = -module -avoid-version +module_tunnel_source_la_LIBADD = $(AM_LIBADD) libpulsecore.la libsocket-client.la libpstream.la libpstream-util.la libpdispatch.la libtagstruct.la libauthkey.la libauthkey-prop.la libsocket-util.la libiochannel.la + +# X11 + +module_x11_bell_la_SOURCES = modules/module-x11-bell.c +module_x11_bell_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS) +module_x11_bell_la_LDFLAGS = -module -avoid-version +module_x11_bell_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libx11wrap.la libpulsecore.la + +module_x11_publish_la_SOURCES = modules/module-x11-publish.c +module_x11_publish_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS) +module_x11_publish_la_LDFLAGS = -module -avoid-version +module_x11_publish_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libx11wrap.la libauthkey.la libauthkey-prop.la libx11prop.la libstrlist.la libpulsecore.la + +module_x11_xsmp_la_SOURCES = modules/module-x11-xsmp.c +module_x11_xsmp_la_CFLAGS = $(AM_CFLAGS) $(X_CFLAGS) +module_x11_xsmp_la_LDFLAGS = -module -avoid-version +module_x11_xsmp_la_LIBADD = $(AM_LIBADD) $(X_PRE_LIBS) -lX11 $(X_LIBS) $(X_EXTRA_LIBS) libpulsecore.la + +# OSS + +liboss_util_la_SOURCES = modules/oss-util.c modules/oss-util.h +liboss_util_la_LDFLAGS = -avoid-version +liboss_util_la_LIBADD = libpulsecore.la + +module_oss_la_SOURCES = modules/module-oss.c +module_oss_la_LDFLAGS = -module -avoid-version +module_oss_la_LIBADD = $(AM_LIBADD) libiochannel.la liboss-util.la libpulsecore.la + +# ALSA + +libalsa_util_la_SOURCES = modules/alsa-util.c modules/alsa-util.h +libalsa_util_la_LDFLAGS = -avoid-version +libalsa_util_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libpulsecore.la +libalsa_util_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) + +module_alsa_sink_la_SOURCES = modules/module-alsa-sink.c +module_alsa_sink_la_LDFLAGS = -module -avoid-version +module_alsa_sink_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la libpulsecore.la +module_alsa_sink_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) + +module_alsa_source_la_SOURCES = modules/module-alsa-source.c +module_alsa_source_la_LDFLAGS = -module -avoid-version +module_alsa_source_la_LIBADD = $(AM_LIBADD) $(ASOUNDLIB_LIBS) libalsa-util.la libpulsecore.la +module_alsa_source_la_CFLAGS = $(AM_CFLAGS) $(ASOUNDLIB_CFLAGS) + +# Solaris + +module_solaris_la_SOURCES = modules/module-solaris.c +module_solaris_la_LDFLAGS = -module -avoid-version +module_solaris_la_LIBADD = $(AM_LIBADD) libiochannel.la libpulsecore.la + +# Avahi + +module_zeroconf_publish_la_SOURCES = modules/module-zeroconf-publish.c +module_zeroconf_publish_la_LDFLAGS = -module -avoid-version +module_zeroconf_publish_la_LIBADD = $(AM_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libpulsecore.la +module_zeroconf_publish_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS) + +module_zeroconf_discover_la_SOURCES = modules/module-zeroconf-discover.c +module_zeroconf_discover_la_LDFLAGS = -module -avoid-version +module_zeroconf_discover_la_LIBADD = $(AM_LIBADD) $(AVAHI_LIBS) libavahi-wrap.la libpulsecore.la +module_zeroconf_discover_la_CFLAGS = $(AM_CFLAGS) $(AVAHI_CFLAGS) + +# LIRC + +module_lirc_la_SOURCES = modules/module-lirc.c +module_lirc_la_LDFLAGS = -module -avoid-version +module_lirc_la_LIBADD = $(AM_LIBADD) $(LIRC_LIBS) libpulsecore.la +module_lirc_la_CFLAGS = $(AM_CFLAGS) $(LIRC_CFLAGS) + +# Linux evdev + +module_mmkbd_evdev_la_SOURCES = modules/module-mmkbd-evdev.c +module_mmkbd_evdev_la_LDFLAGS = -module -avoid-version +module_mmkbd_evdev_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_mmkbd_evdev_la_CFLAGS = $(AM_CFLAGS) + +# Windows waveout + +#module_waveout_la_SOURCES = modules/module-waveout.c +#module_waveout_la_LDFLAGS = -module -avoid-version +#module_waveout_la_LIBADD = $(AM_LIBADD) libpulsecore.la -lwinmm +#module_waveout_la_CFLAGS = $(AM_CFLAGS) + +# Hardware autodetection module +module_detect_la_SOURCES = modules/module-detect.c +module_detect_la_LDFLAGS = -module -avoid-version +module_detect_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_detect_la_CFLAGS = $(AM_CFLAGS) + +# Volume restore module +module_volume_restore_la_SOURCES = modules/module-volume-restore.c +module_volume_restore_la_LDFLAGS = -module -avoid-version +module_volume_restore_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_volume_restore_la_CFLAGS = $(AM_CFLAGS) + +# Default sink/source restore module +module_default_device_restore_la_SOURCES = modules/module-default-device-restore.c +module_default_device_restore_la_LDFLAGS = -module -avoid-version +module_default_device_restore_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_default_device_restore_la_CFLAGS = $(AM_CFLAGS) + +# Rescue streams module +module_rescue_streams_la_SOURCES = modules/module-rescue-streams.c +module_rescue_streams_la_LDFLAGS = -module -avoid-version +module_rescue_streams_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_rescue_streams_la_CFLAGS = $(AM_CFLAGS) + +# Suspend-on-idle module +module_suspend_on_idle_la_SOURCES = modules/module-suspend-on-idle.c +module_suspend_on_idle_la_LDFLAGS = -module -avoid-version +module_suspend_on_idle_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_suspend_on_idle_la_CFLAGS = $(AM_CFLAGS) + +# RTP modules +module_rtp_send_la_SOURCES = modules/rtp/module-rtp-send.c +module_rtp_send_la_LDFLAGS = -module -avoid-version +module_rtp_send_la_LIBADD = $(AM_LIBADD) libpulsecore.la librtp.la libsocket-util.la +module_rtp_send_la_CFLAGS = $(AM_CFLAGS) + +module_rtp_recv_la_SOURCES = modules/rtp/module-rtp-recv.c +module_rtp_recv_la_LDFLAGS = -module -avoid-version +module_rtp_recv_la_LIBADD = $(AM_LIBADD) libpulsecore.la librtp.la +module_rtp_recv_la_CFLAGS = $(AM_CFLAGS) + +# JACK + +module_jack_sink_la_SOURCES = modules/module-jack-sink.c +module_jack_sink_la_LDFLAGS = -module -avoid-version +module_jack_sink_la_LIBADD = $(AM_LIBADD) libpulsecore.la $(JACK_LIBS) +module_jack_sink_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS) + +module_jack_source_la_SOURCES = modules/module-jack-source.c +module_jack_source_la_LDFLAGS = -module -avoid-version +module_jack_source_la_LIBADD = $(AM_LIBADD) libpulsecore.la $(JACK_LIBS) +module_jack_source_la_CFLAGS = $(AM_CFLAGS) $(JACK_CFLAGS) + +# HAL +libdbus_util_la_SOURCES = modules/dbus-util.c modules/dbus-util.h +libdbus_util_la_LDFLAGS = -avoid-version +libdbus_util_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore.la +libdbus_util_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) + +module_hal_detect_la_SOURCES = modules/module-hal-detect.c +module_hal_detect_la_LDFLAGS = -module -avoid-version +module_hal_detect_la_LIBADD = $(AM_LIBADD) $(HAL_LIBS) libpulsecore.la libdbus-util.la +module_hal_detect_la_CFLAGS = $(AM_CFLAGS) $(HAL_CFLAGS) + +# GConf support +module_gconf_la_SOURCES = modules/gconf/module-gconf.c +module_gconf_la_LDFLAGS = -module -avoid-version +module_gconf_la_LIBADD = $(AM_LIBADD) libpulsecore.la +module_gconf_la_CFLAGS = $(AM_CFLAGS) -DPA_GCONF_HELPER=\"$(pulselibexecdir)/gconf-helper\" + +gconf_helper_SOURCES = modules/gconf/gconf-helper.c +gconf_helper_LDADD = $(AM_LDADD) $(GCONF_LIBS) libpulsecore.la +gconf_helper_CFLAGS = $(AM_CFLAGS) $(GCONF_CFLAGS) +gconf_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +# Bluetooth proximity +module_bt_proximity_la_SOURCES = modules/module-bt-proximity.c +module_bt_proximity_la_LDFLAGS = -module -avoid-version +module_bt_proximity_la_LIBADD = $(AM_LIBADD) $(DBUS_LIBS) libpulsecore.la libdbus-util.la +module_bt_proximity_la_CFLAGS = $(AM_CFLAGS) $(DBUS_CFLAGS) -DPA_BT_PROXIMITY_HELPER=\"$(pulselibexecdir)/bt-proximity-helper\" + +bt_proximity_helper_SOURCES = modules/bt-proximity-helper.c +bt_proximity_helper_LDADD = $(AM_LDADD) $(BLUEZ_LIBS) +bt_proximity_helper_CFLAGS = $(AM_CFLAGS) $(BLUEZ_CFLAGS) +bt_proximity_helper_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) + +################################### +# Some minor stuff # +################################### + +suid: pulseaudio .libs/lt-pulseaudio + chown root $^ + chmod u+s $^ + +CLEANFILES = esdcompat client.conf default.pa daemon.conf + +esdcompat: daemon/esdcompat.in Makefile + sed -e 's,@PACKAGE_VERSION\@,$(PACKAGE_VERSION),g' \ + -e 's,@PACKAGE_NAME\@,$(PACKAGE_NAME),g' \ + -e 's,@PA_BINARY\@,$(PA_BINARY),g' < $< > $@ + chmod +x esdcompat + +client.conf: pulse/client.conf.in Makefile + sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' < $< > $@ + +if OS_IS_WIN32 +default.pa: daemon/default.pa.win32 + cp $< $@ +else +default.pa: daemon/default.pa.in Makefile + sed -e 's,@PA_BINARY\@,$(PA_BINARY),g' \ + -e 's,@PA_DLSEARCHPATH\@,$(modlibexecdir),g' \ + -e 's,@PA_SOEXT\@,.so,g' < $< > $@ +endif + +daemon.conf: daemon/daemon.conf.in Makefile + sed -e 's,@PA_DLSEARCHPATH\@,$(modlibexecdir),g' \ + -e 's,@PA_DEFAULT_CONFIG_FILE\@,$(DEFAULT_CONFIG_DIR),g' < $< > $@ + +install-exec-hook: + chown root $(DESTDIR)$(bindir)/pulseaudio ; true + chmod u+s $(DESTDIR)$(bindir)/pulseaudio + -chmod u+s $(DESTDIR)$(pulselibexecdir)/bt-proximity-helper + ln -sf pacat $(DESTDIR)$(bindir)/parec + rm -f $(DESTDIR)$(modlibexecdir)/*.a + rm -f $(DESTDIR)$(libdir)/libpulsedsp.a + rm -f $(DESTDIR)$(libdir)/libpulsedsp.la + rm -f $(DESTDIR)$(modlibexecdir)/*.la + +massif: pulseaudio + libtool --mode=execute valgrind --tool=massif --depth=6 --alloc-fn=pa_xmalloc --alloc-fn=pa_xmalloc0 --alloc-fn=pa_xrealloc --alloc-fn=dbus_realloc --alloc-fn=pa_xnew0_internal --alloc-fn=pa_xnew_internal ./pulseaudio + +update-speex: + wget -O pulsecore/speex/speex_resampler.h http://svn.xiph.org/trunk/speex/include/speex/speex_resampler.h + wget -O pulsecore/speex/resample.c http://svn.xiph.org/trunk/speex/libspeex/resample.c + wget -O pulsecore/speex/arch.h http://svn.xiph.org/trunk/speex/libspeex/arch.h + wget -O pulsecore/speex/fixed_generic.h http://svn.xiph.org/trunk/speex/libspeex/fixed_generic.h + +update-ffmpeg: + wget -O pulsecore/ffmpeg/resample2.c http://svn.mplayerhq.hu/ffmpeg/trunk/libavcodec/resample2.c?view=co + +# Automatically generate linker version script. We use the same one for all public .sos +update-map-file: + ( echo "PULSE_0 {" ; \ + echo "global:" ; \ + ctags -I PA_GCC_PURE,PA_GCC_CONST -f - --c-kinds=p $(pulseinclude_HEADERS) | awk '/^pa_/ { print $$1 ";" }' | sort ; \ + echo "local:" ; \ + echo "*;" ; \ + echo "};" ) > $(srcdir)/map-file + +.PHONY: utils/padsp diff --git a/src/daemon/Makefile b/src/daemon/Makefile new file mode 120000 index 00000000..c110232d --- /dev/null +++ b/src/daemon/Makefile @@ -0,0 +1 @@ +../pulse/Makefile
\ No newline at end of file diff --git a/src/daemon/PulseAudio.policy b/src/daemon/PulseAudio.policy new file mode 100644 index 00000000..cf9499ee --- /dev/null +++ b/src/daemon/PulseAudio.policy @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?><!--*-nxml-*--> +<!DOCTYPE policyconfig PUBLIC + "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN" + "http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd"> + +<!-- $Id$ --> + +<!-- +This file is part of PulseAudio. + +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. +--> + +<policyconfig> + + <action id="org.pulseaudio.acquire-real-time"> + <description>Real-time scheduling for the PulseAudio daemon</description> + <message>System policy prevents PulseAudio from acquiring real-time scheduling.</message> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>no</allow_active> + </defaults> + </action> + + <action id="org.pulseaudio.acquire-high-priority"> + <description>High-priority scheduling (negative Unix nice level) for the PulseAudio daemon</description> + <message>System policy prevents PulseAudio from acquiring high-priority scheduling.</message> + <defaults> + <allow_any>no</allow_any> + <allow_inactive>no</allow_inactive> + <allow_active>no</allow_active> + </defaults> + </action> + +</policyconfig> diff --git a/src/daemon/caps.c b/src/daemon/caps.c new file mode 100644 index 00000000..44ee355e --- /dev/null +++ b/src/daemon/caps.c @@ -0,0 +1,152 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <pulsecore/macro.h> + +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#include <pulsecore/core-error.h> + +#include <pulsecore/log.h> + +#include "caps.h" + +/* Glibc <= 2.2 has broken unistd.h */ +#if defined(linux) && (__GLIBC__ <= 2 && __GLIBC_MINOR__ <= 2) +int setresgid(gid_t r, gid_t e, gid_t s); +int setresuid(uid_t r, uid_t e, uid_t s); +#endif + +#ifdef HAVE_GETUID + +/* Drop root rights when called SUID root */ +void pa_drop_root(void) { + uid_t uid = getuid(); + + if (uid == 0 || geteuid() != 0) + return; + + pa_log_info("Dropping root priviliges."); + +#if defined(HAVE_SETRESUID) + pa_assert_se(setresuid(uid, uid, uid) >= 0); +#elif defined(HAVE_SETREUID) + pa_assert_se(setreuid(uid, uid) >= 0); +#else + pa_assert_se(setuid(uid) >= 0); + pa_assert_se(seteuid(uid) >= 0); +#endif + + pa_assert_se(getuid() == uid); + pa_assert_se(geteuid() == uid); +} + +#else + +void pa_drop_root(void) { +} + +#endif + +#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_SYS_PRCTL_H) + +/* Limit permitted capabilities set to CAPSYS_NICE */ +int pa_limit_caps(void) { + int r = -1; + cap_t caps; + cap_value_t nice_cap = CAP_SYS_NICE; + + caps = cap_init(); + pa_assert(caps); + cap_clear(caps); + cap_set_flag(caps, CAP_EFFECTIVE, 1, &nice_cap, CAP_SET); + cap_set_flag(caps, CAP_PERMITTED, 1, &nice_cap, CAP_SET); + + if (cap_set_proc(caps) < 0) + goto fail; + + if (prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) + goto fail; + + pa_log_info("Dropped capabilities successfully."); + + r = 1; + +fail: + cap_free(caps); + + return r; +} + +/* Drop all capabilities, effectively becoming a normal user */ +int pa_drop_caps(void) { + cap_t caps; + int r = -1; + + caps = cap_init(); + pa_assert(caps); + + cap_clear(caps); + + prctl(PR_SET_KEEPCAPS, 0, 0, 0, 0); + + if (cap_set_proc(caps) < 0) { + pa_log("Failed to drop capabilities: %s", pa_cstrerror(errno)); + goto fail; + } + + r = 0; + +fail: + cap_free(caps); + + return r; +} + +#else + +/* NOOPs in case capabilities are not available. */ +int pa_limit_caps(void) { + return 0; +} + +int pa_drop_caps(void) { + pa_drop_root(); + return 0; +} + +#endif diff --git a/src/daemon/caps.h b/src/daemon/caps.h new file mode 100644 index 00000000..4cd09578 --- /dev/null +++ b/src/daemon/caps.h @@ -0,0 +1,31 @@ +#ifndef foocapshfoo +#define foocapshfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004 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 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. +***/ + +void pa_drop_root(void); +int pa_limit_caps(void); +int pa_drop_caps(void); + +#endif diff --git a/src/daemon/cmdline.c b/src/daemon/cmdline.c new file mode 100644 index 00000000..f1e1282c --- /dev/null +++ b/src/daemon/cmdline.c @@ -0,0 +1,369 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <getopt.h> +#include <sys/stat.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/macro.h> + +#include "cmdline.h" + +/* Argument codes for getopt_long() */ +enum { + ARG_HELP = 256, + ARG_VERSION, + ARG_DUMP_CONF, + ARG_DUMP_MODULES, + ARG_DAEMONIZE, + ARG_FAIL, + ARG_LOG_LEVEL, + ARG_HIGH_PRIORITY, + ARG_REALTIME, + ARG_DISALLOW_MODULE_LOADING, + ARG_EXIT_IDLE_TIME, + ARG_MODULE_IDLE_TIME, + ARG_SCACHE_IDLE_TIME, + ARG_LOG_TARGET, + ARG_LOAD, + ARG_FILE, + ARG_DL_SEARCH_PATH, + ARG_RESAMPLE_METHOD, + ARG_KILL, + ARG_USE_PID_FILE, + ARG_CHECK, + ARG_NO_CPU_LIMIT, + ARG_DISABLE_SHM, + ARG_DUMP_RESAMPLE_METHODS, + ARG_SYSTEM, + ARG_CLEANUP_SHM +}; + +/* Tabel for getopt_long() */ +static const struct option long_options[] = { + {"help", 0, 0, ARG_HELP}, + {"version", 0, 0, ARG_VERSION}, + {"dump-conf", 0, 0, ARG_DUMP_CONF}, + {"dump-modules", 0, 0, ARG_DUMP_MODULES}, + {"daemonize", 2, 0, ARG_DAEMONIZE}, + {"fail", 2, 0, ARG_FAIL}, + {"verbose", 2, 0, ARG_LOG_LEVEL}, + {"log-level", 2, 0, ARG_LOG_LEVEL}, + {"high-priority", 2, 0, ARG_HIGH_PRIORITY}, + {"realtime", 2, 0, ARG_REALTIME}, + {"disallow-module-loading", 2, 0, ARG_DISALLOW_MODULE_LOADING}, + {"exit-idle-time", 2, 0, ARG_EXIT_IDLE_TIME}, + {"module-idle-time", 2, 0, ARG_MODULE_IDLE_TIME}, + {"scache-idle-time", 2, 0, ARG_SCACHE_IDLE_TIME}, + {"log-target", 1, 0, ARG_LOG_TARGET}, + {"load", 1, 0, ARG_LOAD}, + {"file", 1, 0, ARG_FILE}, + {"dl-search-path", 1, 0, ARG_DL_SEARCH_PATH}, + {"resample-method", 1, 0, ARG_RESAMPLE_METHOD}, + {"kill", 0, 0, ARG_KILL}, + {"use-pid-file", 2, 0, ARG_USE_PID_FILE}, + {"check", 0, 0, ARG_CHECK}, + {"system", 2, 0, ARG_SYSTEM}, + {"no-cpu-limit", 2, 0, ARG_NO_CPU_LIMIT}, + {"disable-shm", 2, 0, ARG_DISABLE_SHM}, + {"dump-resample-methods", 2, 0, ARG_DUMP_RESAMPLE_METHODS}, + {"cleanup-shm", 2, 0, ARG_CLEANUP_SHM}, + {NULL, 0, 0, 0} +}; + +void pa_cmdline_help(const char *argv0) { + const char *e; + + pa_assert(argv0); + + if ((e = strrchr(argv0, '/'))) + e++; + else + e = argv0; + + printf("%s [options]\n\n" + "COMMANDS:\n" + " -h, --help Show this help\n" + " --version Show version\n" + " --dump-conf Dump default configuration\n" + " --dump-modules Dump list of available modules\n" + " --dump-resample-methods Dump available resample methods\n" + " --cleanup-shm Cleanup stale shared memory segments\n" + " -k --kill Kill a running daemon\n" + " --check Check for a running daemon\n\n" + + "OPTIONS:\n" + " --system[=BOOL] Run as system-wide instance\n" + " -D, --daemonize[=BOOL] Daemonize after startup\n" + " --fail[=BOOL] Quit when startup fails\n" + " --high-priority[=BOOL] Try to set high nice level\n" + " (only available as root, when SUID or\n" + " with elevated RLIMIT_NICE)\n" + " --realtime[=BOOL] Try to enable realtime scheduling\n" + " (only available as root, when SUID or\n" + " with elevated RLIMIT_RTPRIO)\n" + " --disallow-module-loading[=BOOL] Disallow module loading after startup\n" + " --exit-idle-time=SECS Terminate the daemon when idle and this\n" + " time passed\n" + " --module-idle-time=SECS Unload autoloaded modules when idle and\n" + " this time passed\n" + " --scache-idle-time=SECS Unload autoloaded samples when idle and\n" + " this time passed\n" + " --log-level[=LEVEL] Increase or set verbosity level\n" + " -v Increase the verbosity level\n" + " --log-target={auto,syslog,stderr} Specify the log target\n" + " -p, --dl-search-path=PATH Set the search path for dynamic shared\n" + " objects (plugins)\n" + " --resample-method=METHOD Use the specified resampling method\n" + " (See --dump-resample-methods for\n" + " possible values)\n" + " --use-pid-file[=BOOL] Create a PID file\n" + " --no-cpu-limit[=BOOL] Do not install CPU load limiter on\n" + " platforms that support it.\n" + " --disable-shm[=BOOL] Disable shared memory support.\n\n" + + "STARTUP SCRIPT:\n" + " -L, --load=\"MODULE ARGUMENTS\" Load the specified plugin module with\n" + " the specified argument\n" + " -F, --file=FILENAME Run the specified script\n" + " -C Open a command line on the running TTY\n" + " after startup\n\n" + + " -n Don't load default script file\n", e); +} + +int pa_cmdline_parse(pa_daemon_conf *conf, int argc, char *const argv [], int *d) { + pa_strbuf *buf = NULL; + int c; + + pa_assert(conf); + pa_assert(argc > 0); + pa_assert(argv); + + buf = pa_strbuf_new(); + + if (conf->script_commands) + pa_strbuf_puts(buf, conf->script_commands); + + while ((c = getopt_long(argc, argv, "L:F:ChDnp:kv", long_options, NULL)) != -1) { + switch (c) { + case ARG_HELP: + case 'h': + conf->cmd = PA_CMD_HELP; + break; + + case ARG_VERSION: + conf->cmd = PA_CMD_VERSION; + break; + + case ARG_DUMP_CONF: + conf->cmd = PA_CMD_DUMP_CONF; + break; + + case ARG_DUMP_MODULES: + conf->cmd = PA_CMD_DUMP_MODULES; + break; + + case ARG_DUMP_RESAMPLE_METHODS: + conf->cmd = PA_CMD_DUMP_RESAMPLE_METHODS; + break; + + case ARG_CLEANUP_SHM: + conf->cmd = PA_CMD_CLEANUP_SHM; + break; + + case 'k': + case ARG_KILL: + conf->cmd = PA_CMD_KILL; + break; + + case ARG_CHECK: + conf->cmd = PA_CMD_CHECK; + break; + + case ARG_LOAD: + case 'L': + pa_strbuf_printf(buf, "load-module %s\n", optarg); + break; + + case ARG_FILE: + case 'F': { + char *p; + pa_strbuf_printf(buf, ".include %s\n", p = pa_make_path_absolute(optarg)); + pa_xfree(p); + break; + } + + case 'C': + pa_strbuf_puts(buf, "load-module module-cli exit_on_eof=1\n"); + break; + + case ARG_DAEMONIZE: + case 'D': + if ((conf->daemonize = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--daemonize expects boolean argument"); + goto fail; + } + break; + + case ARG_FAIL: + if ((conf->fail = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--fail expects boolean argument"); + goto fail; + } + break; + + case 'v': + case ARG_LOG_LEVEL: + + if (optarg) { + if (pa_daemon_conf_set_log_level(conf, optarg) < 0) { + pa_log("--log-level expects log level argument (either numeric in range 0..4 or one of debug, info, notice, warn, error)."); + goto fail; + } + } else { + if (conf->log_level < PA_LOG_LEVEL_MAX-1) + conf->log_level++; + } + + break; + + case ARG_HIGH_PRIORITY: + if ((conf->high_priority = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--high-priority expects boolean argument"); + goto fail; + } + break; + + case ARG_REALTIME: + if ((conf->realtime_scheduling = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--realtime expects boolean argument"); + goto fail; + } + break; + + case ARG_DISALLOW_MODULE_LOADING: + if ((conf->disallow_module_loading = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--disallow-module-loading expects boolean argument"); + goto fail; + } + break; + + case ARG_USE_PID_FILE: + if ((conf->use_pid_file = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--use-pid-file expects boolean argument"); + goto fail; + } + break; + + case 'p': + case ARG_DL_SEARCH_PATH: + pa_xfree(conf->dl_search_path); + conf->dl_search_path = *optarg ? pa_xstrdup(optarg) : NULL; + break; + + case 'n': + pa_xfree(conf->default_script_file); + conf->default_script_file = NULL; + break; + + case ARG_LOG_TARGET: + if (pa_daemon_conf_set_log_target(conf, optarg) < 0) { + pa_log("Invalid log target: use either 'syslog', 'stderr' or 'auto'."); + goto fail; + } + break; + + case ARG_EXIT_IDLE_TIME: + conf->exit_idle_time = atoi(optarg); + break; + + case ARG_MODULE_IDLE_TIME: + conf->module_idle_time = atoi(optarg); + break; + + case ARG_SCACHE_IDLE_TIME: + conf->scache_idle_time = atoi(optarg); + break; + + case ARG_RESAMPLE_METHOD: + if (pa_daemon_conf_set_resample_method(conf, optarg) < 0) { + pa_log("Invalid resample method '%s'.", optarg); + goto fail; + } + break; + + case ARG_SYSTEM: + if ((conf->system_instance = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--system expects boolean argument"); + goto fail; + } + break; + + case ARG_NO_CPU_LIMIT: + if ((conf->no_cpu_limit = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--no-cpu-limit expects boolean argument"); + goto fail; + } + break; + + case ARG_DISABLE_SHM: + if ((conf->disable_shm = optarg ? pa_parse_boolean(optarg) : TRUE) < 0) { + pa_log("--disable-shm expects boolean argument"); + goto fail; + } + break; + + default: + goto fail; + } + } + + pa_xfree(conf->script_commands); + conf->script_commands = pa_strbuf_tostring_free(buf); + + if (!conf->script_commands) { + pa_xfree(conf->script_commands); + conf->script_commands = NULL; + } + + *d = optind; + + return 0; + +fail: + if (buf) + pa_strbuf_free(buf); + + return -1; +} diff --git a/src/daemon/cmdline.h b/src/daemon/cmdline.h new file mode 100644 index 00000000..18418894 --- /dev/null +++ b/src/daemon/cmdline.h @@ -0,0 +1,37 @@ +#ifndef foocmdlinehfoo +#define foocmdlinehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 "daemon-conf.h" + +/* Parese the command line and store its data in *c. Return the index + * of the first unparsed argument in *d. */ +int pa_cmdline_parse(pa_daemon_conf*c, int argc, char *const argv [], int *d); + +/* Show the command line help. The command name is extracted from + * argv[0] which should be passed in argv0. */ +void pa_cmdline_help(const char *argv0); + +#endif diff --git a/src/daemon/cpulimit.c b/src/daemon/cpulimit.c new file mode 100644 index 00000000..b77dd443 --- /dev/null +++ b/src/daemon/cpulimit.c @@ -0,0 +1,247 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/error.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cpulimit.h" + +#ifdef HAVE_SIGXCPU + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <sys/time.h> +#include <unistd.h> +#include <signal.h> + +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +/* This module implements a watchdog that makes sure that the current + * process doesn't consume more than 70% CPU time for 10 seconds. This + * is very useful when using SCHED_FIFO scheduling which effectively + * disables multitasking. */ + +/* Method of operation: Using SIGXCPU a signal handler is called every + * 10s process CPU time. That function checks if less than 14s system + * time have passed. In that case, it tries to contact the main event + * loop through a pipe. After two additional seconds it is checked + * whether the main event loop contact was successful. If not, the + * program is terminated forcibly. */ + +/* Utilize this much CPU time at maximum */ +#define CPUTIME_PERCENT 70 + +/* Check every 10s */ +#define CPUTIME_INTERVAL_SOFT (10) + +/* Recheck after 5s */ +#define CPUTIME_INTERVAL_HARD (5) + +/* Time of the last CPU load check */ +static time_t last_time = 0; + +/* Pipe for communicating with the main loop */ +static int the_pipe[2] = {-1, -1}; + +/* Main event loop and IO event for the FIFO */ +static pa_mainloop_api *api = NULL; +static pa_io_event *io_event = NULL; + +/* Saved sigaction struct for SIGXCPU */ +static struct sigaction sigaction_prev; + +/* Nonzero after pa_cpu_limit_init() */ +static int installed = 0; + +/* The current state of operation */ +static enum { + PHASE_IDLE, /* Normal state */ + PHASE_SOFT /* After CPU overload has been detected */ +} phase = PHASE_IDLE; + +/* Reset the SIGXCPU timer to the next t seconds */ +static void reset_cpu_time(int t) { + long n; + struct rlimit rl; + struct rusage ru; + + /* Get the current CPU time of the current process */ + pa_assert_se(getrusage(RUSAGE_SELF, &ru) >= 0); + + n = ru.ru_utime.tv_sec + ru.ru_stime.tv_sec + t; + pa_assert_se(getrlimit(RLIMIT_CPU, &rl) >= 0); + + rl.rlim_cur = n; + pa_assert_se(setrlimit(RLIMIT_CPU, &rl) >= 0); +} + +/* A simple, thread-safe puts() work-alike */ +static void write_err(const char *p) { + pa_loop_write(2, p, strlen(p), NULL); +} + +/* The signal handler, called on every SIGXCPU */ +static void signal_handler(int sig) { + int saved_errno; + + saved_errno = errno; + pa_assert(sig == SIGXCPU); + + if (phase == PHASE_IDLE) { + time_t now; + +#ifdef PRINT_CPU_LOAD + char t[256]; +#endif + + time(&now); + +#ifdef PRINT_CPU_LOAD + pa_snprintf(t, sizeof(t), "Using %0.1f%% CPU\n", (double)CPUTIME_INTERVAL_SOFT/(now-last_time)*100); + write_err(t); +#endif + + if (CPUTIME_INTERVAL_SOFT >= ((now-last_time)*(double)CPUTIME_PERCENT/100)) { + static const char c = 'X'; + + write_err("Soft CPU time limit exhausted, terminating.\n"); + + /* Try a soft cleanup */ + write(the_pipe[1], &c, sizeof(c)); + phase = PHASE_SOFT; + reset_cpu_time(CPUTIME_INTERVAL_HARD); + + } else { + + /* Everything's fine */ + reset_cpu_time(CPUTIME_INTERVAL_SOFT); + last_time = now; + } + + } else if (phase == PHASE_SOFT) { + write_err("Hard CPU time limit exhausted, terminating forcibly.\n"); + _exit(1); /* Forced exit */ + } + + errno = saved_errno; +} + +/* Callback for IO events on the FIFO */ +static void callback(pa_mainloop_api*m, pa_io_event*e, int fd, pa_io_event_flags_t f, void *userdata) { + char c; + pa_assert(m); + pa_assert(e); + pa_assert(f == PA_IO_EVENT_INPUT); + pa_assert(e == io_event); + pa_assert(fd == the_pipe[0]); + + pa_read(the_pipe[0], &c, sizeof(c), NULL); + m->quit(m, 1); /* Quit the main loop */ +} + +/* Initializes CPU load limiter */ +int pa_cpu_limit_init(pa_mainloop_api *m) { + struct sigaction sa; + + pa_assert(m); + pa_assert(!api); + pa_assert(!io_event); + pa_assert(the_pipe[0] == -1); + pa_assert(the_pipe[1] == -1); + pa_assert(!installed); + + time(&last_time); + + /* Prepare the main loop pipe */ + if (pipe(the_pipe) < 0) { + pa_log("pipe() failed: %s", pa_cstrerror(errno)); + return -1; + } + + pa_make_fd_nonblock(the_pipe[0]); + pa_make_fd_nonblock(the_pipe[1]); + pa_make_fd_cloexec(the_pipe[0]); + pa_make_fd_cloexec(the_pipe[1]); + + api = m; + io_event = api->io_new(m, the_pipe[0], PA_IO_EVENT_INPUT, callback, NULL); + + phase = PHASE_IDLE; + + /* Install signal handler for SIGXCPU */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (sigaction(SIGXCPU, &sa, &sigaction_prev) < 0) { + pa_cpu_limit_done(); + return -1; + } + + installed = 1; + + reset_cpu_time(CPUTIME_INTERVAL_SOFT); + + return 0; +} + +/* Shutdown CPU load limiter */ +void pa_cpu_limit_done(void) { + + if (io_event) { + pa_assert(api); + api->io_free(io_event); + io_event = NULL; + api = NULL; + } + + pa_close_pipe(the_pipe); + + if (installed) { + pa_assert_se(sigaction(SIGXCPU, &sigaction_prev, NULL) >= 0); + installed = 0; + } +} + +#else /* HAVE_SIGXCPU */ + +int pa_cpu_limit_init(PA_GCC_UNUSED pa_mainloop_api *m) { + return 0; +} + +void pa_cpu_limit_done(void) { +} + +#endif diff --git a/src/daemon/cpulimit.h b/src/daemon/cpulimit.h new file mode 100644 index 00000000..271109b4 --- /dev/null +++ b/src/daemon/cpulimit.h @@ -0,0 +1,36 @@ +#ifndef foocpulimithfoo +#define foocpulimithfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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> + +/* This kills the pulseaudio process if it eats more than 70% of the + * CPU time. This is build around setrlimit() and SIGXCPU. It is handy + * in case of using SCHED_FIFO which may freeze the whole machine */ + +int pa_cpu_limit_init(pa_mainloop_api *m); +void pa_cpu_limit_done(void); + +#endif diff --git a/src/daemon/daemon-conf.c b/src/daemon/daemon-conf.c new file mode 100644 index 00000000..c98c0218 --- /dev/null +++ b/src/daemon/daemon-conf.c @@ -0,0 +1,597 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <errno.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sched.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/conf-parser.h> +#include <pulsecore/resampler.h> +#include <pulsecore/macro.h> + +#include "daemon-conf.h" + +#define DEFAULT_SCRIPT_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "default.pa" +#define DEFAULT_SCRIPT_FILE_USER PA_PATH_SEP "default.pa" +#define DEFAULT_CONFIG_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "daemon.conf" +#define DEFAULT_CONFIG_FILE_USER PA_PATH_SEP "daemon.conf" + +#define ENV_SCRIPT_FILE "PULSE_SCRIPT" +#define ENV_CONFIG_FILE "PULSE_CONFIG" +#define ENV_DL_SEARCH_PATH "PULSE_DLPATH" + +static const pa_daemon_conf default_conf = { + .cmd = PA_CMD_DAEMON, + .daemonize = FALSE, + .fail = TRUE, + .high_priority = TRUE, + .nice_level = -11, + .realtime_scheduling = FALSE, + .realtime_priority = 5, /* Half of JACK's default rtprio */ + .disallow_module_loading = FALSE, + .exit_idle_time = -1, + .module_idle_time = 20, + .scache_idle_time = 20, + .auto_log_target = 1, + .script_commands = NULL, + .dl_search_path = NULL, + .default_script_file = NULL, + .log_target = PA_LOG_SYSLOG, + .log_level = PA_LOG_NOTICE, + .resample_method = PA_RESAMPLER_AUTO, + .disable_remixing = FALSE, + .config_file = NULL, + .use_pid_file = TRUE, + .system_instance = FALSE, + .no_cpu_limit = FALSE, + .disable_shm = FALSE, + .default_n_fragments = 4, + .default_fragment_size_msec = 25, + .default_sample_spec = { .format = PA_SAMPLE_S16NE, .rate = 44100, .channels = 2 } +#ifdef HAVE_SYS_RESOURCE_H + , .rlimit_as = { .value = 0, .is_set = FALSE }, + .rlimit_core = { .value = 0, .is_set = FALSE }, + .rlimit_data = { .value = 0, .is_set = FALSE }, + .rlimit_fsize = { .value = 0, .is_set = FALSE }, + .rlimit_nofile = { .value = 256, .is_set = TRUE }, + .rlimit_stack = { .value = 0, .is_set = FALSE } +#ifdef RLIMIT_NPROC + , .rlimit_nproc = { .value = 0, .is_set = FALSE } +#endif +#ifdef RLIMIT_MEMLOCK + , .rlimit_memlock = { .value = 0, .is_set = FALSE } +#endif +#ifdef RLIMIT_NICE + , .rlimit_nice = { .value = 31, .is_set = TRUE } /* nice level of -11 */ +#endif +#ifdef RLIMIT_RTPRIO + , .rlimit_rtprio = { .value = 9, .is_set = TRUE } /* One below JACK's default for the server */ +#endif +#endif +}; + +pa_daemon_conf* pa_daemon_conf_new(void) { + FILE *f; + pa_daemon_conf *c = pa_xnewdup(pa_daemon_conf, &default_conf, 1); + + if ((f = pa_open_config_file(DEFAULT_SCRIPT_FILE, DEFAULT_SCRIPT_FILE_USER, ENV_SCRIPT_FILE, &c->default_script_file, "r"))) + fclose(f); + + c->dl_search_path = pa_xstrdup(PA_DLSEARCHPATH); + return c; +} + +void pa_daemon_conf_free(pa_daemon_conf *c) { + pa_assert(c); + pa_xfree(c->script_commands); + pa_xfree(c->dl_search_path); + pa_xfree(c->default_script_file); + pa_xfree(c->config_file); + pa_xfree(c); +} + +int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string) { + pa_assert(c); + pa_assert(string); + + if (!strcmp(string, "auto")) + c->auto_log_target = 1; + else if (!strcmp(string, "syslog")) { + c->auto_log_target = 0; + c->log_target = PA_LOG_SYSLOG; + } else if (!strcmp(string, "stderr")) { + c->auto_log_target = 0; + c->log_target = PA_LOG_STDERR; + } else + return -1; + + return 0; +} + +int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string) { + uint32_t u; + pa_assert(c); + pa_assert(string); + + if (pa_atou(string, &u) >= 0) { + if (u >= PA_LOG_LEVEL_MAX) + return -1; + + c->log_level = (pa_log_level_t) u; + } else if (pa_startswith(string, "debug")) + c->log_level = PA_LOG_DEBUG; + else if (pa_startswith(string, "info")) + c->log_level = PA_LOG_INFO; + else if (pa_startswith(string, "notice")) + c->log_level = PA_LOG_NOTICE; + else if (pa_startswith(string, "warn")) + c->log_level = PA_LOG_WARN; + else if (pa_startswith(string, "err")) + c->log_level = PA_LOG_ERROR; + else + return -1; + + return 0; +} + +int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string) { + int m; + pa_assert(c); + pa_assert(string); + + if ((m = pa_parse_resample_method(string)) < 0) + return -1; + + c->resample_method = m; + return 0; +} + +static int parse_log_target(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_daemon_conf_set_log_target(c, rvalue) < 0) { + pa_log("[%s:%u] Invalid log target '%s'.", filename, line, rvalue); + return -1; + } + + return 0; +} + +static int parse_log_level(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_daemon_conf_set_log_level(c, rvalue) < 0) { + pa_log("[%s:%u] Invalid log level '%s'.", filename, line, rvalue); + return -1; + } + + return 0; +} + +static int parse_resample_method(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_daemon_conf_set_resample_method(c, rvalue) < 0) { + pa_log("[%s:%u] Invalid resample method '%s'.", filename, line, rvalue); + return -1; + } + + return 0; +} + +static int parse_rlimit(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { +#ifdef HAVE_SYS_RESOURCE_H + struct pa_rlimit *r = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(r); + + if (rvalue[strspn(rvalue, "\t ")] == 0) { + /* Empty string */ + r->is_set = 0; + r->value = 0; + } else { + int32_t k; + if (pa_atoi(rvalue, &k) < 0) { + pa_log("[%s:%u] Invalid rlimit '%s'.", filename, line, rvalue); + return -1; + } + r->is_set = k >= 0; + r->value = k >= 0 ? (rlim_t) k : 0; + } +#else + pa_log_warn("[%s:%u] rlimit not supported on this platform.", filename, line); +#endif + + return 0; +} + +static int parse_sample_format(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + pa_sample_format_t f; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if ((f = pa_parse_sample_format(rvalue)) < 0) { + pa_log("[%s:%u] Invalid sample format '%s'.", filename, line, rvalue); + return -1; + } + + c->default_sample_spec.format = f; + return 0; +} + +static int parse_sample_rate(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t r; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &r) < 0 || r > PA_RATE_MAX || r <= 0) { + pa_log("[%s:%u] Invalid sample rate '%s'.", filename, line, rvalue); + return -1; + } + + c->default_sample_spec.rate = r; + return 0; +} + +static int parse_sample_channels(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t n; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &n) < 0 || n > PA_CHANNELS_MAX || n <= 0) { + pa_log("[%s:%u] Invalid sample channels '%s'.", filename, line, rvalue); + return -1; + } + + c->default_sample_spec.channels = (uint8_t) n; + return 0; +} + +static int parse_fragments(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t n; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &n) < 0 || n < 2) { + pa_log("[%s:%u] Invalid number of fragments '%s'.", filename, line, rvalue); + return -1; + } + + c->default_n_fragments = (unsigned) n; + return 0; +} + +static int parse_fragment_size_msec(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t n; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &n) < 0 || n < 1) { + pa_log("[%s:%u] Invalid fragment size '%s'.", filename, line, rvalue); + return -1; + } + + c->default_fragment_size_msec = (unsigned) n; + return 0; +} + +static int parse_nice_level(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t level; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &level) < 0 || level < -20 || level > 19) { + pa_log("[%s:%u] Invalid nice level '%s'.", filename, line, rvalue); + return -1; + } + + c->nice_level = (int) level; + return 0; +} + +static int parse_rtprio(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + pa_daemon_conf *c = data; + int32_t rtprio; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &rtprio) < 0 || rtprio < sched_get_priority_min(SCHED_FIFO) || rtprio > sched_get_priority_max(SCHED_FIFO)) { + pa_log("[%s:%u] Invalid realtime priority '%s'.", filename, line, rvalue); + return -1; + } + + c->realtime_priority = (int) rtprio; + return 0; +} + +int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename) { + int r = -1; + FILE *f = NULL; + + pa_config_item table[] = { + { "daemonize", pa_config_parse_bool, NULL }, + { "fail", pa_config_parse_bool, NULL }, + { "high-priority", pa_config_parse_bool, NULL }, + { "realtime-scheduling", pa_config_parse_bool, NULL }, + { "disallow-module-loading", pa_config_parse_bool, NULL }, + { "use-pid-file", pa_config_parse_bool, NULL }, + { "system-instance", pa_config_parse_bool, NULL }, + { "no-cpu-limit", pa_config_parse_bool, NULL }, + { "disable-shm", pa_config_parse_bool, NULL }, + { "exit-idle-time", pa_config_parse_int, NULL }, + { "module-idle-time", pa_config_parse_int, NULL }, + { "scache-idle-time", pa_config_parse_int, NULL }, + { "realtime-priority", parse_rtprio, NULL }, + { "dl-search-path", pa_config_parse_string, NULL }, + { "default-script-file", pa_config_parse_string, NULL }, + { "log-target", parse_log_target, NULL }, + { "log-level", parse_log_level, NULL }, + { "verbose", parse_log_level, NULL }, + { "resample-method", parse_resample_method, NULL }, + { "default-sample-format", parse_sample_format, NULL }, + { "default-sample-rate", parse_sample_rate, NULL }, + { "default-sample-channels", parse_sample_channels, NULL }, + { "default-fragments", parse_fragments, NULL }, + { "default-fragment-size-msec", parse_fragment_size_msec, NULL }, + { "nice-level", parse_nice_level, NULL }, + { "disable-remixing", pa_config_parse_bool, NULL }, +#ifdef HAVE_SYS_RESOURCE_H + { "rlimit-as", parse_rlimit, NULL }, + { "rlimit-core", parse_rlimit, NULL }, + { "rlimit-data", parse_rlimit, NULL }, + { "rlimit-fsize", parse_rlimit, NULL }, + { "rlimit-nofile", parse_rlimit, NULL }, + { "rlimit-stack", parse_rlimit, NULL }, +#ifdef RLIMIT_NPROC + { "rlimit-nproc", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_MEMLOCK + { "rlimit-memlock", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_NICE + { "rlimit-nice", parse_rlimit, NULL }, +#endif +#ifdef RLIMIT_RTPRIO + { "rlimit-rtprio", parse_rlimit, NULL }, +#endif +#endif + { NULL, NULL, NULL }, + }; + + table[0].data = &c->daemonize; + table[1].data = &c->fail; + table[2].data = &c->high_priority; + table[3].data = &c->realtime_scheduling; + table[4].data = &c->disallow_module_loading; + table[5].data = &c->use_pid_file; + table[6].data = &c->system_instance; + table[7].data = &c->no_cpu_limit; + table[8].data = &c->disable_shm; + table[9].data = &c->exit_idle_time; + table[10].data = &c->module_idle_time; + table[11].data = &c->scache_idle_time; + table[12].data = c; + table[13].data = &c->dl_search_path; + table[14].data = &c->default_script_file; + table[15].data = c; + table[16].data = c; + table[17].data = c; + table[18].data = c; + table[19].data = c; + table[20].data = c; + table[21].data = c; + table[22].data = c; + table[23].data = c; + table[24].data = c; + table[25].data = &c->disable_remixing; +#ifdef HAVE_SYS_RESOURCE_H + table[26].data = &c->rlimit_as; + table[27].data = &c->rlimit_core; + table[28].data = &c->rlimit_data; + table[29].data = &c->rlimit_fsize; + table[30].data = &c->rlimit_nofile; + table[31].data = &c->rlimit_stack; +#ifdef RLIMIT_NPROC + table[32].data = &c->rlimit_nproc; +#endif +#ifdef RLIMIT_MEMLOCK +#ifndef RLIMIT_NPROC +#error "Houston, we have a numbering problem!" +#endif + table[33].data = &c->rlimit_memlock; +#endif +#ifdef RLIMIT_NICE +#ifndef RLIMIT_MEMLOCK +#error "Houston, we have a numbering problem!" +#endif + table[34].data = &c->rlimit_nice; +#endif +#ifdef RLIMIT_RTPRIO +#ifndef RLIMIT_NICE +#error "Houston, we have a numbering problem!" +#endif + table[35].data = &c->rlimit_rtprio; +#endif +#endif + + pa_xfree(c->config_file); + c->config_file = NULL; + + f = filename ? + fopen(c->config_file = pa_xstrdup(filename), "r") : + pa_open_config_file(DEFAULT_CONFIG_FILE, DEFAULT_CONFIG_FILE_USER, ENV_CONFIG_FILE, &c->config_file, "r"); + + if (!f && errno != ENOENT) { + pa_log_warn("Failed to open configuration file '%s': %s", c->config_file, pa_cstrerror(errno)); + goto finish; + } + + r = f ? pa_config_parse(c->config_file, f, table, NULL) : 0; + +finish: + if (f) + fclose(f); + + return r; +} + +int pa_daemon_conf_env(pa_daemon_conf *c) { + char *e; + + if ((e = getenv(ENV_DL_SEARCH_PATH))) { + pa_xfree(c->dl_search_path); + c->dl_search_path = pa_xstrdup(e); + } + if ((e = getenv(ENV_SCRIPT_FILE))) { + pa_xfree(c->default_script_file); + c->default_script_file = pa_xstrdup(e); + } + + return 0; +} + +static const char* const log_level_to_string[] = { + [PA_LOG_DEBUG] = "debug", + [PA_LOG_INFO] = "info", + [PA_LOG_NOTICE] = "notice", + [PA_LOG_WARN] = "warning", + [PA_LOG_ERROR] = "error" +}; + +char *pa_daemon_conf_dump(pa_daemon_conf *c) { + pa_strbuf *s; + + pa_assert(c); + + s = pa_strbuf_new(); + + if (c->config_file) + pa_strbuf_printf(s, "### Read from configuration file: %s ###\n", c->config_file); + + pa_assert(c->log_level <= PA_LOG_LEVEL_MAX); + + pa_strbuf_printf(s, "daemonize = %s\n", pa_yes_no(c->daemonize)); + pa_strbuf_printf(s, "fail = %s\n", pa_yes_no(c->fail)); + pa_strbuf_printf(s, "high-priority = %s\n", pa_yes_no(c->high_priority)); + pa_strbuf_printf(s, "nice-level = %i\n", c->nice_level); + pa_strbuf_printf(s, "realtime-scheduling = %s\n", pa_yes_no(c->realtime_scheduling)); + pa_strbuf_printf(s, "realtime-priority = %i\n", c->realtime_priority); + pa_strbuf_printf(s, "disallow-module-loading = %s\n", pa_yes_no(c->disallow_module_loading)); + pa_strbuf_printf(s, "use-pid-file = %s\n", pa_yes_no(c->use_pid_file)); + pa_strbuf_printf(s, "system-instance = %s\n", pa_yes_no(c->system_instance)); + pa_strbuf_printf(s, "no-cpu-limit = %s\n", pa_yes_no(c->no_cpu_limit)); + pa_strbuf_printf(s, "disable-shm = %s\n", pa_yes_no(c->disable_shm)); + pa_strbuf_printf(s, "exit-idle-time = %i\n", c->exit_idle_time); + pa_strbuf_printf(s, "module-idle-time = %i\n", c->module_idle_time); + pa_strbuf_printf(s, "scache-idle-time = %i\n", c->scache_idle_time); + pa_strbuf_printf(s, "dl-search-path = %s\n", c->dl_search_path ? c->dl_search_path : ""); + pa_strbuf_printf(s, "default-script-file = %s\n", c->default_script_file); + pa_strbuf_printf(s, "log-target = %s\n", c->auto_log_target ? "auto" : (c->log_target == PA_LOG_SYSLOG ? "syslog" : "stderr")); + pa_strbuf_printf(s, "log-level = %s\n", log_level_to_string[c->log_level]); + pa_strbuf_printf(s, "resample-method = %s\n", pa_resample_method_to_string(c->resample_method)); + pa_strbuf_printf(s, "disable-remixing = %s\n", pa_yes_no(c->disable_remixing)); + pa_strbuf_printf(s, "default-sample-format = %s\n", pa_sample_format_to_string(c->default_sample_spec.format)); + pa_strbuf_printf(s, "default-sample-rate = %u\n", c->default_sample_spec.rate); + pa_strbuf_printf(s, "default-sample-channels = %u\n", c->default_sample_spec.channels); + pa_strbuf_printf(s, "default-fragments = %u\n", c->default_n_fragments); + pa_strbuf_printf(s, "default-fragment-size-msec = %u\n", c->default_fragment_size_msec); +#ifdef HAVE_SYS_RESOURCE_H + pa_strbuf_printf(s, "rlimit-as = %li\n", c->rlimit_as.is_set ? (long int) c->rlimit_as.value : -1); + pa_strbuf_printf(s, "rlimit-core = %li\n", c->rlimit_core.is_set ? (long int) c->rlimit_core.value : -1); + pa_strbuf_printf(s, "rlimit-data = %li\n", c->rlimit_data.is_set ? (long int) c->rlimit_data.value : -1); + pa_strbuf_printf(s, "rlimit-fsize = %li\n", c->rlimit_fsize.is_set ? (long int) c->rlimit_fsize.value : -1); + pa_strbuf_printf(s, "rlimit-nofile = %li\n", c->rlimit_nofile.is_set ? (long int) c->rlimit_nofile.value : -1); + pa_strbuf_printf(s, "rlimit-stack = %li\n", c->rlimit_stack.is_set ? (long int) c->rlimit_stack.value : -1); +#ifdef RLIMIT_NPROC + pa_strbuf_printf(s, "rlimit-nproc = %li\n", c->rlimit_nproc.is_set ? (long int) c->rlimit_nproc.value : -1); +#endif +#ifdef RLIMIT_MEMLOCK + pa_strbuf_printf(s, "rlimit-memlock = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_memlock.value : -1); +#endif +#ifdef RLIMIT_NICE + pa_strbuf_printf(s, "rlimit-nice = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_nice.value : -1); +#endif +#ifdef RLIMIT_RTPRIO + pa_strbuf_printf(s, "rlimit-rtprio = %li\n", c->rlimit_memlock.is_set ? (long int) c->rlimit_rtprio.value : -1); +#endif +#endif + + return pa_strbuf_tostring_free(s); +} diff --git a/src/daemon/daemon-conf.h b/src/daemon/daemon-conf.h new file mode 100644 index 00000000..3dcafbfe --- /dev/null +++ b/src/daemon/daemon-conf.h @@ -0,0 +1,124 @@ +#ifndef foodaemonconfhfoo +#define foodaemonconfhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulse/sample.h> + +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +/* The actual command to execute */ +typedef enum pa_daemon_conf_cmd { + PA_CMD_DAEMON, /* the default */ + PA_CMD_HELP, + PA_CMD_VERSION, + PA_CMD_DUMP_CONF, + PA_CMD_DUMP_MODULES, + PA_CMD_KILL, + PA_CMD_CHECK, + PA_CMD_DUMP_RESAMPLE_METHODS, + PA_CMD_CLEANUP_SHM +} pa_daemon_conf_cmd_t; + +#ifdef HAVE_SYS_RESOURCE_H +typedef struct pa_rlimit { + rlim_t value; + pa_bool_t is_set; +} pa_rlimit; +#endif + +/* A structure containing configuration data for the PulseAudio server . */ +typedef struct pa_daemon_conf { + pa_daemon_conf_cmd_t cmd; + pa_bool_t daemonize, + fail, + high_priority, + realtime_scheduling, + disallow_module_loading, + use_pid_file, + system_instance, + no_cpu_limit, + disable_shm, + disable_remixing; + int exit_idle_time, + module_idle_time, + scache_idle_time, + auto_log_target, + realtime_priority, + nice_level, + resample_method; + char *script_commands, *dl_search_path, *default_script_file; + pa_log_target_t log_target; + pa_log_level_t log_level; + char *config_file; + +#ifdef HAVE_SYS_RESOURCE_H + pa_rlimit rlimit_as, rlimit_core, rlimit_data, rlimit_fsize, rlimit_nofile, rlimit_stack; +#ifdef RLIMIT_NPROC + pa_rlimit rlimit_nproc; +#endif +#ifdef RLIMIT_MEMLOCK + pa_rlimit rlimit_memlock; +#endif +#ifdef RLIMIT_NICE + pa_rlimit rlimit_nice; +#endif +#ifdef RLIMIT_RTPRIO + pa_rlimit rlimit_rtprio; +#endif +#endif + + unsigned default_n_fragments, default_fragment_size_msec; + pa_sample_spec default_sample_spec; +} pa_daemon_conf; + +/* Allocate a new structure and fill it with sane defaults */ +pa_daemon_conf* pa_daemon_conf_new(void); +void pa_daemon_conf_free(pa_daemon_conf*c); + +/* Load configuration data from the specified file overwriting the + * current settings in *c. If filename is NULL load the default daemon + * configuration file */ +int pa_daemon_conf_load(pa_daemon_conf *c, const char *filename); + +/* Pretty print the current configuration data of the daemon. The + * returned string has to be freed manually. The output of this + * function may be parsed with pa_daemon_conf_load(). */ +char *pa_daemon_conf_dump(pa_daemon_conf *c); + +/* Load the configuration data from the process' environment + * overwriting the current settings in *c. */ +int pa_daemon_conf_env(pa_daemon_conf *c); + +/* Set these configuration variables in the structure by passing a string */ +int pa_daemon_conf_set_log_target(pa_daemon_conf *c, const char *string); +int pa_daemon_conf_set_log_level(pa_daemon_conf *c, const char *string); +int pa_daemon_conf_set_resample_method(pa_daemon_conf *c, const char *string); + +#endif diff --git a/src/daemon/daemon.conf.in b/src/daemon/daemon.conf.in new file mode 100644 index 00000000..d664962e --- /dev/null +++ b/src/daemon/daemon.conf.in @@ -0,0 +1,69 @@ +# $Id$ +# +# This file is part of PulseAudio. +# +# 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. + +## Configuration file for the PulseAudio daemon. See pulse-daemon.conf(5) for +## more information. Default values a commented out. Use either ; or # for +## commenting. + +; daemonize = no +; fail = yes +; disallow-module-loading = no +; use-pid-file = yes +; system-instance = no +; disable-shm = no + +; high-priority = yes +; nice-level = -11 + +; realtime-scheduling = no +; realtime-priority = 5 + +; exit-idle-time = -1 +; module-idle-time = 20 +; scache-idle-time = 20 + +; dl-search-path = @PA_DLSEARCHPATH@ + +; default-script-file = @PA_DEFAULT_CONFIG_FILE@ + +; log-target = auto +; log-level = notice + +; resample-method = speex-float-3 +; disable-remixing = no + +; no-cpu-limit = no + +; rlimit-as = -1 +; rlimit-core = -1 +; rlimit-data = -1 +; rlimit-fsize = -1 +; rlimit-nofile = 256 +; rlimit-stack = -1 +; rlimit-nproc = -1 +; rlimit-memlock = -1 +; rlimit-nice = 31 +; rlimit-rtprio = 9 + +; default-sample-format = s16le +; default-sample-rate = 44100 +; default-sample-channels = 2 + +; default-fragments = 4 +; default-fragment-size-msec = 25 diff --git a/src/daemon/default.pa.in b/src/daemon/default.pa.in new file mode 100755 index 00000000..597993c4 --- /dev/null +++ b/src/daemon/default.pa.in @@ -0,0 +1,100 @@ +#!@PA_BINARY@ -nF +# +# This file is part of PulseAudio. +# +# 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. + +.nofail + +### Load something into the sample cache +#load-sample-lazy x11-bell /usr/share/sounds/gtk-events/activate.wav +load-sample-lazy pulse-hotplug /usr/share/sounds/startup3.wav +#load-sample-lazy pulse-coldplug /usr/share/sounds/startup3.wav +#load-sample-lazy pulse-access /usr/share/sounds/generic.wav + +.fail + +### Load audio drivers statically (it's probably better to not load +### these drivers manually, but instead use module-hal-detect -- +### see below -- for doing this automatically) +#load-module module-alsa-sink +#load-module module-alsa-source device=hw:1,0 +#load-module module-oss device="/dev/dsp" sink_name=output source_name=input +#load-module module-oss-mmap device="/dev/dsp" sink_name=output source_name=input +#load-module module-null-sink +#load-module module-pipe-sink + +### Automatically load driver modules depending on the hardware available +.ifexists @PA_DLSEARCHPATH@/module-hal-detect@PA_SOEXT@ +load-module module-hal-detect +.else +### Alternatively use the static hardware detection module (for systems that +### lack HAL support) +load-module module-detect +.endif + +### Load several protocols +load-module module-esound-protocol-unix +load-module module-native-protocol-unix + +### Network access (may be configured with paprefs, so leave this commented +### here if you plan to use paprefs) +#load-module module-esound-protocol-tcp +#load-module module-native-protocol-tcp +#load-module module-zeroconf-publish + +### Load the RTP reciever module (also configured via paprefs, see above) +#load-module module-rtp-recv + +### Load the RTP sender module (also configured via paprefs, see above) +#load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 description="RTP Multicast Sink" +#load-module module-rtp-send source=rtp.monitor + +### Automatically restore the volume of playback streams +load-module module-volume-restore + +### Automatically restore the default sink/source when changed by the user during runtime +load-module module-default-device-restore + +### Automatically move streams to the default sink if the sink they are +### connected to dies, similar for sources +load-module module-rescue-streams + +### Automatically suspend sinks/sources that become idle for too long +load-module module-suspend-on-idle + +### Load X11 bell module +#load-module module-x11-bell sample=x11-bell + +### Publish connection data in the X11 root window +.ifexists @PA_DLSEARCHPATH@/module-x11-publish@PA_SOEXT@ +load-module module-x11-publish +.endif + +### Register ourselves in the X11 session manager +# Deactivated by default, to avoid deadlock when PA is started as esd from gnome-session +# Instead we load this via /etc/xdg/autostart/ and "pactl load-module" now +# load-module module-x11-xsmp + +### Load additional modules from GConf settings. This can be configured with the paprefs tool. +### Please keep in mind that the modules configured by paprefs might conflict with manually +### loaded modules. +.ifexists @PA_DLSEARCHPATH@/module-gconf@PA_SOEXT@ +load-module module-gconf +.endif + +### Make some devices default +#set-default-sink output +#set-default-source input diff --git a/src/daemon/default.pa.win32 b/src/daemon/default.pa.win32 new file mode 100644 index 00000000..d5a1e183 --- /dev/null +++ b/src/daemon/default.pa.win32 @@ -0,0 +1,43 @@ +#
+# This file is part of PulseAudio.
+#
+# 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.
+
+
+# Load audio drivers statically
+
+load-module module-waveout sink_name=output source_name=input
+load-module module-null-sink
+
+# Load audio drivers automatically on access
+
+#add-autoload-sink output module-waveout sink_name=output source_name=input
+#add-autoload-source input module-waveout sink_name=output source_name=input
+
+# Load several protocols
+#load-module module-esound-protocol-tcp
+#load-module module-native-protocol-tcp
+#load-module module-simple-protocol-tcp
+#load-module module-cli-protocol-tcp
+
+# Make some devices default
+set-default-sink output
+set-default-source input
+
+.nofail
+
+# Load something to the sample cache
+load-sample x11-bell %WINDIR%\Media\ding.wav
+load-sample-dir-lazy %WINDIR%\Media\*.wav
diff --git a/src/daemon/dumpmodules.c b/src/daemon/dumpmodules.c new file mode 100644 index 00000000..68236c70 --- /dev/null +++ b/src/daemon/dumpmodules.c @@ -0,0 +1,158 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> +#include <getopt.h> +#include <stdio.h> +#include <ltdl.h> + +#include <pulse/util.h> + +#include <pulsecore/modinfo.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "dumpmodules.h" + +#define PREFIX "module-" + +static void short_info(const char *name, PA_GCC_UNUSED const char *path, pa_modinfo *i) { + pa_assert(name); + pa_assert(i); + + printf("%-40s%s\n", name, i->description ? i->description : "n/a"); +} + +static void long_info(const char *name, const char *path, pa_modinfo *i) { + static int nl = 0; + pa_assert(name); + pa_assert(i); + + if (nl) + printf("\n"); + + nl = 1; + + printf("Name: %s\n", name); + + if (!i->description && !i->version && !i->author && !i->usage) + printf("No module information available\n"); + else { + if (i->version) + printf("Version: %s\n", i->version); + if (i->description) + printf("Description: %s\n", i->description); + if (i->author) + printf("Author: %s\n", i->author); + if (i->usage) + printf("Usage: %s\n", i->usage); + printf("Load Once: %s\n", pa_yes_no(i->load_once)); + } + + if (path) + printf("Path: %s\n", path); +} + +static void show_info(const char *name, const char *path, void (*info)(const char *name, const char *path, pa_modinfo*i)) { + pa_modinfo *i; + + pa_assert(name); + + if ((i = pa_modinfo_get_by_name(path ? path : name))) { + info(name, path, i); + pa_modinfo_free(i); + } +} + +extern const lt_dlsymlist lt_preloaded_symbols[]; + +static int is_preloaded(const char *name) { + const lt_dlsymlist *l; + + for (l = lt_preloaded_symbols; l->name; l++) { + char buf[64], *e; + + if (l->address) + continue; + + pa_snprintf(buf, sizeof(buf), "%s", l->name); + if ((e = strrchr(buf, '.'))) + *e = 0; + + if (!strcmp(name, buf)) + return 1; + } + + return 0; +} + +static int callback(const char *path, lt_ptr data) { + const char *e; + pa_daemon_conf *c = (data); + + e = pa_path_get_filename(path); + + if (strlen(e) <= sizeof(PREFIX)-1 || strncmp(e, PREFIX, sizeof(PREFIX)-1)) + return 0; + + if (is_preloaded(e)) + return 0; + + show_info(e, path, c->log_level >= PA_LOG_INFO ? long_info : short_info); + return 0; +} + +void pa_dump_modules(pa_daemon_conf *c, int argc, char * const argv[]) { + pa_assert(c); + + if (argc > 0) { + int i; + for (i = 0; i < argc; i++) + show_info(argv[i], NULL, long_info); + } else { + const lt_dlsymlist *l; + + for (l = lt_preloaded_symbols; l->name; l++) { + char buf[64], *e; + + if (l->address) + continue; + + if (strlen(l->name) <= sizeof(PREFIX)-1 || strncmp(l->name, PREFIX, sizeof(PREFIX)-1)) + continue; + + pa_snprintf(buf, sizeof(buf), "%s", l->name); + if ((e = strrchr(buf, '.'))) + *e = 0; + + show_info(buf, NULL, c->log_level >= PA_LOG_INFO ? long_info : short_info); + } + + lt_dlforeachfile(NULL, callback, c); + } +} diff --git a/src/daemon/dumpmodules.h b/src/daemon/dumpmodules.h new file mode 100644 index 00000000..ab2ddb64 --- /dev/null +++ b/src/daemon/dumpmodules.h @@ -0,0 +1,33 @@ +#ifndef foodumpmoduleshfoo +#define foodumpmoduleshfoo + +/* $Id*/ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 "daemon-conf.h" + +/* Dump all available modules to STDOUT. If argc > 0 print information + * about the modules specified in argv[] instead. */ +void pa_dump_modules(pa_daemon_conf *c, int argc, char * const argv[]); + +#endif diff --git a/src/daemon/esdcompat.in b/src/daemon/esdcompat.in new file mode 100755 index 00000000..942389d2 --- /dev/null +++ b/src/daemon/esdcompat.in @@ -0,0 +1,98 @@ +#!/bin/sh + +# $Id$ +# +# This file is part of PulseAudio. +# +# 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. + +VERSION_STRING="@PACKAGE_NAME@ esd wrapper @PACKAGE_VERSION@" + +fail() { + echo "ERROR: $1" + exit 1 +} + +ARGS=" --log-target=syslog" + +while [ "$#" -gt "0" ]; do + + case "$1" in + "") + ;; + + -v|--version) + echo "$VERSION_STRING" + exit 0 + ;; + + -h|--help) + cat <<EOF +$VERSION_STRING + +Usage: $0 [options] + + -v --version print version information + -h --help show this help + +Ignored directives: + + -tcp use tcp/ip sockets in addition to unix domain + -promiscuous don't require authentication + -d DEVICE force esd to use sound device DEVICE + -b run server in 8 bit sound mode + -r RATE run server at sample rate of RATE + -as SECS free audio device after SECS of inactivity + -unix use unix domain sockets instead of tcp/ip + -public make tcp/ip access public (other than localhost) + -terminate terminate esd daemone after last client exits + -nobeeps disable startup beeps + -trust start esd even if use of /tmp/.esd can be insecure + -port PORT listen for connections at PORT (only for tcp/ip) + -bind ADDRESS binds to ADDRESS (only for tcp/ip) +EOF + exit 0 + ;; + + -spawnpid) + shift + ARGS="$ARGS '-Lmodule-esound-compat-spawnpid pid=$1'" + ;; + + -spawnfd) + shift + ARGS="$ARGS '-Lmodule-esound-compat-spawnfd fd=$1'" + ;; + + -unix|-b|-public|-terminate|-nobeeps|-trust|-tcp|-promiscuous) + # Ignore these commands + ;; + + -d|-r|-as|-port|-bind) + # Ignore these commands and their arguments + shift + + ;; + + *) + fail "Unknown command: $1" + ;; + esac + + shift +done + +eval "exec '@PA_BINARY@'$ARGS" diff --git a/src/daemon/ltdl-bind-now.c b/src/daemon/ltdl-bind-now.c new file mode 100644 index 00000000..6915fe0c --- /dev/null +++ b/src/daemon/ltdl-bind-now.c @@ -0,0 +1,197 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 + +#if HAVE_DLFCN_H +#include <dlfcn.h> +#endif + +#if HAVE_SYS_DL_H +#include <sys/dl.h> +#endif + +#ifndef HAVE_STRUCT_LT_USER_DLLOADER +/* Only used with ltdl 2.2 */ +#include <string.h> +#endif + +#include <ltdl.h> + +#include <pulsecore/macro.h> +#include <pulsecore/mutex.h> +#include <pulsecore/thread.h> +#include <pulsecore/log.h> + +#include "ltdl-bind-now.h" + +#ifdef RTLD_NOW +#define PA_BIND_NOW RTLD_NOW +#elif defined(DL_NOW) +#define PA_BIND_NOW DL_NOW +#else +#undef PA_BIND_NOW +#endif + +static pa_mutex *libtool_mutex = NULL; + +PA_STATIC_TLS_DECLARE_NO_FREE(libtool_tls); + +static void libtool_lock(void) { + pa_mutex_lock(libtool_mutex); +} + +static void libtool_unlock(void) { + pa_mutex_unlock(libtool_mutex); +} + +static void libtool_set_error(const char *error) { + PA_STATIC_TLS_SET(libtool_tls, (char*) error); +} + +static const char *libtool_get_error(void) { + return PA_STATIC_TLS_GET(libtool_tls); +} + +#ifdef PA_BIND_NOW + +/* + To avoid lazy relocations during runtime in our RT threads we add + our own shared object loader with uses RTLD_NOW if it is + available. The standard ltdl loader prefers RTLD_LAZY. + + Please note that this loader doesn't have any influence on + relocations on any libraries that are already loaded into our + process, i.e. because the pulseaudio binary links directly to + them. To disable lazy relocations for those libraries it is possible + to set $LT_BIND_NOW before starting the pulsaudio binary. +*/ + +#ifndef HAVE_LT_DLADVISE +static lt_module bind_now_open(lt_user_data d, const char *fname) { +#else + static lt_module bind_now_open(lt_user_data d, const char *fname, lt_dladvise advise) { +#endif + lt_module m; + + pa_assert(fname); + + if (!(m = dlopen(fname, PA_BIND_NOW))) { + libtool_set_error(dlerror()); + return NULL; + } + + return m; +} + +static int bind_now_close(lt_user_data d, lt_module m) { + + pa_assert(m); + + if (dlclose(m) != 0){ + libtool_set_error(dlerror()); + return 1; + } + + return 0; +} + +static lt_ptr bind_now_find_sym(lt_user_data d, lt_module m, const char *symbol) { + lt_ptr ptr; + + pa_assert(m); + pa_assert(symbol); + + if (!(ptr = dlsym(m, symbol))) { + libtool_set_error(dlerror()); + return NULL; + } + + return ptr; +} + +#endif + +void pa_ltdl_init(void) { + +#ifdef PA_BIND_NOW +# ifdef HAVE_STRUCT_LT_USER_DLLOADER + lt_dlloader *place; + static const struct lt_user_dlloader loader = { + .module_open = bind_now_open, + .module_close = bind_now_close, + .find_sym = bind_now_find_sym + }; +# else + static const lt_dlvtable *dlopen_loader; + static lt_dlvtable bindnow_loader; +# endif +#endif + + pa_assert_se(lt_dlinit() == 0); + pa_assert_se(libtool_mutex = pa_mutex_new(TRUE, FALSE)); +#ifdef HAVE_LT_DLMUTEX_REGISTER + pa_assert_se(lt_dlmutex_register(libtool_lock, libtool_unlock, libtool_set_error, libtool_get_error) == 0); +#endif + +#ifdef PA_BIND_NOW +# ifdef HAVE_STRUCT_LT_USER_DLLOADER + + if (!(place = lt_dlloader_find("dlopen"))) + place = lt_dlloader_next(NULL); + + /* Add our BIND_NOW loader as the default module loader. */ + if (lt_dlloader_add(place, &loader, "bind-now-loader") != 0) + pa_log_warn("Failed to add bind-now-loader."); +# else + /* Already initialised */ + if ( dlopen_loader != NULL ) return; + + if (!(dlopen_loader = lt_dlloader_find("dlopen"))) { + pa_log_warn("Failed to find original dlopen loader."); + return; + } + + memcpy(&bindnow_loader, dlopen_loader, sizeof(bindnow_loader)); + bindnow_loader.name = "bind-now-loader"; + bindnow_loader.module_open = bind_now_open; + bindnow_loader.module_close = bind_now_close; + bindnow_loader.find_sym = bind_now_find_sym; + bindnow_loader.priority = LT_DLLOADER_PREPEND; + + /* Add our BIND_NOW loader as the default module loader. */ + if (lt_dlloader_add(&bindnow_loader) != 0) + pa_log_warn("Failed to add bind-now-loader."); +# endif +#endif +} + +void pa_ltdl_done(void) { + pa_assert_se(lt_dlexit() == 0); + pa_mutex_free(libtool_mutex); + libtool_mutex = NULL; +} + diff --git a/src/daemon/ltdl-bind-now.h b/src/daemon/ltdl-bind-now.h new file mode 100644 index 00000000..e19c7bc1 --- /dev/null +++ b/src/daemon/ltdl-bind-now.h @@ -0,0 +1,32 @@ +#ifndef foopulsecoreltdlbindnowhfoo +#define foopulsecoreltdlbindnowhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + + +void pa_ltdl_init(void); +void pa_ltdl_done(void); + +#endif + diff --git a/src/daemon/main.c b/src/daemon/main.c new file mode 100644 index 00000000..7823180a --- /dev/null +++ b/src/daemon/main.c @@ -0,0 +1,850 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> +#include <stddef.h> +#include <ltdl.h> +#include <limits.h> +#include <fcntl.h> +#include <unistd.h> +#include <locale.h> +#include <sys/types.h> + +#include <liboil/liboil.h> + +#ifdef HAVE_SYS_IOCTL_H +#include <sys/ioctl.h> +#endif + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#ifdef HAVE_LIBWRAP +#include <syslog.h> +#include <tcpd.h> +#endif + +#ifdef HAVE_DBUS +#include <dbus/dbus.h> +#endif + +#include <pulse/mainloop.h> +#include <pulse/mainloop-signal.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/winsock.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core.h> +#include <pulsecore/memblock.h> +#include <pulsecore/module.h> +#include <pulsecore/cli-command.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sioman.h> +#include <pulsecore/cli-text.h> +#include <pulsecore/pid.h> +#include <pulsecore/namereg.h> +#include <pulsecore/random.h> +#include <pulsecore/rtsig.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/macro.h> +#include <pulsecore/mutex.h> +#include <pulsecore/thread.h> +#include <pulsecore/once.h> +#include <pulsecore/shm.h> + +#include "cmdline.h" +#include "cpulimit.h" +#include "daemon-conf.h" +#include "dumpmodules.h" +#include "caps.h" +#include "ltdl-bind-now.h" +#include "polkit.h" + +#ifdef HAVE_LIBWRAP +/* Only one instance of these variables */ +int allow_severity = LOG_INFO; +int deny_severity = LOG_WARNING; +#endif + +#ifdef HAVE_OSS +/* padsp looks for this symbol in the running process and disables + * itself if it finds it and it is set to 7 (which is actually a bit + * mask). For details see padsp. */ +int __padsp_disabled__ = 7; +#endif + +#ifdef OS_IS_WIN32 + +static void message_cb(pa_mainloop_api*a, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { + MSG msg; + struct timeval tvnext; + + while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { + if (msg.message == WM_QUIT) + raise(SIGTERM); + else { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + + pa_timeval_add(pa_gettimeofday(&tvnext), 100000); + a->time_restart(e, &tvnext); +} + +#endif + +static void signal_callback(pa_mainloop_api*m, PA_GCC_UNUSED pa_signal_event *e, int sig, void *userdata) { + pa_log_info("Got signal %s.", pa_sig2str(sig)); + + switch (sig) { +#ifdef SIGUSR1 + case SIGUSR1: + pa_module_load(userdata, "module-cli", NULL); + break; +#endif + +#ifdef SIGUSR2 + case SIGUSR2: + pa_module_load(userdata, "module-cli-protocol-unix", NULL); + break; +#endif + +#ifdef SIGHUP + case SIGHUP: { + char *c = pa_full_status_string(userdata); + pa_log_notice("%s", c); + pa_xfree(c); + return; + } +#endif + + case SIGINT: + case SIGTERM: + default: + pa_log_info("Exiting."); + m->quit(m, 1); + break; + } +} + +#define set_env(key, value) putenv(pa_sprintf_malloc("%s=%s", (key), (value))) + +#if defined(HAVE_PWD_H) && defined(HAVE_GRP_H) + +static int change_user(void) { + struct passwd *pw; + struct group * gr; + int r; + + /* This function is called only in system-wide mode. It creates a + * runtime dir in /var/run/ with proper UID/GID and drops privs + * afterwards. */ + + if (!(pw = getpwnam(PA_SYSTEM_USER))) { + pa_log("Failed to find user '%s'.", PA_SYSTEM_USER); + return -1; + } + + if (!(gr = getgrnam(PA_SYSTEM_GROUP))) { + pa_log("Failed to find group '%s'.", PA_SYSTEM_GROUP); + return -1; + } + + pa_log_info("Found user '%s' (UID %lu) and group '%s' (GID %lu).", + PA_SYSTEM_USER, (unsigned long) pw->pw_uid, + PA_SYSTEM_GROUP, (unsigned long) gr->gr_gid); + + if (pw->pw_gid != gr->gr_gid) { + pa_log("GID of user '%s' and of group '%s' don't match.", PA_SYSTEM_USER, PA_SYSTEM_GROUP); + return -1; + } + + if (strcmp(pw->pw_dir, PA_SYSTEM_RUNTIME_PATH) != 0) + pa_log_warn("Warning: home directory of user '%s' is not '%s', ignoring.", PA_SYSTEM_USER, PA_SYSTEM_RUNTIME_PATH); + + if (pa_make_secure_dir(PA_SYSTEM_RUNTIME_PATH, 0755, pw->pw_uid, gr->gr_gid) < 0) { + pa_log("Failed to create '%s': %s", PA_SYSTEM_RUNTIME_PATH, pa_cstrerror(errno)); + return -1; + } + + if (initgroups(PA_SYSTEM_USER, gr->gr_gid) != 0) { + pa_log("Failed to change group list: %s", pa_cstrerror(errno)); + return -1; + } + +#if defined(HAVE_SETRESGID) + r = setresgid(gr->gr_gid, gr->gr_gid, gr->gr_gid); +#elif defined(HAVE_SETEGID) + if ((r = setgid(gr->gr_gid)) >= 0) + r = setegid(gr->gr_gid); +#elif defined(HAVE_SETREGID) + r = setregid(gr->gr_gid, gr->gr_gid); +#else +#error "No API to drop priviliges" +#endif + + if (r < 0) { + pa_log("Failed to change GID: %s", pa_cstrerror(errno)); + return -1; + } + +#if defined(HAVE_SETRESUID) + r = setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid); +#elif defined(HAVE_SETEUID) + if ((r = setuid(pw->pw_uid)) >= 0) + r = seteuid(pw->pw_uid); +#elif defined(HAVE_SETREUID) + r = setreuid(pw->pw_uid, pw->pw_uid); +#else +#error "No API to drop priviliges" +#endif + + if (r < 0) { + pa_log("Failed to change UID: %s", pa_cstrerror(errno)); + return -1; + } + + set_env("USER", PA_SYSTEM_USER); + set_env("LOGNAME", PA_SYSTEM_GROUP); + set_env("HOME", PA_SYSTEM_RUNTIME_PATH); + + /* Relevant for pa_runtime_path() */ + set_env("PULSE_RUNTIME_PATH", PA_SYSTEM_RUNTIME_PATH); + set_env("PULSE_CONFIG_PATH", PA_SYSTEM_RUNTIME_PATH); + + pa_log_info("Successfully dropped root privileges."); + + return 0; +} + +#else /* HAVE_PWD_H && HAVE_GRP_H */ + +static int change_user(void) { + pa_log("System wide mode unsupported on this platform."); + return -1; +} + +#endif /* HAVE_PWD_H && HAVE_GRP_H */ + +static int create_runtime_dir(void) { + char fn[PATH_MAX]; + + pa_runtime_path(NULL, fn, sizeof(fn)); + + /* This function is called only when the daemon is started in + * per-user mode. We create the runtime directory somewhere in + * /tmp/ with the current UID/GID */ + + if (pa_make_secure_dir(fn, 0700, (uid_t)-1, (gid_t)-1) < 0) { + pa_log("Failed to create '%s': %s", fn, pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +#ifdef HAVE_SYS_RESOURCE_H + +static int set_one_rlimit(const pa_rlimit *r, int resource, const char *name) { + struct rlimit rl; + pa_assert(r); + + if (!r->is_set) + return 0; + + rl.rlim_cur = rl.rlim_max = r->value; + + if (setrlimit(resource, &rl) < 0) { + pa_log_warn("setrlimit(%s, (%u, %u)) failed: %s", name, (unsigned) r->value, (unsigned) r->value, pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +static void set_all_rlimits(const pa_daemon_conf *conf) { + set_one_rlimit(&conf->rlimit_as, RLIMIT_AS, "RLIMIT_AS"); + set_one_rlimit(&conf->rlimit_core, RLIMIT_CORE, "RLIMIT_CORE"); + set_one_rlimit(&conf->rlimit_data, RLIMIT_DATA, "RLIMIT_DATA"); + set_one_rlimit(&conf->rlimit_fsize, RLIMIT_FSIZE, "RLIMIT_FSIZE"); + set_one_rlimit(&conf->rlimit_nofile, RLIMIT_NOFILE, "RLIMIT_NOFILE"); + set_one_rlimit(&conf->rlimit_stack, RLIMIT_STACK, "RLIMIT_STACK"); +#ifdef RLIMIT_NPROC + set_one_rlimit(&conf->rlimit_nproc, RLIMIT_NPROC, "RLIMIT_NPROC"); +#endif +#ifdef RLIMIT_MEMLOCK + set_one_rlimit(&conf->rlimit_memlock, RLIMIT_MEMLOCK, "RLIMIT_MEMLOCK"); +#endif +#ifdef RLIMIT_NICE + set_one_rlimit(&conf->rlimit_nice, RLIMIT_NICE, "RLIMIT_NICE"); +#endif +#ifdef RLIMIT_RTPRIO + set_one_rlimit(&conf->rlimit_rtprio, RLIMIT_RTPRIO, "RLIMIT_RTPRIO"); +#endif +} +#endif + +int main(int argc, char *argv[]) { + pa_core *c = NULL; + pa_strbuf *buf = NULL; + pa_daemon_conf *conf = NULL; + pa_mainloop *mainloop = NULL; + char *s; + int r = 0, retval = 1, d = 0; + int daemon_pipe[2] = { -1, -1 }; + pa_bool_t suid_root, real_root; + int valid_pid_file = 0; + gid_t gid = (gid_t) -1; + pa_bool_t allow_realtime, allow_high_priority; + pa_bool_t ltdl_init = FALSE; + +#ifdef OS_IS_WIN32 + pa_time_event *timer; + struct timeval tv; +#endif + + +#if defined(__linux__) && defined(__OPTIMIZE__) + /* + Disable lazy relocations to make usage of external libraries + more deterministic for our RT threads. We abuse __OPTIMIZE__ as + a check whether we are a debug build or not. + */ + + if (!getenv("LD_BIND_NOW")) { + char *rp; + + /* We have to execute ourselves, because the libc caches the + * value of $LD_BIND_NOW on initialization. */ + + putenv(pa_xstrdup("LD_BIND_NOW=1")); + pa_assert_se(rp = pa_readlink("/proc/self/exe")); + pa_assert_se(execv(rp, argv) == 0); + } +#endif + +#ifdef HAVE_GETUID + real_root = getuid() == 0; + suid_root = !real_root && geteuid() == 0; +#else + real_root = FALSE; + suid_root = FALSE; +#endif + + if (suid_root) { + /* Drop all capabilities except CAP_SYS_NICE */ + pa_limit_caps(); + + /* Drop priviliges, but keep CAP_SYS_NICE */ + pa_drop_root(); + + /* After dropping root, the effective set is reset, hence, + * let's raise it again */ + pa_limit_caps(); + + /* When capabilities are not supported we will not be able to + * aquire RT sched anymore. But yes, that's the way it is. It + * is just too risky tun let PA run as root all the time. */ + } + + /* At this point, we are a normal user, possibly with CAP_NICE if + * we were started SUID. If we are started as normal root, than we + * still are normal root. */ + + setlocale(LC_ALL, ""); + pa_log_set_maximal_level(PA_LOG_INFO); + pa_log_set_ident("pulseaudio"); + + conf = pa_daemon_conf_new(); + + if (pa_daemon_conf_load(conf, NULL) < 0) + goto finish; + + if (pa_daemon_conf_env(conf) < 0) + goto finish; + + if (pa_cmdline_parse(conf, argc, argv, &d) < 0) { + pa_log("Failed to parse command line."); + goto finish; + } + + pa_log_set_maximal_level(conf->log_level); + pa_log_set_target(conf->auto_log_target ? PA_LOG_STDERR : conf->log_target, NULL); + + if (suid_root) { + /* Ok, we're suid root, so let's better not enable high prio + * or RT by default */ + + allow_high_priority = allow_realtime = FALSE; + +#ifdef HAVE_POLKIT + if (conf->high_priority) { + if (pa_polkit_check("org.pulseaudio.acquire-high-priority") > 0) { + pa_log_info("PolicyKit grants us acquire-high-priority privilige."); + allow_high_priority = TRUE; + } else + pa_log_info("PolicyKit refuses acquire-high-priority privilige."); + } + + if (conf->realtime_scheduling) { + if (pa_polkit_check("org.pulseaudio.acquire-real-time") > 0) { + pa_log_info("PolicyKit grants us acquire-real-time privilige."); + allow_realtime = TRUE; + } else + pa_log_info("PolicyKit refuses acquire-real-time privilige."); + } +#endif + + if ((conf->high_priority || conf->realtime_scheduling) && pa_own_uid_in_group(PA_REALTIME_GROUP, &gid) > 0) { + pa_log_info("We're in the group '"PA_REALTIME_GROUP"', allowing real-time and high-priority scheduling."); + allow_realtime = conf->realtime_scheduling; + allow_high_priority = conf->high_priority; + } + + if (!allow_high_priority && !allow_realtime) { + + /* OK, there's no further need to keep CAP_NICE. Hence + * let's give it up early */ + + pa_drop_caps(); + pa_drop_root(); + suid_root = real_root = FALSE; + + if (conf->high_priority || conf->realtime_scheduling) + pa_log_notice("Called SUID root and real-time/high-priority scheduling was requested in the configuration. However, we lack the necessary priviliges:\n" + "We are not in group '"PA_REALTIME_GROUP"' and PolicyKit refuse to grant us priviliges. Dropping SUID again.\n" + "For enabling real-time scheduling please acquire the appropriate PolicyKit priviliges, or become a member of '"PA_REALTIME_GROUP"', or increase the RLIMIT_NICE/RLIMIT_RTPRIO resource limits for this user."); + } + + } else { + + /* OK, we're a normal user, so let's allow the user evrything + * he asks for, it's now the kernel's job to enforce limits, + * not ours anymore */ + allow_high_priority = allow_realtime = TRUE; + } + + if (conf->high_priority && !allow_high_priority) { + pa_log_info("High-priority scheduling enabled in configuration but now allowed by policy. Disabling forcibly."); + conf->high_priority = FALSE; + } + + if (conf->realtime_scheduling && !allow_realtime) { + pa_log_info("Real-time scheduling enabled in configuration but now allowed by policy. Disabling forcibly."); + conf->realtime_scheduling = FALSE; + } + + if (conf->high_priority && conf->cmd == PA_CMD_DAEMON) + pa_raise_priority(conf->nice_level); + + if (suid_root) { + pa_bool_t drop; + + drop = conf->cmd != PA_CMD_DAEMON || !conf->realtime_scheduling; + +#ifdef RLIMIT_RTPRIO + if (!drop) { + + /* At this point we still have CAP_NICE if we were loaded + * SUID root. If possible let's acquire RLIMIT_RTPRIO + * instead and give CAP_NICE up. */ + + const pa_rlimit rl = { 9, TRUE }; + + if (set_one_rlimit(&rl, RLIMIT_RTPRIO, "RLIMIT_RTPRIO") >= 0) { + pa_log_info("Successfully increased RLIMIT_RTPRIO, giving up CAP_NICE."); + drop = TRUE; + } else + pa_log_warn("RLIMIT_RTPRIO failed: %s", pa_cstrerror(errno)); + } +#endif + + if (drop) { + pa_drop_caps(); + pa_drop_root(); + suid_root = real_root = FALSE; + } + } + + LTDL_SET_PRELOADED_SYMBOLS(); + pa_ltdl_init(); + ltdl_init = TRUE; + + if (conf->dl_search_path) + lt_dlsetsearchpath(conf->dl_search_path); + +#ifdef OS_IS_WIN32 + { + WSADATA data; + WSAStartup(MAKEWORD(2, 0), &data); + } +#endif + + pa_random_seed(); + + switch (conf->cmd) { + case PA_CMD_DUMP_MODULES: + pa_dump_modules(conf, argc-d, argv+d); + retval = 0; + goto finish; + + case PA_CMD_DUMP_CONF: { + s = pa_daemon_conf_dump(conf); + fputs(s, stdout); + pa_xfree(s); + retval = 0; + goto finish; + } + + case PA_CMD_DUMP_RESAMPLE_METHODS: { + int i; + + for (i = 0; i < PA_RESAMPLER_MAX; i++) + if (pa_resample_method_supported(i)) + printf("%s\n", pa_resample_method_to_string(i)); + + goto finish; + } + + case PA_CMD_HELP : + pa_cmdline_help(argv[0]); + retval = 0; + goto finish; + + case PA_CMD_VERSION : + printf(PACKAGE_NAME" "PACKAGE_VERSION"\n"); + retval = 0; + goto finish; + + case PA_CMD_CHECK: { + pid_t pid; + + if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) + pa_log_info("Daemon not running"); + else { + pa_log_info("Daemon running as PID %u", pid); + retval = 0; + } + + goto finish; + + } + case PA_CMD_KILL: + + if (pa_pid_file_kill(SIGINT, NULL, "pulseaudio") < 0) + pa_log("Failed to kill daemon."); + else + retval = 0; + + goto finish; + + case PA_CMD_CLEANUP_SHM: + + if (pa_shm_cleanup() >= 0) + retval = 0; + + goto finish; + + default: + pa_assert(conf->cmd == PA_CMD_DAEMON); + } + + if (real_root && !conf->system_instance) + pa_log_warn("This program is not intended to be run as root (unless --system is specified)."); + else if (!real_root && conf->system_instance) { + pa_log("Root priviliges required."); + goto finish; + } + + if (conf->daemonize) { + pid_t child; + int tty_fd; + + if (pa_stdio_acquire() < 0) { + pa_log("Failed to acquire stdio."); + goto finish; + } + +#ifdef HAVE_FORK + if (pipe(daemon_pipe) < 0) { + pa_log("Failed to create pipe."); + goto finish; + } + + if ((child = fork()) < 0) { + pa_log("fork() failed: %s", pa_cstrerror(errno)); + goto finish; + } + + if (child != 0) { + /* Father */ + + pa_assert_se(pa_close(daemon_pipe[1]) == 0); + daemon_pipe[1] = -1; + + if (pa_loop_read(daemon_pipe[0], &retval, sizeof(retval), NULL) != sizeof(retval)) { + pa_log("read() failed: %s", pa_cstrerror(errno)); + retval = 1; + } + + if (retval) + pa_log("daemon startup failed."); + else + pa_log_info("daemon startup successful."); + + goto finish; + } + + pa_assert_se(pa_close(daemon_pipe[0]) == 0); + daemon_pipe[0] = -1; +#endif + + if (conf->auto_log_target) + pa_log_set_target(PA_LOG_SYSLOG, NULL); + +#ifdef HAVE_SETSID + setsid(); +#endif +#ifdef HAVE_SETPGID + setpgid(0,0); +#endif + +#ifndef OS_IS_WIN32 + pa_close(0); + pa_close(1); + pa_close(2); + + open("/dev/null", O_RDONLY); + open("/dev/null", O_WRONLY); + open("/dev/null", O_WRONLY); +#else + FreeConsole(); +#endif + +#ifdef SIGTTOU + signal(SIGTTOU, SIG_IGN); +#endif +#ifdef SIGTTIN + signal(SIGTTIN, SIG_IGN); +#endif +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + +#ifdef TIOCNOTTY + if ((tty_fd = open("/dev/tty", O_RDWR)) >= 0) { + ioctl(tty_fd, TIOCNOTTY, (char*) 0); + pa_assert_se(pa_close(tty_fd) == 0); + } +#endif + } + + pa_assert_se(chdir("/") == 0); + umask(0022); + + if (conf->system_instance) { + if (change_user() < 0) + goto finish; + } else if (create_runtime_dir() < 0) + goto finish; + + if (conf->use_pid_file) { + if (pa_pid_file_create() < 0) { + pa_log("pa_pid_file_create() failed."); +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); +#endif + goto finish; + } + + valid_pid_file = 1; + } + +#ifdef HAVE_SYS_RESOURCE_H + set_all_rlimits(conf); +#endif + +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + pa_log_info("This is PulseAudio " PACKAGE_VERSION); + pa_log_info("Page size is %lu bytes", (unsigned long) PA_PAGE_SIZE); + + if (pa_rtclock_hrtimer()) + pa_log_info("Fresh high-resolution timers available! Bon appetit!"); + else + pa_log_info("Dude, your kernel stinks! The chef's recommendation today is Linux with high-resolution timers enabled!"); + +#ifdef SIGRTMIN + /* Valgrind uses SIGRTMAX. To easy debugging we don't use it here */ + pa_rtsig_configure(SIGRTMIN, SIGRTMAX-1); +#endif + + pa_assert_se(mainloop = pa_mainloop_new()); + + if (!(c = pa_core_new(pa_mainloop_get_api(mainloop), !conf->disable_shm))) { + pa_log("pa_core_new() failed."); + goto finish; + } + + c->is_system_instance = !!conf->system_instance; + c->default_sample_spec = conf->default_sample_spec; + c->default_n_fragments = conf->default_n_fragments; + c->default_fragment_size_msec = conf->default_fragment_size_msec; + c->exit_idle_time = conf->exit_idle_time; + c->module_idle_time = conf->module_idle_time; + c->scache_idle_time = conf->scache_idle_time; + c->resample_method = conf->resample_method; + c->realtime_priority = conf->realtime_priority; + c->realtime_scheduling = !!conf->realtime_scheduling; + c->disable_remixing = !!conf->disable_remixing; + + pa_assert_se(pa_signal_init(pa_mainloop_get_api(mainloop)) == 0); + pa_signal_new(SIGINT, signal_callback, c); + pa_signal_new(SIGTERM, signal_callback, c); + +#ifdef SIGUSR1 + pa_signal_new(SIGUSR1, signal_callback, c); +#endif +#ifdef SIGUSR2 + pa_signal_new(SIGUSR2, signal_callback, c); +#endif +#ifdef SIGHUP + pa_signal_new(SIGHUP, signal_callback, c); +#endif + +#ifdef OS_IS_WIN32 + pa_assert_se(timer = pa_mainloop_get_api(mainloop)->time_new(pa_mainloop_get_api(mainloop), pa_gettimeofday(&tv), message_cb, NULL)); +#endif + + if (conf->daemonize) + c->running_as_daemon = TRUE; + + oil_init(); + + if (!conf->no_cpu_limit) + pa_assert_se(pa_cpu_limit_init(pa_mainloop_get_api(mainloop)) == 0); + + buf = pa_strbuf_new(); + if (conf->default_script_file) + r = pa_cli_command_execute_file(c, conf->default_script_file, buf, &conf->fail); + + if (r >= 0) + r = pa_cli_command_execute(c, conf->script_commands, buf, &conf->fail); + pa_log_error("%s", s = pa_strbuf_tostring_free(buf)); + pa_xfree(s); + + /* We completed the initial module loading, so let's disable it + * from now on, if requested */ + c->disallow_module_loading = !!conf->disallow_module_loading; + + if (r < 0 && conf->fail) { + pa_log("failed to initialize daemon."); +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); +#endif + } else if (!c->modules || pa_idxset_size(c->modules) == 0) { + pa_log("daemon startup without any loaded modules, refusing to work."); +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); +#endif + } else { + + retval = 0; +#ifdef HAVE_FORK + if (conf->daemonize) + pa_loop_write(daemon_pipe[1], &retval, sizeof(retval), NULL); +#endif + + if (c->default_sink_name && + pa_namereg_get(c, c->default_sink_name, PA_NAMEREG_SINK, 1) == NULL) { + pa_log_error("%s : Fatal error. Default sink name (%s) does not exist in name register.", __FILE__, c->default_sink_name); + retval = 1; + } else { + pa_log_info("Daemon startup complete."); + if (pa_mainloop_run(mainloop, &retval) < 0) + retval = 1; + pa_log_info("Daemon shutdown initiated."); + } + } + +#ifdef OS_IS_WIN32 + pa_mainloop_get_api(mainloop)->time_free(timer); +#endif + + pa_core_unref(c); + + if (!conf->no_cpu_limit) + pa_cpu_limit_done(); + + pa_signal_done(); + + pa_log_info("Daemon terminated."); + +finish: + + if (mainloop) + pa_mainloop_free(mainloop); + + if (conf) + pa_daemon_conf_free(conf); + + if (valid_pid_file) + pa_pid_file_remove(); + + pa_close_pipe(daemon_pipe); + +#ifdef OS_IS_WIN32 + WSACleanup(); +#endif + + if (ltdl_init) + pa_ltdl_done(); + +#ifdef HAVE_DBUS + dbus_shutdown(); +#endif + + return retval; +} diff --git a/src/daemon/polkit.c b/src/daemon/polkit.c new file mode 100644 index 00000000..362c5194 --- /dev/null +++ b/src/daemon/polkit.c @@ -0,0 +1,223 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <stdlib.h> +#include <inttypes.h> + +#include <dbus/dbus.h> +#include <polkit-dbus/polkit-dbus.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "polkit.h" + +static pa_bool_t show_grant_dialog(const char *action_id) { + DBusError dbus_error; + DBusConnection *bus = NULL; + DBusMessage *m = NULL, *reply = NULL; + pa_bool_t r = FALSE; + uint32_t xid = 0; + int verdict; + + dbus_error_init(&dbus_error); + + if (!(bus = dbus_bus_get(DBUS_BUS_SESSION, &dbus_error))) { + pa_log_error("Cannot connect to session bus: %s", dbus_error.message); + goto finish; + } + + if (!(m = dbus_message_new_method_call("org.gnome.PolicyKit", "/org/gnome/PolicyKit/Manager", "org.gnome.PolicyKit.Manager", "ShowDialog"))) { + pa_log_error("Failed to allocate D-Bus message."); + goto finish; + } + + if (!(dbus_message_append_args(m, DBUS_TYPE_STRING, &action_id, DBUS_TYPE_UINT32, &xid, DBUS_TYPE_INVALID))) { + pa_log_error("Failed to append arguments to D-Bus message."); + goto finish; + } + + if (!(reply = dbus_connection_send_with_reply_and_block(bus, m, -1, &dbus_error))) { + pa_log_warn("Failed to show grant dialog: %s", dbus_error.message); + goto finish; + } + + if (!(dbus_message_get_args(reply, &dbus_error, DBUS_TYPE_BOOLEAN, &verdict, DBUS_TYPE_INVALID))) { + pa_log_warn("Malformed response from grant manager: %s", dbus_error.message); + goto finish; + } + + r = !!verdict; + +finish: + + if (bus) + dbus_connection_unref(bus); + + dbus_error_free(&dbus_error); + + if (m) + dbus_message_unref(m); + + if (reply) + dbus_message_unref(reply); + + return r; +} + +int pa_polkit_check(const char *action_id) { + int ret = -1; + DBusError dbus_error; + DBusConnection *bus = NULL; + PolKitCaller *caller = NULL; + PolKitAction *action = NULL; + PolKitContext *context = NULL; + PolKitError *polkit_error = NULL; + PolKitSession *session = NULL; + PolKitResult polkit_result; + + dbus_error_init(&dbus_error); + + if (!(bus = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error))) { + pa_log_error("Cannot connect to system bus: %s", dbus_error.message); + goto finish; + } + + if (!(caller = polkit_caller_new_from_pid(bus, getpid(), &dbus_error))) { + pa_log_error("Cannot get caller from PID: %s", dbus_error.message); + goto finish; + } + + /* This function is called when PulseAudio is called SUID root. We + * want to authenticate the real user that called us and not the + * effective user we gained through being SUID root. Hence we + * overwrite the UID caller data here explicitly, just for + * paranoia. In fact PolicyKit should fill in the UID here anyway + * -- an not the EUID or any other user id. */ + + if (!(polkit_caller_set_uid(caller, getuid()))) { + pa_log_error("Cannot set UID on caller object."); + goto finish; + } + + if (!(polkit_caller_get_ck_session(caller, &session))) { + pa_log_error("Failed to get CK session."); + goto finish; + } + + /* We need to overwrite the UID in both the caller and the session + * object */ + + if (!(polkit_session_set_uid(session, getuid()))) { + pa_log_error("Cannot set UID on session object."); + goto finish; + } + + if (!(action = polkit_action_new())) { + pa_log_error("Cannot allocate PolKitAction."); + goto finish; + } + + if (!polkit_action_set_action_id(action, action_id)) { + pa_log_error("Cannot set action_id"); + goto finish; + } + + if (!(context = polkit_context_new())) { + pa_log_error("Cannot allocate PolKitContext."); + goto finish; + } + + if (!polkit_context_init(context, &polkit_error)) { + pa_log_error("Cannot initialize PolKitContext: %s", polkit_error_get_error_message(polkit_error)); + goto finish; + } + + for (;;) { + +#ifdef HAVE_POLKIT_CONTEXT_IS_CALLER_AUTHORIZED + polkit_result = polkit_context_is_caller_authorized(context, action, caller, TRUE, &polkit_error); + + if (polkit_error_is_set(polkit_error)) { + pa_log_error("Could not determine whether caller is authorized: %s", polkit_error_get_error_message(polkit_error)); + goto finish; + } +#else + + polkit_result = polkit_context_can_caller_do_action(context, action, caller); + +#endif + + if (polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH || + polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_SESSION || + polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_KEEP_ALWAYS || +#ifdef POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_ONE_SHOT + polkit_result == POLKIT_RESULT_ONLY_VIA_ADMIN_AUTH_ONE_SHOT || +#endif + polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH || + polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_SESSION || + polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_KEEP_ALWAYS +#ifdef POLKIT_RESULT_ONLY_VIA_SELF_AUTH_ONE_SHOT + || polkit_result == POLKIT_RESULT_ONLY_VIA_SELF_AUTH_ONE_SHOT +#endif + ) { + + if (show_grant_dialog(action_id)) + continue; + } + + break; + } + + if (polkit_result != POLKIT_RESULT_YES && polkit_result != POLKIT_RESULT_NO) + pa_log_warn("PolicyKit responded with '%s'", polkit_result_to_string_representation(polkit_result)); + + ret = polkit_result == POLKIT_RESULT_YES; + +finish: + + if (caller) + polkit_caller_unref(caller); + + if (action) + polkit_action_unref(action); + + if (context) + polkit_context_unref(context); + + if (bus) + dbus_connection_unref(bus); + + dbus_error_free(&dbus_error); + + if (polkit_error) + polkit_error_free(polkit_error); + + return ret; +} diff --git a/src/daemon/polkit.h b/src/daemon/polkit.h new file mode 100644 index 00000000..cbcf6a6a --- /dev/null +++ b/src/daemon/polkit.h @@ -0,0 +1,29 @@ +#ifndef foopolkithfoo +#define foopolkithfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 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. +***/ + +int pa_polkit_check(const char *action); + +#endif diff --git a/src/daemon/pulseaudio-module-xsmp.desktop b/src/daemon/pulseaudio-module-xsmp.desktop new file mode 100644 index 00000000..fa719a73 --- /dev/null +++ b/src/daemon/pulseaudio-module-xsmp.desktop @@ -0,0 +1,10 @@ +[Desktop Entry] +Version=1.0 +Encoding=UTF-8 +Name=PulseAudio Session Management +Comment=Load module-x11-xsmp into PulseAudio +Exec=pactl load-module module-x11-xsmp +Terminal=false +Type=Application +Categories= +GenericName= diff --git a/src/depmod.py b/src/depmod.py new file mode 100755 index 00000000..a20bc7c0 --- /dev/null +++ b/src/depmod.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# $Id$ +# +# This file is part of PulseAudio. +# +# 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. + +import sys, os, string + +exported_symbols = {} +imported_symbols = {} + +for fn in sys.argv[1:]: + f = os.popen("nm '%s'" % fn, "r") + + imported_symbols[fn] = [] + + for line in f: + sym_address = line[:7].strip() + sym_type = line[9].strip() + sym_name = line[11:].strip() + + if sym_name in ('_fini', '_init'): + continue + + if sym_type in ('T', 'B', 'R', 'D' 'G', 'S', 'D'): + if exported_symbols.has_key(sym_name): + sys.stderr.write("CONFLICT: %s defined in both '%s' and '%s'.\n" % (sym_name, fn, exported_symbols[sym_name])) + else: + exported_symbols[sym_name] = fn + elif sym_type in ('U',): + if sym_name[:3] == 'pa_': + imported_symbols[fn].append(sym_name) + + f.close() + +dependencies = {} +unresolved_symbols = {} + +for fn in imported_symbols: + dependencies[fn] = [] + + for sym in imported_symbols[fn]: + if exported_symbols.has_key(sym): + if exported_symbols[sym] not in dependencies[fn]: + dependencies[fn].append(exported_symbols[sym]) + else: + if unresolved_symbols.has_key(sym): + unresolved_symbols[sym].append(fn) + else: + unresolved_symbols[sym] = [fn] + +for sym, files in unresolved_symbols.iteritems(): + print "WARNING: Unresolved symbol '%s' in %s" % (sym, `files`) + +k = dependencies.keys() +k.sort() +for fn in k: + dependencies[fn].sort() + print "%s: %s" % (fn, string.join(dependencies[fn], " ")) diff --git a/src/map-file b/src/map-file new file mode 100644 index 00000000..ffa5d103 --- /dev/null +++ b/src/map-file @@ -0,0 +1,237 @@ +PULSE_0 { +global: +pa_browser_new; +pa_browser_new_full; +pa_browser_ref; +pa_browser_set_callback; +pa_browser_set_error_callback; +pa_browser_unref; +pa_bytes_per_second; +pa_bytes_snprint; +pa_bytes_to_usec; +pa_channel_map_equal; +pa_channel_map_init; +pa_channel_map_init_auto; +pa_channel_map_init_mono; +pa_channel_map_init_stereo; +pa_channel_map_parse; +pa_channel_map_snprint; +pa_channel_map_valid; +pa_channel_position_to_pretty_string; +pa_channel_position_to_string; +pa_context_add_autoload; +pa_context_connect; +pa_context_disconnect; +pa_context_drain; +pa_context_errno; +pa_context_exit_daemon; +pa_context_get_autoload_info_by_index; +pa_context_get_autoload_info_by_name; +pa_context_get_autoload_info_list; +pa_context_get_client_info; +pa_context_get_client_info_list; +pa_context_get_module_info; +pa_context_get_module_info_list; +pa_context_get_protocol_version; +pa_context_get_sample_info_by_index; +pa_context_get_sample_info_by_name; +pa_context_get_sample_info_list; +pa_context_get_server; +pa_context_get_server_info; +pa_context_get_server_protocol_version; +pa_context_get_sink_info_by_index; +pa_context_get_sink_info_by_name; +pa_context_get_sink_info_list; +pa_context_get_sink_input_info; +pa_context_get_sink_input_info_list; +pa_context_get_source_info_by_index; +pa_context_get_source_info_by_name; +pa_context_get_source_info_list; +pa_context_get_source_output_info; +pa_context_get_source_output_info_list; +pa_context_get_state; +pa_context_is_local; +pa_context_is_pending; +pa_context_kill_client; +pa_context_kill_sink_input; +pa_context_kill_source_output; +pa_context_load_module; +pa_context_move_sink_input_by_index; +pa_context_move_sink_input_by_name; +pa_context_move_source_output_by_index; +pa_context_move_source_output_by_name; +pa_context_new; +pa_context_play_sample; +pa_context_ref; +pa_context_remove_autoload_by_index; +pa_context_remove_autoload_by_name; +pa_context_remove_sample; +pa_context_set_default_sink; +pa_context_set_default_source; +pa_context_set_name; +pa_context_set_sink_input_mute; +pa_context_set_sink_input_volume; +pa_context_set_sink_mute_by_index; +pa_context_set_sink_mute_by_name; +pa_context_set_sink_volume_by_index; +pa_context_set_sink_volume_by_name; +pa_context_set_source_mute_by_index; +pa_context_set_source_mute_by_name; +pa_context_set_source_volume_by_index; +pa_context_set_source_volume_by_name; +pa_context_set_state_callback; +pa_context_set_subscribe_callback; +pa_context_stat; +pa_context_subscribe; +pa_context_suspend_sink_by_index; +pa_context_suspend_sink_by_name; +pa_context_suspend_source_by_index; +pa_context_suspend_source_by_name; +pa_context_unload_module; +pa_context_unref; +pa_cvolume_avg; +pa_cvolume_channels_equal_to; +pa_cvolume_equal; +pa_cvolume_set; +pa_cvolume_snprint; +pa_cvolume_valid; +pa_frame_size; +pa_get_binary_name; +pa_get_fqdn; +pa_get_home_dir; +pa_get_host_name; +pa_get_library_version; +pa_gettimeofday; +pa_get_user_name; +pa_glib_mainloop_free; +pa_glib_mainloop_get_api; +pa_glib_mainloop_new; +pa_locale_to_utf8; +pa_mainloop_api_once; +pa_mainloop_dispatch; +pa_mainloop_free; +pa_mainloop_get_api; +pa_mainloop_get_retval; +pa_mainloop_iterate; +pa_mainloop_new; +pa_mainloop_poll; +pa_mainloop_prepare; +pa_mainloop_quit; +pa_mainloop_run; +pa_mainloop_set_poll_func; +pa_mainloop_wakeup; +pa_msleep; +pa_operation_cancel; +pa_operation_get_state; +pa_operation_ref; +pa_operation_unref; +pa_parse_sample_format; +pa_path_get_filename; +pa_proplist_free; +pa_proplist_get; +pa_proplist_gets; +pa_proplist_iterate; +pa_proplist_merge; +pa_proplist_new; +pa_proplist_put; +pa_proplist_puts; +pa_proplist_remove; +pa_proplist_to_string; +pa_sample_format_to_string; +pa_sample_size; +pa_sample_spec_equal; +pa_sample_spec_snprint; +pa_sample_spec_valid; +pa_signal_done; +pa_signal_free; +pa_signal_init; +pa_signal_new; +pa_signal_set_destroy; +pa_simple_drain; +pa_simple_flush; +pa_simple_free; +pa_simple_get_latency; +pa_simple_new; +pa_simple_read; +pa_simple_write; +pa_stream_connect_playback; +pa_stream_connect_record; +pa_stream_connect_upload; +pa_stream_cork; +pa_stream_disconnect; +pa_stream_drain; +pa_stream_drop; +pa_stream_finish_upload; +pa_stream_flush; +pa_stream_get_buffer_attr; +pa_stream_get_channel_map; +pa_stream_get_context; +pa_stream_get_device_index; +pa_stream_get_device_name; +pa_stream_get_index; +pa_stream_get_latency; +pa_stream_get_sample_spec; +pa_stream_get_state; +pa_stream_get_time; +pa_stream_get_timing_info; +pa_stream_is_suspended; +pa_stream_new; +pa_stream_peek; +pa_stream_prebuf; +pa_stream_readable_size; +pa_stream_ref; +pa_stream_set_buffer_attr; +pa_stream_set_latency_update_callback; +pa_stream_set_moved_callback; +pa_stream_set_name; +pa_stream_set_overflow_callback; +pa_stream_set_read_callback; +pa_stream_set_state_callback; +pa_stream_set_suspended_callback; +pa_stream_set_underflow_callback; +pa_stream_set_write_callback; +pa_stream_trigger; +pa_stream_unref; +pa_stream_update_sample_rate; +pa_stream_update_timing_info; +pa_stream_writable_size; +pa_stream_write; +pa_strerror; +pa_sw_cvolume_multiply; +pa_sw_volume_from_dB; +pa_sw_volume_from_linear; +pa_sw_volume_multiply; +pa_sw_volume_to_dB; +pa_sw_volume_to_linear; +pa_threaded_mainloop_accept; +pa_threaded_mainloop_free; +pa_threaded_mainloop_get_api; +pa_threaded_mainloop_get_retval; +pa_threaded_mainloop_in_thread; +pa_threaded_mainloop_lock; +pa_threaded_mainloop_new; +pa_threaded_mainloop_signal; +pa_threaded_mainloop_start; +pa_threaded_mainloop_stop; +pa_threaded_mainloop_unlock; +pa_threaded_mainloop_wait; +pa_timeval_add; +pa_timeval_age; +pa_timeval_cmp; +pa_timeval_diff; +pa_timeval_load; +pa_timeval_store; +pa_usec_to_bytes; +pa_utf8_filter; +pa_utf8_to_locale; +pa_utf8_valid; +pa_xfree; +pa_xmalloc; +pa_xmalloc0; +pa_xmemdup; +pa_xrealloc; +pa_xstrdup; +pa_xstrndup; +local: +*; +}; diff --git a/src/modules/Makefile b/src/modules/Makefile new file mode 120000 index 00000000..c110232d --- /dev/null +++ b/src/modules/Makefile @@ -0,0 +1 @@ +../pulse/Makefile
\ No newline at end of file diff --git a/src/modules/alsa-util.c b/src/modules/alsa-util.c new file mode 100644 index 00000000..6afec3bc --- /dev/null +++ b/src/modules/alsa-util.c @@ -0,0 +1,799 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> +#include <asoundlib.h> + +#include <pulse/sample.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "alsa-util.h" + +struct pa_alsa_fdlist { + int num_fds; + struct pollfd *fds; + /* This is a temporary buffer used to avoid lots of mallocs */ + struct pollfd *work_fds; + + snd_mixer_t *mixer; + + pa_mainloop_api *m; + pa_defer_event *defer; + pa_io_event **ios; + + int polled; + + void (*cb)(void *userdata); + void *userdata; +}; + +static void io_cb(pa_mainloop_api*a, pa_io_event* e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void *userdata) { + + struct pa_alsa_fdlist *fdl = userdata; + int err, i; + unsigned short revents; + + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); + pa_assert(fdl->fds); + pa_assert(fdl->work_fds); + + if (fdl->polled) + return; + + fdl->polled = 1; + + memcpy(fdl->work_fds, fdl->fds, sizeof(struct pollfd) * fdl->num_fds); + + for (i = 0;i < fdl->num_fds; i++) { + if (e == fdl->ios[i]) { + if (events & PA_IO_EVENT_INPUT) + fdl->work_fds[i].revents |= POLLIN; + if (events & PA_IO_EVENT_OUTPUT) + fdl->work_fds[i].revents |= POLLOUT; + if (events & PA_IO_EVENT_ERROR) + fdl->work_fds[i].revents |= POLLERR; + if (events & PA_IO_EVENT_HANGUP) + fdl->work_fds[i].revents |= POLLHUP; + break; + } + } + + pa_assert(i != fdl->num_fds); + + if ((err = snd_mixer_poll_descriptors_revents(fdl->mixer, fdl->work_fds, fdl->num_fds, &revents)) < 0) { + pa_log_error("Unable to get poll revent: %s", snd_strerror(err)); + return; + } + + a->defer_enable(fdl->defer, 1); + + if (revents) + snd_mixer_handle_events(fdl->mixer); +} + +static void defer_cb(pa_mainloop_api*a, PA_GCC_UNUSED pa_defer_event* e, void *userdata) { + struct pa_alsa_fdlist *fdl = userdata; + int num_fds, i, err; + struct pollfd *temp; + + pa_assert(a); + pa_assert(fdl); + pa_assert(fdl->mixer); + + a->defer_enable(fdl->defer, 0); + + num_fds = snd_mixer_poll_descriptors_count(fdl->mixer); + pa_assert(num_fds > 0); + + if (num_fds != fdl->num_fds) { + if (fdl->fds) + pa_xfree(fdl->fds); + if (fdl->work_fds) + pa_xfree(fdl->work_fds); + fdl->fds = pa_xnew0(struct pollfd, num_fds); + fdl->work_fds = pa_xnew(struct pollfd, num_fds); + } + + memset(fdl->work_fds, 0, sizeof(struct pollfd) * num_fds); + + if ((err = snd_mixer_poll_descriptors(fdl->mixer, fdl->work_fds, num_fds)) < 0) { + pa_log_error("Unable to get poll descriptors: %s", snd_strerror(err)); + return; + } + + fdl->polled = 0; + + if (memcmp(fdl->fds, fdl->work_fds, sizeof(struct pollfd) * num_fds) == 0) + return; + + if (fdl->ios) { + for (i = 0; i < fdl->num_fds; i++) + a->io_free(fdl->ios[i]); + + if (num_fds != fdl->num_fds) { + pa_xfree(fdl->ios); + fdl->ios = NULL; + } + } + + if (!fdl->ios) + fdl->ios = pa_xnew(pa_io_event*, num_fds); + + /* Swap pointers */ + temp = fdl->work_fds; + fdl->work_fds = fdl->fds; + fdl->fds = temp; + + fdl->num_fds = num_fds; + + for (i = 0;i < num_fds;i++) + fdl->ios[i] = a->io_new(a, fdl->fds[i].fd, + ((fdl->fds[i].events & POLLIN) ? PA_IO_EVENT_INPUT : 0) | + ((fdl->fds[i].events & POLLOUT) ? PA_IO_EVENT_OUTPUT : 0), + io_cb, fdl); +} + +struct pa_alsa_fdlist *pa_alsa_fdlist_new(void) { + struct pa_alsa_fdlist *fdl; + + fdl = pa_xnew0(struct pa_alsa_fdlist, 1); + + fdl->num_fds = 0; + fdl->fds = NULL; + fdl->work_fds = NULL; + fdl->mixer = NULL; + fdl->m = NULL; + fdl->defer = NULL; + fdl->ios = NULL; + fdl->polled = 0; + + return fdl; +} + +void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl) { + pa_assert(fdl); + + if (fdl->defer) { + pa_assert(fdl->m); + fdl->m->defer_free(fdl->defer); + } + + if (fdl->ios) { + int i; + pa_assert(fdl->m); + for (i = 0;i < fdl->num_fds;i++) + fdl->m->io_free(fdl->ios[i]); + pa_xfree(fdl->ios); + } + + if (fdl->fds) + pa_xfree(fdl->fds); + if (fdl->work_fds) + pa_xfree(fdl->work_fds); + + pa_xfree(fdl); +} + +int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m) { + pa_assert(fdl); + pa_assert(mixer_handle); + pa_assert(m); + pa_assert(!fdl->m); + + fdl->mixer = mixer_handle; + fdl->m = m; + fdl->defer = m->defer_new(m, defer_cb, fdl); + + return 0; +} + +static int set_format(snd_pcm_t *pcm_handle, snd_pcm_hw_params_t *hwparams, pa_sample_format_t *f) { + + static const snd_pcm_format_t format_trans[] = { + [PA_SAMPLE_U8] = SND_PCM_FORMAT_U8, + [PA_SAMPLE_ALAW] = SND_PCM_FORMAT_A_LAW, + [PA_SAMPLE_ULAW] = SND_PCM_FORMAT_MU_LAW, + [PA_SAMPLE_S16LE] = SND_PCM_FORMAT_S16_LE, + [PA_SAMPLE_S16BE] = SND_PCM_FORMAT_S16_BE, + [PA_SAMPLE_FLOAT32LE] = SND_PCM_FORMAT_FLOAT_LE, + [PA_SAMPLE_FLOAT32BE] = SND_PCM_FORMAT_FLOAT_BE, + [PA_SAMPLE_S32LE] = SND_PCM_FORMAT_S32_LE, + [PA_SAMPLE_S32BE] = SND_PCM_FORMAT_S32_BE, + }; + + static const pa_sample_format_t try_order[] = { + PA_SAMPLE_FLOAT32NE, + PA_SAMPLE_FLOAT32RE, + PA_SAMPLE_S32NE, + PA_SAMPLE_S32RE, + PA_SAMPLE_S16NE, + PA_SAMPLE_S16RE, + PA_SAMPLE_ALAW, + PA_SAMPLE_ULAW, + PA_SAMPLE_U8, + PA_SAMPLE_INVALID + }; + + int i, ret; + + pa_assert(pcm_handle); + pa_assert(f); + + if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) + return ret; + + if (*f == PA_SAMPLE_FLOAT32BE) + *f = PA_SAMPLE_FLOAT32LE; + else if (*f == PA_SAMPLE_FLOAT32LE) + *f = PA_SAMPLE_FLOAT32BE; + else if (*f == PA_SAMPLE_S16BE) + *f = PA_SAMPLE_S16LE; + else if (*f == PA_SAMPLE_S16LE) + *f = PA_SAMPLE_S16BE; + else if (*f == PA_SAMPLE_S32BE) + *f = PA_SAMPLE_S32LE; + else if (*f == PA_SAMPLE_S32LE) + *f = PA_SAMPLE_S32BE; + else + goto try_auto; + + if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) + return ret; + +try_auto: + + for (i = 0; try_order[i] != PA_SAMPLE_INVALID; i++) { + *f = try_order[i]; + + if ((ret = snd_pcm_hw_params_set_format(pcm_handle, hwparams, format_trans[*f])) >= 0) + return ret; + } + + return -1; +} + +/* Set the hardware parameters of the given ALSA device. Returns the + * selected fragment settings in *period and *period_size */ +int pa_alsa_set_hw_params( + snd_pcm_t *pcm_handle, + pa_sample_spec *ss, + uint32_t *periods, + snd_pcm_uframes_t *period_size, + pa_bool_t *use_mmap, + pa_bool_t require_exact_channel_number) { + + int ret = -1; + snd_pcm_uframes_t buffer_size; + unsigned int r = ss->rate; + unsigned int c = ss->channels; + pa_sample_format_t f = ss->format; + snd_pcm_hw_params_t *hwparams; + pa_bool_t _use_mmap = use_mmap && *use_mmap; + + pa_assert(pcm_handle); + pa_assert(ss); + pa_assert(periods); + pa_assert(period_size); + + snd_pcm_hw_params_alloca(&hwparams); + + buffer_size = *periods * *period_size; + + if ((ret = snd_pcm_hw_params_any(pcm_handle, hwparams)) < 0) + goto finish; + + if ((ret = snd_pcm_hw_params_set_rate_resample(pcm_handle, hwparams, 0)) < 0) + goto finish; + + if (_use_mmap) { + if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_MMAP_INTERLEAVED)) < 0) { + + /* mmap() didn't work, fall back to interleaved */ + + if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + goto finish; + + _use_mmap = FALSE; + } + + } else if ((ret = snd_pcm_hw_params_set_access(pcm_handle, hwparams, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) + goto finish; + + if ((ret = set_format(pcm_handle, hwparams, &f)) < 0) + goto finish; + + if ((ret = snd_pcm_hw_params_set_rate_near(pcm_handle, hwparams, &r, NULL)) < 0) + goto finish; + + if (require_exact_channel_number) { + if ((ret = snd_pcm_hw_params_set_channels(pcm_handle, hwparams, c)) < 0) + goto finish; + } else { + if ((ret = snd_pcm_hw_params_set_channels_near(pcm_handle, hwparams, &c)) < 0) + goto finish; + } + + if ((*period_size > 0 && (ret = snd_pcm_hw_params_set_period_size_near(pcm_handle, hwparams, period_size, NULL)) < 0) || + (*periods > 0 && (ret = snd_pcm_hw_params_set_buffer_size_near(pcm_handle, hwparams, &buffer_size)) < 0)) + goto finish; + + if ((ret = snd_pcm_hw_params(pcm_handle, hwparams)) < 0) + goto finish; + + if (ss->rate != r) + pa_log_warn("Device %s doesn't support %u Hz, changed to %u Hz.", snd_pcm_name(pcm_handle), ss->rate, r); + + if (ss->channels != c) + pa_log_warn("Device %s doesn't support %u channels, changed to %u.", snd_pcm_name(pcm_handle), ss->channels, c); + + if (ss->format != f) + pa_log_warn("Device %s doesn't support sample format %s, changed to %s.", snd_pcm_name(pcm_handle), pa_sample_format_to_string(ss->format), pa_sample_format_to_string(f)); + + if ((ret = snd_pcm_prepare(pcm_handle)) < 0) + goto finish; + + if ((ret = snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size)) < 0 || + (ret = snd_pcm_hw_params_get_period_size(hwparams, period_size, NULL)) < 0) + goto finish; + + /* If the sample rate deviates too much, we need to resample */ + if (r < ss->rate*.95 || r > ss->rate*1.05) + ss->rate = r; + ss->channels = c; + ss->format = f; + + pa_assert(buffer_size > 0); + pa_assert(*period_size > 0); + *periods = buffer_size / *period_size; + pa_assert(*periods > 0); + + if (use_mmap) + *use_mmap = _use_mmap; + + ret = 0; + +finish: + + return ret; +} + +int pa_alsa_set_sw_params(snd_pcm_t *pcm) { + snd_pcm_sw_params_t *swparams; + int err; + + pa_assert(pcm); + + snd_pcm_sw_params_alloca(&swparams); + + if ((err = snd_pcm_sw_params_current(pcm, swparams) < 0)) { + pa_log_warn("Unable to determine current swparams: %s\n", snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params_set_stop_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) { + pa_log_warn("Unable to set stop threshold: %s\n", snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params_set_start_threshold(pcm, swparams, (snd_pcm_uframes_t) -1)) < 0) { + pa_log_warn("Unable to set start threshold: %s\n", snd_strerror(err)); + return err; + } + + if ((err = snd_pcm_sw_params(pcm, swparams)) < 0) { + pa_log_warn("Unable to set sw params: %s\n", snd_strerror(err)); + return err; + } + + return 0; +} + +struct device_info { + pa_channel_map map; + const char *name; +}; + +static const struct device_info device_table[] = { + {{ 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }, "front" }, + + {{ 4, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT }}, "surround40" }, + + {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_LFE }}, "surround41" }, + + {{ 5, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_CENTER }}, "surround50" }, + + {{ 6, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE }}, "surround51" }, + + {{ 8, { PA_CHANNEL_POSITION_FRONT_LEFT, PA_CHANNEL_POSITION_FRONT_RIGHT, + PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, + PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT }} , "surround71" }, + + {{ 0, { 0 }}, NULL } +}; + +static pa_bool_t channel_map_superset(const pa_channel_map *a, const pa_channel_map *b) { + pa_bool_t in_a[PA_CHANNEL_POSITION_MAX]; + unsigned i; + + pa_assert(a); + pa_assert(b); + + memset(in_a, 0, sizeof(in_a)); + + for (i = 0; i < a->channels; i++) + in_a[a->map[i]] = TRUE; + + for (i = 0; i < b->channels; i++) + if (!in_a[b->map[i]]) + return FALSE; + + return TRUE; +} + +snd_pcm_t *pa_alsa_open_by_device_id( + const char *dev_id, + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + uint32_t *nfrags, + snd_pcm_uframes_t *period_size, + pa_bool_t *use_mmap) { + + int i; + int direction = 1; + int err; + char *d; + snd_pcm_t *pcm_handle; + + pa_assert(dev_id); + pa_assert(dev); + pa_assert(ss); + pa_assert(map); + pa_assert(nfrags); + pa_assert(period_size); + + /* First we try to find a device string with a superset of the + * requested channel map and open it without the plug: prefix. We + * iterate through our device table from top to bottom and take + * the first that matches. If we didn't find a working device that + * way, we iterate backwards, and check all devices that do not + * provide a superset of the requested channel map.*/ + + for (i = 0;; i += direction) { + pa_sample_spec try_ss; + + if (i < 0) { + pa_assert(direction == -1); + + /* OK, so we iterated backwards, and now are at the + * beginning of our list. */ + + break; + + } else if (!device_table[i].name) { + pa_assert(direction == 1); + + /* OK, so we are at the end of our list. at iterated + * forwards. */ + + i--; + direction = -1; + } + + if ((direction > 0) == !channel_map_superset(&device_table[i].map, map)) + continue; + + d = pa_sprintf_malloc("%s:%s", device_table[i].name, dev_id); + pa_log_debug("Trying %s...", d); + + if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK)) < 0) { + pa_log_info("Couldn't open PCM device %s: %s", d, snd_strerror(err)); + pa_xfree(d); + continue; + } + + try_ss.channels = device_table[i].map.channels; + try_ss.rate = ss->rate; + try_ss.format = ss->format; + + if ((err = pa_alsa_set_hw_params(pcm_handle, &try_ss, nfrags, period_size, use_mmap, TRUE)) < 0) { + pa_log_info("PCM device %s refused our hw parameters: %s", d, snd_strerror(err)); + pa_xfree(d); + snd_pcm_close(pcm_handle); + continue; + } + + *ss = try_ss; + *map = device_table[i].map; + pa_assert(map->channels == ss->channels); + *dev = d; + return pcm_handle; + } + + /* OK, we didn't find any good device, so let's try the raw hw: stuff */ + + d = pa_sprintf_malloc("hw:%s", dev_id); + pa_log_debug("Trying %s as last resort...", d); + pcm_handle = pa_alsa_open_by_device_string(d, dev, ss, map, mode, nfrags, period_size, use_mmap); + pa_xfree(d); + + return pcm_handle; +} + +snd_pcm_t *pa_alsa_open_by_device_string( + const char *device, + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + uint32_t *nfrags, + snd_pcm_uframes_t *period_size, + pa_bool_t *use_mmap) { + + int err; + char *d; + snd_pcm_t *pcm_handle; + + pa_assert(device); + pa_assert(dev); + pa_assert(ss); + pa_assert(map); + pa_assert(nfrags); + pa_assert(period_size); + + d = pa_xstrdup(device); + + for (;;) { + + if ((err = snd_pcm_open(&pcm_handle, d, mode, SND_PCM_NONBLOCK)) < 0) { + pa_log("Error opening PCM device %s: %s", d, snd_strerror(err)); + pa_xfree(d); + return NULL; + } + + if ((err = pa_alsa_set_hw_params(pcm_handle, ss, nfrags, period_size, use_mmap, FALSE)) < 0) { + + if (err == -EPERM) { + /* Hmm, some hw is very exotic, so we retry with plug, if without it didn't work */ + + if (pa_startswith(d, "hw:")) { + char *t = pa_sprintf_malloc("plughw:%s", d+3); + pa_log_debug("Opening the device as '%s' didn't work, retrying with '%s'.", d, t); + pa_xfree(d); + d = t; + + snd_pcm_close(pcm_handle); + continue; + } + + pa_log("Failed to set hardware parameters on %s: %s", d, snd_strerror(err)); + pa_xfree(d); + snd_pcm_close(pcm_handle); + return NULL; + } + } + + *dev = d; + + if (ss->channels != map->channels) { + pa_assert_se(pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_AUX)); + pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_ALSA); + } + + return pcm_handle; + } +} + +int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev) { + int err; + + pa_assert(mixer); + pa_assert(dev); + + if ((err = snd_mixer_attach(mixer, dev)) < 0) { + pa_log_info("Unable to attach to mixer %s: %s", dev, snd_strerror(err)); + return -1; + } + + if ((err = snd_mixer_selem_register(mixer, NULL, NULL)) < 0) { + pa_log_warn("Unable to register mixer: %s", snd_strerror(err)); + return -1; + } + + if ((err = snd_mixer_load(mixer)) < 0) { + pa_log_warn("Unable to load mixer: %s", snd_strerror(err)); + return -1; + } + + pa_log_info("Successfully attached to mixer '%s'", dev); + + return 0; +} + +snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback) { + snd_mixer_elem_t *elem; + snd_mixer_selem_id_t *sid = NULL; + + snd_mixer_selem_id_alloca(&sid); + + pa_assert(mixer); + pa_assert(name); + + snd_mixer_selem_id_set_name(sid, name); + + if (!(elem = snd_mixer_find_selem(mixer, sid))) { + pa_log_info("Cannot find mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); + + if (fallback) { + snd_mixer_selem_id_set_name(sid, fallback); + + if (!(elem = snd_mixer_find_selem(mixer, sid))) + pa_log_warn("Cannot find fallback mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); + } + } + + if (elem) + pa_log_info("Using mixer control \"%s\".", snd_mixer_selem_id_get_name(sid)); + + return elem; +} + +static const snd_mixer_selem_channel_id_t alsa_channel_ids[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = SND_MIXER_SCHN_MONO, /* The ALSA name is just an alias! */ + + [PA_CHANNEL_POSITION_FRONT_CENTER] = SND_MIXER_SCHN_FRONT_CENTER, + [PA_CHANNEL_POSITION_FRONT_LEFT] = SND_MIXER_SCHN_FRONT_LEFT, + [PA_CHANNEL_POSITION_FRONT_RIGHT] = SND_MIXER_SCHN_FRONT_RIGHT, + + [PA_CHANNEL_POSITION_REAR_CENTER] = SND_MIXER_SCHN_REAR_CENTER, + [PA_CHANNEL_POSITION_REAR_LEFT] = SND_MIXER_SCHN_REAR_LEFT, + [PA_CHANNEL_POSITION_REAR_RIGHT] = SND_MIXER_SCHN_REAR_RIGHT, + + [PA_CHANNEL_POSITION_LFE] = SND_MIXER_SCHN_WOOFER, + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_SIDE_LEFT] = SND_MIXER_SCHN_SIDE_LEFT, + [PA_CHANNEL_POSITION_SIDE_RIGHT] = SND_MIXER_SCHN_SIDE_RIGHT, + + [PA_CHANNEL_POSITION_AUX0] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX1] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX2] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX3] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX4] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX5] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX6] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX7] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX8] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX9] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX10] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX11] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX12] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX13] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX14] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX15] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX16] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX17] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX18] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX19] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX20] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX21] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX22] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX23] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX24] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX25] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX26] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX27] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX28] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX29] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX30] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_AUX31] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_CENTER] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = SND_MIXER_SCHN_UNKNOWN, + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = SND_MIXER_SCHN_UNKNOWN, + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = SND_MIXER_SCHN_UNKNOWN +}; + + +int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback) { + unsigned i; + pa_bool_t alsa_channel_used[SND_MIXER_SCHN_LAST]; + pa_bool_t mono_used = FALSE; + + pa_assert(elem); + pa_assert(channel_map); + pa_assert(mixer_map); + + memset(&alsa_channel_used, 0, sizeof(alsa_channel_used)); + + if (channel_map->channels > 1 && + ((playback && snd_mixer_selem_has_playback_volume_joined(elem)) || + (!playback && snd_mixer_selem_has_capture_volume_joined(elem)))) { + pa_log_info("ALSA device lacks independant volume controls for each channel, falling back to software volume control."); + return -1; + } + + for (i = 0; i < channel_map->channels; i++) { + snd_mixer_selem_channel_id_t id; + pa_bool_t is_mono; + + is_mono = channel_map->map[i] == PA_CHANNEL_POSITION_MONO; + id = alsa_channel_ids[channel_map->map[i]]; + + if (!is_mono && id == SND_MIXER_SCHN_UNKNOWN) { + pa_log_info("Configured channel map contains channel '%s' that is unknown to the ALSA mixer. Falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + return -1; + } + + if ((is_mono && mono_used) || (!is_mono && alsa_channel_used[id])) { + pa_log_info("Channel map has duplicate channel '%s', failling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + return -1; + } + + if ((playback && (!snd_mixer_selem_has_playback_channel(elem, id) || (is_mono && !snd_mixer_selem_is_playback_mono(elem)))) || + (!playback && (!snd_mixer_selem_has_capture_channel(elem, id) || (is_mono && !snd_mixer_selem_is_capture_mono(elem))))) { + + pa_log_info("ALSA device lacks separate volumes control for channel '%s', falling back to software volume control.", pa_channel_position_to_string(channel_map->map[i])); + return -1; + } + + if (is_mono) { + mixer_map[i] = SND_MIXER_SCHN_MONO; + mono_used = TRUE; + } else { + mixer_map[i] = id; + alsa_channel_used[id] = TRUE; + } + } + + pa_log_info("All %u channels can be mapped to mixer channels. Using hardware volume control.", channel_map->channels); + + return 0; +} diff --git a/src/modules/alsa-util.h b/src/modules/alsa-util.h new file mode 100644 index 00000000..53d9a2fb --- /dev/null +++ b/src/modules/alsa-util.h @@ -0,0 +1,76 @@ +#ifndef fooalsautilhfoo +#define fooalsautilhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <asoundlib.h> + +#include <pulse/sample.h> +#include <pulse/mainloop-api.h> + +#include <pulse/channelmap.h> + +typedef struct pa_alsa_fdlist pa_alsa_fdlist; + +struct pa_alsa_fdlist *pa_alsa_fdlist_new(void); +void pa_alsa_fdlist_free(struct pa_alsa_fdlist *fdl); +int pa_alsa_fdlist_set_mixer(struct pa_alsa_fdlist *fdl, snd_mixer_t *mixer_handle, pa_mainloop_api* m); + +int pa_alsa_set_hw_params( + snd_pcm_t *pcm_handle, + pa_sample_spec *ss, + uint32_t *periods, + snd_pcm_uframes_t *period_size, + pa_bool_t *use_mmap, + pa_bool_t require_exact_channel_number); + +int pa_alsa_set_sw_params(snd_pcm_t *pcm); + +int pa_alsa_prepare_mixer(snd_mixer_t *mixer, const char *dev); +snd_mixer_elem_t *pa_alsa_find_elem(snd_mixer_t *mixer, const char *name, const char *fallback); + +snd_pcm_t *pa_alsa_open_by_device_id( + const char *dev_id, + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + uint32_t *nfrags, + snd_pcm_uframes_t *period_size, + pa_bool_t *use_mmap); + +snd_pcm_t *pa_alsa_open_by_device_string( + const char *device, + char **dev, + pa_sample_spec *ss, + pa_channel_map* map, + int mode, + uint32_t *nfrags, + snd_pcm_uframes_t *period_size, + pa_bool_t *use_mmap); + +int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback); + +#endif diff --git a/src/modules/bt-proximity-helper.c b/src/modules/bt-proximity-helper.c new file mode 100644 index 00000000..d80cc0c1 --- /dev/null +++ b/src/modules/bt-proximity-helper.c @@ -0,0 +1,210 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 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. +***/ + +/* + * Small SUID helper that allows us to ping a BT device. Borrows + * heavily from bluez-utils' l2ping, which is licensed as GPL2+, too + * and comes with a copyright like this: + * + * Copyright (C) 2000-2001 Qualcomm Incorporated + * Copyright (C) 2002-2003 Maxim Krasnyansky <maxk@qualcomm.com> + * Copyright (C) 2002-2007 Marcel Holtmann <marcel@holtmann.org> + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#undef NDEBUG + +#include <assert.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/time.h> +#include <sys/select.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/l2cap.h> + +#define PING_STRING "PulseAudio" +#define IDENT 200 +#define TIMEOUT 4 +#define INTERVAL 2 + +static void update_status(int found) { + static int status = -1; + + if (!found && status != 0) + printf("-"); + if (found && status <= 0) + printf("+"); + + fflush(stdout); + status = !!found; +} + +int main(int argc, char *argv[]) { + struct sockaddr_l2 addr; + union { + l2cap_cmd_hdr hdr; + uint8_t buf[L2CAP_CMD_HDR_SIZE + sizeof(PING_STRING)]; + } packet; + int fd = -1; + uint8_t id = IDENT; + int connected = 0; + + assert(argc == 2); + + for (;;) { + fd_set fds; + struct timeval end; + ssize_t r; + + if (!connected) { + + if (fd >= 0) + close(fd); + + if ((fd = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP)) < 0) { + fprintf(stderr, "socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP) failed: %s", strerror(errno)); + goto finish; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, BDADDR_ANY); + + if (bind(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + fprintf(stderr, "bind() failed: %s", strerror(errno)); + goto finish; + } + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + str2ba(argv[1], &addr.l2_bdaddr); + + if (connect(fd, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + + if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) { + update_status(0); + sleep(INTERVAL); + continue; + } + + fprintf(stderr, "connect() failed: %s", strerror(errno)); + goto finish; + } + + connected = 1; + } + + assert(connected); + + memset(&packet, 0, sizeof(packet)); + strcpy((char*) packet.buf + L2CAP_CMD_HDR_SIZE, PING_STRING); + packet.hdr.ident = id; + packet.hdr.len = htobs(sizeof(PING_STRING)); + packet.hdr.code = L2CAP_ECHO_REQ; + + if ((r = send(fd, &packet, sizeof(packet), 0)) < 0) { + + if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) { + update_status(0); + connected = 0; + sleep(INTERVAL); + continue; + } + + fprintf(stderr, "send() failed: %s", strerror(errno)); + goto finish; + } + + assert(r == sizeof(packet)); + + gettimeofday(&end, NULL); + end.tv_sec += TIMEOUT; + + for (;;) { + struct timeval now, delta; + + gettimeofday(&now, NULL); + + if (timercmp(&end, &now, <=)) { + update_status(0); + connected = 0; + sleep(INTERVAL); + break; + } + + timersub(&end, &now, &delta); + + FD_ZERO(&fds); + FD_SET(fd, &fds); + + if (select(fd+1, &fds, NULL, NULL, &delta) < 0) { + fprintf(stderr, "select() failed: %s", strerror(errno)); + goto finish; + } + + if ((r = recv(fd, &packet, sizeof(packet), 0)) <= 0) { + + if (errno == EHOSTDOWN || errno == ECONNRESET || errno == ETIMEDOUT) { + update_status(0); + connected = 0; + sleep(INTERVAL); + break; + } + + fprintf(stderr, "send() failed: %s", r == 0 ? "EOF" : strerror(errno)); + goto finish; + } + + assert(r >= L2CAP_CMD_HDR_SIZE); + + if (packet.hdr.ident != id) + continue; + + if (packet.hdr.code == L2CAP_ECHO_RSP || packet.hdr.code == L2CAP_COMMAND_REJ) { + + if (++id >= 0xFF) + id = IDENT; + + update_status(1); + sleep(INTERVAL); + break; + } + } + } + +finish: + + if (fd >= 0) + close(fd); + + return 1; +} diff --git a/src/modules/dbus-util.c b/src/modules/dbus-util.c new file mode 100644 index 00000000..fc1e91ea --- /dev/null +++ b/src/modules/dbus-util.c @@ -0,0 +1,329 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Shams E. King + + 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 <pulse/xmalloc.h> +#include <pulse/timeval.h> +#include <pulsecore/log.h> +#include <pulsecore/props.h> + +#include "dbus-util.h" + +struct pa_dbus_connection { + PA_REFCNT_DECLARE; + + pa_core *core; + DBusConnection *connection; + const char *property_name; + pa_defer_event* dispatch_event; +}; + +static void dispatch_cb(pa_mainloop_api *ea, pa_defer_event *ev, void *userdata) { + DBusConnection *conn = userdata; + + if (dbus_connection_dispatch(conn) == DBUS_DISPATCH_COMPLETE) { + /* no more data to process, disable the deferred */ + ea->defer_enable(ev, 0); + } +} + +/* DBusDispatchStatusFunction callback for the pa mainloop */ +static void dispatch_status(DBusConnection *conn, DBusDispatchStatus status, void *userdata) { + pa_dbus_connection *c = userdata; + + pa_assert(c); + + switch(status) { + + case DBUS_DISPATCH_COMPLETE: + c->core->mainloop->defer_enable(c->dispatch_event, 0); + break; + + case DBUS_DISPATCH_DATA_REMAINS: + case DBUS_DISPATCH_NEED_MEMORY: + default: + c->core->mainloop->defer_enable(c->dispatch_event, 1); + break; + } +} + +static pa_io_event_flags_t get_watch_flags(DBusWatch *watch) { + unsigned int flags; + pa_io_event_flags_t events = 0; + + pa_assert(watch); + + flags = dbus_watch_get_flags(watch); + + /* no watch flags for disabled watches */ + if (!dbus_watch_get_enabled(watch)) + return PA_IO_EVENT_NULL; + + if (flags & DBUS_WATCH_READABLE) + events |= PA_IO_EVENT_INPUT; + if (flags & DBUS_WATCH_WRITABLE) + events |= PA_IO_EVENT_OUTPUT; + + return events | PA_IO_EVENT_HANGUP | PA_IO_EVENT_ERROR; +} + +/* pa_io_event_cb_t IO event handler */ +static void handle_io_event(PA_GCC_UNUSED pa_mainloop_api *ea, pa_io_event *e, int fd, pa_io_event_flags_t events, void *userdata) { + unsigned int flags = 0; + DBusWatch *watch = userdata; + +#if HAVE_DBUS_WATCH_GET_UNIX_FD + pa_assert(fd == dbus_watch_get_unix_fd(watch)); +#else + pa_assert(fd == dbus_watch_get_fd(watch)); +#endif + + if (!dbus_watch_get_enabled(watch)) { + pa_log_warn("Asked to handle disabled watch: %p %i", (void*) watch, fd); + return; + } + + if (events & PA_IO_EVENT_INPUT) + flags |= DBUS_WATCH_READABLE; + if (events & PA_IO_EVENT_OUTPUT) + flags |= DBUS_WATCH_WRITABLE; + if (events & PA_IO_EVENT_HANGUP) + flags |= DBUS_WATCH_HANGUP; + if (events & PA_IO_EVENT_ERROR) + flags |= DBUS_WATCH_ERROR; + + dbus_watch_handle(watch, flags); +} + +/* pa_time_event_cb_t timer event handler */ +static void handle_time_event(pa_mainloop_api *ea, pa_time_event* e, const struct timeval *tv, void *userdata) { + DBusTimeout *timeout = userdata; + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval next = *tv; + dbus_timeout_handle(timeout); + + /* restart it for the next scheduled time */ + pa_timeval_add(&next, dbus_timeout_get_interval(timeout) * 1000); + ea->time_restart(e, &next); + } +} + +/* DBusAddWatchFunction callback for pa mainloop */ +static dbus_bool_t add_watch(DBusWatch *watch, void *data) { + pa_core *c = PA_CORE(data); + pa_io_event *ev; + + pa_assert(watch); + pa_assert(c); + + ev = c->mainloop->io_new( + c->mainloop, +#if HAVE_DBUS_WATCH_GET_UNIX_FD + dbus_watch_get_unix_fd(watch), +#else + dbus_watch_get_fd(watch), +#endif + get_watch_flags(watch), handle_io_event, watch); + + dbus_watch_set_data(watch, ev, NULL); + + return TRUE; +} + +/* DBusRemoveWatchFunction callback for pa mainloop */ +static void remove_watch(DBusWatch *watch, void *data) { + pa_core *c = PA_CORE(data); + pa_io_event *ev; + + pa_assert(watch); + pa_assert(c); + + if ((ev = dbus_watch_get_data(watch))) + c->mainloop->io_free(ev); +} + +/* DBusWatchToggledFunction callback for pa mainloop */ +static void toggle_watch(DBusWatch *watch, void *data) { + pa_core *c = PA_CORE(data); + pa_io_event *ev; + + pa_assert(watch); + pa_core_assert_ref(c); + + pa_assert_se(ev = dbus_watch_get_data(watch)); + + /* get_watch_flags() checks if the watch is enabled */ + c->mainloop->io_enable(ev, get_watch_flags(watch)); +} + +/* DBusAddTimeoutFunction callback for pa mainloop */ +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *data) { + pa_core *c = PA_CORE(data); + pa_time_event *ev; + struct timeval tv; + + pa_assert(timeout); + pa_assert(c); + + if (!dbus_timeout_get_enabled(timeout)) + return FALSE; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000); + + ev = c->mainloop->time_new(c->mainloop, &tv, handle_time_event, timeout); + + dbus_timeout_set_data(timeout, ev, NULL); + + return TRUE; +} + +/* DBusRemoveTimeoutFunction callback for pa mainloop */ +static void remove_timeout(DBusTimeout *timeout, void *data) { + pa_core *c = PA_CORE(data); + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(c); + + if ((ev = dbus_timeout_get_data(timeout))) + c->mainloop->time_free(ev); +} + +/* DBusTimeoutToggledFunction callback for pa mainloop */ +static void toggle_timeout(DBusTimeout *timeout, void *data) { + pa_core *c = PA_CORE(data); + pa_time_event *ev; + + pa_assert(timeout); + pa_assert(c); + + pa_assert_se(ev = dbus_timeout_get_data(timeout)); + + if (dbus_timeout_get_enabled(timeout)) { + struct timeval tv; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, dbus_timeout_get_interval(timeout) * 1000); + + c->mainloop->time_restart(ev, &tv); + } else + c->mainloop->time_restart(ev, NULL); +} + +static void wakeup_main(void *userdata) { + pa_dbus_connection *c = userdata; + + pa_assert(c); + + /* this will wakeup the mainloop and dispatch events, although + * it may not be the cleanest way of accomplishing it */ + c->core->mainloop->defer_enable(c->dispatch_event, 1); +} + +static pa_dbus_connection* pa_dbus_connection_new(pa_core* c, DBusConnection *conn, const char* name) { + pa_dbus_connection *pconn; + + pconn = pa_xnew(pa_dbus_connection, 1); + PA_REFCNT_INIT(pconn); + pconn->core = c; + pconn->property_name = name; + pconn->connection = conn; + pconn->dispatch_event = c->mainloop->defer_new(c->mainloop, dispatch_cb, conn); + + pa_property_set(c, name, pconn); + + return pconn; +} + +DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c){ + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + pa_assert(c->connection); + + return c->connection; +} + +void pa_dbus_connection_unref(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + + if (PA_REFCNT_DEC(c) > 0) + return; + + if (dbus_connection_get_is_connected(c->connection)) { + dbus_connection_close(c->connection); + /* must process remaining messages, bit of a kludge to handle + * both unload and shutdown */ + while (dbus_connection_read_write_dispatch(c->connection, -1)); + } + + /* already disconnected, just free */ + pa_property_remove(c->core, c->property_name); + c->core->mainloop->defer_free(c->dispatch_event); + dbus_connection_unref(c->connection); + pa_xfree(c); +} + +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) > 0); + + PA_REFCNT_INC(c); + + return c; +} + +pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error) { + + static const char *const prop_name[] = { + [DBUS_BUS_SESSION] = "dbus-connection-session", + [DBUS_BUS_SYSTEM] = "dbus-connection-system", + [DBUS_BUS_STARTER] = "dbus-connection-starter" + }; + DBusConnection *conn; + pa_dbus_connection *pconn; + + pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); + + if ((pconn = pa_property_get(c, prop_name[type]))) + return pa_dbus_connection_ref(pconn); + + if (!(conn = dbus_bus_get_private(type, error))) + return NULL; + + pconn = pa_dbus_connection_new(c, conn, prop_name[type]); + + dbus_connection_set_exit_on_disconnect(conn, FALSE); + dbus_connection_set_dispatch_status_function(conn, dispatch_status, pconn, NULL); + dbus_connection_set_watch_functions(conn, add_watch, remove_watch, toggle_watch, c, NULL); + dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, toggle_timeout, c, NULL); + dbus_connection_set_wakeup_main_function(conn, wakeup_main, pconn, NULL); + + return pconn; +} diff --git a/src/modules/dbus-util.h b/src/modules/dbus-util.h new file mode 100644 index 00000000..8dca54fe --- /dev/null +++ b/src/modules/dbus-util.h @@ -0,0 +1,40 @@ +#ifndef foodbusutilhfoo +#define foodbusutilhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Shams E. King + + 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 <dbus/dbus.h> + +typedef struct pa_dbus_connection pa_dbus_connection; + +/* return the DBusConnection of the specified type for the given core, + * like dbus_bus_get(), but integrates the connection with the pa_core */ +pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *error); + +DBusConnection* pa_dbus_connection_get(pa_dbus_connection *conn); + +pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *conn); +void pa_dbus_connection_unref(pa_dbus_connection *conn); + +#endif diff --git a/src/modules/gconf/Makefile b/src/modules/gconf/Makefile new file mode 100644 index 00000000..316beb72 --- /dev/null +++ b/src/modules/gconf/Makefile @@ -0,0 +1,13 @@ +# This is a dirty trick just to ease compilation with emacs +# +# This file is not intended to be distributed or anything +# +# So: don't touch it, even better ignore it! + +all: + $(MAKE) -C ../.. + +clean: + $(MAKE) -C ../.. clean + +.PHONY: all clean diff --git a/src/modules/gconf/gconf-helper.c b/src/modules/gconf/gconf-helper.c new file mode 100644 index 00000000..abd13287 --- /dev/null +++ b/src/modules/gconf/gconf-helper.c @@ -0,0 +1,135 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <string.h> +#include <stdlib.h> +#include <stdio.h> + +#include <gconf/gconf-client.h> +#include <glib.h> + +#include <pulsecore/core-util.h> + +#define PA_GCONF_ROOT "/system/pulseaudio" +#define PA_GCONF_PATH_MODULES PA_GCONF_ROOT"/modules" + +static void handle_module(GConfClient *client, const char *name) { + gchar p[1024]; + gboolean enabled, locked; + int i; + + pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/locked", name); + locked = gconf_client_get_bool(client, p, FALSE); + + if (locked) + return; + + pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/enabled", name); + enabled = gconf_client_get_bool(client, p, FALSE); + + printf("%c%s%c", enabled ? '+' : '-', name, 0); + + if (enabled) { + + for (i = 0; i < 10; i++) { + gchar *n, *a; + + pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/name%i", name, i); + if (!(n = gconf_client_get_string(client, p, NULL)) || !*n) + break; + + pa_snprintf(p, sizeof(p), PA_GCONF_PATH_MODULES"/%s/args%i", name, i); + a = gconf_client_get_string(client, p, NULL); + + printf("%s%c%s%c", n, 0, a ? a : "", 0); + + g_free(n); + g_free(a); + } + + printf("%c", 0); + } + + fflush(stdout); +} + +static void modules_callback( + GConfClient* client, + guint cnxn_id, + GConfEntry *entry, + gpointer user_data) { + + const char *n; + char buf[128]; + + g_assert(strncmp(entry->key, PA_GCONF_PATH_MODULES"/", sizeof(PA_GCONF_PATH_MODULES)) == 0); + + n = entry->key + sizeof(PA_GCONF_PATH_MODULES); + + g_strlcpy(buf, n, sizeof(buf)); + buf[strcspn(buf, "/")] = 0; + + handle_module(client, buf); +} + +int main(int argc, char *argv[]) { + GMainLoop *g; + GConfClient *client; + GSList *modules, *m; + + g_type_init(); + + if (!(client = gconf_client_get_default())) + goto fail; + + gconf_client_add_dir(client, PA_GCONF_ROOT, GCONF_CLIENT_PRELOAD_RECURSIVE, NULL); + gconf_client_notify_add(client, PA_GCONF_PATH_MODULES, modules_callback, NULL, NULL, NULL); + + modules = gconf_client_all_dirs(client, PA_GCONF_PATH_MODULES, NULL); + + for (m = modules; m; m = m->next) { + char *e = strrchr(m->data, '/'); + handle_module(client, e ? e+1 : m->data); + } + + g_slist_free(modules); + + /* Signal the parent that we are now initialized */ + printf("!"); + fflush(stdout); + + g = g_main_loop_new(NULL, FALSE); + g_main_loop_run(g); + g_main_loop_unref(g); + + g_object_unref(G_OBJECT(client)); + + return 0; + +fail: + return 1; +} diff --git a/src/modules/gconf/module-gconf.c b/src/modules/gconf/module-gconf.c new file mode 100644 index 00000000..836157d0 --- /dev/null +++ b/src/modules/gconf/module-gconf.c @@ -0,0 +1,397 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <errno.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <fcntl.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/module.h> +#include <pulsecore/core.h> +#include <pulsecore/llist.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulse/mainloop-api.h> +#include <pulsecore/core-error.h> +#include <pulsecore/start-child.h> + +#include "module-gconf-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("GConf Adapter"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define MAX_MODULES 10 +#define BUF_MAX 2048 + +/* #undef PA_GCONF_HELPER */ +/* #define PA_GCONF_HELPER "/home/lennart/projects/pulseaudio/src/gconf-helper" */ + +struct module_item { + char *name; + char *args; + uint32_t index; +}; + +struct module_info { + char *name; + + struct module_item items[MAX_MODULES]; + unsigned n_items; +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_hashmap *module_infos; + + pid_t pid; + + int fd; + int fd_type; + pa_io_event *io_event; + + char buf[BUF_MAX]; + size_t buf_fill; +}; + +static int fill_buf(struct userdata *u) { + ssize_t r; + pa_assert(u); + + if (u->buf_fill >= BUF_MAX) { + pa_log("read buffer overflow"); + return -1; + } + + if ((r = pa_read(u->fd, u->buf + u->buf_fill, BUF_MAX - u->buf_fill, &u->fd_type)) <= 0) + return -1; + + u->buf_fill += r; + return 0; +} + +static int read_byte(struct userdata *u) { + int ret; + pa_assert(u); + + if (u->buf_fill < 1) + if (fill_buf(u) < 0) + return -1; + + ret = u->buf[0]; + pa_assert(u->buf_fill > 0); + u->buf_fill--; + memmove(u->buf, u->buf+1, u->buf_fill); + return ret; +} + +static char *read_string(struct userdata *u) { + pa_assert(u); + + for (;;) { + char *e; + + if ((e = memchr(u->buf, 0, u->buf_fill))) { + char *ret = pa_xstrdup(u->buf); + u->buf_fill -= e - u->buf +1; + memmove(u->buf, e+1, u->buf_fill); + return ret; + } + + if (fill_buf(u) < 0) + return NULL; + } +} + +static void unload_one_module(struct userdata *u, struct module_info*m, unsigned i) { + pa_assert(u); + pa_assert(m); + pa_assert(i < m->n_items); + + if (m->items[i].index == PA_INVALID_INDEX) + return; + + pa_log_debug("Unloading module #%i", m->items[i].index); + pa_module_unload_by_index(u->core, m->items[i].index); + m->items[i].index = PA_INVALID_INDEX; + pa_xfree(m->items[i].name); + pa_xfree(m->items[i].args); + m->items[i].name = m->items[i].args = NULL; +} + +static void unload_all_modules(struct userdata *u, struct module_info*m) { + unsigned i; + + pa_assert(u); + pa_assert(m); + + for (i = 0; i < m->n_items; i++) + unload_one_module(u, m, i); + + m->n_items = 0; +} + +static void load_module( + struct userdata *u, + struct module_info *m, + int i, + const char *name, + const char *args, + int is_new) { + + pa_module *mod; + + pa_assert(u); + pa_assert(m); + pa_assert(name); + pa_assert(args); + + if (!is_new) { + if (m->items[i].index != PA_INVALID_INDEX && + strcmp(m->items[i].name, name) == 0 && + strcmp(m->items[i].args, args) == 0) + return; + + unload_one_module(u, m, i); + } + + pa_log_debug("Loading module '%s' with args '%s' due to GConf configuration.", name, args); + + m->items[i].name = pa_xstrdup(name); + m->items[i].args = pa_xstrdup(args); + m->items[i].index = PA_INVALID_INDEX; + + if (!(mod = pa_module_load(u->core, name, args))) { + pa_log("pa_module_load() failed"); + return; + } + + m->items[i].index = mod->index; +} + +static void module_info_free(void *p, void *userdata) { + struct module_info *m = p; + struct userdata *u = userdata; + + pa_assert(m); + pa_assert(u); + + unload_all_modules(u, m); + pa_xfree(m->name); + pa_xfree(m); +} + +static int handle_event(struct userdata *u) { + int opcode; + int ret = 0; + + do { + if ((opcode = read_byte(u)) < 0){ + if (errno == EINTR || errno == EAGAIN) + break; + goto fail; + } + + switch (opcode) { + case '!': + /* The helper tool is now initialized */ + ret = 1; + break; + + case '+': { + char *name; + struct module_info *m; + unsigned i, j; + + if (!(name = read_string(u))) + goto fail; + + if (!(m = pa_hashmap_get(u->module_infos, name))) { + m = pa_xnew(struct module_info, 1); + m->name = name; + m->n_items = 0; + pa_hashmap_put(u->module_infos, m->name, m); + } else + pa_xfree(name); + + i = 0; + while (i < MAX_MODULES) { + char *module, *args; + + if (!(module = read_string(u))) { + if (i > m->n_items) m->n_items = i; + goto fail; + } + + if (!*module) { + pa_xfree(module); + break; + } + + if (!(args = read_string(u))) { + pa_xfree(module); + + if (i > m->n_items) m->n_items = i; + goto fail; + } + + load_module(u, m, i, module, args, i >= m->n_items); + + i++; + + pa_xfree(module); + pa_xfree(args); + } + + /* Unload all removed modules */ + for (j = i; j < m->n_items; j++) + unload_one_module(u, m, j); + + m->n_items = i; + + break; + } + + case '-': { + char *name; + struct module_info *m; + + if (!(name = read_string(u))) + goto fail; + + if ((m = pa_hashmap_get(u->module_infos, name))) { + pa_hashmap_remove(u->module_infos, name); + module_info_free(m, u); + } + + pa_xfree(name); + + break; + } + } + } while (u->buf_fill > 0 && ret == 0); + + return ret; + +fail: + pa_log("Unable to read or parse data from client."); + return -1; +} + +static void io_event_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct userdata *u = userdata; + + if (handle_event(u) < 0) { + + if (u->io_event) { + u->core->mainloop->io_free(u->io_event); + u->io_event = NULL; + } + + pa_module_unload_request(u->module); + } +} + +int pa__init(pa_module*m) { + struct userdata *u; + int r; + + u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->module_infos = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + u->pid = (pid_t) -1; + u->fd = -1; + u->fd_type = 0; + u->io_event = NULL; + u->buf_fill = 0; + + if ((u->fd = pa_start_child_for_read(PA_GCONF_HELPER, NULL, &u->pid)) < 0) + goto fail; + + u->io_event = m->core->mainloop->io_new( + m->core->mainloop, + u->fd, + PA_IO_EVENT_INPUT, + io_event_cb, + u); + + do { + if ((r = handle_event(u)) < 0) + goto fail; + + /* Read until the client signalled us that it is ready with + * initialization */ + } while (r != 1); + + return 0; + +fail: + pa__done(m); + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->pid != (pid_t) -1) { + kill(u->pid, SIGTERM); + waitpid(u->pid, NULL, 0); + } + + if (u->io_event) + m->core->mainloop->io_free(u->io_event); + + if (u->fd >= 0) + pa_close(u->fd); + + + if (u->module_infos) + pa_hashmap_free(u->module_infos, module_info_free, u); + + pa_xfree(u); +} diff --git a/src/modules/ladspa.h b/src/modules/ladspa.h new file mode 100644 index 00000000..b1a9c4e5 --- /dev/null +++ b/src/modules/ladspa.h @@ -0,0 +1,603 @@ +/* ladspa.h + + Linux Audio Developer's Simple Plugin API Version 1.1[LGPL]. + Copyright (C) 2000-2002 Richard W.E. Furse, Paul Barton-Davis, + Stefan Westerfeld. + + This library 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. + + This library 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 this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. */ + +#ifndef LADSPA_INCLUDED +#define LADSPA_INCLUDED + +#define LADSPA_VERSION "1.1" +#define LADSPA_VERSION_MAJOR 1 +#define LADSPA_VERSION_MINOR 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/*****************************************************************************/ + +/* Overview: + + There is a large number of synthesis packages in use or development + on the Linux platform at this time. This API (`The Linux Audio + Developer's Simple Plugin API') attempts to give programmers the + ability to write simple `plugin' audio processors in C/C++ and link + them dynamically (`plug') into a range of these packages (`hosts'). + It should be possible for any host and any plugin to communicate + completely through this interface. + + This API is deliberately short and simple. To achieve compatibility + with a range of promising Linux sound synthesis packages it + attempts to find the `greatest common divisor' in their logical + behaviour. Having said this, certain limiting decisions are + implicit, notably the use of a fixed type (LADSPA_Data) for all + data transfer and absence of a parameterised `initialisation' + phase. See below for the LADSPA_Data typedef. + + Plugins are expected to distinguish between control and audio + data. Plugins have `ports' that are inputs or outputs for audio or + control data and each plugin is `run' for a `block' corresponding + to a short time interval measured in samples. Audio data is + communicated using arrays of LADSPA_Data, allowing a block of audio + to be processed by the plugin in a single pass. Control data is + communicated using single LADSPA_Data values. Control data has a + single value at the start of a call to the `run()' or `run_adding()' + function, and may be considered to remain this value for its + duration. The plugin may assume that all its input and output ports + have been connected to the relevant data location (see the + `connect_port()' function below) before it is asked to run. + + Plugins will reside in shared object files suitable for dynamic + linking by dlopen() and family. The file will provide a number of + `plugin types' that can be used to instantiate actual plugins + (sometimes known as `plugin instances') that can be connected + together to perform tasks. + + This API contains very limited error-handling. */ + +/*****************************************************************************/ + +/* Fundamental data type passed in and out of plugin. This data type + is used to communicate audio samples and control values. It is + assumed that the plugin will work sensibly given any numeric input + value although it may have a preferred range (see hints below). + + For audio it is generally assumed that 1.0f is the `0dB' reference + amplitude and is a `normal' signal level. */ + +typedef float LADSPA_Data; + +/*****************************************************************************/ + +/* Special Plugin Properties: + + Optional features of the plugin type are encapsulated in the + LADSPA_Properties type. This is assembled by ORing individual + properties together. */ + +typedef int LADSPA_Properties; + +/* Property LADSPA_PROPERTY_REALTIME indicates that the plugin has a + real-time dependency (e.g. listens to a MIDI device) and so its + output must not be cached or subject to significant latency. */ +#define LADSPA_PROPERTY_REALTIME 0x1 + +/* Property LADSPA_PROPERTY_INPLACE_BROKEN indicates that the plugin + may cease to work correctly if the host elects to use the same data + location for both input and output (see connect_port()). This + should be avoided as enabling this flag makes it impossible for + hosts to use the plugin to process audio `in-place.' */ +#define LADSPA_PROPERTY_INPLACE_BROKEN 0x2 + +/* Property LADSPA_PROPERTY_HARD_RT_CAPABLE indicates that the plugin + is capable of running not only in a conventional host but also in a + `hard real-time' environment. To qualify for this the plugin must + satisfy all of the following: + + (1) The plugin must not use malloc(), free() or other heap memory + management within its run() or run_adding() functions. All new + memory used in run() must be managed via the stack. These + restrictions only apply to the run() function. + + (2) The plugin will not attempt to make use of any library + functions with the exceptions of functions in the ANSI standard C + and C maths libraries, which the host is expected to provide. + + (3) The plugin will not access files, devices, pipes, sockets, IPC + or any other mechanism that might result in process or thread + blocking. + + (4) The plugin will take an amount of time to execute a run() or + run_adding() call approximately of form (A+B*SampleCount) where A + and B depend on the machine and host in use. This amount of time + may not depend on input signals or plugin state. The host is left + the responsibility to perform timings to estimate upper bounds for + A and B. */ +#define LADSPA_PROPERTY_HARD_RT_CAPABLE 0x4 + +#define LADSPA_IS_REALTIME(x) ((x) & LADSPA_PROPERTY_REALTIME) +#define LADSPA_IS_INPLACE_BROKEN(x) ((x) & LADSPA_PROPERTY_INPLACE_BROKEN) +#define LADSPA_IS_HARD_RT_CAPABLE(x) ((x) & LADSPA_PROPERTY_HARD_RT_CAPABLE) + +/*****************************************************************************/ + +/* Plugin Ports: + + Plugins have `ports' that are inputs or outputs for audio or + data. Ports can communicate arrays of LADSPA_Data (for audio + inputs/outputs) or single LADSPA_Data values (for control + input/outputs). This information is encapsulated in the + LADSPA_PortDescriptor type which is assembled by ORing individual + properties together. + + Note that a port must be an input or an output port but not both + and that a port must be a control or audio port but not both. */ + +typedef int LADSPA_PortDescriptor; + +/* Property LADSPA_PORT_INPUT indicates that the port is an input. */ +#define LADSPA_PORT_INPUT 0x1 + +/* Property LADSPA_PORT_OUTPUT indicates that the port is an output. */ +#define LADSPA_PORT_OUTPUT 0x2 + +/* Property LADSPA_PORT_CONTROL indicates that the port is a control + port. */ +#define LADSPA_PORT_CONTROL 0x4 + +/* Property LADSPA_PORT_AUDIO indicates that the port is a audio + port. */ +#define LADSPA_PORT_AUDIO 0x8 + +#define LADSPA_IS_PORT_INPUT(x) ((x) & LADSPA_PORT_INPUT) +#define LADSPA_IS_PORT_OUTPUT(x) ((x) & LADSPA_PORT_OUTPUT) +#define LADSPA_IS_PORT_CONTROL(x) ((x) & LADSPA_PORT_CONTROL) +#define LADSPA_IS_PORT_AUDIO(x) ((x) & LADSPA_PORT_AUDIO) + +/*****************************************************************************/ + +/* Plugin Port Range Hints: + + The host may wish to provide a representation of data entering or + leaving a plugin (e.g. to generate a GUI automatically). To make + this more meaningful, the plugin should provide `hints' to the host + describing the usual values taken by the data. + + Note that these are only hints. The host may ignore them and the + plugin must not assume that data supplied to it is meaningful. If + the plugin receives invalid input data it is expected to continue + to run without failure and, where possible, produce a sensible + output (e.g. a high-pass filter given a negative cutoff frequency + might switch to an all-pass mode). + + Hints are meaningful for all input and output ports but hints for + input control ports are expected to be particularly useful. + + More hint information is encapsulated in the + LADSPA_PortRangeHintDescriptor type which is assembled by ORing + individual hint types together. Hints may require further + LowerBound and UpperBound information. + + All the hint information for a particular port is aggregated in the + LADSPA_PortRangeHint structure. */ + +typedef int LADSPA_PortRangeHintDescriptor; + +/* Hint LADSPA_HINT_BOUNDED_BELOW indicates that the LowerBound field + of the LADSPA_PortRangeHint should be considered meaningful. The + value in this field should be considered the (inclusive) lower + bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also + specified then the value of LowerBound should be multiplied by the + sample rate. */ +#define LADSPA_HINT_BOUNDED_BELOW 0x1 + +/* Hint LADSPA_HINT_BOUNDED_ABOVE indicates that the UpperBound field + of the LADSPA_PortRangeHint should be considered meaningful. The + value in this field should be considered the (inclusive) upper + bound of the valid range. If LADSPA_HINT_SAMPLE_RATE is also + specified then the value of UpperBound should be multiplied by the + sample rate. */ +#define LADSPA_HINT_BOUNDED_ABOVE 0x2 + +/* Hint LADSPA_HINT_TOGGLED indicates that the data item should be + considered a Boolean toggle. Data less than or equal to zero should + be considered `off' or `false,' and data above zero should be + considered `on' or `true.' LADSPA_HINT_TOGGLED may not be used in + conjunction with any other hint except LADSPA_HINT_DEFAULT_0 or + LADSPA_HINT_DEFAULT_1. */ +#define LADSPA_HINT_TOGGLED 0x4 + +/* Hint LADSPA_HINT_SAMPLE_RATE indicates that any bounds specified + should be interpreted as multiples of the sample rate. For + instance, a frequency range from 0Hz to the Nyquist frequency (half + the sample rate) could be requested by this hint in conjunction + with LowerBound = 0 and UpperBound = 0.5. Hosts that support bounds + at all must support this hint to retain meaning. */ +#define LADSPA_HINT_SAMPLE_RATE 0x8 + +/* Hint LADSPA_HINT_LOGARITHMIC indicates that it is likely that the + user will find it more intuitive to view values using a logarithmic + scale. This is particularly useful for frequencies and gains. */ +#define LADSPA_HINT_LOGARITHMIC 0x10 + +/* Hint LADSPA_HINT_INTEGER indicates that a user interface would + probably wish to provide a stepped control taking only integer + values. Any bounds set should be slightly wider than the actual + integer range required to avoid floating point rounding errors. For + instance, the integer set {0,1,2,3} might be described as [-0.1, + 3.1]. */ +#define LADSPA_HINT_INTEGER 0x20 + +/* The various LADSPA_HINT_HAS_DEFAULT_* hints indicate a `normal' + value for the port that is sensible as a default. For instance, + this value is suitable for use as an initial value in a user + interface or as a value the host might assign to a control port + when the user has not provided one. Defaults are encoded using a + mask so only one default may be specified for a port. Some of the + hints make use of lower and upper bounds, in which case the + relevant bound or bounds must be available and + LADSPA_HINT_SAMPLE_RATE must be applied as usual. The resulting + default must be rounded if LADSPA_HINT_INTEGER is present. Default + values were introduced in LADSPA v1.1. */ +#define LADSPA_HINT_DEFAULT_MASK 0x3C0 + +/* This default values indicates that no default is provided. */ +#define LADSPA_HINT_DEFAULT_NONE 0x0 + +/* This default hint indicates that the suggested lower bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MINIMUM 0x40 + +/* This default hint indicates that a low value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.75 + + log(upper) * 0.25). Otherwise, this should be (lower * 0.75 + upper + * 0.25). */ +#define LADSPA_HINT_DEFAULT_LOW 0x80 + +/* This default hint indicates that a middle value between the + suggested lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.5 + + log(upper) * 0.5). Otherwise, this should be (lower * 0.5 + upper * + 0.5). */ +#define LADSPA_HINT_DEFAULT_MIDDLE 0xC0 + +/* This default hint indicates that a high value between the suggested + lower and upper bounds should be chosen. For ports with + LADSPA_HINT_LOGARITHMIC, this should be exp(log(lower) * 0.25 + + log(upper) * 0.75). Otherwise, this should be (lower * 0.25 + upper + * 0.75). */ +#define LADSPA_HINT_DEFAULT_HIGH 0x100 + +/* This default hint indicates that the suggested upper bound for the + port should be used. */ +#define LADSPA_HINT_DEFAULT_MAXIMUM 0x140 + +/* This default hint indicates that the number 0 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_0 0x200 + +/* This default hint indicates that the number 1 should be used. Note + that this default may be used in conjunction with + LADSPA_HINT_TOGGLED. */ +#define LADSPA_HINT_DEFAULT_1 0x240 + +/* This default hint indicates that the number 100 should be used. */ +#define LADSPA_HINT_DEFAULT_100 0x280 + +/* This default hint indicates that the Hz frequency of `concert A' + should be used. This will be 440 unless the host uses an unusual + tuning convention, in which case it may be within a few Hz. */ +#define LADSPA_HINT_DEFAULT_440 0x2C0 + +#define LADSPA_IS_HINT_BOUNDED_BELOW(x) ((x) & LADSPA_HINT_BOUNDED_BELOW) +#define LADSPA_IS_HINT_BOUNDED_ABOVE(x) ((x) & LADSPA_HINT_BOUNDED_ABOVE) +#define LADSPA_IS_HINT_TOGGLED(x) ((x) & LADSPA_HINT_TOGGLED) +#define LADSPA_IS_HINT_SAMPLE_RATE(x) ((x) & LADSPA_HINT_SAMPLE_RATE) +#define LADSPA_IS_HINT_LOGARITHMIC(x) ((x) & LADSPA_HINT_LOGARITHMIC) +#define LADSPA_IS_HINT_INTEGER(x) ((x) & LADSPA_HINT_INTEGER) + +#define LADSPA_IS_HINT_HAS_DEFAULT(x) ((x) & LADSPA_HINT_DEFAULT_MASK) +#define LADSPA_IS_HINT_DEFAULT_MINIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MINIMUM) +#define LADSPA_IS_HINT_DEFAULT_LOW(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_LOW) +#define LADSPA_IS_HINT_DEFAULT_MIDDLE(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MIDDLE) +#define LADSPA_IS_HINT_DEFAULT_HIGH(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_HIGH) +#define LADSPA_IS_HINT_DEFAULT_MAXIMUM(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_MAXIMUM) +#define LADSPA_IS_HINT_DEFAULT_0(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_0) +#define LADSPA_IS_HINT_DEFAULT_1(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_1) +#define LADSPA_IS_HINT_DEFAULT_100(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_100) +#define LADSPA_IS_HINT_DEFAULT_440(x) (((x) & LADSPA_HINT_DEFAULT_MASK) \ + == LADSPA_HINT_DEFAULT_440) + +typedef struct _LADSPA_PortRangeHint { + + /* Hints about the port. */ + LADSPA_PortRangeHintDescriptor HintDescriptor; + + /* Meaningful when hint LADSPA_HINT_BOUNDED_BELOW is active. When + LADSPA_HINT_SAMPLE_RATE is also active then this value should be + multiplied by the relevant sample rate. */ + LADSPA_Data LowerBound; + + /* Meaningful when hint LADSPA_HINT_BOUNDED_ABOVE is active. When + LADSPA_HINT_SAMPLE_RATE is also active then this value should be + multiplied by the relevant sample rate. */ + LADSPA_Data UpperBound; + +} LADSPA_PortRangeHint; + +/*****************************************************************************/ + +/* Plugin Handles: + + This plugin handle indicates a particular instance of the plugin + concerned. It is valid to compare this to NULL (0 for C++) but + otherwise the host should not attempt to interpret it. The plugin + may use it to reference internal instance data. */ + +typedef void * LADSPA_Handle; + +/*****************************************************************************/ + +/* Descriptor for a Type of Plugin: + + This structure is used to describe a plugin type. It provides a + number of functions to examine the type, instantiate it, link it to + buffers and workspaces and to run it. */ + +typedef struct _LADSPA_Descriptor { + + /* This numeric identifier indicates the plugin type + uniquely. Plugin programmers may reserve ranges of IDs from a + central body to avoid clashes. Hosts may assume that IDs are + below 0x1000000. */ + unsigned long UniqueID; + + /* This identifier can be used as a unique, case-sensitive + identifier for the plugin type within the plugin file. Plugin + types should be identified by file and label rather than by index + or plugin name, which may be changed in new plugin + versions. Labels must not contain white-space characters. */ + const char * Label; + + /* This indicates a number of properties of the plugin. */ + LADSPA_Properties Properties; + + /* This member points to the null-terminated name of the plugin + (e.g. "Sine Oscillator"). */ + const char * Name; + + /* This member points to the null-terminated string indicating the + maker of the plugin. This can be an empty string but not NULL. */ + const char * Maker; + + /* This member points to the null-terminated string indicating any + copyright applying to the plugin. If no Copyright applies the + string "None" should be used. */ + const char * Copyright; + + /* This indicates the number of ports (input AND output) present on + the plugin. */ + unsigned long PortCount; + + /* This member indicates an array of port descriptors. Valid indices + vary from 0 to PortCount-1. */ + const LADSPA_PortDescriptor * PortDescriptors; + + /* This member indicates an array of null-terminated strings + describing ports (e.g. "Frequency (Hz)"). Valid indices vary from + 0 to PortCount-1. */ + const char * const * PortNames; + + /* This member indicates an array of range hints for each port (see + above). Valid indices vary from 0 to PortCount-1. */ + const LADSPA_PortRangeHint * PortRangeHints; + + /* This may be used by the plugin developer to pass any custom + implementation data into an instantiate call. It must not be used + or interpreted by the host. It is expected that most plugin + writers will not use this facility as LADSPA_Handle should be + used to hold instance data. */ + void * ImplementationData; + + /* This member is a function pointer that instantiates a plugin. A + handle is returned indicating the new plugin instance. The + instantiation function accepts a sample rate as a parameter. The + plugin descriptor from which this instantiate function was found + must also be passed. This function must return NULL if + instantiation fails. + + Note that instance initialisation should generally occur in + activate() rather than here. */ + LADSPA_Handle (*instantiate)(const struct _LADSPA_Descriptor * Descriptor, + unsigned long SampleRate); + + /* This member is a function pointer that connects a port on an + instantiated plugin to a memory location at which a block of data + for the port will be read/written. The data location is expected + to be an array of LADSPA_Data for audio ports or a single + LADSPA_Data value for control ports. Memory issues will be + managed by the host. The plugin must read/write the data at these + locations every time run() or run_adding() is called and the data + present at the time of this connection call should not be + considered meaningful. + + connect_port() may be called more than once for a plugin instance + to allow the host to change the buffers that the plugin is + reading or writing. These calls may be made before or after + activate() or deactivate() calls. + + connect_port() must be called at least once for each port before + run() or run_adding() is called. When working with blocks of + LADSPA_Data the plugin should pay careful attention to the block + size passed to the run function as the block allocated may only + just be large enough to contain the block of samples. + + Plugin writers should be aware that the host may elect to use the + same buffer for more than one port and even use the same buffer + for both input and output (see LADSPA_PROPERTY_INPLACE_BROKEN). + However, overlapped buffers or use of a single buffer for both + audio and control data may result in unexpected behaviour. */ + void (*connect_port)(LADSPA_Handle Instance, + unsigned long Port, + LADSPA_Data * DataLocation); + + /* This member is a function pointer that initialises a plugin + instance and activates it for use. This is separated from + instantiate() to aid real-time support and so that hosts can + reinitialise a plugin instance by calling deactivate() and then + activate(). In this case the plugin instance must reset all state + information dependent on the history of the plugin instance + except for any data locations provided by connect_port() and any + gain set by set_run_adding_gain(). If there is nothing for + activate() to do then the plugin writer may provide a NULL rather + than an empty function. + + When present, hosts must call this function once before run() (or + run_adding()) is called for the first time. This call should be + made as close to the run() call as possible and indicates to + real-time plugins that they are now live. Plugins should not rely + on a prompt call to run() after activate(). activate() may not be + called again unless deactivate() is called first. Note that + connect_port() may be called before or after a call to + activate(). */ + void (*activate)(LADSPA_Handle Instance); + + /* This method is a function pointer that runs an instance of a + plugin for a block. Two parameters are required: the first is a + handle to the particular instance to be run and the second + indicates the block size (in samples) for which the plugin + instance may run. + + Note that if an activate() function exists then it must be called + before run() or run_adding(). If deactivate() is called for a + plugin instance then the plugin instance may not be reused until + activate() has been called again. + + If the plugin has the property LADSPA_PROPERTY_HARD_RT_CAPABLE + then there are various things that the plugin should not do + within the run() or run_adding() functions (see above). */ + void (*run)(LADSPA_Handle Instance, + unsigned long SampleCount); + + /* This method is a function pointer that runs an instance of a + plugin for a block. This has identical behaviour to run() except + in the way data is output from the plugin. When run() is used, + values are written directly to the memory areas associated with + the output ports. However when run_adding() is called, values + must be added to the values already present in the memory + areas. Furthermore, output values written must be scaled by the + current gain set by set_run_adding_gain() (see below) before + addition. + + run_adding() is optional. When it is not provided by a plugin, + this function pointer must be set to NULL. When it is provided, + the function set_run_adding_gain() must be provided also. */ + void (*run_adding)(LADSPA_Handle Instance, + unsigned long SampleCount); + + /* This method is a function pointer that sets the output gain for + use when run_adding() is called (see above). If this function is + never called the gain is assumed to default to 1. Gain + information should be retained when activate() or deactivate() + are called. + + This function should be provided by the plugin if and only if the + run_adding() function is provided. When it is absent this + function pointer must be set to NULL. */ + void (*set_run_adding_gain)(LADSPA_Handle Instance, + LADSPA_Data Gain); + + /* This is the counterpart to activate() (see above). If there is + nothing for deactivate() to do then the plugin writer may provide + a NULL rather than an empty function. + + Hosts must deactivate all activated units after they have been + run() (or run_adding()) for the last time. This call should be + made as close to the last run() call as possible and indicates to + real-time plugins that they are no longer live. Plugins should + not rely on prompt deactivation. Note that connect_port() may be + called before or after a call to deactivate(). + + Deactivation is not similar to pausing as the plugin instance + will be reinitialised when activate() is called to reuse it. */ + void (*deactivate)(LADSPA_Handle Instance); + + /* Once an instance of a plugin has been finished with it can be + deleted using the following function. The instance handle passed + ceases to be valid after this call. + + If activate() was called for a plugin instance then a + corresponding call to deactivate() must be made before cleanup() + is called. */ + void (*cleanup)(LADSPA_Handle Instance); + +} LADSPA_Descriptor; + +/**********************************************************************/ + +/* Accessing a Plugin: */ + +/* The exact mechanism by which plugins are loaded is host-dependent, + however all most hosts will need to know is the name of shared + object file containing the plugin types. To allow multiple hosts to + share plugin types, hosts may wish to check for environment + variable LADSPA_PATH. If present, this should contain a + colon-separated path indicating directories that should be searched + (in order) when loading plugin types. + + A plugin programmer must include a function called + "ladspa_descriptor" with the following function prototype within + the shared object file. This function will have C-style linkage (if + you are using C++ this is taken care of by the `extern "C"' clause + at the top of the file). + + A host will find the plugin shared object file by one means or + another, find the ladspa_descriptor() function, call it, and + proceed from there. + + Plugin types are accessed by index (not ID) using values from 0 + upwards. Out of range indexes must result in this function + returning NULL, so the plugin count can be determined by checking + for the least index that results in NULL being returned. */ + +const LADSPA_Descriptor * ladspa_descriptor(unsigned long Index); + +/* Datatype corresponding to the ladspa_descriptor() function. */ +typedef const LADSPA_Descriptor * +(*LADSPA_Descriptor_Function)(unsigned long Index); + +/**********************************************************************/ + +#ifdef __cplusplus +} +#endif + +#endif /* LADSPA_INCLUDED */ + +/* EOF */ diff --git a/src/modules/module-alsa-sink.c b/src/modules/module-alsa-sink.c new file mode 100644 index 00000000..14aef7c9 --- /dev/null +++ b/src/modules/module-alsa-sink.c @@ -0,0 +1,995 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <asoundlib.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core.h> +#include <pulsecore/module.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/sink.h> +#include <pulsecore/modargs.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread.h> +#include <pulsecore/core-error.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> + +#include "alsa-util.h" +#include "module-alsa-sink-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ALSA Sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "device=<ALSA device> " + "device_id=<ALSA device id> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "fragments=<number of fragments> " + "fragment_size=<fragment size> " + "channel_map=<channel map> " + "mmap=<enable memory mapping?>"); + +#define DEFAULT_DEVICE "default" + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + snd_pcm_t *pcm_handle; + + pa_alsa_fdlist *mixer_fdl; + snd_mixer_t *mixer_handle; + snd_mixer_elem_t *mixer_elem; + long hw_volume_max, hw_volume_min; + + size_t frame_size, fragment_size, hwbuf_size; + unsigned nfragments; + pa_memchunk memchunk; + + char *device_name; + + pa_bool_t use_mmap; + + pa_bool_t first; + + pa_rtpoll_item *alsa_rtpoll_item; + + snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; +}; + +static const char* const valid_modargs[] = { + "device", + "device_id", + "sink_name", + "format", + "channels", + "rate", + "fragments", + "fragment_size", + "channel_map", + "mmap", + NULL +}; + +static int mmap_write(struct userdata *u) { + int work_done = 0; + + pa_assert(u); + pa_sink_assert_ref(u->sink); + + for (;;) { + pa_memchunk chunk; + void *p; + snd_pcm_sframes_t n; + int err; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, frames; + + if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) { + + if (n == -EPIPE) { + pa_log_debug("snd_pcm_avail_update: Buffer underrun!"); + u->first = TRUE; + } + + if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0) + continue; + + if (err == -EAGAIN) + return work_done; + + pa_log("snd_pcm_avail_update: %s", snd_strerror(err)); + return -1; + } + +/* pa_log("Got request for %i samples", (int) n); */ + + if (n <= 0) + return work_done; + + frames = n; + + if ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) { + + if (err == -EPIPE) { + pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!"); + u->first = TRUE; + } + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) + continue; + + if (err == -EAGAIN) + return work_done; + + pa_log("Failed to write data to DSP: %s", snd_strerror(err)); + return -1; + } + + /* Check these are multiples of 8 bit */ + pa_assert((areas[0].first & 7) == 0); + pa_assert((areas[0].step & 7)== 0); + + /* We assume a single interleaved memory buffer */ + pa_assert((areas[0].first >> 3) == 0); + pa_assert((areas[0].step >> 3) == u->frame_size); + + p = (uint8_t*) areas[0].addr + (offset * u->frame_size); + + chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1); + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_sink_render_into_full(u->sink, &chunk); + + /* FIXME: Maybe we can do something to keep this memory block + * a little bit longer around? */ + pa_memblock_unref_fixed(chunk.memblock); + + if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) { + + if (err == -EPIPE) { + pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!"); + u->first = TRUE; + } + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) + continue; + + if (err == -EAGAIN) + return work_done; + + pa_log("Failed to write data to DSP: %s", snd_strerror(err)); + return -1; + } + + work_done = 1; + + if (frames >= (snd_pcm_uframes_t) n) + return work_done; + +/* pa_log("wrote %i samples", (int) frames); */ + } +} + +static int unix_write(struct userdata *u) { + snd_pcm_status_t *status; + int work_done = 0; + + snd_pcm_status_alloca(&status); + + pa_assert(u); + pa_sink_assert_ref(u->sink); + + for (;;) { + void *p; + snd_pcm_sframes_t t; + ssize_t l; + int err; + + if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) { + pa_log("Failed to query DSP status data: %s", snd_strerror(err)); + return -1; + } + + if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size) + pa_log_debug("Buffer underrun!"); + + l = snd_pcm_status_get_avail(status) * u->frame_size; + +/* pa_log("%u bytes to write", l); */ + + if (l <= 0) + return work_done; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, l, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + p = pa_memblock_acquire(u->memchunk.memblock); + t = snd_pcm_writei(u->pcm_handle, (const uint8_t*) p + u->memchunk.index, u->memchunk.length / u->frame_size); + pa_memblock_release(u->memchunk.memblock); + +/* pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l); */ + + pa_assert(t != 0); + + if (t < 0) { + + if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0) + continue; + + if (t == -EAGAIN) { + pa_log_debug("EAGAIN"); + return work_done; + } else { + pa_log("Failed to write data to DSP: %s", snd_strerror(t)); + return -1; + } + } + + u->memchunk.index += t * u->frame_size; + u->memchunk.length -= t * u->frame_size; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + work_done = 1; + + if (t * u->frame_size >= (unsigned) l) + return work_done; + } +} + +static pa_usec_t sink_get_latency(struct userdata *u) { + pa_usec_t r = 0; + snd_pcm_status_t *status; + snd_pcm_sframes_t frames = 0; + int err; + + snd_pcm_status_alloca(&status); + + pa_assert(u); + pa_assert(u->pcm_handle); + + if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) + pa_log("Failed to get delay: %s", snd_strerror(err)); + else + frames = snd_pcm_status_get_delay(status); + + if (frames > 0) + r = pa_bytes_to_usec(frames * u->frame_size, &u->sink->sample_spec); + + if (u->memchunk.memblock) + r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + + return r; +} + +static int build_pollfd(struct userdata *u) { + int err; + struct pollfd *pollfd; + int n; + + pa_assert(u); + pa_assert(u->pcm_handle); + + if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) { + pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n)); + return -1; + } + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n); + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL); + + if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) { + pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); + return -1; + } + + return 0; +} + +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); + + /* Let's suspend */ + snd_pcm_drain(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + + if (u->alsa_rtpoll_item) { + pa_rtpoll_item_free(u->alsa_rtpoll_item); + u->alsa_rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +static int unsuspend(struct userdata *u) { + pa_sample_spec ss; + int err; + pa_bool_t b; + unsigned nfrags; + snd_pcm_uframes_t period_size; + + pa_assert(u); + pa_assert(!u->pcm_handle); + + pa_log_info("Trying resume..."); + + snd_config_update_free_global(); + if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) < 0) { + pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); + goto fail; + } + + ss = u->sink->sample_spec; + nfrags = u->nfragments; + period_size = u->fragment_size / u->frame_size; + b = u->use_mmap; + + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b, TRUE)) < 0) { + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + goto fail; + } + + if (b != u->use_mmap) { + pa_log_warn("Resume failed, couldn't get original access mode."); + goto fail; + } + + if (!pa_sample_spec_equal(&ss, &u->sink->sample_spec)) { + pa_log_warn("Resume failed, couldn't restore original sample settings."); + goto fail; + } + + if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings."); + goto fail; + } + + if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + goto fail; + } + + if (build_pollfd(u) < 0) + goto fail; + + /* FIXME: We need to reload the volume somehow */ + + u->first = TRUE; + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + if (u->pcm_handle) { + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + } + + return -1; +} + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->pcm_handle) + r = sink_get_latency(u); + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SINK_MESSAGE_SET_STATE: + + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + + if (suspend(u) < 0) + return -1; + + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_INIT) { + if (build_pollfd(u) < 0) + return -1; + } + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + if (unsuspend(u) < 0) + return -1; + } + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct userdata *u = snd_mixer_elem_get_callback_private(elem); + + pa_assert(u); + pa_assert(u->mixer_handle); + + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; + + if (mask & SND_CTL_EVENT_MASK_VALUE) { + pa_sink_get_volume(u->sink); + pa_sink_get_mute(u->sink); + } + + return 0; +} + +static int sink_get_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int err; + int i; + + pa_assert(u); + pa_assert(u->mixer_elem); + + for (i = 0; i < s->sample_spec.channels; i++) { + long set_vol, vol; + + pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); + + if ((err = snd_mixer_selem_get_playback_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0) + goto fail; + + set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + + /* Try to avoid superfluous volume changes */ + if (set_vol != vol) + s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); + } + + return 0; + +fail: + pa_log_error("Unable to read volume: %s", snd_strerror(err)); + + s->get_volume = NULL; + s->set_volume = NULL; + return -1; +} + +static int sink_set_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int err; + int i; + + pa_assert(u); + pa_assert(u->mixer_elem); + + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; + pa_volume_t vol; + + pa_assert(snd_mixer_selem_has_playback_channel(u->mixer_elem, u->mixer_map[i])); + + vol = s->volume.values[i]; + + if (vol > PA_VOLUME_NORM) + vol = PA_VOLUME_NORM; + + alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + + if ((err = snd_mixer_selem_set_playback_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) + goto fail; + } + + return 0; + +fail: + pa_log_error("Unable to set volume: %s", snd_strerror(err)); + + s->get_volume = NULL; + s->set_volume = NULL; + return -1; +} + +static int sink_get_mute_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int err, sw; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if ((err = snd_mixer_selem_get_playback_switch(u->mixer_elem, 0, &sw)) < 0) { + pa_log_error("Unable to get switch: %s", snd_strerror(err)); + + s->get_mute = NULL; + s->set_mute = NULL; + return -1; + } + + s->muted = !sw; + + return 0; +} + +static int sink_set_mute_cb(pa_sink *s) { + struct userdata *u = s->userdata; + int err; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if ((err = snd_mixer_selem_set_playback_switch_all(u->mixer_elem, !s->muted)) < 0) { + pa_log_error("Unable to set switch: %s", snd_strerror(err)); + + s->get_mute = NULL; + s->set_mute = NULL; + return -1; + } + + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + + /* Render some data and write it to the dsp */ + if (PA_SINK_OPENED(u->sink->thread_info.state)) { + int work_done = 0; + + if (u->use_mmap) { + if ((work_done = mmap_write(u)) < 0) + goto fail; + } else { + if ((work_done = unix_write(u)) < 0) + goto fail; + } + + if (work_done && u->first) { + pa_log_info("Starting playback."); + snd_pcm_start(u->pcm_handle); + u->first = FALSE; + continue; + } + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + /* Tell ALSA about this and process its response */ + if (PA_SINK_OPENED(u->sink->thread_info.state)) { + struct pollfd *pollfd; + unsigned short revents = 0; + int err; + unsigned n; + + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n); + + if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { + pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); + goto fail; + } + + if (revents & (POLLERR|POLLNVAL|POLLHUP)) { + + if (revents & POLLERR) + pa_log_warn("Got POLLERR from ALSA"); + if (revents & POLLNVAL) + pa_log_warn("Got POLLNVAL from ALSA"); + if (revents & POLLHUP) + pa_log_warn("Got POLLHUP from ALSA"); + + /* Try to recover from this error */ + + switch (snd_pcm_state(u->pcm_handle)) { + + case SND_PCM_STATE_XRUN: + if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err)); + goto fail; + } + break; + + case SND_PCM_STATE_SUSPENDED: + if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err)); + goto fail; + } + break; + + default: + + snd_pcm_drop(u->pcm_handle); + + if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err)); + goto fail; + } + break; + } + } + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + + pa_modargs *ma = NULL; + struct userdata *u = NULL; + const char *dev_id; + pa_sample_spec ss; + pa_channel_map map; + uint32_t nfrags, frag_size; + snd_pcm_uframes_t period_size; + size_t frame_size; + snd_pcm_info_t *pcm_info = NULL; + int err; + char *t; + const char *name; + char *name_buf = NULL; + int namereg_fail; + pa_bool_t use_mmap = TRUE, b; + + snd_pcm_info_alloca(&pcm_info); + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { + pa_log("Failed to parse sample specification and channel map"); + goto fail; + } + + frame_size = pa_frame_size(&ss); + + nfrags = m->core->default_n_fragments; + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + if (frag_size <= 0) + frag_size = frame_size; + + if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0) { + pa_log("Failed to parse buffer metrics"); + goto fail; + } + period_size = frag_size/frame_size; + + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->use_mmap = use_mmap; + u->first = TRUE; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + u->alsa_rtpoll_item = NULL; + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + snd_config_update_free_global(); + + b = use_mmap; + + if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { + + if (!(u->pcm_handle = pa_alsa_open_by_device_id( + dev_id, + &u->device_name, + &ss, &map, + SND_PCM_STREAM_PLAYBACK, + &nfrags, &period_size, + &b))) + + goto fail; + + } else { + + if (!(u->pcm_handle = pa_alsa_open_by_device_string( + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), + &u->device_name, + &ss, &map, + SND_PCM_STREAM_PLAYBACK, + &nfrags, &period_size, + &b))) + goto fail; + + } + + pa_assert(u->device_name); + pa_log_info("Successfully opened device %s.", u->device_name); + + if (use_mmap && !b) { + pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); + u->use_mmap = use_mmap = b; + } + + if (u->use_mmap) + pa_log_info("Successfully enabled mmap() mode."); + + if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { + pa_log("Error fetching PCM info: %s", snd_strerror(err)); + goto fail; + } + + if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + goto fail; + } + + /* ALSA might tweak the sample spec, so recalculate the frame size */ + frame_size = pa_frame_size(&ss); + + if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) + pa_log_warn("Error opening mixer: %s", snd_strerror(err)); + else { + pa_bool_t found = FALSE; + + if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) + found = TRUE; + else { + char *md = pa_sprintf_malloc("hw:%s", dev_id); + + if (strcmp(u->device_name, md)) + if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) + found = TRUE; + + pa_xfree(md); + } + + if (found) + if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Master", "PCM"))) + found = FALSE; + + if (!found) { + snd_mixer_close(u->mixer_handle); + u->mixer_handle = NULL; + } + } + + if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) + namereg_fail = 1; + else { + name = name_buf = pa_sprintf_malloc("alsa_output.%s", u->device_name); + namereg_fail = 0; + } + + u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + pa_xfree(name_buf); + + if (!u->sink) { + pa_log("Failed to create sink object"); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc( + "ALSA PCM on %s (%s)%s", + u->device_name, + snd_pcm_info_get_name(pcm_info), + use_mmap ? " via DMA" : "")); + pa_xfree(t); + + u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY; + + u->frame_size = frame_size; + u->fragment_size = frag_size = period_size * frame_size; + u->nfragments = nfrags; + u->hwbuf_size = u->fragment_size * nfrags; + + pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size); + + pa_memchunk_reset(&u->memchunk); + + if (u->mixer_handle) { + pa_assert(u->mixer_elem); + + if (snd_mixer_selem_has_playback_volume(u->mixer_elem)) + if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, TRUE) >= 0) { + u->sink->get_volume = sink_get_volume_cb; + u->sink->set_volume = sink_set_volume_cb; + snd_mixer_selem_get_playback_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); + u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; + } + + if (snd_mixer_selem_has_playback_switch(u->mixer_elem)) { + u->sink->get_mute = sink_get_mute_cb; + u->sink->set_mute = sink_set_mute_cb; + u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; + } + + u->mixer_fdl = pa_alsa_fdlist_new(); + + if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { + pa_log("Failed to initialize file descriptor monitoring"); + goto fail; + } + + snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); + snd_mixer_elem_set_callback_private(u->mixer_elem, u); + } else + u->mixer_fdl = NULL; + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + /* Get initial mixer settings */ + if (u->sink->get_volume) + u->sink->get_volume(u->sink); + if (u->sink->get_mute) + u->sink->get_mute(u->sink); + + pa_sink_put(u->sink); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->mixer_fdl) + pa_alsa_fdlist_free(u->mixer_fdl); + + if (u->mixer_handle) + snd_mixer_close(u->mixer_handle); + + if (u->pcm_handle) { + snd_pcm_drop(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + } + + pa_xfree(u->device_name); + pa_xfree(u); + + snd_config_update_free_global(); +} diff --git a/src/modules/module-alsa-source.c b/src/modules/module-alsa-source.c new file mode 100644 index 00000000..23a2f921 --- /dev/null +++ b/src/modules/module-alsa-source.c @@ -0,0 +1,968 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <asoundlib.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core.h> +#include <pulsecore/module.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/sink.h> +#include <pulsecore/modargs.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread.h> +#include <pulsecore/core-error.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> + +#include "alsa-util.h" +#include "module-alsa-source-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ALSA Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "source_name=<name for the source> " + "device=<ALSA device> " + "device_id=<ALSA device id> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "fragments=<number of fragments> " + "fragment_size=<fragment size> " + "channel_map=<channel map> " + "mmap=<enable memory mapping?>"); + +#define DEFAULT_DEVICE "default" + +struct userdata { + pa_core *core; + pa_module *module; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + snd_pcm_t *pcm_handle; + + pa_alsa_fdlist *mixer_fdl; + snd_mixer_t *mixer_handle; + snd_mixer_elem_t *mixer_elem; + long hw_volume_max, hw_volume_min; + + size_t frame_size, fragment_size, hwbuf_size; + unsigned nfragments; + + char *device_name; + + pa_bool_t use_mmap; + + pa_rtpoll_item *alsa_rtpoll_item; + + snd_mixer_selem_channel_id_t mixer_map[SND_MIXER_SCHN_LAST]; +}; + +static const char* const valid_modargs[] = { + "device", + "device_id", + "source_name", + "channels", + "rate", + "format", + "fragments", + "fragment_size", + "channel_map", + "mmap", + NULL +}; + +static int mmap_read(struct userdata *u) { + int work_done = 0; + + pa_assert(u); + pa_source_assert_ref(u->source); + + for (;;) { + snd_pcm_sframes_t n; + int err; + const snd_pcm_channel_area_t *areas; + snd_pcm_uframes_t offset, frames; + pa_memchunk chunk; + void *p; + + if ((n = snd_pcm_avail_update(u->pcm_handle)) < 0) { + + if (n == -EPIPE) + pa_log_debug("snd_pcm_avail_update: Buffer underrun!"); + + if ((err = snd_pcm_recover(u->pcm_handle, n, 1)) == 0) + continue; + + if (err == -EAGAIN) + return work_done; + + pa_log("snd_pcm_avail_update: %s", snd_strerror(err)); + return -1; + } + +/* pa_log("Got request for %i samples", (int) n); */ + + if (n <= 0) + return work_done; + + frames = n; + + if ((err = snd_pcm_mmap_begin(u->pcm_handle, &areas, &offset, &frames)) < 0) { + + if (err == -EPIPE) + pa_log_debug("snd_pcm_mmap_begin: Buffer underrun!"); + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) + continue; + + if (err == -EAGAIN) + return work_done; + + pa_log("Failed to write data to DSP: %s", snd_strerror(err)); + return -1; + } + + /* Check these are multiples of 8 bit */ + pa_assert((areas[0].first & 7) == 0); + pa_assert((areas[0].step & 7)== 0); + + /* We assume a single interleaved memory buffer */ + pa_assert((areas[0].first >> 3) == 0); + pa_assert((areas[0].step >> 3) == u->frame_size); + + p = (uint8_t*) areas[0].addr + (offset * u->frame_size); + + chunk.memblock = pa_memblock_new_fixed(u->core->mempool, p, frames * u->frame_size, 1); + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_source_post(u->source, &chunk); + + /* FIXME: Maybe we can do something to keep this memory block + * a little bit longer around? */ + pa_memblock_unref_fixed(chunk.memblock); + + if ((err = snd_pcm_mmap_commit(u->pcm_handle, offset, frames)) < 0) { + + if (err == -EPIPE) + pa_log_debug("snd_pcm_mmap_commit: Buffer underrun!"); + + if ((err = snd_pcm_recover(u->pcm_handle, err, 1)) == 0) + continue; + + if (err == -EAGAIN) + return work_done; + + pa_log("Failed to write data to DSP: %s", snd_strerror(err)); + return -1; + } + + work_done = 1; + +/* pa_log("wrote %i samples", (int) frames); */ + } +} + +static int unix_read(struct userdata *u) { + snd_pcm_status_t *status; + int work_done = 0; + + snd_pcm_status_alloca(&status); + + pa_assert(u); + pa_source_assert_ref(u->source); + + for (;;) { + void *p; + snd_pcm_sframes_t t, k; + ssize_t l; + int err; + pa_memchunk chunk; + + if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) { + pa_log("Failed to query DSP status data: %s", snd_strerror(err)); + return -1; + } + + if (snd_pcm_status_get_avail_max(status)*u->frame_size >= u->hwbuf_size) + pa_log_debug("Buffer overrun!"); + + l = snd_pcm_status_get_avail(status) * u->frame_size; + + if (l <= 0) + return work_done; + + chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); + + k = pa_memblock_get_length(chunk.memblock); + + if (k > l) + k = l; + + k = (k/u->frame_size)*u->frame_size; + + p = pa_memblock_acquire(chunk.memblock); + t = snd_pcm_readi(u->pcm_handle, (uint8_t*) p, k / u->frame_size); + pa_memblock_release(chunk.memblock); + +/* pa_log("wrote %i bytes of %u (%u)", t*u->frame_size, u->memchunk.length, l); */ + + pa_assert(t != 0); + + if (t < 0) { + pa_memblock_unref(chunk.memblock); + + if ((t = snd_pcm_recover(u->pcm_handle, t, 1)) == 0) + continue; + + if (t == -EAGAIN) { + pa_log_debug("EAGAIN"); + return work_done; + } else { + pa_log("Failed to read data from DSP: %s", snd_strerror(t)); + return -1; + } + } + + chunk.index = 0; + chunk.length = t * u->frame_size; + + pa_source_post(u->source, &chunk); + pa_memblock_unref(chunk.memblock); + + work_done = 1; + + if (t * u->frame_size >= (unsigned) l) + return work_done; + } +} + +static pa_usec_t source_get_latency(struct userdata *u) { + pa_usec_t r = 0; + snd_pcm_status_t *status; + snd_pcm_sframes_t frames = 0; + int err; + + snd_pcm_status_alloca(&status); + + pa_assert(u); + pa_assert(u->pcm_handle); + + if ((err = snd_pcm_status(u->pcm_handle, status)) < 0) + pa_log("Failed to get delay: %s", snd_strerror(err)); + else + frames = snd_pcm_status_get_delay(status); + + if (frames > 0) + r = pa_bytes_to_usec(frames * u->frame_size, &u->source->sample_spec); + + return r; +} + +static int build_pollfd(struct userdata *u) { + int err; + struct pollfd *pollfd; + int n; + + pa_assert(u); + pa_assert(u->pcm_handle); + + if ((n = snd_pcm_poll_descriptors_count(u->pcm_handle)) < 0) { + pa_log("snd_pcm_poll_descriptors_count() failed: %s", snd_strerror(n)); + return -1; + } + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + u->alsa_rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, n); + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, NULL); + + if ((err = snd_pcm_poll_descriptors(u->pcm_handle, pollfd, n)) < 0) { + pa_log("snd_pcm_poll_descriptors() failed: %s", snd_strerror(err)); + return -1; + } + + return 0; +} + +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->pcm_handle); + + /* Let's suspend */ + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + + if (u->alsa_rtpoll_item) { + pa_rtpoll_item_free(u->alsa_rtpoll_item); + u->alsa_rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +static int unsuspend(struct userdata *u) { + pa_sample_spec ss; + int err; + pa_bool_t b; + unsigned nfrags; + snd_pcm_uframes_t period_size; + + pa_assert(u); + pa_assert(!u->pcm_handle); + + pa_log_info("Trying resume..."); + + snd_config_update_free_global(); + if ((err = snd_pcm_open(&u->pcm_handle, u->device_name, SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK)) < 0) { + pa_log("Error opening PCM device %s: %s", u->device_name, snd_strerror(err)); + goto fail; + } + + ss = u->source->sample_spec; + nfrags = u->nfragments; + period_size = u->fragment_size / u->frame_size; + b = u->use_mmap; + + if ((err = pa_alsa_set_hw_params(u->pcm_handle, &ss, &nfrags, &period_size, &b, TRUE)) < 0) { + pa_log("Failed to set hardware parameters: %s", snd_strerror(err)); + goto fail; + } + + if (b != u->use_mmap) { + pa_log_warn("Resume failed, couldn't get original access mode."); + goto fail; + } + + if (!pa_sample_spec_equal(&ss, &u->source->sample_spec)) { + pa_log_warn("Resume failed, couldn't restore original sample settings."); + goto fail; + } + + if (nfrags != u->nfragments || period_size*u->frame_size != u->fragment_size) { + pa_log_warn("Resume failed, couldn't restore original fragment settings."); + goto fail; + } + + if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + goto fail; + } + + if (build_pollfd(u) < 0) + goto fail; + + snd_pcm_start(u->pcm_handle); + + /* FIXME: We need to reload the volume somehow */ + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + if (u->pcm_handle) { + snd_pcm_close(u->pcm_handle); + u->pcm_handle = NULL; + } + + return -1; +} + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->pcm_handle) + r = source_get_latency(u); + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SOURCE_MESSAGE_SET_STATE: + + switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SOURCE_SUSPENDED: + pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state)); + + if (suspend(u) < 0) + return -1; + + break; + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: + + if (u->source->thread_info.state == PA_SOURCE_INIT) { + if (build_pollfd(u) < 0) + return -1; + + snd_pcm_start(u->pcm_handle); + } + + if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { + if (unsuspend(u) < 0) + return -1; + } + + break; + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + ; + } + + break; + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static int mixer_callback(snd_mixer_elem_t *elem, unsigned int mask) { + struct userdata *u = snd_mixer_elem_get_callback_private(elem); + + pa_assert(u); + pa_assert(u->mixer_handle); + + if (mask == SND_CTL_EVENT_MASK_REMOVE) + return 0; + + if (mask & SND_CTL_EVENT_MASK_VALUE) { + pa_source_get_volume(u->source); + pa_source_get_mute(u->source); + } + + return 0; +} + +static int source_get_volume_cb(pa_source *s) { + struct userdata *u = s->userdata; + int err; + int i; + + pa_assert(u); + pa_assert(u->mixer_elem); + + for (i = 0; i < s->sample_spec.channels; i++) { + long set_vol, vol; + + pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i])); + + if ((err = snd_mixer_selem_get_capture_volume(u->mixer_elem, u->mixer_map[i], &vol)) < 0) + goto fail; + + set_vol = (long) roundf(((float) s->volume.values[i] * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + + /* Try to avoid superfluous volume changes */ + if (set_vol != vol) + s->volume.values[i] = (pa_volume_t) roundf(((float) (vol - u->hw_volume_min) * PA_VOLUME_NORM) / (u->hw_volume_max - u->hw_volume_min)); + } + + return 0; + +fail: + pa_log_error("Unable to read volume: %s", snd_strerror(err)); + + s->get_volume = NULL; + s->set_volume = NULL; + return -1; +} + +static int source_set_volume_cb(pa_source *s) { + struct userdata *u = s->userdata; + int err; + int i; + + pa_assert(u); + pa_assert(u->mixer_elem); + + for (i = 0; i < s->sample_spec.channels; i++) { + long alsa_vol; + pa_volume_t vol; + + pa_assert(snd_mixer_selem_has_capture_channel(u->mixer_elem, u->mixer_map[i])); + + vol = s->volume.values[i]; + + if (vol > PA_VOLUME_NORM) + vol = PA_VOLUME_NORM; + + alsa_vol = (long) roundf(((float) vol * (u->hw_volume_max - u->hw_volume_min)) / PA_VOLUME_NORM) + u->hw_volume_min; + + if ((err = snd_mixer_selem_set_capture_volume(u->mixer_elem, u->mixer_map[i], alsa_vol)) < 0) + goto fail; + } + + return 0; + +fail: + pa_log_error("Unable to set volume: %s", snd_strerror(err)); + + s->get_volume = NULL; + s->set_volume = NULL; + return -1; +} + +static int source_get_mute_cb(pa_source *s) { + struct userdata *u = s->userdata; + int err, sw; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if ((err = snd_mixer_selem_get_capture_switch(u->mixer_elem, 0, &sw)) < 0) { + pa_log_error("Unable to get switch: %s", snd_strerror(err)); + + s->get_mute = NULL; + s->set_mute = NULL; + return -1; + } + + s->muted = !sw; + + return 0; +} + +static int source_set_mute_cb(pa_source *s) { + struct userdata *u = s->userdata; + int err; + + pa_assert(u); + pa_assert(u->mixer_elem); + + if ((err = snd_mixer_selem_set_capture_switch_all(u->mixer_elem, !s->muted)) < 0) { + pa_log_error("Unable to set switch: %s", snd_strerror(err)); + + s->get_mute = NULL; + s->set_mute = NULL; + return -1; + } + + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + + /* Read some data and pass it to the sources */ + if (PA_SOURCE_OPENED(u->source->thread_info.state)) { + + if (u->use_mmap) { + if (mmap_read(u) < 0) + goto fail; + + } else { + if (unix_read(u) < 0) + goto fail; + } + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + /* Tell ALSA about this and process its response */ + if (PA_SOURCE_OPENED(u->source->thread_info.state)) { + struct pollfd *pollfd; + unsigned short revents = 0; + int err; + unsigned n; + + pollfd = pa_rtpoll_item_get_pollfd(u->alsa_rtpoll_item, &n); + + if ((err = snd_pcm_poll_descriptors_revents(u->pcm_handle, pollfd, n, &revents)) < 0) { + pa_log("snd_pcm_poll_descriptors_revents() failed: %s", snd_strerror(err)); + goto fail; + } + + if (revents & (POLLERR|POLLNVAL|POLLHUP)) { + + if (revents & POLLERR) + pa_log_warn("Got POLLERR from ALSA"); + if (revents & POLLNVAL) + pa_log_warn("Got POLLNVAL from ALSA"); + if (revents & POLLHUP) + pa_log_warn("Got POLLHUP from ALSA"); + + /* Try to recover from this error */ + + switch (snd_pcm_state(u->pcm_handle)) { + + case SND_PCM_STATE_XRUN: + if ((err = snd_pcm_recover(u->pcm_handle, -EPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and XRUN: %s", snd_strerror(err)); + goto fail; + } + break; + + case SND_PCM_STATE_SUSPENDED: + if ((err = snd_pcm_recover(u->pcm_handle, -ESTRPIPE, 1)) != 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP and SUSPENDED: %s", snd_strerror(err)); + goto fail; + } + break; + + default: + + snd_pcm_drop(u->pcm_handle); + + if ((err = snd_pcm_prepare(u->pcm_handle)) < 0) { + pa_log_warn("Could not recover from POLLERR|POLLNVAL|POLLHUP with snd_pcm_prepare(): %s", snd_strerror(err)); + goto fail; + } + break; + } + } + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + + pa_modargs *ma = NULL; + struct userdata *u = NULL; + const char *dev_id; + pa_sample_spec ss; + pa_channel_map map; + uint32_t nfrags, frag_size; + snd_pcm_uframes_t period_size; + size_t frame_size; + snd_pcm_info_t *pcm_info = NULL; + int err; + char *t; + const char *name; + char *name_buf = NULL; + int namereg_fail; + pa_bool_t use_mmap = TRUE, b; + + snd_pcm_info_alloca(&pcm_info); + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_ALSA) < 0) { + pa_log("Failed to parse sample specification"); + goto fail; + } + + frame_size = pa_frame_size(&ss); + + nfrags = m->core->default_n_fragments; + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + if (frag_size <= 0) + frag_size = frame_size; + + if (pa_modargs_get_value_u32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_u32(ma, "fragment_size", &frag_size) < 0) { + pa_log("Failed to parse buffer metrics"); + goto fail; + } + period_size = frag_size/frame_size; + + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->use_mmap = use_mmap; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + u->alsa_rtpoll_item = NULL; + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + snd_config_update_free_global(); + + b = use_mmap; + + if ((dev_id = pa_modargs_get_value(ma, "device_id", NULL))) { + + if (!(u->pcm_handle = pa_alsa_open_by_device_id( + dev_id, + &u->device_name, + &ss, &map, + SND_PCM_STREAM_CAPTURE, + &nfrags, &period_size, + &b))) + goto fail; + + } else { + + if (!(u->pcm_handle = pa_alsa_open_by_device_string( + pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), + &u->device_name, + &ss, &map, + SND_PCM_STREAM_CAPTURE, + &nfrags, &period_size, + &b))) + goto fail; + } + + pa_assert(u->device_name); + pa_log_info("Successfully opened device %s.", u->device_name); + + if (use_mmap && !b) { + pa_log_info("Device doesn't support mmap(), falling back to UNIX read/write mode."); + u->use_mmap = use_mmap = b; + } + + if (u->use_mmap) + pa_log_info("Successfully enabled mmap() mode."); + + if ((err = snd_pcm_info(u->pcm_handle, pcm_info)) < 0) { + pa_log("Error fetching PCM info: %s", snd_strerror(err)); + goto fail; + } + + if ((err = pa_alsa_set_sw_params(u->pcm_handle)) < 0) { + pa_log("Failed to set software parameters: %s", snd_strerror(err)); + goto fail; + } + + /* ALSA might tweak the sample spec, so recalculate the frame size */ + frame_size = pa_frame_size(&ss); + + if ((err = snd_mixer_open(&u->mixer_handle, 0)) < 0) + pa_log("Error opening mixer: %s", snd_strerror(err)); + else { + pa_bool_t found = FALSE; + + if (pa_alsa_prepare_mixer(u->mixer_handle, u->device_name) >= 0) + found = TRUE; + else { + char *md = pa_sprintf_malloc("hw:%s", dev_id); + + if (strcmp(u->device_name, md)) + if (pa_alsa_prepare_mixer(u->mixer_handle, md) >= 0) + found = TRUE; + + pa_xfree(md); + } + + if (found) + if (!(u->mixer_elem = pa_alsa_find_elem(u->mixer_handle, "Capture", "Mic"))) + found = FALSE; + + if (!found) { + snd_mixer_close(u->mixer_handle); + u->mixer_handle = NULL; + } + } + + if ((name = pa_modargs_get_value(ma, "source_name", NULL))) + namereg_fail = 1; + else { + name = name_buf = pa_sprintf_malloc("alsa_input.%s", u->device_name); + namereg_fail = 0; + } + + u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + pa_xfree(name_buf); + + if (!u->source) { + pa_log("Failed to create source object"); + goto fail; + } + + u->source->parent.process_msg = source_process_msg; + u->source->userdata = u; + + pa_source_set_module(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_description(u->source, t = pa_sprintf_malloc( + "ALSA PCM on %s (%s)%s", + u->device_name, + snd_pcm_info_get_name(pcm_info), + use_mmap ? " via DMA" : "")); + pa_xfree(t); + + u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY; + + u->frame_size = frame_size; + u->fragment_size = frag_size = period_size * frame_size; + u->nfragments = nfrags; + u->hwbuf_size = u->fragment_size * nfrags; + + pa_log_info("Using %u fragments of size %lu bytes.", nfrags, (long unsigned) u->fragment_size); + + if (u->mixer_handle) { + pa_assert(u->mixer_elem); + + if (snd_mixer_selem_has_capture_volume(u->mixer_elem)) + if (pa_alsa_calc_mixer_map(u->mixer_elem, &map, u->mixer_map, FALSE) >= 0) { + u->source->get_volume = source_get_volume_cb; + u->source->set_volume = source_set_volume_cb; + snd_mixer_selem_get_capture_volume_range(u->mixer_elem, &u->hw_volume_min, &u->hw_volume_max); + u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; + } + + if (snd_mixer_selem_has_capture_switch(u->mixer_elem)) { + u->source->get_mute = source_get_mute_cb; + u->source->set_mute = source_set_mute_cb; + u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; + } + + u->mixer_fdl = pa_alsa_fdlist_new(); + + if (pa_alsa_fdlist_set_mixer(u->mixer_fdl, u->mixer_handle, m->core->mainloop) < 0) { + pa_log("Failed to initialize file descriptor monitoring"); + goto fail; + } + + snd_mixer_elem_set_callback(u->mixer_elem, mixer_callback); + snd_mixer_elem_set_callback_private(u->mixer_elem, u); + } else + u->mixer_fdl = NULL; + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + /* Get initial mixer settings */ + if (u->source->get_volume) + u->source->get_volume(u->source); + if (u->source->get_mute) + u->source->get_mute(u->source); + + pa_source_put(u->source); + + 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->source) + pa_source_unlink(u->source); + + 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->source) + pa_source_unref(u->source); + + if (u->alsa_rtpoll_item) + pa_rtpoll_item_free(u->alsa_rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->mixer_fdl) + pa_alsa_fdlist_free(u->mixer_fdl); + + if (u->mixer_handle) + snd_mixer_close(u->mixer_handle); + + if (u->pcm_handle) { + snd_pcm_drop(u->pcm_handle); + snd_pcm_close(u->pcm_handle); + } + + pa_xfree(u->device_name); + pa_xfree(u); + + snd_config_update_free_global(); +} diff --git a/src/modules/module-bt-proximity.c b/src/modules/module-bt-proximity.c new file mode 100644 index 00000000..62d530d4 --- /dev/null +++ b/src/modules/module-bt-proximity.c @@ -0,0 +1,492 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 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 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 <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/wait.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/modargs.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/start-child.h> + +#include "dbus-util.h" +#include "module-bt-proximity-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Bluetooth Proximity Volume Control"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "sink=<sink name> " + "hci=<hci device> " +); + +#define DEFAULT_HCI "hci0" + +static const char* const valid_modargs[] = { + "sink", + "rssi", + "hci", + NULL, +}; + +struct bonding { + struct userdata *userdata; + char address[18]; + + pid_t pid; + int fd; + + pa_io_event *io_event; + + enum { + UNKNOWN, + FOUND, + NOT_FOUND + } state; +}; + +struct userdata { + pa_module *module; + pa_dbus_connection *dbus_connection; + + char *sink_name; + char *hci, *hci_path; + + pa_hashmap *bondings; + + unsigned n_found; + unsigned n_unknown; + + pa_bool_t muted; +}; + +static void update_volume(struct userdata *u) { + pa_assert(u); + + if (u->muted && u->n_found > 0) { + pa_sink *s; + + u->muted = FALSE; + + if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) { + pa_log_warn("Sink device '%s' not available for unmuting.", pa_strnull(u->sink_name)); + return; + } + + pa_log_info("Found %u BT devices, unmuting.", u->n_found); + pa_sink_set_mute(s, FALSE); + + } else if (!u->muted && (u->n_found+u->n_unknown) <= 0) { + pa_sink *s; + + u->muted = TRUE; + + if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, FALSE))) { + pa_log_warn("Sink device '%s' not available for muting.", pa_strnull(u->sink_name)); + return; + } + + pa_log_info("No BT devices found, muting."); + pa_sink_set_mute(s, TRUE); + + } else + pa_log_info("%u devices now active, %u with unknown state.", u->n_found, u->n_unknown); +} + +static void bonding_free(struct bonding *b) { + pa_assert(b); + + if (b->state == FOUND) + pa_assert_se(b->userdata->n_found-- >= 1); + + if (b->state == UNKNOWN) + pa_assert_se(b->userdata->n_unknown-- >= 1); + + if (b->pid != (pid_t) -1) { + kill(b->pid, SIGTERM); + waitpid(b->pid, NULL, 0); + } + + if (b->fd >= 0) + pa_close(b->fd); + + if (b->io_event) + b->userdata->module->core->mainloop->io_free(b->io_event); + + pa_xfree(b); +} + +static void io_event_cb( + pa_mainloop_api*a, + pa_io_event* e, + int fd, + pa_io_event_flags_t events, + void *userdata) { + + struct bonding *b = userdata; + char x; + ssize_t r; + + pa_assert(b); + + if ((r = read(fd, &x, 1)) <= 0) { + pa_log_warn("Child watching '%s' died abnormally: %s", b->address, r == 0 ? "EOF" : pa_cstrerror(errno)); + + pa_assert_se(pa_hashmap_remove(b->userdata->bondings, b->address) == b); + bonding_free(b); + return; + } + + pa_assert_se(r == 1); + + if (b->state == UNKNOWN) + pa_assert_se(b->userdata->n_unknown-- >= 1); + + if (x == '+') { + pa_assert(b->state == UNKNOWN || b->state == NOT_FOUND); + + b->state = FOUND; + b->userdata->n_found++; + + pa_log_info("Device '%s' is alive.", b->address); + + } else { + pa_assert(x == '-'); + pa_assert(b->state == UNKNOWN || b->state == FOUND); + + if (b->state == FOUND) + b->userdata->n_found--; + + b->state = NOT_FOUND; + + pa_log_info("Device '%s' is dead.", b->address); + } + + update_volume(b->userdata); +} + +static struct bonding* bonding_new(struct userdata *u, const char *a) { + struct bonding *b = NULL; + DBusMessage *m = NULL, *r = NULL; + DBusError e; + const char *class; + + pa_assert(u); + pa_assert(a); + + pa_return_val_if_fail(strlen(a) == 17, NULL); + pa_return_val_if_fail(!pa_hashmap_get(u->bondings, a), NULL); + + dbus_error_init(&e); + + pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "GetRemoteMajorClass")); + pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID)); + r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), m, -1, &e); + + if (!r) { + pa_log("org.bluez.Adapter.GetRemoteMajorClass(%s) failed: %s", a, e.message); + goto fail; + } + + if (!(dbus_message_get_args(r, &e, DBUS_TYPE_STRING, &class, DBUS_TYPE_INVALID))) { + pa_log("Malformed org.bluez.Adapter.GetRemoteMajorClass signal: %s", e.message); + goto fail; + } + + if (strcmp(class, "phone")) { + pa_log_info("Found device '%s' of class '%s', ignoring.", a, class); + goto fail; + } + + b = pa_xnew(struct bonding, 1); + b->userdata = u; + pa_strlcpy(b->address, a, sizeof(b->address)); + b->pid = (pid_t) -1; + b->fd = -1; + b->io_event = NULL; + b->state = UNKNOWN; + u->n_unknown ++; + + pa_log_info("Watching device '%s' of class '%s'.", b->address, class); + + if ((b->fd = pa_start_child_for_read(PA_BT_PROXIMITY_HELPER, a, &b->pid)) < 0) { + pa_log("Failed to start helper tool."); + goto fail; + } + + b->io_event = u->module->core->mainloop->io_new( + u->module->core->mainloop, + b->fd, + PA_IO_EVENT_INPUT, + io_event_cb, + b); + + dbus_message_unref(m); + dbus_message_unref(r); + + pa_hashmap_put(u->bondings, b->address, b); + + return b; + +fail: + if (m) + dbus_message_unref(m); + if (r) + dbus_message_unref(r); + + if (b) + bonding_free(b); + + dbus_error_free(&e); + return NULL; +} + +static void bonding_remove(struct userdata *u, const char *a) { + struct bonding *b; + pa_assert(u); + + pa_return_if_fail((b = pa_hashmap_remove(u->bondings, a))); + + pa_log_info("No longer watching device '%s'", b->address); + bonding_free(b); +} + +static DBusHandlerResult filter_func(DBusConnection *connection, DBusMessage *m, void *userdata) { + struct userdata *u = userdata; + DBusError e; + + dbus_error_init(&e); + + if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingCreated")) { + const char *a; + + if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) { + pa_log("Malformed org.bluez.Adapter.BondingCreated signal: %s", e.message); + goto finish; + } + + bonding_new(u, a); + + return DBUS_HANDLER_RESULT_HANDLED; + + } else if (dbus_message_is_signal(m, "org.bluez.Adapter", "BondingRemoved")) { + + const char *a; + + if (!(dbus_message_get_args(m, &e, DBUS_TYPE_STRING, &a, DBUS_TYPE_INVALID))) { + pa_log("Malformed org.bluez.Adapter.BondingRemoved signal: %s", e.message); + goto finish; + } + + bonding_remove(u, a); + + return DBUS_HANDLER_RESULT_HANDLED; + } + +finish: + + dbus_error_free(&e); + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int add_matches(struct userdata *u, pa_bool_t add) { + char *filter1, *filter2; + DBusError e; + int r = -1; + + pa_assert(u); + dbus_error_init(&e); + + filter1 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingCreated',path='%s'", u->hci_path); + filter2 = pa_sprintf_malloc("type='signal',sender='org.bluez',interface='org.bluez.Adapter',member='BondingRemoved',path='%s'", u->hci_path); + + if (add) { + dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e); + + if (dbus_error_is_set(&e)) { + pa_log("dbus_bus_add_match(%s) failed: %s", filter1, e.message); + goto finish; + } + } else + dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter1, &e); + + + if (add) { + dbus_bus_add_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); + + if (dbus_error_is_set(&e)) { + pa_log("dbus_bus_add_match(%s) failed: %s", filter2, e.message); + dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); + goto finish; + } + } else + dbus_bus_remove_match(pa_dbus_connection_get(u->dbus_connection), filter2, &e); + + + if (add) + pa_assert_se(dbus_connection_add_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u, NULL)); + else + dbus_connection_remove_filter(pa_dbus_connection_get(u->dbus_connection), filter_func, u); + + r = 0; + +finish: + pa_xfree(filter1); + pa_xfree(filter2); + dbus_error_free(&e); + + return r; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + DBusError e; + DBusMessage *msg = NULL, *r = NULL; + DBusMessageIter iter, sub; + + pa_assert(m); + dbus_error_init(&e); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->module = m; + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + u->hci = pa_xstrdup(pa_modargs_get_value(ma, "hci", DEFAULT_HCI)); + u->hci_path = pa_sprintf_malloc("/org/bluez/%s", u->hci); + u->n_found = u->n_unknown = 0; + u->muted = FALSE; + + u->bondings = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + if (!(u->dbus_connection = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &e))) { + pa_log("Failed to get D-Bus connection: %s", e.message); + goto fail; + } + + if (add_matches(u, TRUE) < 0) + goto fail; + + pa_assert_se(msg = dbus_message_new_method_call("org.bluez", u->hci_path, "org.bluez.Adapter", "ListBondings")); + + if (!(r = dbus_connection_send_with_reply_and_block(pa_dbus_connection_get(u->dbus_connection), msg, -1, &e))) { + pa_log("org.bluez.Adapter.ListBondings failed: %s", e.message); + goto fail; + } + + dbus_message_iter_init(r, &iter); + + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY) { + pa_log("Malformed reply to org.bluez.Adapter.ListBondings."); + goto fail; + } + + dbus_message_iter_recurse(&iter, &sub); + + while (dbus_message_iter_get_arg_type(&sub) == DBUS_TYPE_STRING) { + const char *a = NULL; + + dbus_message_iter_get_basic(&sub, &a); + bonding_new(u, a); + + dbus_message_iter_next(&sub); + } + + dbus_message_unref(r); + dbus_message_unref(msg); + + pa_modargs_free(ma); + + if (pa_hashmap_size(u->bondings) == 0) + pa_log_warn("Warning: no phone device bonded."); + + update_volume(u); + + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + dbus_error_free(&e); + + if (msg) + dbus_message_unref(msg); + + if (r) + dbus_message_unref(r); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->bondings) { + struct bonding *b; + + while ((b = pa_hashmap_steal_first(u->bondings))) + bonding_free(b); + + pa_hashmap_free(u->bondings, NULL, NULL); + } + + if (u->dbus_connection) { + add_matches(u, FALSE); + pa_dbus_connection_unref(u->dbus_connection); + } + + pa_xfree(u->sink_name); + pa_xfree(u->hci_path); + pa_xfree(u->hci); + pa_xfree(u); +} diff --git a/src/modules/module-cli.c b/src/modules/module-cli.c new file mode 100644 index 00000000..ab311a82 --- /dev/null +++ b/src/modules/module-cli.c @@ -0,0 +1,123 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <unistd.h> + +#include <pulsecore/module.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/cli.h> +#include <pulsecore/sioman.h> +#include <pulsecore/log.h> +#include <pulsecore/modargs.h> +#include <pulsecore/macro.h> + +#include "module-cli-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Command line interface"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("exit_on_eof=<exit daemon after EOF?>"); + +static const char* const valid_modargs[] = { + "exit_on_eof", + NULL +}; + +static void eof_and_unload_cb(pa_cli*c, void *userdata) { + pa_module *m = userdata; + + pa_assert(c); + pa_assert(m); + + pa_module_unload_request(m); +} + +static void eof_and_exit_cb(pa_cli*c, void *userdata) { + pa_module *m = userdata; + + pa_assert(c); + pa_assert(m); + + m->core->mainloop->quit(m->core->mainloop, 0); +} + +int pa__init(pa_module*m) { + pa_iochannel *io; + pa_modargs *ma; + pa_bool_t exit_on_eof = FALSE; + + pa_assert(m); + + if (m->core->running_as_daemon) { + pa_log_info("Running as daemon, refusing to load this module."); + return 0; + } + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "exit_on_eof", &exit_on_eof) < 0) { + pa_log("exit_on_eof= expects boolean argument."); + goto fail; + } + + if (pa_stdio_acquire() < 0) { + pa_log("STDIN/STDUSE already in use."); + goto fail; + } + + io = pa_iochannel_new(m->core->mainloop, STDIN_FILENO, STDOUT_FILENO); + pa_iochannel_set_noclose(io, 1); + + m->userdata = pa_cli_new(m->core, io, m); + + pa_cli_set_eof_callback(m->userdata, exit_on_eof ? eof_and_exit_cb : eof_and_unload_cb, m); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + pa_assert(m); + + if (m->core->running_as_daemon == 0) { + pa_cli_free(m->userdata); + pa_stdio_release(); + } +} diff --git a/src/modules/module-combine.c b/src/modules/module-combine.c new file mode 100644 index 00000000..996cd4f6 --- /dev/null +++ b/src/modules/module-combine.c @@ -0,0 +1,1193 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <errno.h> + +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/module.h> +#include <pulsecore/llist.h> +#include <pulsecore/sink.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/mutex.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/core-error.h> + +#include "module-combine-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Combine multiple sinks to one"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "master=<master sink> " + "slaves=<slave sinks> " + "adjust_time=<seconds> " + "resample_method=<method> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "channel_map=<channel map>"); + +#define DEFAULT_SINK_NAME "combined" +#define MEMBLOCKQ_MAXLENGTH (1024*170) + +#define DEFAULT_ADJUST_TIME 10 + +static const char* const valid_modargs[] = { + "sink_name", + "master", + "slaves", + "adjust_time", + "resample_method", + "format", + "channels", + "rate", + "channel_map", + NULL +}; + +struct output { + struct userdata *userdata; + + pa_sink *sink; + pa_sink_input *sink_input; + + pa_asyncmsgq *inq, /* Message queue from the sink thread to this sink input */ + *outq; /* Message queue from this sink input to the sink thread */ + pa_rtpoll_item *inq_rtpoll_item, *outq_rtpoll_item; + + pa_memblockq *memblockq; + + pa_usec_t total_latency; + + PA_LLIST_FIELDS(struct output); +}; + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + pa_time_event *time_event; + uint32_t adjust_time; + + pa_bool_t automatic; + size_t block_size; + + pa_hook_slot *sink_new_slot, *sink_unlink_slot, *sink_state_changed_slot; + + pa_resample_method_t resample_method; + + struct timeval adjust_timestamp; + + struct output *master; + pa_idxset* outputs; /* managed in main context */ + + struct { + PA_LLIST_HEAD(struct output, active_outputs); /* managed in IO thread context */ + pa_atomic_t running; /* we cache that value here, so that every thread can query it cheaply */ + struct timeval timestamp; + pa_bool_t in_null_mode; + } thread_info; +}; + +enum { + SINK_MESSAGE_ADD_OUTPUT = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_REMOVE_OUTPUT, + SINK_MESSAGE_NEED +}; + +enum { + SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX +}; + +static void output_free(struct output *o); +static int output_create_sink_input(struct output *o); +static void update_master(struct userdata *u, struct output *o); +static void pick_master(struct userdata *u, struct output *except); + +static void adjust_rates(struct userdata *u) { + struct output *o; + pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, target_latency; + uint32_t base_rate; + uint32_t idx; + + pa_assert(u); + pa_sink_assert_ref(u->sink); + + if (pa_idxset_size(u->outputs) <= 0) + return; + + if (!u->master) + return; + + if (!PA_SINK_OPENED(pa_sink_get_state(u->sink))) + return; + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + pa_usec_t sink_latency; + + if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink))) + continue; + + sink_latency = pa_sink_get_latency(o->sink); + o->total_latency = sink_latency + pa_sink_input_get_latency(o->sink_input); + + if (sink_latency > max_sink_latency) + max_sink_latency = sink_latency; + + if (min_total_latency == (pa_usec_t) -1 || o->total_latency < min_total_latency) + min_total_latency = o->total_latency; + } + + if (min_total_latency == (pa_usec_t) -1) + return; + + target_latency = max_sink_latency > min_total_latency ? max_sink_latency : min_total_latency; + + pa_log_info("[%s] target latency is %0.0f usec.", u->sink->name, (float) target_latency); + pa_log_info("[%s] master %s latency %0.0f usec.", u->sink->name, u->master->sink->name, (float) u->master->total_latency); + + base_rate = u->sink->sample_spec.rate; + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + uint32_t r = base_rate; + + if (!o->sink_input || !PA_SINK_OPENED(pa_sink_get_state(o->sink))) + continue; + + if (o->total_latency < target_latency) + r -= (uint32_t) (((((double) target_latency - o->total_latency))/u->adjust_time)*r/PA_USEC_PER_SEC); + else if (o->total_latency > target_latency) + r += (uint32_t) (((((double) o->total_latency - target_latency))/u->adjust_time)*r/PA_USEC_PER_SEC); + + if (r < (uint32_t) (base_rate*0.9) || r > (uint32_t) (base_rate*1.1)) { + pa_log_warn("[%s] sample rates too different, not adjusting (%u vs. %u).", o->sink_input->name, base_rate, r); + pa_sink_input_set_rate(o->sink_input, base_rate); + } else { + pa_log_info("[%s] new rate is %u Hz; ratio is %0.3f; latency is %0.0f usec.", o->sink_input->name, r, (double) r / base_rate, (float) o->total_latency); + pa_sink_input_set_rate(o->sink_input, r); + } + } +} + +static void time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + struct timeval n; + + pa_assert(u); + pa_assert(a); + pa_assert(u->time_event == e); + + adjust_rates(u); + + pa_gettimeofday(&n); + n.tv_sec += u->adjust_time; + u->sink->core->mainloop->time_restart(e, &n); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority+1); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + pa_rtclock_get(&u->thread_info.timestamp); + u->thread_info.in_null_mode = FALSE; + + for (;;) { + int ret; + + /* If no outputs are connected, render some data and drop it immediately. */ + if (u->sink->thread_info.state == PA_SINK_RUNNING && !u->thread_info.active_outputs) { + struct timeval now; + + pa_rtclock_get(&now); + + if (!u->thread_info.in_null_mode || pa_timeval_cmp(&u->thread_info.timestamp, &now) <= 0) { + pa_sink_skip(u->sink, u->block_size); + + if (!u->thread_info.in_null_mode) + u->thread_info.timestamp = now; + + pa_timeval_add(&u->thread_info.timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec)); + } + + pa_rtpoll_set_timer_absolute(u->rtpoll, &u->thread_info.timestamp); + u->thread_info.in_null_mode = TRUE; + + } else { + pa_rtpoll_set_timer_disabled(u->rtpoll); + u->thread_info.in_null_mode = FALSE; + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) { + pa_log_info("pa_rtpoll_run() = %i", ret); + goto fail; + } + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +/* Called from I/O thread context */ +static void render_memblock(struct userdata *u, struct output *o, size_t length) { + pa_assert(u); + pa_assert(o); + + /* We are run by the sink thread, on behalf of an output (o). The + * other output is waiting for us, hence it is safe to access its + * mainblockq and asyncmsgq directly. */ + + /* If we are not running, we cannot produce any data */ + if (!pa_atomic_load(&u->thread_info.running)) + return; + + /* Maybe there's some data in the requesting output's queue + * now? */ + while (pa_asyncmsgq_process_one(o->inq) > 0) + ; + + /* Ok, now let's prepare some data if we really have to */ + while (!pa_memblockq_is_readable(o->memblockq)) { + struct output *j; + pa_memchunk chunk; + + /* Render data! */ + pa_sink_render(u->sink, length, &chunk); + + /* OK, let's send this data to the other threads */ + for (j = u->thread_info.active_outputs; j; j = j->next) + + /* Send to other outputs, which are not the requesting + * one */ + + if (j != o) + pa_asyncmsgq_post(j->inq, PA_MSGOBJECT(j->sink_input), SINK_INPUT_MESSAGE_POST, NULL, 0, &chunk, NULL); + + /* And place it directly into the requesting output's queue */ + if (o) + pa_memblockq_push_align(o->memblockq, &chunk); + + pa_memblock_unref(chunk.memblock); + } +} + +/* Called from I/O thread context */ +static void request_memblock(struct output *o, size_t length) { + pa_assert(o); + pa_sink_input_assert_ref(o->sink_input); + pa_sink_assert_ref(o->userdata->sink); + + /* If another thread already prepared some data we received + * the data over the asyncmsgq, hence let's first process + * it. */ + while (pa_asyncmsgq_process_one(o->inq) > 0) + ; + + /* Check whether we're now readable */ + if (pa_memblockq_is_readable(o->memblockq)) + return; + + /* OK, we need to prepare new data, but only if the sink is actually running */ + if (pa_atomic_load(&o->userdata->thread_info.running)) + pa_asyncmsgq_send(o->outq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_NEED, o, length, NULL); +} + +/* Called from I/O thread context */ +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + /* If necessary, get some new data */ + request_memblock(o, length); + + return pa_memblockq_peek(o->memblockq, chunk); +} + +/* Called from I/O thread context */ +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert(length > 0); + pa_assert_se(o = i->userdata); + + pa_memblockq_drop(o->memblockq, length); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + /* Set up the queue from the sink thread to us */ + pa_assert(!o->inq_rtpoll_item); + o->inq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq( + i->sink->rtpoll, + PA_RTPOLL_LATE, /* This one is not that important, since we check for data in _peek() anyway. */ + o->inq); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert_se(o = i->userdata); + + /* Shut down the queue from the sink thread to us */ + pa_assert(o->inq_rtpoll_item); + pa_rtpoll_item_free(o->inq_rtpoll_item); + o->inq_rtpoll_item = NULL; +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct output *o; + + pa_sink_input_assert_ref(i); + pa_assert(o = i->userdata); + + pa_module_unload_request(o->userdata->module); + output_free(o); +} + +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *obj, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct output *o = PA_SINK_INPUT(obj)->userdata; + + switch (code) { + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = data; + + *r = pa_bytes_to_usec(pa_memblockq_get_length(o->memblockq), &o->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + case SINK_INPUT_MESSAGE_POST: + + if (PA_SINK_OPENED(o->sink_input->sink->thread_info.state)) + pa_memblockq_push_align(o->memblockq, chunk); + else + pa_memblockq_flush(o->memblockq); + + break; + } + + return pa_sink_input_process_msg(obj, code, data, offset, chunk); +} + +/* Called from main context */ +static void disable_output(struct output *o) { + pa_assert(o); + + if (!o->sink_input) + return; + + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); + pa_sink_input_unlink(o->sink_input); + pa_sink_input_unref(o->sink_input); + o->sink_input = NULL; + +} + +/* Called from main context */ +static void enable_output(struct output *o) { + pa_assert(o); + + if (o->sink_input) + return; + + if (output_create_sink_input(o) >= 0) { + + pa_memblockq_flush(o->memblockq); + + pa_sink_input_put(o->sink_input); + + if (o->userdata->sink && PA_SINK_LINKED(pa_sink_get_state(o->userdata->sink))) + pa_asyncmsgq_send(o->userdata->sink->asyncmsgq, PA_MSGOBJECT(o->userdata->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); + } +} + +/* Called from main context */ +static void suspend(struct userdata *u) { + struct output *o; + uint32_t idx; + + pa_assert(u); + + /* Let's suspend by unlinking all streams */ + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + disable_output(o); + + pick_master(u, NULL); + + pa_log_info("Device suspended..."); +} + +/* Called from main context */ +static void unsuspend(struct userdata *u) { + struct output *o; + uint32_t idx; + + pa_assert(u); + + /* Let's resume */ + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + + pa_sink_suspend(o->sink, FALSE); + + if (PA_SINK_OPENED(pa_sink_get_state(o->sink))) + enable_output(o); + } + + pick_master(u, NULL); + + pa_log_info("Resumed successfully..."); +} + +/* Called from main context */ +static int sink_set_state(pa_sink *sink, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(sink); + pa_assert_se(u = sink->userdata); + + /* Please note that in contrast to the ALSA modules we call + * suspend/unsuspend from main context here! */ + + switch (state) { + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(pa_sink_get_state(u->sink))); + + suspend(u); + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (pa_sink_get_state(u->sink) == PA_SINK_SUSPENDED) + unsuspend(u); + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + return 0; +} + +/* Called from thread context of the master */ +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: + pa_atomic_store(&u->thread_info.running, PA_PTR_TO_UINT(data) == PA_SINK_RUNNING); + break; + + case PA_SINK_MESSAGE_GET_LATENCY: + + /* This code will only be called when running in NULL + * mode, i.e. when no output is attached. See + * sink_get_latency_cb() below */ + + if (u->thread_info.in_null_mode) { + struct timeval now; + + if (pa_timeval_cmp(&u->thread_info.timestamp, pa_rtclock_get(&now)) > 0) { + *((pa_usec_t*) data) = pa_timeval_diff(&u->thread_info.timestamp, &now); + break; + } + } + + *((pa_usec_t*) data) = 0; + + break; + + case SINK_MESSAGE_ADD_OUTPUT: { + struct output *op = data; + + PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, op); + + pa_assert(!op->outq_rtpoll_item); + + /* Create pa_asyncmsgq to the sink thread */ + + op->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq( + u->rtpoll, + PA_RTPOLL_EARLY-1, /* This item is very important */ + op->outq); + + return 0; + } + + case SINK_MESSAGE_REMOVE_OUTPUT: { + struct output *op = data; + + PA_LLIST_REMOVE(struct output, u->thread_info.active_outputs, op); + + /* Remove the q that leads from this output to the sink thread */ + + pa_assert(op->outq_rtpoll_item); + pa_rtpoll_item_free(op->outq_rtpoll_item); + op->outq_rtpoll_item = NULL; + + return 0; + } + + case SINK_MESSAGE_NEED: + render_memblock(u, data, (size_t) offset); + return 0; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static pa_usec_t sink_get_latency_cb(pa_sink *s) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (u->master) { + /* If we have a master sink, we just return the latency of it + * and add our own buffering on top */ + + if (!u->master->sink_input) + return 0; + + return + pa_sink_input_get_latency(u->master->sink_input) + + pa_sink_get_latency(u->master->sink); + + } else { + pa_usec_t usec = 0; + + /* We have no master, hence let's ask our own thread which + * implements the NULL sink */ + + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + return 0; + + return usec; + } +} + +static void update_description(struct userdata *u) { + int first = 1; + char *t; + struct output *o; + uint32_t idx; + + pa_assert(u); + + if (pa_idxset_isempty(u->outputs)) { + pa_sink_set_description(u->sink, "Simultaneous output"); + return; + } + + t = pa_xstrdup("Simultaneous output to"); + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) { + char *e; + + if (first) { + e = pa_sprintf_malloc("%s %s", t, o->sink->description); + first = 0; + } else + e = pa_sprintf_malloc("%s, %s", t, o->sink->description); + + pa_xfree(t); + t = e; + } + + pa_sink_set_description(u->sink, t); + pa_xfree(t); +} + +static void update_master(struct userdata *u, struct output *o) { + pa_assert(u); + + if (u->master == o) + return; + + if ((u->master = o)) + pa_log_info("Master sink is now '%s'", o->sink_input->sink->name); + else + pa_log_info("No master selected, lacking suitable outputs."); +} + +static void pick_master(struct userdata *u, struct output *except) { + struct output *o; + uint32_t idx; + pa_assert(u); + + if (u->master && + u->master != except && + u->master->sink_input && + PA_SINK_OPENED(pa_sink_get_state(u->master->sink))) { + update_master(u, u->master); + return; + } + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + if (o != except && + o->sink_input && + PA_SINK_OPENED(pa_sink_get_state(o->sink))) { + update_master(u, o); + return; + } + + update_master(u, NULL); +} + +static int output_create_sink_input(struct output *o) { + pa_sink_input_new_data data; + char *t; + + pa_assert(o); + + if (o->sink_input) + return 0; + + t = pa_sprintf_malloc("Simultaneous output on %s", o->sink->description); + + pa_sink_input_new_data_init(&data); + data.sink = o->sink; + data.driver = __FILE__; + data.name = t; + pa_sink_input_new_data_set_sample_spec(&data, &o->userdata->sink->sample_spec); + pa_sink_input_new_data_set_channel_map(&data, &o->userdata->sink->channel_map); + data.module = o->userdata->module; + data.resample_method = o->userdata->resample_method; + + o->sink_input = pa_sink_input_new(o->userdata->core, &data, PA_SINK_INPUT_VARIABLE_RATE|PA_SINK_INPUT_DONT_MOVE); + + pa_xfree(t); + + if (!o->sink_input) + return -1; + + o->sink_input->parent.process_msg = sink_input_process_msg; + o->sink_input->peek = sink_input_peek_cb; + o->sink_input->drop = sink_input_drop_cb; + o->sink_input->attach = sink_input_attach_cb; + o->sink_input->detach = sink_input_detach_cb; + o->sink_input->kill = sink_input_kill_cb; + o->sink_input->userdata = o; + + + return 0; +} + +static struct output *output_new(struct userdata *u, pa_sink *sink) { + struct output *o; + + pa_assert(u); + pa_assert(sink); + pa_assert(u->sink); + + o = pa_xnew(struct output, 1); + o->userdata = u; + o->inq = pa_asyncmsgq_new(0); + o->outq = pa_asyncmsgq_new(0); + o->inq_rtpoll_item = NULL; + o->outq_rtpoll_item = NULL; + o->sink = sink; + o->sink_input = NULL; + o->memblockq = pa_memblockq_new( + 0, + MEMBLOCKQ_MAXLENGTH, + MEMBLOCKQ_MAXLENGTH, + pa_frame_size(&u->sink->sample_spec), + 1, + 0, + NULL); + + pa_assert_se(pa_idxset_put(u->outputs, o, NULL) == 0); + + if (u->sink && PA_SINK_LINKED(pa_sink_get_state(u->sink))) + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ADD_OUTPUT, o, 0, NULL); + else { + /* If the sink is not yet started, we need to do the activation ourselves */ + PA_LLIST_PREPEND(struct output, u->thread_info.active_outputs, o); + + o->outq_rtpoll_item = pa_rtpoll_item_new_asyncmsgq( + u->rtpoll, + PA_RTPOLL_EARLY-1, /* This item is very important */ + o->outq); + } + + if (PA_SINK_OPENED(pa_sink_get_state(u->sink)) || pa_sink_get_state(u->sink) == PA_SINK_INIT) { + pa_sink_suspend(sink, FALSE); + + if (PA_SINK_OPENED(pa_sink_get_state(sink))) + if (output_create_sink_input(o) < 0) + goto fail; + } + + + update_description(u); + + return o; + +fail: + + if (o) { + pa_idxset_remove_by_data(u->outputs, o, NULL); + + if (o->sink_input) { + pa_sink_input_unlink(o->sink_input); + pa_sink_input_unref(o->sink_input); + } + + if (o->memblockq) + pa_memblockq_free(o->memblockq); + + if (o->inq) + pa_asyncmsgq_unref(o->inq); + + if (o->outq) + pa_asyncmsgq_unref(o->outq); + + pa_xfree(o); + } + + return NULL; +} + +static pa_hook_result_t sink_new_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; + + pa_core_assert_ref(c); + pa_sink_assert_ref(s); + pa_assert(u); + pa_assert(u->automatic); + + if (!(s->flags & PA_SINK_HARDWARE) || s == u->sink) + return PA_HOOK_OK; + + pa_log_info("Configuring new sink: %s", s->name); + + if (!(o = output_new(u, s))) { + pa_log("Failed to create sink input on sink '%s'.", s->name); + return PA_HOOK_OK; + } + + if (o->sink_input) + pa_sink_input_put(o->sink_input); + + pick_master(u, NULL); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_unlink_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; + uint32_t idx; + + pa_assert(c); + pa_sink_assert_ref(s); + pa_assert(u); + + if (s == u->sink) + return PA_HOOK_OK; + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + if (o->sink == s) + break; + + if (!o) + return PA_HOOK_OK; + + pa_log_info("Unconfiguring sink: %s", s->name); + + output_free(o); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) { + struct output *o; + uint32_t idx; + pa_sink_state_t state; + + if (s == u->sink) + return PA_HOOK_OK; + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + if (o->sink == s) + break; + + if (!o) + return PA_HOOK_OK; + + state = pa_sink_get_state(s); + + if (PA_SINK_OPENED(state) && PA_SINK_OPENED(pa_sink_get_state(u->sink)) && !o->sink_input) { + enable_output(o); + pick_master(u, NULL); + } + + if (state == PA_SINK_SUSPENDED && o->sink_input) { + disable_output(o); + pick_master(u, o); + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_modargs *ma = NULL; + const char *master_name, *slaves, *rm; + pa_sink *master_sink = NULL; + int resample_method = PA_RESAMPLER_TRIVIAL; + pa_sample_spec ss; + pa_channel_map map; + struct output *o; + uint32_t idx; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments"); + goto fail; + } + + if ((rm = pa_modargs_get_value(ma, "resample_method", NULL))) { + if ((resample_method = pa_parse_resample_method(rm)) < 0) { + pa_log("invalid resample method '%s'", rm); + goto fail; + } + } + + u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->sink = NULL; + u->master = NULL; + u->time_event = NULL; + u->adjust_time = DEFAULT_ADJUST_TIME; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + u->thread = NULL; + u->resample_method = resample_method; + u->outputs = pa_idxset_new(NULL, NULL); + memset(&u->adjust_timestamp, 0, sizeof(u->adjust_timestamp)); + u->sink_new_slot = u->sink_unlink_slot = u->sink_state_changed_slot = NULL; + PA_LLIST_HEAD_INIT(struct output, u->thread_info.active_outputs); + pa_atomic_store(&u->thread_info.running, FALSE); + u->thread_info.in_null_mode = FALSE; + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + if (pa_modargs_get_value_u32(ma, "adjust_time", &u->adjust_time) < 0) { + pa_log("Failed to parse adjust_time value"); + goto fail; + } + + master_name = pa_modargs_get_value(ma, "master", NULL); + slaves = pa_modargs_get_value(ma, "slaves", NULL); + if (!master_name != !slaves) { + pa_log("No master or slave sinks specified"); + goto fail; + } + + if (master_name) { + if (!(master_sink = pa_namereg_get(m->core, master_name, PA_NAMEREG_SINK, 1))) { + pa_log("Invalid master sink '%s'", master_name); + goto fail; + } + + ss = master_sink->sample_spec; + u->automatic = FALSE; + } else { + master_sink = NULL; + ss = m->core->default_sample_spec; + u->automatic = TRUE; + } + + if ((pa_modargs_get_sample_spec(ma, &ss) < 0)) { + pa_log("Invalid sample specification."); + goto fail; + } + + if (master_sink && ss.channels == master_sink->sample_spec.channels) + map = master_sink->channel_map; + else { + pa_assert_se(pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_AUX)); + pa_channel_map_init_auto(&map, ss.channels, PA_CHANNEL_MAP_DEFAULT); + } + + if ((pa_modargs_get_channel_map(ma, NULL, &map) < 0)) { + pa_log("Invalid channel map."); + goto fail; + } + + if (ss.channels != map.channels) { + pa_log("Channel map and sample specification don't match."); + goto fail; + } + + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_log("Failed to create sink"); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->get_latency = sink_get_latency_cb; + u->sink->set_state = sink_set_state; + u->sink->userdata = u; + + u->sink->flags = PA_SINK_LATENCY; + pa_sink_set_module(u->sink, m); + pa_sink_set_description(u->sink, "Simultaneous output"); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + + u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */ + if (u->block_size <= 0) + u->block_size = pa_frame_size(&ss); + + if (!u->automatic) { + const char*split_state; + char *n = NULL; + pa_assert(slaves); + + /* The master and slaves have been specified manually */ + + if (!(u->master = output_new(u, master_sink))) { + pa_log("Failed to create master sink input on sink '%s'.", master_sink->name); + goto fail; + } + + split_state = NULL; + while ((n = pa_split(slaves, ",", &split_state))) { + pa_sink *slave_sink; + + if (!(slave_sink = pa_namereg_get(m->core, n, PA_NAMEREG_SINK, 1)) || slave_sink == u->sink) { + pa_log("Invalid slave sink '%s'", n); + pa_xfree(n); + goto fail; + } + + pa_xfree(n); + + if (!output_new(u, slave_sink)) { + pa_log("Failed to create slave sink input on sink '%s'.", slave_sink->name); + goto fail; + } + } + + if (pa_idxset_size(u->outputs) <= 1) + pa_log_warn("No slave sinks specified."); + + u->sink_new_slot = NULL; + + } else { + pa_sink *s; + + /* We're in automatic mode, we elect one hw sink to the master + * and attach all other hw sinks as slaves to it */ + + for (s = pa_idxset_first(m->core->sinks, &idx); s; s = pa_idxset_next(m->core->sinks, &idx)) { + + if (!(s->flags & PA_SINK_HARDWARE) || s == u->sink) + continue; + + if (!output_new(u, s)) { + pa_log("Failed to create sink input on sink '%s'.", s->name); + goto fail; + } + } + + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) sink_new_hook_cb, u); + } + + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_unlink_hook_cb, u); + u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) sink_state_changed_hook_cb, u); + + pick_master(u, NULL); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + /* Activate the sink and the sink inputs */ + pa_sink_put(u->sink); + + for (o = pa_idxset_first(u->outputs, &idx); o; o = pa_idxset_next(u->outputs, &idx)) + if (o->sink_input) + pa_sink_input_put(o->sink_input); + + if (u->adjust_time > 0) { + struct timeval tv; + pa_gettimeofday(&tv); + tv.tv_sec += u->adjust_time; + u->time_event = m->core->mainloop->time_new(m->core->mainloop, &tv, time_callback, u); + } + + pa_modargs_free(ma); + + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +static void output_free(struct output *o) { + pa_assert(o); + + pick_master(o->userdata, o); + + disable_output(o); + + pa_assert_se(pa_idxset_remove_by_data(o->userdata->outputs, o, NULL)); + + update_description(o->userdata); + + if (o->inq_rtpoll_item) + pa_rtpoll_item_free(o->inq_rtpoll_item); + + if (o->outq_rtpoll_item) + pa_rtpoll_item_free(o->outq_rtpoll_item); + + if (o->inq) + pa_asyncmsgq_unref(o->inq); + + if (o->outq) + pa_asyncmsgq_unref(o->outq); + + if (o->memblockq) + pa_memblockq_free(o->memblockq); + + pa_xfree(o); +} + +void pa__done(pa_module*m) { + struct userdata *u; + struct output *o; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_new_slot) + pa_hook_slot_free(u->sink_new_slot); + + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + + if (u->sink_state_changed_slot) + pa_hook_slot_free(u->sink_state_changed_slot); + + if (u->outputs) { + while ((o = pa_idxset_first(u->outputs, NULL))) + output_free(o); + + pa_idxset_free(u->outputs, NULL, NULL); + } + + 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) + pa_rtpoll_free(u->rtpoll); + + if (u->time_event) + u->core->mainloop->time_free(u->time_event); + + pa_xfree(u); +} diff --git a/src/modules/module-default-device-restore.c b/src/modules/module-default-device-restore.c new file mode 100644 index 00000000..b550ae78 --- /dev/null +++ b/src/modules/module-default-device-restore.c @@ -0,0 +1,101 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/core-util.h> +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> + +#include "module-default-device-restore-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Automatically restore the default sink and source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define DEFAULT_SINK_FILE "default-sink" +#define DEFAULT_SOURCE_FILE "default-source" + +int pa__init(pa_module *m) { + FILE *f; + + /* We never overwrite manually configured settings */ + + if (m->core->default_sink_name) + pa_log_info("Manually configured default sink, not overwriting."); + else if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "r"))) { + char ln[256] = ""; + + fgets(ln, sizeof(ln)-1, f); + pa_strip_nl(ln); + fclose(f); + + if (!ln[0]) + pa_log_debug("No previous default sink setting, ignoring."); + else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SINK, 1)) { + pa_namereg_set_default(m->core, ln, PA_NAMEREG_SINK); + pa_log_debug("Restored default sink '%s'.", ln); + } else + pa_log_info("Saved default sink '%s' not existant, not restoring default sink setting.", ln); + } + + if (m->core->default_source_name) + pa_log_info("Manually configured default source, not overwriting."); + else if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "r"))) { + char ln[256] = ""; + + fgets(ln, sizeof(ln)-1, f); + pa_strip_nl(ln); + fclose(f); + + if (!ln[0]) + pa_log_debug("No previous default source setting, ignoring."); + else if (pa_namereg_get(m->core, ln, PA_NAMEREG_SOURCE, 1)) { + pa_namereg_set_default(m->core, ln, PA_NAMEREG_SOURCE); + pa_log_debug("Restored default source '%s'.", ln); + } else + pa_log_info("Saved default source '%s' not existant, not restoring default source setting.", ln); + } + + return 0; +} + +void pa__done(pa_module*m) { + FILE *f; + + if ((f = pa_open_config_file(NULL, DEFAULT_SINK_FILE, NULL, NULL, "w"))) { + const char *n = pa_namereg_get_default_sink_name(m->core); + fprintf(f, "%s\n", n ? n : ""); + fclose(f); + } + + if ((f = pa_open_config_file(NULL, DEFAULT_SOURCE_FILE, NULL, NULL, "w"))) { + const char *n = pa_namereg_get_default_source_name(m->core); + fprintf(f, "%s\n", n ? n : ""); + fclose(f); + } +} diff --git a/src/modules/module-defs.h.m4 b/src/modules/module-defs.h.m4 new file mode 100644 index 00000000..a49e8329 --- /dev/null +++ b/src/modules/module-defs.h.m4 @@ -0,0 +1,32 @@ +dnl $Id$ +changecom(`/*', `*/')dnl +define(`module_name', patsubst(patsubst(patsubst(fname, `-symdef.h$'), `^.*/'), `[^0-9a-zA-Z]', `_'))dnl +define(`c_symbol', patsubst(module_name, `[^0-9a-zA-Z]', `_'))dnl +define(`c_macro', patsubst(module_name, `[^0-9a-zA-Z]', `'))dnl +define(`incmacro', `foo'c_macro`symdeffoo')dnl +define(`gen_symbol', `#define $1 'module_name`_LTX_$1')dnl +#ifndef incmacro +#define incmacro + +#include <pulsecore/core.h> +#include <pulsecore/module.h> +#include <pulsecore/macro.h> + +gen_symbol(pa__init) +gen_symbol(pa__done) +gen_symbol(pa__get_author) +gen_symbol(pa__get_description) +gen_symbol(pa__get_usage) +gen_symbol(pa__get_version) +gen_symbol(pa__load_once) + +int pa__init(pa_module*m); +void pa__done(pa_module*m); + +const char* pa__get_author(void); +const char* pa__get_description(void); +const char* pa__get_usage(void); +const char* pa__get_version(void); +pa_bool_t pa__load_once(void); + +#endif diff --git a/src/modules/module-detect.c b/src/modules/module-detect.c new file mode 100644 index 00000000..ee650dfd --- /dev/null +++ b/src/modules/module-detect.c @@ -0,0 +1,272 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + Copyright 2006 Diego Pettenò + + 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 <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "module-detect-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("just-one=<boolean>"); + +static const char* const valid_modargs[] = { + "just-one", + NULL +}; + +#ifdef HAVE_ALSA + +static int detect_alsa(pa_core *c, int just_one) { + FILE *f; + int n = 0, n_sink = 0, n_source = 0; + + if (!(f = fopen("/proc/asound/devices", "r"))) { + + if (errno != ENOENT) + pa_log_error("open(\"/proc/asound/devices\") failed: %s", pa_cstrerror(errno)); + + return -1; + } + + while (!feof(f)) { + char line[64], args[64]; + unsigned device, subdevice; + int is_sink; + + if (!fgets(line, sizeof(line), f)) + break; + + line[strcspn(line, "\r\n")] = 0; + + if (pa_endswith(line, "digital audio playback")) + is_sink = 1; + else if (pa_endswith(line, "digital audio capture")) + is_sink = 0; + else + continue; + + if (just_one && is_sink && n_sink >= 1) + continue; + + if (just_one && !is_sink && n_source >= 1) + continue; + + if (sscanf(line, " %*i: [%u- %u]: ", &device, &subdevice) != 2) + continue; + + /* Only one sink per device */ + if (subdevice != 0) + continue; + + pa_snprintf(args, sizeof(args), "device=hw:%u", device); + if (!pa_module_load(c, is_sink ? "module-alsa-sink" : "module-alsa-source", args)) + continue; + + n++; + + if (is_sink) + n_sink++; + else + n_source++; + } + + fclose(f); + + return n; +} +#endif + +#ifdef HAVE_OSS +static int detect_oss(pa_core *c, int just_one) { + FILE *f; + int n = 0, b = 0; + + if (!(f = fopen("/dev/sndstat", "r")) && + !(f = fopen("/proc/sndstat", "r")) && + !(f = fopen("/proc/asound/oss/sndstat", "r"))) { + + if (errno != ENOENT) + pa_log_error("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); + + return -1; + } + + while (!feof(f)) { + char line[64], args[64]; + unsigned device; + + if (!fgets(line, sizeof(line), f)) + break; + + line[strcspn(line, "\r\n")] = 0; + + if (!b) { + b = strcmp(line, "Audio devices:") == 0 || strcmp(line, "Installed devices:") == 0; + continue; + } + + if (line[0] == 0) + break; + + if (sscanf(line, "%u: ", &device) == 1) { + if (device == 0) + pa_snprintf(args, sizeof(args), "device=/dev/dsp"); + else + pa_snprintf(args, sizeof(args), "device=/dev/dsp%u", device); + + if (!pa_module_load(c, "module-oss", args)) + continue; + + } else if (sscanf(line, "pcm%u: ", &device) == 1) { + /* FreeBSD support, the devices are named /dev/dsp0.0, dsp0.1 and so on */ + pa_snprintf(args, sizeof(args), "device=/dev/dsp%u.0", device); + + if (!pa_module_load(c, "module-oss", args)) + continue; + } + + n++; + + if (just_one) + break; + } + + fclose(f); + return n; +} +#endif + +#ifdef HAVE_SOLARIS +static int detect_solaris(pa_core *c, int just_one) { + struct stat s; + const char *dev; + char args[64]; + + dev = getenv("AUDIODEV"); + if (!dev) + dev = "/dev/audio"; + + if (stat(dev, &s) < 0) { + if (errno != ENOENT) + pa_log_error("failed to open device %s: %s", dev, pa_cstrerror(errno)); + return -1; + } + + if (!S_ISCHR(s.st_mode)) + return 0; + + pa_snprintf(args, sizeof(args), "device=%s", dev); + + if (!pa_module_load(c, "module-solaris", args)) + return 0; + + return 1; +} +#endif + +#ifdef OS_IS_WIN32 +static int detect_waveout(pa_core *c, int just_one) { + /* + * FIXME: No point in enumerating devices until the plugin supports + * selecting anything but the first. + */ + if (!pa_module_load(c, "module-waveout", "")) + return 0; + + return 1; +} +#endif + +int pa__init(pa_module*m) { + pa_bool_t just_one = FALSE; + int n = 0; + pa_modargs *ma; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "just-one", &just_one) < 0) { + pa_log("just_one= expects a boolean argument."); + goto fail; + } + +#if HAVE_ALSA + if ((n = detect_alsa(m->core, just_one)) <= 0) +#endif +#if HAVE_OSS + if ((n = detect_oss(m->core, just_one)) <= 0) +#endif +#if HAVE_SOLARIS + if ((n = detect_solaris(m->core, just_one)) <= 0) +#endif +#if OS_IS_WIN32 + if ((n = detect_waveout(m->core, just_one)) <= 0) +#endif + { + pa_log_warn("failed to detect any sound hardware."); + goto fail; + } + + pa_log_info("loaded %i modules.", n); + + /* We were successful and can unload ourselves now. */ + pa_module_unload_request(m); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + return -1; +} diff --git a/src/modules/module-esound-compat-spawnfd.c b/src/modules/module-esound-compat-spawnfd.c new file mode 100644 index 00000000..8321192b --- /dev/null +++ b/src/modules/module-esound-compat-spawnfd.c @@ -0,0 +1,80 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "module-esound-compat-spawnfd-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnfd emulation"); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_USAGE("fd=<file descriptor>"); + +static const char* const valid_modargs[] = { + "fd", + NULL, +}; + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + int ret = -1, fd = -1; + char x = 1; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs)) || + pa_modargs_get_value_s32(ma, "fd", &fd) < 0 || + fd < 0) { + + pa_log("Failed to parse module arguments"); + goto finish; + } + + if (pa_loop_write(fd, &x, sizeof(x), NULL) != sizeof(x)) + pa_log_warn("write(%u, 1, 1) failed: %s", fd, pa_cstrerror(errno)); + + pa_assert_se(pa_close(fd) == 0); + + pa_module_unload_request(m); + + ret = 0; + +finish: + if (ma) + pa_modargs_free(ma); + + return ret; +} diff --git a/src/modules/module-esound-compat-spawnpid.c b/src/modules/module-esound-compat-spawnpid.c new file mode 100644 index 00000000..67f0a231 --- /dev/null +++ b/src/modules/module-esound-compat-spawnpid.c @@ -0,0 +1,77 @@ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <signal.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> + +#include "module-esound-compat-spawnpid-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ESOUND compatibility module: -spawnpid emulation"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("pid=<process id>"); + +static const char* const valid_modargs[] = { + "pid", + NULL, +}; + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + int ret = -1; + uint32_t pid = 0; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs)) || + pa_modargs_get_value_u32(ma, "pid", &pid) < 0 || + !pid) { + pa_log("Failed to parse module arguments"); + goto finish; + } + + if (kill(pid, SIGUSR1) < 0) + pa_log_warn("kill(%u) failed: %s", pid, pa_cstrerror(errno)); + + pa_module_unload_request(m); + + ret = 0; + +finish: + if (ma) + pa_modargs_free(ma); + + return ret; +} diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c new file mode 100644 index 00000000..f9bea63d --- /dev/null +++ b/src/modules/module-esound-sink.c @@ -0,0 +1,661 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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/esound.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-esound-sink-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("ESOUND Sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "server=<address> cookie=<filename> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate>"); + +#define DEFAULT_SINK_NAME "esound_out" + +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 memchunk; + + void *write_data; + size_t write_length, write_index; + + void *read_data; + size_t read_length, read_index; + + enum { + STATE_AUTH, + STATE_LATENCY, + STATE_PREPARE, + STATE_RUNNING, + STATE_DEAD + } state; + + pa_usec_t latency; + + esd_format_t format; + int32_t rate; + + pa_smoother *smoother; + int fd; + + int64_t offset; + + pa_iochannel *io; + pa_socket_client *client; + + size_t block_size; +}; + +static const char* const valid_modargs[] = { + "server", + "cookie", + "rate", + "format", + "channels", + "sink_name", + NULL +}; + +enum { + SINK_MESSAGE_PASS_SOCKET = PA_SINK_MESSAGE_MAX +}; + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_SET_STATE: + + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t w, r; + + r = pa_smoother_get(u->smoother, pa_rtclock_usec()); + w = pa_bytes_to_usec(u->offset + u->memchunk.length, &u->sink->sample_spec); + + *((pa_usec_t*) data) = w > r ? w - r : 0; + break; + } + + case SINK_MESSAGE_PASS_SOCKET: { + struct pollfd *pollfd; + + pa_assert(!u->rtpoll_item); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->fd; + pollfd->events = pollfd->revents = 0; + + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int write_type = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + + for (;;) { + int ret; + + if (u->rtpoll_item) { + struct pollfd *pollfd; + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + /* Render some data and write it to the fifo */ + if (PA_SINK_OPENED(u->sink->thread_info.state) && pollfd->revents) { + pa_usec_t usec; + int64_t n; + + for (;;) { + ssize_t l; + void *p; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, u->block_size, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + p = pa_memblock_acquire(u->memchunk.memblock); + l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); + pa_memblock_release(u->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->memchunk.index += l; + u->memchunk.length -= l; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + pollfd->revents = 0; + + if (u->memchunk.length > 0) + + /* OK, we wrote less that we asked for, + * hence we can assume that the socket + * buffers are full now */ + goto filled_up; + } + } + + 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; + +#ifdef SIOCOUTQ + { + int l; + if (ioctl(u->fd, SIOCOUTQ, &l) >= 0 && l > 0) + n -= l; + } +#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 = PA_SINK_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) { + pa_log("FIFO shutdown."); + goto fail; + } + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +static int do_write(struct userdata *u) { + ssize_t r; + pa_assert(u); + + if (!pa_iochannel_is_writable(u->io)) + return 0; + + if (u->write_data) { + pa_assert(u->write_index < u->write_length); + + if ((r = pa_iochannel_write(u->io, (uint8_t*) u->write_data + u->write_index, u->write_length - u->write_index)) <= 0) { + pa_log("write() failed: %s", pa_cstrerror(errno)); + return -1; + } + + u->write_index += r; + pa_assert(u->write_index <= u->write_length); + + if (u->write_index == u->write_length) { + pa_xfree(u->write_data); + u->write_data = NULL; + u->write_index = u->write_length = 0; + } + } + + if (!u->write_data && u->state == STATE_PREPARE) { + /* OK, we're done with sending all control data we need to, so + * let's hand the socket over to the IO thread now */ + + pa_assert(u->fd < 0); + u->fd = pa_iochannel_get_send_fd(u->io); + + pa_iochannel_set_noclose(u->io, TRUE); + pa_iochannel_free(u->io); + u->io = NULL; + + pa_make_tcp_socket_low_delay(u->fd); + + pa_log_debug("Connection authenticated, handing fd to IO thread..."); + + pa_asyncmsgq_post(u->thread_mq.inq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_PASS_SOCKET, NULL, 0, NULL, NULL); + u->state = STATE_RUNNING; + } + + return 0; +} + +static int handle_response(struct userdata *u) { + pa_assert(u); + + switch (u->state) { + + case STATE_AUTH: + pa_assert(u->read_length == sizeof(int32_t)); + + /* Process auth data */ + if (!*(int32_t*) u->read_data) { + pa_log("Authentication failed: %s", pa_cstrerror(errno)); + return -1; + } + + /* Request latency data */ + pa_assert(!u->write_data); + *(int32_t*) (u->write_data = pa_xmalloc(u->write_length = sizeof(int32_t))) = ESD_PROTO_LATENCY; + + u->write_index = 0; + u->state = STATE_LATENCY; + + /* Space for next response */ + pa_assert(u->read_length >= sizeof(int32_t)); + u->read_index = 0; + u->read_length = sizeof(int32_t); + + break; + + case STATE_LATENCY: { + int32_t *p; + pa_assert(u->read_length == sizeof(int32_t)); + + /* Process latency info */ + u->latency = (pa_usec_t) ((double) (*(int32_t*) u->read_data) * 1000000 / 44100); + if (u->latency > 10000000) { + pa_log_warn("Invalid latency information received from server"); + u->latency = 0; + } + + /* Create stream */ + pa_assert(!u->write_data); + p = u->write_data = pa_xmalloc0(u->write_length = sizeof(int32_t)*3+ESD_NAME_MAX); + *(p++) = ESD_PROTO_STREAM_PLAY; + *(p++) = u->format; + *(p++) = u->rate; + pa_strlcpy((char*) p, "PulseAudio Tunnel", ESD_NAME_MAX); + + u->write_index = 0; + u->state = STATE_PREPARE; + + /* Don't read any further */ + pa_xfree(u->read_data); + u->read_data = NULL; + u->read_index = u->read_length = 0; + + break; + } + + default: + pa_assert_not_reached(); + } + + return 0; +} + +static int do_read(struct userdata *u) { + pa_assert(u); + + if (!pa_iochannel_is_readable(u->io)) + return 0; + + if (u->state == STATE_AUTH || u->state == STATE_LATENCY) { + ssize_t r; + + if (!u->read_data) + return 0; + + pa_assert(u->read_index < u->read_length); + + if ((r = pa_iochannel_read(u->io, (uint8_t*) u->read_data + u->read_index, u->read_length - u->read_index)) <= 0) { + pa_log("read() failed: %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + return -1; + } + + u->read_index += r; + pa_assert(u->read_index <= u->read_length); + + if (u->read_index == u->read_length) + return handle_response(u); + } + + return 0; +} + +static void io_callback(PA_GCC_UNUSED pa_iochannel *io, void*userdata) { + struct userdata *u = userdata; + pa_assert(u); + + if (do_read(u) < 0 || do_write(u) < 0) { + + if (u->io) { + pa_iochannel_free(u->io); + u->io = NULL; + } + + pa_module_unload_request(u->module); + } +} + +static void on_connection(PA_GCC_UNUSED pa_socket_client *c, pa_iochannel*io, void *userdata) { + struct userdata *u = userdata; + + pa_socket_client_unref(u->client); + u->client = NULL; + + if (!io) { + pa_log("Connection failed: %s", pa_cstrerror(errno)); + pa_module_unload_request(u->module); + return; + } + + pa_assert(!u->io); + u->io = io; + pa_iochannel_set_callback(u->io, io_callback, u); + + pa_log_debug("Connection established, authenticating ..."); +} + +int pa__init(pa_module*m) { + struct userdata *u = NULL; + const char *p; + pa_sample_spec ss; + pa_modargs *ma = NULL; + char *t; + const char *espeaker; + uint32_t key; + + 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("esound 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); + pa_memchunk_reset(&u->memchunk); + u->offset = 0; + + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + u->rtpoll_item = NULL; + + u->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 (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, NULL))) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY|PA_SINK_NETWORK; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + if (!(espeaker = getenv("ESPEAKER"))) + espeaker = ESD_UNIX_SOCKET_NAME; + + if (!(u->client = pa_socket_client_new_string(u->core->mainloop, p = pa_modargs_get_value(ma, "server", espeaker), ESD_DEFAULT_PORT))) { + pa_log("Failed to connect to server."); + goto fail; + } + + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Esound sink '%s'", p)); + pa_xfree(t); + + pa_socket_client_set_callback(u->client, on_connection, u); + + /* Prepare the initial request */ + u->write_data = pa_xmalloc(u->write_length = ESD_KEY_LEN + sizeof(int32_t)); + if (pa_authkey_load_auto(pa_modargs_get_value(ma, "cookie", ".esd_auth"), u->write_data, ESD_KEY_LEN) < 0) { + pa_log("Failed to load cookie"); + goto fail; + } + + key = ESD_ENDIAN_KEY; + memcpy((uint8_t*) u->write_data + ESD_KEY_LEN, &key, sizeof(key)); + + /* Reserve space for the response */ + u->read_data = pa_xmalloc(u->read_length = sizeof(int32_t)); + + 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->io) + pa_iochannel_free(u->io); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->client) + pa_socket_client_unref(u->client); + + pa_xfree(u->read_data); + pa_xfree(u->write_data); + + if (u->smoother) + pa_smoother_free(u->smoother); + + if (u->fd >= 0) + pa_close(u->fd); + + pa_xfree(u); +} diff --git a/src/modules/module-hal-detect.c b/src/modules/module-hal-detect.c new file mode 100644 index 00000000..832bc73e --- /dev/null +++ b/src/modules/module-hal-detect.c @@ -0,0 +1,851 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Shams E. King + + 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 <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/idxset.h> +#include <pulsecore/core-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> + +#include <hal/libhal.h> + +#include "dbus-util.h" +#include "module-hal-detect-symdef.h" + +PA_MODULE_AUTHOR("Shahms King"); +PA_MODULE_DESCRIPTION("Detect available audio hardware and load matching drivers"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +#if defined(HAVE_ALSA) && defined(HAVE_OSS) +PA_MODULE_USAGE("api=<alsa or oss>"); +#elif defined(HAVE_ALSA) +PA_MODULE_USAGE("api=<alsa>"); +#elif defined(HAVE_OSS) +PA_MODULE_USAGE("api=<oss>"); +#endif + +struct device { + uint32_t index; + char *udi; + char *sink_name, *source_name; + int acl_race_fix; +}; + +struct userdata { + pa_core *core; + LibHalContext *context; + pa_dbus_connection *connection; + pa_hashmap *devices; + const char *capability; +}; + +struct timerdata { + struct userdata *u; + char *udi; +}; + +#define CAPABILITY_ALSA "alsa" +#define CAPABILITY_OSS "oss" + +static const char* const valid_modargs[] = { + "api", + NULL +}; + +static void hal_device_free(struct device* d) { + pa_assert(d); + + pa_xfree(d->udi); + pa_xfree(d->sink_name); + pa_xfree(d->source_name); + pa_xfree(d); +} + +static void hal_device_free_cb(void *d, PA_GCC_UNUSED void *data) { + hal_device_free(d); +} + +static const char *strip_udi(const char *udi) { + const char *slash; + + if ((slash = strrchr(udi, '/'))) + return slash+1; + + return udi; +} + +#ifdef HAVE_ALSA + +typedef enum { + ALSA_TYPE_SINK, + ALSA_TYPE_SOURCE, + ALSA_TYPE_OTHER, + ALSA_TYPE_MAX +} alsa_type_t; + +static alsa_type_t hal_alsa_device_get_type(LibHalContext *context, const char *udi, DBusError *error) { + char *type; + alsa_type_t t; + + if (!(type = libhal_device_get_property_string(context, udi, "alsa.type", error))) + return ALSA_TYPE_OTHER; + + if (!strcmp(type, "playback")) + t = ALSA_TYPE_SINK; + else if (!strcmp(type, "capture")) + t = ALSA_TYPE_SOURCE; + else + t = ALSA_TYPE_OTHER; + + libhal_free_string(type); + + return t; +} + +static int hal_alsa_device_is_modem(LibHalContext *context, const char *udi, DBusError *error) { + char *class; + int r; + + if (!(class = libhal_device_get_property_string(context, udi, "alsa.pcm_class", error))) + return 0; + + r = strcmp(class, "modem") == 0; + pa_xfree(class); + + return r; +} + +static pa_module* hal_device_load_alsa(struct userdata *u, const char *udi, char **sink_name, char **source_name) { + char *args; + alsa_type_t type; + int device, card; + const char *module_name; + DBusError error; + pa_module *m; + + dbus_error_init(&error); + + pa_assert(u); + pa_assert(sink_name); + pa_assert(source_name); + + *sink_name = *source_name = NULL; + + type = hal_alsa_device_get_type(u->context, udi, &error); + if (dbus_error_is_set(&error) || type == ALSA_TYPE_OTHER) + goto fail; + + device = libhal_device_get_property_int(u->context, udi, "alsa.device", &error); + if (dbus_error_is_set(&error) || device != 0) + goto fail; + + card = libhal_device_get_property_int(u->context, udi, "alsa.card", &error); + if (dbus_error_is_set(&error)) + goto fail; + + if (hal_alsa_device_is_modem(u->context, udi, &error)) + goto fail; + + if (type == ALSA_TYPE_SINK) { + *sink_name = pa_sprintf_malloc("alsa_output.%s", strip_udi(udi)); + + module_name = "module-alsa-sink"; + args = pa_sprintf_malloc("device_id=%u sink_name=%s", card, *sink_name); + } else { + *source_name = pa_sprintf_malloc("alsa_input.%s", strip_udi(udi)); + + module_name = "module-alsa-source"; + args = pa_sprintf_malloc("device_id=%u source_name=%s", card, *source_name); + } + + pa_log_debug("Loading %s with arguments '%s'", module_name, args); + + m = pa_module_load(u->core, module_name, args); + + pa_xfree(args); + + if (!m) { + pa_xfree(*sink_name); + pa_xfree(*source_name); + *sink_name = *source_name = NULL; + } + + return m; + +fail: + if (dbus_error_is_set(&error)) { + pa_log_error("D-Bus error while parsing ALSA data: %s: %s", error.name, error.message); + dbus_error_free(&error); + } + + return NULL; +} + +#endif + +#ifdef HAVE_OSS + +static int hal_oss_device_is_pcm(LibHalContext *context, const char *udi, DBusError *error) { + char *class = NULL, *dev = NULL, *e; + int device; + int r = 0; + + class = libhal_device_get_property_string(context, udi, "oss.type", error); + if (dbus_error_is_set(error) || !class) + goto finish; + + if (strcmp(class, "pcm")) + goto finish; + + dev = libhal_device_get_property_string(context, udi, "oss.device_file", error); + if (dbus_error_is_set(error) || !dev) + goto finish; + + if ((e = strrchr(dev, '/'))) + if (pa_startswith(e + 1, "audio")) + goto finish; + + device = libhal_device_get_property_int(context, udi, "oss.device", error); + if (dbus_error_is_set(error) || device != 0) + goto finish; + + r = 1; + +finish: + + libhal_free_string(class); + libhal_free_string(dev); + + return r; +} + +static pa_module* hal_device_load_oss(struct userdata *u, const char *udi, char **sink_name, char **source_name) { + char* args; + char* device; + DBusError error; + pa_module *m; + + dbus_error_init(&error); + + pa_assert(u); + pa_assert(sink_name); + pa_assert(source_name); + + *sink_name = *source_name = NULL; + + if (!hal_oss_device_is_pcm(u->context, udi, &error) || dbus_error_is_set(&error)) + goto fail; + + device = libhal_device_get_property_string(u->context, udi, "oss.device_file", &error); + if (!device || dbus_error_is_set(&error)) + goto fail; + + *sink_name = pa_sprintf_malloc("oss_output.%s", strip_udi(udi)); + *source_name = pa_sprintf_malloc("oss_input.%s", strip_udi(udi)); + + args = pa_sprintf_malloc("device=%s sink_name=%s source_name=%s", device, *sink_name, *source_name); + libhal_free_string(device); + + pa_log_debug("Loading module-oss with arguments '%s'", args); + m = pa_module_load(u->core, "module-oss", args); + pa_xfree(args); + + if (!m) { + pa_xfree(*sink_name); + pa_xfree(*source_name); + *sink_name = *source_name = NULL; + } + + return m; + +fail: + if (dbus_error_is_set(&error)) { + pa_log_error("D-Bus error while parsing OSS data: %s: %s", error.name, error.message); + dbus_error_free(&error); + } + + return NULL; +} +#endif + +static struct device* hal_device_add(struct userdata *u, const char *udi) { + pa_module* m = NULL; + struct device *d; + char *sink_name = NULL, *source_name = NULL; + + pa_assert(u); + pa_assert(u->capability); + pa_assert(!pa_hashmap_get(u->devices, udi)); + +#ifdef HAVE_ALSA + if (strcmp(u->capability, CAPABILITY_ALSA) == 0) + m = hal_device_load_alsa(u, udi, &sink_name, &source_name); +#endif +#ifdef HAVE_OSS + if (strcmp(u->capability, CAPABILITY_OSS) == 0) + m = hal_device_load_oss(u, udi, &sink_name, &source_name); +#endif + + if (!m) + return NULL; + + d = pa_xnew(struct device, 1); + d->acl_race_fix = 0; + d->udi = pa_xstrdup(udi); + d->index = m->index; + d->sink_name = sink_name; + d->source_name = source_name; + pa_hashmap_put(u->devices, d->udi, d); + + return d; +} + +static int hal_device_add_all(struct userdata *u, const char *capability) { + DBusError error; + int i, n, count = 0; + char** udis; + + pa_assert(u); + + dbus_error_init(&error); + + if (u->capability && strcmp(u->capability, capability) != 0) + return 0; + + pa_log_info("Trying capability %s", capability); + + udis = libhal_find_device_by_capability(u->context, capability, &n, &error); + if (dbus_error_is_set(&error)) { + pa_log_error("Error finding devices: %s: %s", error.name, error.message); + dbus_error_free(&error); + return -1; + } + + if (n > 0) { + u->capability = capability; + + for (i = 0; i < n; i++) { + struct device *d; + + if (!(d = hal_device_add(u, udis[i]))) + pa_log_debug("Not loaded device %s", udis[i]); + else { + if (d->sink_name) + pa_scache_play_item_by_name(u->core, "pulse-coldplug", d->sink_name, PA_VOLUME_NORM, 0); + count++; + } + } + } + + libhal_free_string_array(udis); + return count; +} + +static dbus_bool_t device_has_capability(LibHalContext *context, const char *udi, const char* cap, DBusError *error){ + dbus_bool_t has_prop; + + has_prop = libhal_device_property_exists(context, udi, "info.capabilities", error); + if (!has_prop || dbus_error_is_set(error)) + return FALSE; + + return libhal_device_query_capability(context, udi, cap, error); +} + +static void device_added_time_cb(pa_mainloop_api *ea, pa_time_event *ev, const struct timeval *tv, void *userdata) { + DBusError error; + struct timerdata *td = userdata; + + dbus_error_init(&error); + + if (!pa_hashmap_get(td->u->devices, td->udi)) { + int b; + struct device *d; + + b = libhal_device_exists(td->u->context, td->udi, &error); + + if (dbus_error_is_set(&error)) { + pa_log_error("Error adding device: %s: %s", error.name, error.message); + dbus_error_free(&error); + } else if (b) { + if (!(d = hal_device_add(td->u, td->udi))) + pa_log_debug("Not loaded device %s", td->udi); + else { + if (d->sink_name) + pa_scache_play_item_by_name(td->u->core, "pulse-hotplug", d->sink_name, PA_VOLUME_NORM, 0); + } + } + } + + pa_xfree(td->udi); + pa_xfree(td); + ea->time_free(ev); +} + +static void device_added_cb(LibHalContext *context, const char *udi) { + DBusError error; + struct timeval tv; + struct timerdata *t; + struct userdata *u; + int good = 0; + + pa_assert_se(u = libhal_ctx_get_user_data(context)); + + if (pa_hashmap_get(u->devices, udi)) + return; + + pa_log_debug("HAL Device added: %s", udi); + + dbus_error_init(&error); + + if (u->capability) { + + good = device_has_capability(context, udi, u->capability, &error); + + if (dbus_error_is_set(&error)) { + pa_log_error("Error getting capability: %s: %s", error.name, error.message); + dbus_error_free(&error); + return; + } + + } else { + +#ifdef HAVE_ALSA + good = device_has_capability(context, udi, CAPABILITY_ALSA, &error); + + if (dbus_error_is_set(&error)) { + pa_log_error("Error getting capability: %s: %s", error.name, error.message); + dbus_error_free(&error); + return; + } + + if (good) + u->capability = CAPABILITY_ALSA; +#endif +#if defined(HAVE_OSS) && defined(HAVE_ALSA) + if (!good) { +#endif +#ifdef HAS_OSS + good = device_has_capability(context, udi, CAPABILITY_OSS, &error); + + if (dbus_error_is_set(&error)) { + pa_log_error("Error getting capability: %s: %s", error.name, error.message); + dbus_error_free(&error); + return; + } + + if (good) + u->capability = CAPABILITY_OSS; + +#endif +#if defined(HAVE_OSS) && defined(HAVE_ALSA) + } +#endif + } + + if (!good) + return; + + /* actually add the device 1/2 second later */ + t = pa_xnew(struct timerdata, 1); + t->u = u; + t->udi = pa_xstrdup(udi); + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, 500000); + u->core->mainloop->time_new(u->core->mainloop, &tv, device_added_time_cb, t); +} + +static void device_removed_cb(LibHalContext* context, const char *udi) { + struct device *d; + struct userdata *u; + + pa_assert_se(u = libhal_ctx_get_user_data(context)); + + pa_log_debug("Device removed: %s", udi); + + if ((d = pa_hashmap_remove(u->devices, udi))) { + pa_module_unload_by_index(u->core, d->index); + hal_device_free(d); + } +} + +static void new_capability_cb(LibHalContext *context, const char *udi, const char* capability) { + struct userdata *u; + + pa_assert_se(u = libhal_ctx_get_user_data(context)); + + if (!u->capability || strcmp(u->capability, capability) == 0) + /* capability we care about, pretend it's a new device */ + device_added_cb(context, udi); +} + +static void lost_capability_cb(LibHalContext *context, const char *udi, const char* capability) { + struct userdata *u; + + pa_assert_se(u = libhal_ctx_get_user_data(context)); + + if (u->capability && strcmp(u->capability, capability) == 0) + /* capability we care about, pretend it was removed */ + device_removed_cb(context, udi); +} + +static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, void *userdata) { + struct userdata*u = userdata; + DBusError error; + + pa_assert(bus); + pa_assert(message); + pa_assert(u); + + dbus_error_init(&error); + + pa_log_debug("dbus: interface=%s, path=%s, member=%s\n", + dbus_message_get_interface(message), + dbus_message_get_path(message), + dbus_message_get_member(message)); + + if (dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLAdded") || + dbus_message_is_signal(message, "org.freedesktop.Hal.Device.AccessControl", "ACLRemoved")) { + uint32_t uid; + int suspend = strcmp(dbus_message_get_member(message), "ACLRemoved") == 0; + + if (!dbus_message_get_args(message, &error, DBUS_TYPE_UINT32, &uid, DBUS_TYPE_INVALID) || dbus_error_is_set(&error)) { + pa_log_error("Failed to parse ACL message: %s: %s", error.name, error.message); + goto finish; + } + + if (uid == getuid() || uid == geteuid()) { + struct device *d; + const char *udi; + + udi = dbus_message_get_path(message); + + if ((d = pa_hashmap_get(u->devices, udi))) { + int send_acl_race_fix_message = 0; + + d->acl_race_fix = 0; + + if (d->sink_name) { + pa_sink *sink; + + if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) { + int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED; + + if (prev_suspended && !suspend) { + /* resume */ + if (pa_sink_suspend(sink, 0) >= 0) + pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0); + else + d->acl_race_fix = 1; + + } else if (!prev_suspended && suspend) { + /* suspend */ + if (pa_sink_suspend(sink, 1) >= 0) + send_acl_race_fix_message = 1; + } + } + } + + if (d->source_name) { + pa_source *source; + + if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0))) { + int prev_suspended = pa_source_get_state(source) == PA_SOURCE_SUSPENDED; + + if (prev_suspended && !suspend) { + /* resume */ + if (pa_source_suspend(source, 0) < 0) + d->acl_race_fix = 1; + + } else if (!prev_suspended && suspend) { + /* suspend */ + if (pa_source_suspend(source, 0) >= 0) + send_acl_race_fix_message = 1; + } + } + } + + if (send_acl_race_fix_message) { + DBusMessage *msg; + msg = dbus_message_new_signal(udi, "org.pulseaudio.Server", "DirtyGiveUpMessage"); + dbus_connection_send(pa_dbus_connection_get(u->connection), msg, NULL); + dbus_message_unref(msg); + } + + } else if (!suspend) + device_added_cb(u->context, udi); + } + + } else if (dbus_message_is_signal(message, "org.pulseaudio.Server", "DirtyGiveUpMessage")) { + /* We use this message to avoid a dirty race condition when we + get an ACLAdded message before the previously owning PA + sever has closed the device. We can remove this as + soon as HAL learns frevoke() */ + + const char *udi; + struct device *d; + + udi = dbus_message_get_path(message); + + if ((d = pa_hashmap_get(u->devices, udi)) && d->acl_race_fix) { + pa_log_debug("Got dirty give up message for '%s', trying resume ...", udi); + + d->acl_race_fix = 0; + + if (d->sink_name) { + pa_sink *sink; + + if ((sink = pa_namereg_get(u->core, d->sink_name, PA_NAMEREG_SINK, 0))) { + + int prev_suspended = pa_sink_get_state(sink) == PA_SINK_SUSPENDED; + + if (prev_suspended) { + /* resume */ + if (pa_sink_suspend(sink, 0) >= 0) + pa_scache_play_item_by_name(u->core, "pulse-access", d->sink_name, PA_VOLUME_NORM, 0); + } + } + } + + if (d->source_name) { + pa_source *source; + + if ((source = pa_namereg_get(u->core, d->source_name, PA_NAMEREG_SOURCE, 0))) { + + int prev_suspended = pa_source_get_state(source) == PA_SOURCE_SUSPENDED; + + if (prev_suspended) + pa_source_suspend(source, 0); + } + } + + } else + /* Yes, we don't check the UDI for validity, but hopefully HAL will */ + device_added_cb(u->context, udi); + } + +finish: + dbus_error_free(&error); + + return DBUS_HANDLER_RESULT_HANDLED; +} + +static void hal_context_free(LibHalContext* hal_context) { + DBusError error; + + dbus_error_init(&error); + + libhal_ctx_shutdown(hal_context, &error); + libhal_ctx_free(hal_context); + + dbus_error_free(&error); +} + +static LibHalContext* hal_context_new(pa_core* c, DBusConnection *conn) { + DBusError error; + LibHalContext *hal_context = NULL; + + dbus_error_init(&error); + + if (!(hal_context = libhal_ctx_new())) { + pa_log_error("libhal_ctx_new() failed"); + goto fail; + } + + if (!libhal_ctx_set_dbus_connection(hal_context, conn)) { + pa_log_error("Error establishing DBUS connection: %s: %s", error.name, error.message); + goto fail; + } + + if (!libhal_ctx_init(hal_context, &error)) { + pa_log_error("Couldn't connect to hald: %s: %s", error.name, error.message); + goto fail; + } + + return hal_context; + +fail: + if (hal_context) + hal_context_free(hal_context); + + dbus_error_free(&error); + + return NULL; +} + +int pa__init(pa_module*m) { + DBusError error; + pa_dbus_connection *conn; + struct userdata *u = NULL; + LibHalContext *hal_context = NULL; + int n = 0; + pa_modargs *ma; + const char *api; + + pa_assert(m); + + dbus_error_init(&error); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if ((api = pa_modargs_get_value(ma, "api", NULL))) { + int good = 0; + +#ifdef HAVE_ALSA + if (strcmp(api, CAPABILITY_ALSA) == 0) { + good = 1; + api = CAPABILITY_ALSA; + } +#endif +#ifdef HAVE_OSS + if (strcmp(api, CAPABILITY_OSS) == 0) { + good = 1; + api = CAPABILITY_OSS; + } +#endif + + if (!good) { + pa_log_error("Invalid API specification."); + goto fail; + } + } + + if (!(conn = pa_dbus_bus_get(m->core, DBUS_BUS_SYSTEM, &error)) || dbus_error_is_set(&error)) { + if (conn) + pa_dbus_connection_unref(conn); + pa_log_error("Unable to contact DBUS system bus: %s: %s", error.name, error.message); + goto fail; + } + + if (!(hal_context = hal_context_new(m->core, pa_dbus_connection_get(conn)))) { + /* pa_hal_context_new() logs appropriate errors */ + pa_dbus_connection_unref(conn); + goto fail; + } + + u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->context = hal_context; + u->connection = conn; + u->devices = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + u->capability = api; + m->userdata = u; + +#ifdef HAVE_ALSA + n = hal_device_add_all(u, CAPABILITY_ALSA); +#endif +#if defined(HAVE_ALSA) && defined(HAVE_OSS) + if (n <= 0) +#endif +#ifdef HAVE_OSS + n += hal_device_add_all(u, CAPABILITY_OSS); +#endif + + libhal_ctx_set_user_data(hal_context, u); + libhal_ctx_set_device_added(hal_context, device_added_cb); + libhal_ctx_set_device_removed(hal_context, device_removed_cb); + libhal_ctx_set_device_new_capability(hal_context, new_capability_cb); + libhal_ctx_set_device_lost_capability(hal_context, lost_capability_cb); + + if (!libhal_device_property_watch_all(hal_context, &error)) { + pa_log_error("Error monitoring device list: %s: %s", error.name, error.message); + goto fail; + } + + if (!dbus_connection_add_filter(pa_dbus_connection_get(conn), filter_cb, u, NULL)) { + pa_log_error("Failed to add filter function"); + goto fail; + } + + dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',sender='org.freedesktop.Hal', interface='org.freedesktop.Hal.Device.AccessControl'", &error); + if (dbus_error_is_set(&error)) { + pa_log_error("Unable to subscribe to HAL ACL signals: %s: %s", error.name, error.message); + goto fail; + } + + dbus_bus_add_match(pa_dbus_connection_get(conn), "type='signal',interface='org.pulseaudio.Server'", &error); + if (dbus_error_is_set(&error)) { + pa_log_error("Unable to subscribe to PulseAudio signals: %s: %s", error.name, error.message); + goto fail; + } + + pa_log_info("Loaded %i modules.", n); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + dbus_error_free(&error); + pa__done(m); + + return -1; +} + + +void pa__done(pa_module *m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->context) + hal_context_free(u->context); + + if (u->devices) + pa_hashmap_free(u->devices, hal_device_free_cb, NULL); + + if (u->connection) + pa_dbus_connection_unref(u->connection); + + pa_xfree(u); +} diff --git a/src/modules/module-jack-sink.c b/src/modules/module-jack-sink.c new file mode 100644 index 00000000..a42aa9ef --- /dev/null +++ b/src/modules/module-jack-sink.c @@ -0,0 +1,456 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <jack/jack.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> + +#include "module-jack-sink-symdef.h" + +/* General overview: + * + * Because JACK has a very unflexible event loop management, which + * doesn't allow us to add our own event sources to the event thread + * we cannot use the JACK real-time thread for dispatching our PA + * work. Instead, we run an additional RT thread which does most of + * the PA handling, and have the JACK RT thread request data from it + * via pa_asyncmsgq. The cost is an additional context switch which + * should hopefully not be that expensive if RT scheduling is + * enabled. A better fix would only be possible with additional event + * source support in JACK. + */ + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("JACK Sink"); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_USAGE( + "sink_name=<name of sink> " + "server_name=<jack server name> " + "client_name=<jack client name> " + "channels=<number of channels> " + "connect=<connect ports?> " + "channel_map=<channel map>"); + +#define DEFAULT_SINK_NAME "jack_out" + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + unsigned channels; + + jack_port_t* port[PA_CHANNELS_MAX]; + jack_client_t *client; + + void *buffer[PA_CHANNELS_MAX]; + + pa_thread_mq thread_mq; + pa_asyncmsgq *jack_msgq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; + + pa_thread *thread; + + jack_nframes_t frames_in_buffer; + jack_nframes_t saved_frame_time; + pa_bool_t saved_frame_time_valid; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "server_name", + "client_name", + "channels", + "connect", + "channel_map", + NULL +}; + +enum { + SINK_MESSAGE_RENDER = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_ON_SHUTDOWN +}; + +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *memchunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case SINK_MESSAGE_RENDER: + + /* Handle the request from the JACK thread */ + + if (u->sink->thread_info.state == PA_SINK_RUNNING) { + pa_memchunk chunk; + size_t nbytes; + void *p; + + pa_assert(offset > 0); + nbytes = offset * pa_frame_size(&u->sink->sample_spec); + + pa_sink_render_full(u->sink, nbytes, &chunk); + + p = (uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index; + pa_deinterleave(p, u->buffer, u->channels, sizeof(float), offset); + pa_memblock_release(chunk.memblock); + + pa_memblock_unref(chunk.memblock); + } else { + unsigned c; + pa_sample_spec ss; + + /* Humm, we're not RUNNING, hence let's write some silence */ + + ss = u->sink->sample_spec; + ss.channels = 1; + + for (c = 0; c < u->channels; c++) + pa_silence_memory(u->buffer[c], offset * pa_sample_size(&ss), &ss); + } + + u->frames_in_buffer = offset; + u->saved_frame_time = * (jack_nframes_t*) data; + u->saved_frame_time_valid = TRUE; + + return 0; + + case SINK_MESSAGE_ON_SHUTDOWN: + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + return 0; + + case PA_SINK_MESSAGE_GET_LATENCY: { + jack_nframes_t l, ft, d; + size_t n; + + /* This is the "worst-case" latency */ + l = jack_port_get_total_latency(u->client, u->port[0]) + u->frames_in_buffer; + + if (u->saved_frame_time_valid) { + /* Adjust the worst case latency by the time that + * passed since we last handed data to JACK */ + + ft = jack_frame_time(u->client); + d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0; + l = l > d ? l - d : 0; + } + + /* Convert it to usec */ + n = l * pa_frame_size(&u->sink->sample_spec); + *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); + + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, memchunk); +} + +static int jack_process(jack_nframes_t nframes, void *arg) { + struct userdata *u = arg; + unsigned c; + jack_nframes_t frame_time; + pa_assert(u); + + /* We just forward the request to our other RT thread */ + + for (c = 0; c < u->channels; c++) + pa_assert_se(u->buffer[c] = jack_port_get_buffer(u->port[c], nframes)); + + frame_time = jack_frame_time(u->client); + + pa_assert_se(pa_asyncmsgq_send(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_RENDER, &frame_time, nframes, NULL) == 0); + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +static void jack_error_func(const char*t) { + char *s; + + s = pa_xstrndup(t, strcspn(t, "\n\r")); + pa_log_warn("JACK error >%s<", s); + pa_xfree(s); +} + +static void jack_init(void *arg) { + struct userdata *u = arg; + + pa_log_info("JACK thread starting up."); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority+4); +} + +static void jack_shutdown(void* arg) { + struct userdata *u = arg; + + pa_log_info("JACK thread shutting down.."); + pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL); +} + +int pa__init(pa_module*m) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + jack_status_t status; + const char *server_name, *client_name; + uint32_t channels = 0; + pa_bool_t do_connect = TRUE; + unsigned i; + const char **ports = NULL, **p; + char *t; + + pa_assert(m); + + jack_set_error_function(jack_error_func); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "connect", &do_connect) < 0) { + pa_log("Failed to parse connect= argument."); + goto fail; + } + + server_name = pa_modargs_get_value(ma, "server_name", NULL); + client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Sink"); + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->saved_frame_time_valid = FALSE; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + /* The queue linking the JACK thread and our RT thread */ + u->jack_msgq = pa_asyncmsgq_new(0); + + /* The msgq from the JACK RT thread should have an even higher + * priority than the normal message queues, to match the guarantee + * all other drivers make: supplying the audio device with data is + * the top priority -- and as long as that is possible we don't do + * anything else */ + u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); + + if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) { + pa_log("jack_client_open() failed."); + goto fail; + } + + ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsInput); + + channels = 0; + for (p = ports; *p; p++) + channels++; + + if (!channels) + channels = m->core->default_sample_spec.channels; + + if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels >= PA_CHANNELS_MAX) { + pa_log("Failed to parse channels= argument."); + goto fail; + } + + pa_assert_se(pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_AUX)); + pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA); + if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) { + pa_log("Failed to parse channel_map= argument."); + goto fail; + } + + pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client)); + + ss.channels = u->channels = channels; + ss.rate = jack_get_sample_rate(u->client); + ss.format = PA_SAMPLE_FLOAT32NE; + + pa_assert(pa_sample_spec_valid(&ss)); + + for (i = 0; i < ss.channels; i++) { + if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput|JackPortIsTerminal, 0))) { + pa_log("jack_port_register() failed."); + goto fail; + } + } + + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_log("failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Jack sink (%s)", jack_get_client_name(u->client))); + pa_xfree(t); + + jack_set_process_callback(u->client, jack_process, u); + jack_on_shutdown(u->client, jack_shutdown, u); + jack_set_thread_init_callback(u->client, jack_init, u); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + if (jack_activate(u->client)) { + pa_log("jack_activate() failed"); + goto fail; + } + + if (do_connect) { + for (i = 0, p = ports; i < ss.channels; i++, p++) { + + if (!*p) { + pa_log("Not enough physical output ports, leaving unconnected."); + break; + } + + pa_log_info("Connecting %s to %s", jack_port_name(u->port[i]), *p); + + if (jack_connect(u->client, jack_port_name(u->port[i]), *p)) { + pa_log("Failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p); + break; + } + } + } + + pa_sink_put(u->sink); + + free(ports); + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + free(ports); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->client) + jack_client_close(u->client); + + 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->jack_msgq) + pa_asyncmsgq_unref(u->jack_msgq); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + pa_xfree(u); +} diff --git a/src/modules/module-jack-source.c b/src/modules/module-jack-source.c new file mode 100644 index 00000000..4ee08bf1 --- /dev/null +++ b/src/modules/module-jack-source.c @@ -0,0 +1,427 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <jack/jack.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/source.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> + +#include "module-jack-source-symdef.h" + +/* See module-jack-sink for a few comments how this module basically + * works */ + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("JACK Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "source_name=<name of source> " + "server_name=<jack server name> " + "client_name=<jack client name> " + "channels=<number of channels> " + "connect=<connect ports?>" + "channel_map=<channel map>"); + +#define DEFAULT_SOURCE_NAME "jack_in" + +struct userdata { + pa_core *core; + pa_module *module; + pa_source *source; + + unsigned channels; + + jack_port_t* port[PA_CHANNELS_MAX]; + jack_client_t *client; + + pa_thread_mq thread_mq; + pa_asyncmsgq *jack_msgq; + pa_rtpoll *rtpoll; + pa_rtpoll_item *rtpoll_item; + + pa_thread *thread; + + jack_nframes_t saved_frame_time; + pa_bool_t saved_frame_time_valid; +}; + +static const char* const valid_modargs[] = { + "source_name", + "server_name", + "client_name", + "channels", + "connect", + "channel_map", + NULL +}; + +enum { + SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX, + SOURCE_MESSAGE_ON_SHUTDOWN +}; + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + + case SOURCE_MESSAGE_POST: + + /* Handle the new block from the JACK thread */ + pa_assert(chunk); + pa_assert(chunk->length > 0); + + if (u->source->thread_info.state == PA_SOURCE_RUNNING) + pa_source_post(u->source, chunk); + + u->saved_frame_time = offset; + u->saved_frame_time_valid = TRUE; + + return 0; + + case SOURCE_MESSAGE_ON_SHUTDOWN: + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + return 0; + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + jack_nframes_t l, ft, d; + size_t n; + + /* This is the "worst-case" latency */ + l = jack_port_get_total_latency(u->client, u->port[0]); + + if (u->saved_frame_time_valid) { + /* Adjust the worst case latency by the time that + * passed since we last handed data to JACK */ + + ft = jack_frame_time(u->client); + d = ft > u->saved_frame_time ? ft - u->saved_frame_time : 0; + l += d; + } + + /* Convert it to usec */ + n = l * pa_frame_size(&u->source->sample_spec); + *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->source->sample_spec); + + return 0; + } + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static int jack_process(jack_nframes_t nframes, void *arg) { + unsigned c; + struct userdata *u = arg; + const void *buffer[PA_CHANNELS_MAX]; + void *p; + jack_nframes_t frame_time; + pa_memchunk chunk; + + pa_assert(u); + + for (c = 0; c < u->channels; c++) + pa_assert(buffer[c] = jack_port_get_buffer(u->port[c], nframes)); + + /* We interleave the data and pass it on to the other RT thread */ + + pa_memchunk_reset(&chunk); + chunk.length = nframes * pa_frame_size(&u->source->sample_spec); + chunk.memblock = pa_memblock_new(u->core->mempool, chunk.length); + p = pa_memblock_acquire(chunk.memblock); + pa_interleave(buffer, u->channels, p, sizeof(float), nframes); + pa_memblock_release(chunk.memblock); + + frame_time = jack_frame_time(u->client); + + pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, NULL, frame_time, &chunk, NULL); + + pa_memblock_unref(chunk.memblock); + + return 0; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +static void jack_error_func(const char*t) { + char *s; + + s = pa_xstrndup(t, strcspn(t, "\n\r")); + pa_log_warn("JACK error >%s<", s); + pa_xfree(s); +} + +static void jack_init(void *arg) { + struct userdata *u = arg; + + pa_log_info("JACK thread starting up."); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority+4); +} + +static void jack_shutdown(void* arg) { + struct userdata *u = arg; + + pa_log_info("JACK thread shutting down.."); + pa_asyncmsgq_post(u->jack_msgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_ON_SHUTDOWN, NULL, 0, NULL, NULL); +} + +int pa__init(pa_module*m) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + jack_status_t status; + const char *server_name, *client_name; + uint32_t channels = 0; + pa_bool_t do_connect = TRUE; + unsigned i; + const char **ports = NULL, **p; + char *t; + + pa_assert(m); + + jack_set_error_function(jack_error_func); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "connect", &do_connect) < 0) { + pa_log("Failed to parse connect= argument."); + goto fail; + } + + server_name = pa_modargs_get_value(ma, "server_name", NULL); + client_name = pa_modargs_get_value(ma, "client_name", "PulseAudio JACK Source"); + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->saved_frame_time_valid = FALSE; + + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + u->jack_msgq = pa_asyncmsgq_new(0); + u->rtpoll_item = pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY-1, u->jack_msgq); + + if (!(u->client = jack_client_open(client_name, server_name ? JackServerName : JackNullOption, &status, server_name))) { + pa_log("jack_client_open() failed."); + goto fail; + } + + ports = jack_get_ports(u->client, NULL, NULL, JackPortIsPhysical|JackPortIsOutput); + + channels = 0; + for (p = ports; *p; p++) + channels++; + + if (!channels) + channels = m->core->default_sample_spec.channels; + + if (pa_modargs_get_value_u32(ma, "channels", &channels) < 0 || channels <= 0 || channels >= PA_CHANNELS_MAX) { + pa_log("failed to parse channels= argument."); + goto fail; + } + + pa_assert_se(pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_AUX)); + pa_channel_map_init_auto(&map, channels, PA_CHANNEL_MAP_ALSA); + if (pa_modargs_get_channel_map(ma, NULL, &map) < 0 || map.channels != channels) { + pa_log("failed to parse channel_map= argument."); + goto fail; + } + + pa_log_info("Successfully connected as '%s'", jack_get_client_name(u->client)); + + ss.channels = u->channels = channels; + ss.rate = jack_get_sample_rate(u->client); + ss.format = PA_SAMPLE_FLOAT32NE; + + pa_assert(pa_sample_spec_valid(&ss)); + + for (i = 0; i < ss.channels; i++) { + if (!(u->port[i] = jack_port_register(u->client, pa_channel_position_to_string(map.map[i]), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput|JackPortIsTerminal, 0))) { + pa_log("jack_port_register() failed."); + goto fail; + } + } + + if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { + pa_log("failed to create source."); + goto fail; + } + + u->source->parent.process_msg = source_process_msg; + u->source->userdata = u; + u->source->flags = PA_SOURCE_LATENCY; + + pa_source_set_module(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_description(u->source, t = pa_sprintf_malloc("Jack source (%s)", jack_get_client_name(u->client))); + pa_xfree(t); + + jack_set_process_callback(u->client, jack_process, u); + jack_on_shutdown(u->client, jack_shutdown, u); + jack_set_thread_init_callback(u->client, jack_init, u); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + if (jack_activate(u->client)) { + pa_log("jack_activate() failed"); + goto fail; + } + + if (do_connect) { + for (i = 0, p = ports; i < ss.channels; i++, p++) { + + if (!*p) { + pa_log("not enough physical output ports, leaving unconnected."); + break; + } + + pa_log_info("connecting %s to %s", jack_port_name(u->port[i]), *p); + + if (jack_connect(u->client, *p, jack_port_name(u->port[i]))) { + pa_log("failed to connect %s to %s, leaving unconnected.", jack_port_name(u->port[i]), *p); + break; + } + } + + } + + pa_source_put(u->source); + + free(ports); + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + free(ports); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->client) + jack_client_close(u->client); + + if (u->source) + pa_source_unlink(u->source); + + 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->source) + pa_source_unref(u->source); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->jack_msgq) + pa_asyncmsgq_unref(u->jack_msgq); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + pa_xfree(u); +} diff --git a/src/modules/module-ladspa-sink.c b/src/modules/module-ladspa-sink.c new file mode 100644 index 00000000..b31037b6 --- /dev/null +++ b/src/modules/module-ladspa-sink.c @@ -0,0 +1,684 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + +/* TODO: Some plugins cause latency, and some even report it by using a control + out port. We don't currently use the latency information. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/sample-util.h> + +#include "module-ladspa-sink-symdef.h" +#include "ladspa.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Virtual LADSPA sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "master=<name of sink to remap> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "channel_map=<channel map> " + "plugin=<ladspa plugin name> " + "label=<ladspa plugin label> " + "control=<comma seperated list of input control values>"); + +struct userdata { + pa_core *core; + pa_module *module; + + pa_sink *sink, *master; + pa_sink_input *sink_input; + + const LADSPA_Descriptor *descriptor; + unsigned channels; + LADSPA_Handle handle[PA_CHANNELS_MAX]; + LADSPA_Data *input, *output; + size_t block_size; + unsigned long input_port, output_port; + LADSPA_Data *control; + + /* This is a dummy buffer. Every port must be connected, but we don't care + about control out ports. We connect them all to this single buffer. */ + LADSPA_Data control_out; + + pa_memchunk memchunk; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "master", + "format", + "channels", + "rate", + "channel_map", + "plugin", + "label", + "control", + NULL +}; + +/* Called from I/O thread context */ +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t usec = 0; + + if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; + + *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static int sink_set_state(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input))) + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + + return 0; +} + +/* Called from I/O thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK_INPUT(o)->userdata; + + switch (code) { + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: + *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + return pa_sink_input_process_msg(o, code, data, offset, chunk); +} + +/* Called from I/O thread context */ +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->memchunk.memblock) { + pa_memchunk tchunk; + float *src, *dst; + size_t fs; + unsigned n, c; + + pa_sink_render(u->sink, length, &tchunk); + + fs = pa_frame_size(&i->sample_spec); + n = tchunk.length / fs; + + pa_assert(n > 0); + + u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, tchunk.length); + u->memchunk.index = 0; + u->memchunk.length = tchunk.length; + + src = (float*) ((uint8_t*) pa_memblock_acquire(tchunk.memblock) + tchunk.index); + dst = (float*) pa_memblock_acquire(u->memchunk.memblock); + + for (c = 0; c < u->channels; c++) { + unsigned j; + float *p, *q; + + p = src + c; + q = u->input; + for (j = 0; j < n; j++, p += u->channels, q++) + *q = PA_CLAMP_UNLIKELY(*p, -1.0, 1.0); + + u->descriptor->run(u->handle[c], n); + + q = u->output; + p = dst + c; + for (j = 0; j < n; j++, q++, p += u->channels) + *p = PA_CLAMP_UNLIKELY(*q, -1.0, 1.0); + } + + pa_memblock_release(tchunk.memblock); + pa_memblock_release(u->memchunk.memblock); + + pa_memblock_unref(tchunk.memblock); + } + + pa_assert(u->memchunk.length > 0); + pa_assert(u->memchunk.memblock); + + *chunk = u->memchunk; + pa_memblock_ref(chunk->memblock); + + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + pa_assert(length > 0); + + if (u->memchunk.memblock) { + + if (length < u->memchunk.length) { + u->memchunk.index += length; + u->memchunk.length -= length; + return; + } + + pa_memblock_unref(u->memchunk.memblock); + length -= u->memchunk.length; + pa_memchunk_reset(&u->memchunk); + } + + if (length > 0) + pa_sink_skip(u->sink, length); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_detach_within_thread(u->sink); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); + pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); + + pa_sink_attach_within_thread(u->sink); +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + u->sink = NULL; + + pa_module_unload_request(u->module); +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + char *t; + pa_sink *master; + pa_sink_input_new_data data; + const char *plugin, *label; + LADSPA_Descriptor_Function descriptor_func; + const char *e, *cdata; + const LADSPA_Descriptor *d; + unsigned long input_port, output_port, p, j, n_control; + unsigned c; + pa_bool_t *use_default = NULL; + char *default_sink_name = NULL; + + pa_assert(m); + + pa_assert(sizeof(LADSPA_Data) == sizeof(float)); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK, 1))) { + pa_log("Master sink not found"); + goto fail; + } + + ss = master->sample_spec; + ss.format = PA_SAMPLE_FLOAT32; + map = master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + if (!(plugin = pa_modargs_get_value(ma, "plugin", NULL))) { + pa_log("Missing LADSPA plugin name"); + goto fail; + } + + if (!(label = pa_modargs_get_value(ma, "label", NULL))) { + pa_log("Missing LADSPA plugin label"); + goto fail; + } + + cdata = pa_modargs_get_value(ma, "control", NULL); + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->master = master; + pa_memchunk_reset(&u->memchunk); + + if (!(e = getenv("LADSPA_PATH"))) + e = LADSPA_PATH; + + /* FIXME: This is not exactly thread safe */ + t = pa_xstrdup(lt_dlgetsearchpath()); + lt_dlsetsearchpath(e); + m->dl = lt_dlopenext(plugin); + lt_dlsetsearchpath(t); + pa_xfree(t); + + if (!m->dl) { + pa_log("Failed to load LADSPA plugin: %s", lt_dlerror()); + goto fail; + } + + if (!(descriptor_func = (LADSPA_Descriptor_Function) lt_dlsym(m->dl, "ladspa_descriptor"))) { + pa_log("LADSPA module lacks ladspa_descriptor() symbol."); + goto fail; + } + + for (j = 0;; j++) { + + if (!(d = descriptor_func(j))) { + pa_log("Failed to find plugin label '%s' in plugin '%s'.", plugin, label); + goto fail; + } + + if (strcmp(d->Label, label) == 0) + break; + } + + u->descriptor = d; + + pa_log_debug("Module: %s", plugin); + pa_log_debug("Label: %s", d->Label); + pa_log_debug("Unique ID: %lu", d->UniqueID); + pa_log_debug("Name: %s", d->Name); + pa_log_debug("Maker: %s", d->Maker); + pa_log_debug("Copyright: %s", d->Copyright); + + input_port = output_port = (unsigned long) -1; + n_control = 0; + + for (p = 0; p < d->PortCount; p++) { + + if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) { + + if (strcmp(d->PortNames[p], "Input") == 0) { + pa_assert(input_port == (unsigned long) -1); + input_port = p; + } else { + pa_log("Found audio input port on plugin we cannot handle: %s", d->PortNames[p]); + goto fail; + } + + } else if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_AUDIO(d->PortDescriptors[p])) { + + if (strcmp(d->PortNames[p], "Output") == 0) { + pa_assert(output_port == (unsigned long) -1); + output_port = p; + } else { + pa_log("Found audio output port on plugin we cannot handle: %s", d->PortNames[p]); + goto fail; + } + + } else if (LADSPA_IS_PORT_INPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])) + n_control++; + else { + pa_assert(LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p]) && LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])); + pa_log_debug("Ignored control output port \"%s\".", d->PortNames[p]); + } + } + + if ((input_port == (unsigned long) -1) || (output_port == (unsigned long) -1)) { + pa_log("Failed to identify input and output ports. " + "Right now this module can only deal with plugins which provide an 'Input' and an 'Output' audio port. " + "Patches welcome!"); + goto fail; + } + + u->block_size = pa_frame_align(pa_mempool_block_size_max(m->core->mempool), &ss); + + u->input = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size); + if (LADSPA_IS_INPLACE_BROKEN(d->Properties)) + u->output = (LADSPA_Data*) pa_xnew(uint8_t, u->block_size); + else + u->output = u->input; + + u->channels = ss.channels; + + for (c = 0; c < ss.channels; c++) { + if (!(u->handle[c] = d->instantiate(d, ss.rate))) { + pa_log("Failed to instantiate plugin %s with label %s for channel %i", plugin, d->Label, c); + goto fail; + } + + d->connect_port(u->handle[c], input_port, u->input); + d->connect_port(u->handle[c], output_port, u->output); + } + + if (!cdata && n_control > 0) { + pa_log("This plugin requires specification of %lu control parameters.", n_control); + goto fail; + } + + if (n_control > 0) { + const char *state = NULL; + char *k; + unsigned long h; + + u->control = pa_xnew(LADSPA_Data, n_control); + use_default = pa_xnew(pa_bool_t, n_control); + p = 0; + + while ((k = pa_split(cdata, ",", &state)) && p < n_control) { + float f; + + if (*k == 0) { + use_default[p++] = TRUE; + pa_xfree(k); + continue; + } + + if (pa_atof(k, &f) < 0) { + pa_log("Failed to parse control value '%s'", k); + pa_xfree(k); + goto fail; + } + + pa_xfree(k); + + use_default[p] = FALSE; + u->control[p++] = f; + } + + /* The previous loop doesn't take the last control value into account + if it is left empty, so we do it here. */ + if (*cdata == 0 || cdata[strlen(cdata) - 1] == ',') { + if (p < n_control) + use_default[p] = TRUE; + p++; + } + + if (p > n_control || k) { + pa_log("Too many control values passed, %lu expected.", n_control); + if (k) + pa_xfree(k); + goto fail; + } + + if (p < n_control) { + pa_log("Not enough control values passed, %lu expected, %lu passed.", n_control, p); + goto fail; + } + + h = 0; + for (p = 0; p < d->PortCount; p++) { + LADSPA_PortRangeHintDescriptor hint = d->PortRangeHints[p].HintDescriptor; + + if (!LADSPA_IS_PORT_CONTROL(d->PortDescriptors[p])) + continue; + + if (LADSPA_IS_PORT_OUTPUT(d->PortDescriptors[p])) { + for (c = 0; c < ss.channels; c++) + d->connect_port(u->handle[c], p, &u->control_out); + continue; + } + + pa_assert(h < n_control); + + if (use_default[h]) { + LADSPA_Data lower, upper; + + if (!LADSPA_IS_HINT_HAS_DEFAULT(hint)) { + pa_log("Control port value left empty but plugin defines no default."); + goto fail; + } + + lower = d->PortRangeHints[p].LowerBound; + upper = d->PortRangeHints[p].UpperBound; + + if (LADSPA_IS_HINT_SAMPLE_RATE(hint)) { + lower *= ss.rate; + upper *= ss.rate; + } + + switch (hint & LADSPA_HINT_DEFAULT_MASK) { + + case LADSPA_HINT_DEFAULT_MINIMUM: + u->control[h] = lower; + break; + + case LADSPA_HINT_DEFAULT_MAXIMUM: + u->control[h] = upper; + break; + + case LADSPA_HINT_DEFAULT_LOW: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + u->control[h] = exp(log(lower) * 0.75 + log(upper) * 0.25); + else + u->control[h] = lower * 0.75 + upper * 0.25; + break; + + case LADSPA_HINT_DEFAULT_MIDDLE: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + u->control[h] = exp(log(lower) * 0.5 + log(upper) * 0.5); + else + u->control[h] = lower * 0.5 + upper * 0.5; + break; + + case LADSPA_HINT_DEFAULT_HIGH: + if (LADSPA_IS_HINT_LOGARITHMIC(hint)) + u->control[h] = exp(log(lower) * 0.25 + log(upper) * 0.75); + else + u->control[h] = lower * 0.25 + upper * 0.75; + break; + + case LADSPA_HINT_DEFAULT_0: + u->control[h] = 0; + break; + + case LADSPA_HINT_DEFAULT_1: + u->control[h] = 1; + break; + + case LADSPA_HINT_DEFAULT_100: + u->control[h] = 100; + break; + + case LADSPA_HINT_DEFAULT_440: + u->control[h] = 440; + break; + + default: + pa_assert_not_reached(); + } + } + + if (LADSPA_IS_HINT_INTEGER(hint)) + u->control[h] = roundf(u->control[h]); + + pa_log_debug("Binding %f to port %s", u->control[h], d->PortNames[p]); + + for (c = 0; c < ss.channels; c++) + d->connect_port(u->handle[c], p, &u->control[h]); + + h++; + } + + pa_assert(h == n_control); + } + + if (d->activate) + for (c = 0; c < u->channels; c++) + d->activate(u->handle[c]); + + default_sink_name = pa_sprintf_malloc("%s.ladspa", master->name); + + /* Create sink */ + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &map))) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->set_state = sink_set_state; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("LADSPA plugin '%s' on '%s'", label, master->description)); + pa_xfree(t); + pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); + pa_sink_set_rtpoll(u->sink, master->rtpoll); + + /* Create sink input */ + pa_sink_input_new_data_init(&data); + data.sink = u->master; + data.driver = __FILE__; + data.name = "LADSPA Stream"; + pa_sink_input_new_data_set_sample_spec(&data, &ss); + pa_sink_input_new_data_set_channel_map(&data, &map); + data.module = m; + + if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE))) + goto fail; + + u->sink_input->parent.process_msg = sink_input_process_msg; + u->sink_input->peek = sink_input_peek_cb; + u->sink_input->drop = sink_input_drop_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->userdata = u; + + pa_sink_put(u->sink); + pa_sink_input_put(u->sink_input); + + pa_modargs_free(ma); + + pa_xfree(use_default); + pa_xfree(default_sink_name); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa_xfree(use_default); + pa_xfree(default_sink_name); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + unsigned c; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } + + if (u->sink) { + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + } + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + for (c = 0; c < u->channels; c++) + if (u->handle[c]) { + if (u->descriptor->deactivate) + u->descriptor->deactivate(u->handle[c]); + u->descriptor->cleanup(u->handle[c]); + } + + if (u->output != u->input) + pa_xfree(u->output); + + pa_xfree(u->input); + + pa_xfree(u->control); + + pa_xfree(u); +} diff --git a/src/modules/module-lirc.c b/src/modules/module-lirc.c new file mode 100644 index 00000000..24542172 --- /dev/null +++ b/src/modules/module-lirc.c @@ -0,0 +1,259 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 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 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 <unistd.h> +#include <string.h> +#include <stdlib.h> + +#include <lirc/lirc_client.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/modargs.h> +#include <pulsecore/macro.h> + +#include "module-lirc-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("LIRC volume control"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("config=<config file> sink=<sink name> appname=<lirc application name>"); + +static const char* const valid_modargs[] = { + "config", + "sink", + "appname", + NULL, +}; + +struct userdata { + int lirc_fd; + pa_io_event *io; + struct lirc_config *config; + char *sink_name; + pa_module *module; + float mute_toggle_save; +}; + +static int lirc_in_use = 0; + +static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) { + struct userdata *u = userdata; + char *name = NULL, *code = NULL; + + pa_assert(io); + pa_assert(u); + + if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { + pa_log("Lost connection to LIRC daemon."); + goto fail; + } + + if (events & PA_IO_EVENT_INPUT) { + char *c; + + if (lirc_nextcode(&code) != 0 || !code) { + pa_log("lirc_nextcode() failed."); + goto fail; + } + + c = pa_xstrdup(code); + c[strcspn(c, "\n\r")] = 0; + pa_log_debug("Raw IR code '%s'", c); + pa_xfree(c); + + while (lirc_code2char(u->config, code, &name) == 0 && name) { + enum { + INVALID, + UP, + DOWN, + MUTE, + RESET, + MUTE_TOGGLE + } volchange = INVALID; + + pa_log_info("Translated IR code '%s'", name); + + if (strcasecmp(name, "volume-up") == 0) + volchange = UP; + else if (strcasecmp(name, "volume-down") == 0) + volchange = DOWN; + else if (strcasecmp(name, "mute") == 0) + volchange = MUTE; + else if (strcasecmp(name, "mute-toggle") == 0) + volchange = MUTE_TOGGLE; + else if (strcasecmp(name, "reset") == 0) + volchange = RESET; + + if (volchange == INVALID) + pa_log_warn("Recieved unknown IR code '%s'", name); + else { + pa_sink *s; + + if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) + pa_log("Failed to get sink '%s'", u->sink_name); + else { + int i; + pa_cvolume cv = *pa_sink_get_volume(s); + +#define DELTA (PA_VOLUME_NORM/20) + + switch (volchange) { + case UP: + for (i = 0; i < cv.channels; i++) { + cv.values[i] += DELTA; + + if (cv.values[i] > PA_VOLUME_NORM) + cv.values[i] = PA_VOLUME_NORM; + } + + pa_sink_set_volume(s, &cv); + break; + + case DOWN: + for (i = 0; i < cv.channels; i++) { + if (cv.values[i] >= DELTA) + cv.values[i] -= DELTA; + else + cv.values[i] = PA_VOLUME_MUTED; + } + + pa_sink_set_volume(s, &cv); + break; + + case MUTE: + pa_sink_set_mute(s, 0); + break; + + case RESET: + pa_sink_set_mute(s, 1); + break; + + case MUTE_TOGGLE: + + pa_sink_set_mute(s, !pa_sink_get_mute(s)); + break; + + case INVALID: + ; + } + } + } + } + } + + pa_xfree(code); + + return; + +fail: + u->module->core->mainloop->io_free(u->io); + u->io = NULL; + + pa_module_unload_request(u->module); + + pa_xfree(code); +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + + pa_assert(m); + + if (lirc_in_use) { + pa_log("module-lirc may no be loaded twice."); + return -1; + } + + 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->module = m; + u->io = NULL; + u->config = NULL; + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + u->lirc_fd = -1; + u->mute_toggle_save = 0; + + if ((u->lirc_fd = lirc_init((char*) pa_modargs_get_value(ma, "appname", "pulseaudio"), 1)) < 0) { + pa_log("lirc_init() failed."); + goto fail; + } + + if (lirc_readconfig((char*) pa_modargs_get_value(ma, "config", NULL), &u->config, NULL) < 0) { + pa_log("lirc_readconfig() failed."); + goto fail; + } + + u->io = m->core->mainloop->io_new(m->core->mainloop, u->lirc_fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u); + + lirc_in_use = 1; + + 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->io) + m->core->mainloop->io_free(u->io); + + if (u->config) + lirc_freeconfig(u->config); + + if (u->lirc_fd >= 0) + lirc_deinit(); + + pa_xfree(u->sink_name); + pa_xfree(u); + + lirc_in_use = 0; +} diff --git a/src/modules/module-match.c b/src/modules/module-match.c new file mode 100644 index 00000000..ed5f3076 --- /dev/null +++ b/src/modules/module-match.c @@ -0,0 +1,244 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <regex.h> +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/core-util.h> + +#include "module-match-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Playback stream expression matching module"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("table=<filename>"); + +#define WHITESPACE "\n\r \t" + +#define DEFAULT_MATCH_TABLE_FILE PA_DEFAULT_CONFIG_DIR"/match.table" +#define DEFAULT_MATCH_TABLE_FILE_USER "match.table" + +static const char* const valid_modargs[] = { + "table", + NULL, +}; + +struct rule { + regex_t regex; + pa_volume_t volume; + struct rule *next; +}; + +struct userdata { + struct rule *rules; + pa_subscription *subscription; +}; + +static int load_rules(struct userdata *u, const char *filename) { + FILE *f; + int n = 0; + int ret = -1; + struct rule *end = NULL; + char *fn = NULL; + + pa_assert(u); + + f = filename ? + fopen(fn = pa_xstrdup(filename), "r") : + pa_open_config_file(DEFAULT_MATCH_TABLE_FILE, DEFAULT_MATCH_TABLE_FILE_USER, NULL, &fn, "r"); + + if (!f) { + pa_log("failed to open file '%s': %s", fn, pa_cstrerror(errno)); + goto finish; + } + + pa_lock_fd(fileno(f), 1); + + while (!feof(f)) { + char *d, *v; + pa_volume_t volume; + uint32_t k; + regex_t regex; + char ln[256]; + struct rule *rule; + + if (!fgets(ln, sizeof(ln), f)) + break; + + n++; + + pa_strip_nl(ln); + + if (ln[0] == '#' || !*ln ) + continue; + + d = ln+strcspn(ln, WHITESPACE); + v = d+strspn(d, WHITESPACE); + + + if (!*v) { + pa_log(__FILE__ ": [%s:%u] failed to parse line - too few words", filename, n); + goto finish; + } + + *d = 0; + if (pa_atou(v, &k) < 0) { + pa_log("[%s:%u] failed to parse volume", filename, n); + goto finish; + } + + volume = (pa_volume_t) k; + + + if (regcomp(®ex, ln, REG_EXTENDED|REG_NOSUB) != 0) { + pa_log("[%s:%u] invalid regular expression", filename, n); + goto finish; + } + + rule = pa_xnew(struct rule, 1); + rule->regex = regex; + rule->volume = volume; + rule->next = NULL; + + if (end) + end->next = rule; + else + u->rules = rule; + end = rule; + + *d = 0; + } + + ret = 0; + +finish: + if (f) { + pa_lock_fd(fileno(f), 0); + fclose(f); + } + + if (fn) + pa_xfree(fn); + + return ret; +} + +static void callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + pa_sink_input *si; + struct rule *r; + + pa_assert(c); + pa_assert(u); + + if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW)) + return; + + if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) + return; + + if (!si->name) + return; + + for (r = u->rules; r; r = r->next) { + if (!regexec(&r->regex, si->name, 0, NULL, 0)) { + pa_cvolume cv; + pa_log_debug("changing volume of sink input '%s' to 0x%03x", si->name, r->volume); + pa_cvolume_set(&cv, si->sample_spec.channels, r->volume); + pa_sink_input_set_volume(si, &cv); + } + } +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + u = pa_xnew(struct userdata, 1); + u->rules = NULL; + u->subscription = NULL; + m->userdata = u; + + if (load_rules(u, pa_modargs_get_value(ma, "table", NULL)) < 0) + goto fail; + + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT, callback, u); + + 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; + struct rule *r, *n; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->subscription) + pa_subscription_free(u->subscription); + + for (r = u->rules; r; r = n) { + n = r->next; + + regfree(&r->regex); + pa_xfree(r); + } + + pa_xfree(u); +} diff --git a/src/modules/module-mmkbd-evdev.c b/src/modules/module-mmkbd-evdev.c new file mode 100644 index 00000000..03c0e973 --- /dev/null +++ b/src/modules/module-mmkbd-evdev.c @@ -0,0 +1,262 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 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 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 <unistd.h> +#include <string.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> + +#include <linux/input.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/modargs.h> +#include <pulsecore/core-util.h> + +#include "module-mmkbd-evdev-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Multimedia keyboard support via Linux evdev"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("device=<evdev device> sink=<sink name>"); + +#define DEFAULT_DEVICE "/dev/input/event0" + +/* + * This isn't defined in older kernel headers and there is no way of + * detecting it. + */ +struct _input_id { + __u16 bustype; + __u16 vendor; + __u16 product; + __u16 version; +}; + +static const char* const valid_modargs[] = { + "device", + "sink", + NULL, +}; + +struct userdata { + int fd, fd_type; + pa_io_event *io; + char *sink_name; + pa_module *module; +}; + +static void io_callback(pa_mainloop_api *io, PA_GCC_UNUSED pa_io_event *e, PA_GCC_UNUSED int fd, pa_io_event_flags_t events, void*userdata) { + struct userdata *u = userdata; + + pa_assert(io); + pa_assert(u); + + if (events & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) { + pa_log("Lost connection to evdev device."); + goto fail; + } + + if (events & PA_IO_EVENT_INPUT) { + struct input_event ev; + + if (pa_loop_read(u->fd, &ev, sizeof(ev), &u->fd_type) <= 0) { + pa_log("Failed to read from event device: %s", pa_cstrerror(errno)); + goto fail; + } + + if (ev.type == EV_KEY && (ev.value == 1 || ev.value == 2)) { + enum { INVALID, UP, DOWN, MUTE_TOGGLE } volchange = INVALID; + + pa_log_debug("Key code=%u, value=%u", ev.code, ev.value); + + switch (ev.code) { + case KEY_VOLUMEDOWN: volchange = DOWN; break; + case KEY_VOLUMEUP: volchange = UP; break; + case KEY_MUTE: volchange = MUTE_TOGGLE; break; + } + + if (volchange != INVALID) { + pa_sink *s; + + if (!(s = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) + pa_log("Failed to get sink '%s'", u->sink_name); + else { + int i; + pa_cvolume cv = *pa_sink_get_volume(s); + +#define DELTA (PA_VOLUME_NORM/20) + + switch (volchange) { + case UP: + for (i = 0; i < cv.channels; i++) { + cv.values[i] += DELTA; + + if (cv.values[i] > PA_VOLUME_NORM) + cv.values[i] = PA_VOLUME_NORM; + } + + pa_sink_set_volume(s, &cv); + break; + + case DOWN: + for (i = 0; i < cv.channels; i++) { + if (cv.values[i] >= DELTA) + cv.values[i] -= DELTA; + else + cv.values[i] = PA_VOLUME_MUTED; + } + + pa_sink_set_volume(s, &cv); + break; + + case MUTE_TOGGLE: + + pa_sink_set_mute(s, !pa_sink_get_mute(s)); + break; + + case INVALID: + ; + } + } + } + } + } + + return; + +fail: + u->module->core->mainloop->io_free(u->io); + u->io = NULL; + + pa_module_unload_request(u->module); +} + +#define test_bit(bit, array) (array[bit/8] & (1<<(bit%8))) + +int pa__init(pa_module*m) { + + pa_modargs *ma = NULL; + struct userdata *u; + int version; + struct _input_id input_id; + char name[256]; + uint8_t evtype_bitmask[EV_MAX/8 + 1]; + + pa_assert(m); + + 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->module = m; + u->io = NULL; + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + u->fd = -1; + u->fd_type = 0; + + if ((u->fd = open(pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), O_RDONLY)) < 0) { + pa_log("failed to open evdev device: %s", pa_cstrerror(errno)); + goto fail; + } + + if (ioctl(u->fd, EVIOCGVERSION, &version) < 0) { + pa_log("EVIOCGVERSION failed: %s", pa_cstrerror(errno)); + goto fail; + } + + pa_log_info("evdev driver version %i.%i.%i", version >> 16, (version >> 8) & 0xff, version & 0xff); + + if(ioctl(u->fd, EVIOCGID, &input_id)) { + pa_log("EVIOCGID failed: %s", pa_cstrerror(errno)); + goto fail; + } + + pa_log_info("evdev vendor 0x%04hx product 0x%04hx version 0x%04hx bustype %u", + input_id.vendor, input_id.product, input_id.version, input_id.bustype); + + memset(name, 0, sizeof(name)); + if(ioctl(u->fd, EVIOCGNAME(sizeof(name)), name) < 0) { + pa_log("EVIOCGNAME failed: %s", pa_cstrerror(errno)); + goto fail; + } + + pa_log_info("evdev device name: %s", name); + + memset(evtype_bitmask, 0, sizeof(evtype_bitmask)); + if (ioctl(u->fd, EVIOCGBIT(0, EV_MAX), evtype_bitmask) < 0) { + pa_log("EVIOCGBIT failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (!test_bit(EV_KEY, evtype_bitmask)) { + pa_log("Device has no keys."); + goto fail; + } + + u->io = m->core->mainloop->io_new(m->core->mainloop, u->fd, PA_IO_EVENT_INPUT|PA_IO_EVENT_HANGUP, io_callback, u); + + 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->io) + m->core->mainloop->io_free(u->io); + + if (u->fd >= 0) + pa_assert_se(pa_close(u->fd) == 0); + + pa_xfree(u->sink_name); + pa_xfree(u); +} diff --git a/src/modules/module-native-protocol-fd.c b/src/modules/module-native-protocol-fd.c new file mode 100644 index 00000000..53f41896 --- /dev/null +++ b/src/modules/module-native-protocol-fd.c @@ -0,0 +1,89 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <unistd.h> + +#include <pulsecore/module.h> +#include <pulsecore/macro.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/modargs.h> +#include <pulsecore/protocol-native.h> +#include <pulsecore/log.h> + +#include "module-native-protocol-fd-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Native protocol autospawn helper"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static const char* const valid_modargs[] = { + "fd", + "public", + "cookie", + NULL, +}; + +int pa__init(pa_module*m) { + pa_iochannel *io; + pa_modargs *ma; + int fd, r = -1; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto finish; + } + + if (pa_modargs_get_value_s32(ma, "fd", &fd) < 0) { + pa_log("Invalid file descriptor."); + goto finish; + } + + io = pa_iochannel_new(m->core->mainloop, fd, fd); + + if (!(m->userdata = pa_protocol_native_new_iochannel(m->core, io, m, ma))) { + pa_iochannel_free(io); + goto finish; + } + + r = 0; + +finish: + if (ma) + pa_modargs_free(ma); + + return r; +} + +void pa__done(pa_module*m) { + pa_assert(m); + + pa_protocol_native_free(m->userdata); +} diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c new file mode 100644 index 00000000..de35fff9 --- /dev/null +++ b/src/modules/module-null-sink.c @@ -0,0 +1,257 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/rtclock.h> + +#include "module-null-sink-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Clocked NULL sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "sink_name=<name of sink>" + "channel_map=<channel map>" + "description=<description for the sink>"); + +#define DEFAULT_SINK_NAME "null" + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + size_t block_size; + + struct timeval timestamp; +}; + +static const char* const valid_modargs[] = { + "rate", + "format", + "channels", + "sink_name", + "channel_map", + "description", + 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: + + if (PA_PTR_TO_UINT(data) == PA_SINK_RUNNING) + pa_rtclock_get(&u->timestamp); + + break; + + case PA_SINK_MESSAGE_GET_LATENCY: { + struct timeval now; + + pa_rtclock_get(&now); + + if (pa_timeval_cmp(&u->timestamp, &now) > 0) + *((pa_usec_t*) data) = 0; + else + *((pa_usec_t*) data) = pa_timeval_diff(&u->timestamp, &now); + break; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + pa_rtclock_get(&u->timestamp); + + for (;;) { + int ret; + + /* Render some data and drop it immediately */ + if (u->sink->thread_info.state == PA_SINK_RUNNING) { + struct timeval now; + + pa_rtclock_get(&now); + + if (pa_timeval_cmp(&u->timestamp, &now) <= 0) { + pa_sink_skip(u->sink, u->block_size); + pa_timeval_add(&u->timestamp, pa_bytes_to_usec(u->block_size, &u->sink->sample_spec)); + } + + pa_rtpoll_set_timer_absolute(u->rtpoll, &u->timestamp); + } else + pa_rtpoll_set_timer_disabled(u->rtpoll); + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, pa_modargs_get_value(ma, "description", "NULL sink")); + + u->block_size = pa_bytes_per_second(&ss) / 20; /* 50 ms */ + if (u->block_size <= 0) + u->block_size = pa_frame_size(&ss); + + 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) + pa_rtpoll_free(u->rtpoll); + + pa_xfree(u); +} diff --git a/src/modules/module-oss.c b/src/modules/module-oss.c new file mode 100644 index 00000000..a7df8a0c --- /dev/null +++ b/src/modules/module-oss.c @@ -0,0 +1,1499 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +/* General power management rules: + * + * When SUSPENDED we close the audio device. + * + * We make no difference between IDLE and RUNNING in our handling. + * + * As long as we are in RUNNING/IDLE state we will *always* write data to + * the device. If none is avilable from the inputs, we write silence + * instead. + * + * If power should be saved on IDLE module-suspend-on-idle should be used. + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#include <sys/soundcard.h> +#include <sys/ioctl.h> +#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 <signal.h> +#include <poll.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/thread.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/module.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> + +#include "oss-util.h" +#include "module-oss-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("OSS Sink/Source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "source_name=<name for the source> " + "device=<OSS device> " + "record=<enable source?> " + "playback=<enable sink?> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "fragments=<number of fragments> " + "fragment_size=<fragment size> " + "channel_map=<channel map> " + "mmap=<enable memory mapping?>"); + +#define DEFAULT_DEVICE "/dev/dsp" + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + char *device_name; + + pa_memchunk memchunk; + + size_t frame_size; + uint32_t in_fragment_size, out_fragment_size, in_nfrags, out_nfrags, in_hwbuf_size, out_hwbuf_size; + pa_bool_t use_getospace, use_getispace; + pa_bool_t use_getodelay; + + pa_bool_t sink_suspended, source_suspended; + + int fd; + int mode; + + int mixer_fd; + int mixer_devmask; + + int nfrags, frag_size; + + pa_bool_t use_mmap; + unsigned out_mmap_current, in_mmap_current; + void *in_mmap, *out_mmap; + pa_memblock **in_mmap_memblocks, **out_mmap_memblocks; + + int in_mmap_saved_nfrags, out_mmap_saved_nfrags; + + pa_rtpoll_item *rtpoll_item; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "source_name", + "device", + "record", + "playback", + "fragments", + "fragment_size", + "format", + "rate", + "channels", + "channel_map", + "mmap", + NULL +}; + +static void trigger(struct userdata *u, pa_bool_t quick) { + int enable_bits = 0, zero = 0; + + pa_assert(u); + + if (u->fd < 0) + return; + + pa_log_debug("trigger"); + + if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) + enable_bits |= PCM_ENABLE_INPUT; + + if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) + enable_bits |= PCM_ENABLE_OUTPUT; + + pa_log_debug("trigger: %i", enable_bits); + + + if (u->use_mmap) { + + if (!quick) + ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &zero); + +#ifdef SNDCTL_DSP_HALT + if (enable_bits == 0) + if (ioctl(u->fd, SNDCTL_DSP_HALT, NULL) < 0) + pa_log_warn("SNDCTL_DSP_HALT: %s", pa_cstrerror(errno)); +#endif + + if (ioctl(u->fd, SNDCTL_DSP_SETTRIGGER, &enable_bits) < 0) + pa_log_warn("SNDCTL_DSP_SETTRIGGER: %s", pa_cstrerror(errno)); + + if (u->sink && !(enable_bits & PCM_ENABLE_OUTPUT)) { + pa_log_debug("clearing playback buffer"); + pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &u->sink->sample_spec); + } + + } else { + + if (enable_bits) + if (ioctl(u->fd, SNDCTL_DSP_POST, NULL) < 0) + pa_log_warn("SNDCTL_DSP_POST: %s", pa_cstrerror(errno)); + + if (!quick) { + /* + * Some crappy drivers do not start the recording until we + * read something. Without this snippet, poll will never + * register the fd as ready. + */ + + if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) { + uint8_t *buf = pa_xnew(uint8_t, u->in_fragment_size); + pa_read(u->fd, buf, u->in_fragment_size, NULL); + pa_xfree(buf); + } + } + } +} + +static void mmap_fill_memblocks(struct userdata *u, unsigned n) { + pa_assert(u); + pa_assert(u->out_mmap_memblocks); + +/* pa_log("Mmmap writing %u blocks", n); */ + + while (n > 0) { + pa_memchunk chunk; + + if (u->out_mmap_memblocks[u->out_mmap_current]) + pa_memblock_unref_fixed(u->out_mmap_memblocks[u->out_mmap_current]); + + chunk.memblock = u->out_mmap_memblocks[u->out_mmap_current] = + pa_memblock_new_fixed( + u->core->mempool, + (uint8_t*) u->out_mmap + u->out_fragment_size * u->out_mmap_current, + u->out_fragment_size, + 1); + + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_sink_render_into_full(u->sink, &chunk); + + u->out_mmap_current++; + while (u->out_mmap_current >= u->out_nfrags) + u->out_mmap_current -= u->out_nfrags; + + n--; + } +} + +static int mmap_write(struct userdata *u) { + struct count_info info; + + pa_assert(u); + pa_assert(u->sink); + +/* pa_log("Mmmap writing..."); */ + + if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); + return -1; + } + + info.blocks += u->out_mmap_saved_nfrags; + u->out_mmap_saved_nfrags = 0; + + if (info.blocks > 0) + mmap_fill_memblocks(u, info.blocks); + + return info.blocks; +} + +static void mmap_post_memblocks(struct userdata *u, unsigned n) { + pa_assert(u); + pa_assert(u->in_mmap_memblocks); + +/* pa_log("Mmmap reading %u blocks", n); */ + + while (n > 0) { + pa_memchunk chunk; + + if (!u->in_mmap_memblocks[u->in_mmap_current]) { + + chunk.memblock = u->in_mmap_memblocks[u->in_mmap_current] = + pa_memblock_new_fixed( + u->core->mempool, + (uint8_t*) u->in_mmap + u->in_fragment_size*u->in_mmap_current, + u->in_fragment_size, + 1); + + chunk.length = pa_memblock_get_length(chunk.memblock); + chunk.index = 0; + + pa_source_post(u->source, &chunk); + } + + u->in_mmap_current++; + while (u->in_mmap_current >= u->in_nfrags) + u->in_mmap_current -= u->in_nfrags; + + n--; + } +} + +static void mmap_clear_memblocks(struct userdata*u, unsigned n) { + unsigned i = u->in_mmap_current; + + pa_assert(u); + pa_assert(u->in_mmap_memblocks); + + if (n > u->in_nfrags) + n = u->in_nfrags; + + while (n > 0) { + if (u->in_mmap_memblocks[i]) { + pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); + u->in_mmap_memblocks[i] = NULL; + } + + i++; + while (i >= u->in_nfrags) + i -= u->in_nfrags; + + n--; + } +} + +static int mmap_read(struct userdata *u) { + struct count_info info; + pa_assert(u); + pa_assert(u->source); + +/* pa_log("Mmmap reading..."); */ + + if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); + return -1; + } + +/* pa_log("... %i", info.blocks); */ + + info.blocks += u->in_mmap_saved_nfrags; + u->in_mmap_saved_nfrags = 0; + + if (info.blocks > 0) { + mmap_post_memblocks(u, info.blocks); + mmap_clear_memblocks(u, u->in_nfrags/2); + } + + return info.blocks; +} + +static pa_usec_t mmap_sink_get_latency(struct userdata *u) { + struct count_info info; + size_t bpos, n; + + pa_assert(u); + + if (ioctl(u->fd, SNDCTL_DSP_GETOPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETOPTR: %s", pa_cstrerror(errno)); + return 0; + } + + u->out_mmap_saved_nfrags += info.blocks; + + bpos = ((u->out_mmap_current + u->out_mmap_saved_nfrags) * u->out_fragment_size) % u->out_hwbuf_size; + + if (bpos <= (size_t) info.ptr) + n = u->out_hwbuf_size - (info.ptr - bpos); + else + n = bpos - info.ptr; + +/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->out_fragment_size, u->out_fragments); */ + + return pa_bytes_to_usec(n, &u->sink->sample_spec); +} + +static pa_usec_t mmap_source_get_latency(struct userdata *u) { + struct count_info info; + size_t bpos, n; + + pa_assert(u); + + if (ioctl(u->fd, SNDCTL_DSP_GETIPTR, &info) < 0) { + pa_log("SNDCTL_DSP_GETIPTR: %s", pa_cstrerror(errno)); + return 0; + } + + u->in_mmap_saved_nfrags += info.blocks; + bpos = ((u->in_mmap_current + u->in_mmap_saved_nfrags) * u->in_fragment_size) % u->in_hwbuf_size; + + if (bpos <= (size_t) info.ptr) + n = info.ptr - bpos; + else + n = u->in_hwbuf_size - bpos + info.ptr; + +/* pa_log("n = %u, bpos = %u, ptr = %u, total=%u, fragsize = %u, n_frags = %u\n", n, bpos, (unsigned) info.ptr, total, u->in_fragment_size, u->in_fragments); */ + + return pa_bytes_to_usec(n, &u->source->sample_spec); +} + +static pa_usec_t io_sink_get_latency(struct userdata *u) { + pa_usec_t r = 0; + + pa_assert(u); + + if (u->use_getodelay) { + int arg; + + if (ioctl(u->fd, SNDCTL_DSP_GETODELAY, &arg) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETODELAY: %s", pa_cstrerror(errno)); + u->use_getodelay = 0; + } else + r = pa_bytes_to_usec(arg, &u->sink->sample_spec); + + } + + if (!u->use_getodelay && u->use_getospace) { + struct audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); + u->use_getospace = 0; + } else + r = pa_bytes_to_usec(info.bytes, &u->sink->sample_spec); + } + + if (u->memchunk.memblock) + r += pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + + return r; +} + + +static pa_usec_t io_source_get_latency(struct userdata *u) { + pa_usec_t r = 0; + + pa_assert(u); + + if (u->use_getispace) { + struct audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); + u->use_getispace = 0; + } else + r = pa_bytes_to_usec(info.bytes, &u->source->sample_spec); + } + + return r; +} + +static void build_pollfd(struct userdata *u) { + struct pollfd *pollfd; + + pa_assert(u); + pa_assert(u->fd >= 0); + + if (u->rtpoll_item) + pa_rtpoll_item_free(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 = 0; + pollfd->revents = 0; +} + +static int suspend(struct userdata *u) { + pa_assert(u); + pa_assert(u->fd >= 0); + + pa_log_info("Suspending..."); + + if (u->out_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->out_nfrags; i++) + if (u->out_mmap_memblocks[i]) { + pa_memblock_unref_fixed(u->out_mmap_memblocks[i]); + u->out_mmap_memblocks[i] = NULL; + } + } + + if (u->in_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->in_nfrags; i++) + if (u->in_mmap_memblocks[i]) { + pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); + u->in_mmap_memblocks[i] = NULL; + } + } + + if (u->in_mmap && u->in_mmap != MAP_FAILED) { + munmap(u->in_mmap, u->in_hwbuf_size); + u->in_mmap = NULL; + } + + if (u->out_mmap && u->out_mmap != MAP_FAILED) { + munmap(u->out_mmap, u->out_hwbuf_size); + u->out_mmap = NULL; + } + + /* Let's suspend */ + ioctl(u->fd, SNDCTL_DSP_SYNC, NULL); + pa_close(u->fd); + u->fd = -1; + + if (u->rtpoll_item) { + pa_rtpoll_item_free(u->rtpoll_item); + u->rtpoll_item = NULL; + } + + pa_log_info("Device suspended..."); + + return 0; +} + +static int unsuspend(struct userdata *u) { + int m; + pa_sample_spec ss, *ss_original; + int frag_size, in_frag_size, out_frag_size; + int in_nfrags, out_nfrags; + struct audio_buf_info info; + + pa_assert(u); + pa_assert(u->fd < 0); + + m = u->mode; + + pa_log_info("Trying resume..."); + + if ((u->fd = pa_oss_open(u->device_name, &m, NULL)) < 0) { + pa_log_warn("Resume failed, device busy (%s)", pa_cstrerror(errno)); + return -1; + + if (m != u->mode) + pa_log_warn("Resume failed, couldn't open device with original access mode."); + goto fail; + } + + if (u->nfrags >= 2 && u->frag_size >= 1) + if (pa_oss_set_fragments(u->fd, u->nfrags, u->frag_size) < 0) { + pa_log_warn("Resume failed, couldn't set original fragment settings."); + goto fail; + } + + ss = *(ss_original = u->sink ? &u->sink->sample_spec : &u->source->sample_spec); + if (pa_oss_auto_format(u->fd, &ss) < 0 || !pa_sample_spec_equal(&ss, ss_original)) { + pa_log_warn("Resume failed, couldn't set original sample format settings."); + goto fail; + } + + if (ioctl(u->fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) { + pa_log_warn("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno)); + goto fail; + } + + in_frag_size = out_frag_size = frag_size; + in_nfrags = out_nfrags = u->nfrags; + + if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { + in_frag_size = info.fragsize; + in_nfrags = info.fragstotal; + } + + if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { + out_frag_size = info.fragsize; + out_nfrags = info.fragstotal; + } + + if ((u->source && (in_frag_size != (int) u->in_fragment_size || in_nfrags != (int) u->in_nfrags)) || + (u->sink && (out_frag_size != (int) u->out_fragment_size || out_nfrags != (int) u->out_nfrags))) { + pa_log_warn("Resume failed, input fragment settings don't match."); + goto fail; + } + + if (u->use_mmap) { + if (u->source) { + if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { + pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno)); + goto fail; + } + } + + if (u->sink) { + if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, u->fd, 0)) == MAP_FAILED) { + pa_log("Resume failed, mmap(): %s", pa_cstrerror(errno)); + if (u->in_mmap && u->in_mmap != MAP_FAILED) { + munmap(u->in_mmap, u->in_hwbuf_size); + u->in_mmap = NULL; + } + + goto fail; + } + + pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss); + } + } + + u->out_mmap_current = u->in_mmap_current = 0; + u->out_mmap_saved_nfrags = u->in_mmap_saved_nfrags = 0; + + pa_assert(!u->rtpoll_item); + + build_pollfd(u); + + if (u->sink) + pa_sink_get_volume(u->sink); + if (u->source) + pa_source_get_volume(u->source); + + pa_log_info("Resumed successfully..."); + + return 0; + +fail: + pa_close(u->fd); + u->fd = -1; + return -1; +} + +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; + int ret; + pa_bool_t do_trigger = FALSE, quick = TRUE; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->fd >= 0) { + if (u->use_mmap) + r = mmap_sink_get_latency(u); + else + r = io_sink_get_latency(u); + } + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SINK_MESSAGE_SET_STATE: + + switch ((pa_sink_state_t) PA_PTR_TO_UINT(data)) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(u->sink->thread_info.state)); + + if (!u->source || u->source_suspended) { + if (suspend(u) < 0) + return -1; + } + + do_trigger = TRUE; + + u->sink_suspended = TRUE; + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + + if (u->sink->thread_info.state == PA_SINK_INIT) { + do_trigger = TRUE; + quick = u->source && PA_SOURCE_OPENED(u->source->thread_info.state); + } + + if (u->sink->thread_info.state == PA_SINK_SUSPENDED) { + + if (!u->source || u->source_suspended) { + if (unsuspend(u) < 0) + return -1; + quick = FALSE; + } + + do_trigger = TRUE; + + u->out_mmap_current = 0; + u->out_mmap_saved_nfrags = 0; + + u->sink_suspended = FALSE; + } + + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + break; + + } + + ret = pa_sink_process_msg(o, code, data, offset, chunk); + + if (do_trigger) + trigger(u, quick); + + return ret; +} + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + int ret; + int do_trigger = FALSE, quick = TRUE; + + switch (code) { + + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->fd >= 0) { + if (u->use_mmap) + r = mmap_source_get_latency(u); + else + r = io_source_get_latency(u); + } + + *((pa_usec_t*) data) = r; + return 0; + } + + case PA_SOURCE_MESSAGE_SET_STATE: + + switch ((pa_source_state_t) PA_PTR_TO_UINT(data)) { + case PA_SOURCE_SUSPENDED: + pa_assert(PA_SOURCE_OPENED(u->source->thread_info.state)); + + if (!u->sink || u->sink_suspended) { + if (suspend(u) < 0) + return -1; + } + + do_trigger = TRUE; + + u->source_suspended = TRUE; + break; + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: + + if (u->source->thread_info.state == PA_SOURCE_INIT) { + do_trigger = TRUE; + quick = u->sink && PA_SINK_OPENED(u->sink->thread_info.state); + } + + if (u->source->thread_info.state == PA_SOURCE_SUSPENDED) { + + if (!u->sink || u->sink_suspended) { + if (unsuspend(u) < 0) + return -1; + quick = FALSE; + } + + do_trigger = TRUE; + + u->in_mmap_current = 0; + u->in_mmap_saved_nfrags = 0; + + u->source_suspended = FALSE; + } + break; + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + ; + + } + break; + + } + + ret = pa_source_process_msg(o, code, data, offset, chunk); + + if (do_trigger) + trigger(u, quick); + + return ret; +} + +static int sink_get_volume(pa_sink *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); + + if (u->mixer_devmask & SOUND_MASK_VOLUME) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_VOLUME, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_PCM) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_PCM, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static int sink_set_volume(pa_sink *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM)); + + if (u->mixer_devmask & SOUND_MASK_VOLUME) + if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_VOLUME, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_PCM) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_PCM, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static int source_get_volume(pa_source *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); + + if (u->mixer_devmask & SOUND_MASK_IGAIN) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_IGAIN, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_RECLEV) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_READ_RECLEV, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support reading mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static int source_set_volume(pa_source *s) { + struct userdata *u; + int r; + + pa_assert_se(u = s->userdata); + + pa_assert(u->mixer_devmask & (SOUND_MASK_IGAIN|SOUND_MASK_RECLEV)); + + if (u->mixer_devmask & SOUND_MASK_IGAIN) + if ((r = pa_oss_set_volume(u->mixer_fd, SOUND_MIXER_WRITE_IGAIN, &s->sample_spec, &s->volume)) >= 0) + return r; + + if (u->mixer_devmask & SOUND_MASK_RECLEV) + if ((r = pa_oss_get_volume(u->mixer_fd, SOUND_MIXER_WRITE_RECLEV, &s->sample_spec, &s->volume)) >= 0) + return r; + + pa_log_info("Device doesn't support writing mixer settings: %s", pa_cstrerror(errno)); + return -1; +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int write_type = 0, read_type = 0; + unsigned short revents = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->realtime_scheduling) + pa_make_realtime(u->core->realtime_priority); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + +/* pa_log("loop"); */ + + /* Render some data and write it to the dsp */ + + if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state) && ((revents & POLLOUT) || u->use_mmap || u->use_getospace)) { + + if (u->use_mmap) { + + if ((ret = mmap_write(u)) < 0) + goto fail; + + revents &= ~POLLOUT; + + if (ret > 0) + continue; + + } else { + ssize_t l; + pa_bool_t loop = FALSE, work_done = FALSE; + + l = u->out_fragment_size; + + if (u->use_getospace) { + audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETOSPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETOSPACE: %s", pa_cstrerror(errno)); + u->use_getospace = FALSE; + } else { + l = info.bytes; + + /* We loop only if GETOSPACE worked and we + * actually *know* that we can write more than + * one fragment at a time */ + loop = TRUE; + } + } + + /* Round down to multiples of the fragment size, + * because OSS needs that (at least some versions + * do) */ + l = (l/u->out_fragment_size) * u->out_fragment_size; + + /* Hmm, so poll() signalled us that we can read + * something, but GETOSPACE told us there was nothing? + * Hmm, make the best of it, try to read some data, to + * avoid spinning forever. */ + if (l <= 0 && (revents & POLLOUT)) { + l = u->out_fragment_size; + loop = FALSE; + } + + while (l > 0) { + void *p; + ssize_t t; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, l, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + p = pa_memblock_acquire(u->memchunk.memblock); + t = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); + pa_memblock_release(u->memchunk.memblock); + +/* pa_log("wrote %i bytes of %u", t, l); */ + + pa_assert(t != 0); + + if (t < 0) { + + if (errno == EINTR) + continue; + + else if (errno == EAGAIN) { + pa_log_debug("EAGAIN"); + + revents &= ~POLLOUT; + break; + + } else { + pa_log("Failed to write data to DSP: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + + u->memchunk.index += t; + u->memchunk.length -= t; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + l -= t; + + revents &= ~POLLOUT; + work_done = TRUE; + } + + if (!loop) + break; + } + + if (work_done) + continue; + } + } + + /* Try to read some data and pass it on to the source driver. */ + + if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state) && ((revents & POLLIN) || u->use_mmap || u->use_getispace)) { + + if (u->use_mmap) { + + if ((ret = mmap_read(u)) < 0) + goto fail; + + revents &= ~POLLIN; + + if (ret > 0) + continue; + + } else { + + void *p; + ssize_t l; + pa_memchunk memchunk; + pa_bool_t loop = FALSE, work_done = FALSE; + + l = u->in_fragment_size; + + if (u->use_getispace) { + audio_buf_info info; + + if (ioctl(u->fd, SNDCTL_DSP_GETISPACE, &info) < 0) { + pa_log_info("Device doesn't support SNDCTL_DSP_GETISPACE: %s", pa_cstrerror(errno)); + u->use_getispace = FALSE; + } else { + l = info.bytes; + loop = TRUE; + } + } + + l = (l/u->in_fragment_size) * u->in_fragment_size; + + if (l <= 0 && (revents & POLLIN)) { + l = u->in_fragment_size; + loop = FALSE; + } + + while (l > 0) { + ssize_t t, k; + + pa_assert(l > 0); + + memchunk.memblock = pa_memblock_new(u->core->mempool, (size_t) -1); + + k = pa_memblock_get_length(memchunk.memblock); + + if (k > l) + k = l; + + k = (k/u->frame_size)*u->frame_size; + + p = pa_memblock_acquire(memchunk.memblock); + t = pa_read(u->fd, p, k, &read_type); + pa_memblock_release(memchunk.memblock); + + pa_assert(t != 0); /* EOF cannot happen */ + +/* pa_log("read %i bytes of %u", t, l); */ + + if (t < 0) { + pa_memblock_unref(memchunk.memblock); + + if (errno == EINTR) + continue; + + else if (errno == EAGAIN) { + pa_log_debug("EAGAIN"); + + revents &= ~POLLIN; + break; + + } else { + pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + memchunk.index = 0; + memchunk.length = t; + + pa_source_post(u->source, &memchunk); + pa_memblock_unref(memchunk.memblock); + + l -= t; + + revents &= ~POLLIN; + work_done = TRUE; + } + + if (!loop) + break; + } + + if (work_done) + continue; + } + } + +/* pa_log("loop2 revents=%i", revents); */ + + if (u->rtpoll_item) { + struct pollfd *pollfd; + + pa_assert(u->fd >= 0); + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->events = + ((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0) | + ((u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) ? POLLOUT : 0); + } + + /* Hmm, nothing to do. Let's sleep */ + 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|POLLIN)) { + pa_log("DSP shutdown."); + goto fail; + } + + revents = pollfd->revents; + } else + revents = 0; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + + struct audio_buf_info info; + struct userdata *u = NULL; + const char *dev; + int fd = -1; + int nfrags, frag_size; + int mode, caps; + pa_bool_t record = TRUE, playback = TRUE, use_mmap = TRUE; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + char hwdesc[64], *t; + const char *name; + int namereg_fail; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) { + pa_log("record= and playback= expect boolean argument."); + goto fail; + } + + if (!playback && !record) { + pa_log("Neither playback nor record enabled for device."); + goto fail; + } + + mode = (playback && record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_OSS) < 0) { + pa_log("Failed to parse sample specification or channel map"); + goto fail; + } + + nfrags = m->core->default_n_fragments; + frag_size = pa_usec_to_bytes(m->core->default_fragment_size_msec*1000, &ss); + if (frag_size <= 0) + frag_size = pa_frame_size(&ss); + + if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) { + pa_log("Failed to parse fragments arguments"); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "mmap", &use_mmap) < 0) { + pa_log("Failed to parse mmap argument."); + goto fail; + } + + if ((fd = pa_oss_open(dev = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), &mode, &caps)) < 0) + goto fail; + + if (use_mmap && (!(caps & DSP_CAP_MMAP) || !(caps & DSP_CAP_TRIGGER))) { + pa_log_info("OSS device not mmap capable, falling back to UNIX read/write mode."); + use_mmap = 0; + } + + if (use_mmap && mode == O_WRONLY) { + pa_log_info("Device opened for playback only, cannot do memory mapping, falling back to UNIX write() mode."); + use_mmap = 0; + } + + if (pa_oss_get_hw_description(dev, hwdesc, sizeof(hwdesc)) >= 0) + pa_log_info("Hardware name is '%s'.", hwdesc); + else + hwdesc[0] = 0; + + pa_log_info("Device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR")); + + if (nfrags >= 2 && frag_size >= 1) + if (pa_oss_set_fragments(fd, nfrags, frag_size) < 0) + goto fail; + + if (pa_oss_auto_format(fd, &ss) < 0) + goto fail; + + if (ioctl(fd, SNDCTL_DSP_GETBLKSIZE, &frag_size) < 0) { + pa_log("SNDCTL_DSP_GETBLKSIZE: %s", pa_cstrerror(errno)); + goto fail; + } + pa_assert(frag_size > 0); + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->fd = fd; + u->mixer_fd = -1; + u->use_getospace = u->use_getispace = 1; + u->use_getodelay = 1; + u->mode = mode; + u->frame_size = pa_frame_size(&ss); + u->device_name = pa_xstrdup(dev); + u->in_nfrags = u->out_nfrags = u->nfrags = nfrags; + u->out_fragment_size = u->in_fragment_size = u->frag_size = frag_size; + u->use_mmap = use_mmap; + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + u->rtpoll_item = NULL; + build_pollfd(u); + + if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) >= 0) { + pa_log_info("Input -- %u fragments of size %u.", info.fragstotal, info.fragsize); + u->in_fragment_size = info.fragsize; + u->in_nfrags = info.fragstotal; + u->use_getispace = 1; + } + + if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) >= 0) { + pa_log_info("Output -- %u fragments of size %u.", info.fragstotal, info.fragsize); + u->out_fragment_size = info.fragsize; + u->out_nfrags = info.fragstotal; + u->use_getospace = 1; + } + + u->in_hwbuf_size = u->in_nfrags * u->in_fragment_size; + u->out_hwbuf_size = u->out_nfrags * u->out_fragment_size; + + if (mode != O_WRONLY) { + char *name_buf = NULL; + + if (use_mmap) { + if ((u->in_mmap = mmap(NULL, u->in_hwbuf_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { + pa_log_warn("mmap(PROT_READ) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno)); + use_mmap = u->use_mmap = 0; + u->in_mmap = NULL; + } else + pa_log_debug("Successfully mmap()ed input buffer."); + } + + if ((name = pa_modargs_get_value(ma, "source_name", NULL))) + namereg_fail = 1; + else { + name = name_buf = pa_sprintf_malloc("oss_input.%s", pa_path_get_filename(dev)); + namereg_fail = 0; + } + + u->source = pa_source_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + pa_xfree(name_buf); + if (!u->source) { + pa_log("Failed to create source object"); + goto fail; + } + + u->source->parent.process_msg = source_process_msg; + u->source->userdata = u; + + pa_source_set_module(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_description(u->source, t = pa_sprintf_malloc( + "OSS PCM on %s%s%s%s%s", + dev, + hwdesc[0] ? " (" : "", + hwdesc[0] ? hwdesc : "", + hwdesc[0] ? ")" : "", + use_mmap ? " via DMA" : "")); + pa_xfree(t); + u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY; + u->source->refresh_volume = TRUE; + + if (use_mmap) + u->in_mmap_memblocks = pa_xnew0(pa_memblock*, u->in_nfrags); + } + + if (mode != O_RDONLY) { + char *name_buf = NULL; + + if (use_mmap) { + if ((u->out_mmap = mmap(NULL, u->out_hwbuf_size, PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { + if (mode == O_RDWR) { + pa_log_debug("mmap() failed for input. Changing to O_WRONLY mode."); + mode = O_WRONLY; + goto go_on; + } else { + pa_log_warn("mmap(PROT_WRITE) failed, reverting to non-mmap mode: %s", pa_cstrerror(errno)); + u->use_mmap = (use_mmap = FALSE); + u->out_mmap = NULL; + } + } else { + pa_log_debug("Successfully mmap()ed output buffer."); + pa_silence_memory(u->out_mmap, u->out_hwbuf_size, &ss); + } + } + + if ((name = pa_modargs_get_value(ma, "sink_name", NULL))) + namereg_fail = 1; + else { + name = name_buf = pa_sprintf_malloc("oss_output.%s", pa_path_get_filename(dev)); + namereg_fail = 0; + } + + u->sink = pa_sink_new(m->core, __FILE__, name, namereg_fail, &ss, &map); + pa_xfree(name_buf); + if (!u->sink) { + pa_log("Failed to create sink object"); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc( + "OSS PCM on %s%s%s%s%s", + dev, + hwdesc[0] ? " (" : "", + hwdesc[0] ? hwdesc : "", + hwdesc[0] ? ")" : "", + use_mmap ? " via DMA" : "")); + pa_xfree(t); + u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY; + u->sink->refresh_volume = TRUE; + + if (use_mmap) + u->out_mmap_memblocks = pa_xnew0(pa_memblock*, u->out_nfrags); + } + + if ((u->mixer_fd = pa_oss_open_mixer_for_device(u->device_name)) >= 0) { + int do_close = 1; + u->mixer_devmask = 0; + + if (ioctl(fd, SOUND_MIXER_READ_DEVMASK, &u->mixer_devmask) < 0) + pa_log_warn("SOUND_MIXER_READ_DEVMASK failed: %s", pa_cstrerror(errno)); + + else { + if (u->sink && (u->mixer_devmask & (SOUND_MASK_VOLUME|SOUND_MASK_PCM))) { + pa_log_debug("Found hardware mixer track for playback."); + u->sink->flags |= PA_SINK_HW_VOLUME_CTRL; + u->sink->get_volume = sink_get_volume; + u->sink->set_volume = sink_set_volume; + do_close = 0; + } + + if (u->source && (u->mixer_devmask & (SOUND_MASK_RECLEV|SOUND_MASK_IGAIN))) { + pa_log_debug("Found hardware mixer track for recording."); + u->source->flags |= PA_SOURCE_HW_VOLUME_CTRL; + u->source->get_volume = source_get_volume; + u->source->set_volume = source_set_volume; + do_close = 0; + } + } + + if (do_close) { + pa_close(u->mixer_fd); + u->mixer_fd = -1; + } + } + +go_on: + + pa_assert(u->source || u->sink); + + pa_memchunk_reset(&u->memchunk); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + /* Read mixer settings */ + if (u->sink && u->sink->get_volume) + sink_get_volume(u->sink); + if (u->source && u->source->get_volume) + source_get_volume(u->source); + + if (u->sink) + pa_sink_put(u->sink); + if (u->source) + pa_source_put(u->source); + + pa_modargs_free(ma); + + return 0; + +fail: + + if (u) + pa__done(m); + else if (fd >= 0) + pa_close(fd); + + 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->sink) + pa_sink_unlink(u->sink); + + if (u->source) + pa_source_unlink(u->source); + + 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->source) + pa_source_unref(u->source); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->out_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->out_nfrags; i++) + if (u->out_mmap_memblocks[i]) + pa_memblock_unref_fixed(u->out_mmap_memblocks[i]); + pa_xfree(u->out_mmap_memblocks); + } + + if (u->in_mmap_memblocks) { + unsigned i; + for (i = 0; i < u->in_nfrags; i++) + if (u->in_mmap_memblocks[i]) + pa_memblock_unref_fixed(u->in_mmap_memblocks[i]); + pa_xfree(u->in_mmap_memblocks); + } + + if (u->in_mmap && u->in_mmap != MAP_FAILED) + munmap(u->in_mmap, u->in_hwbuf_size); + + if (u->out_mmap && u->out_mmap != MAP_FAILED) + munmap(u->out_mmap, u->out_hwbuf_size); + + if (u->fd >= 0) + pa_close(u->fd); + + if (u->mixer_fd >= 0) + pa_close(u->mixer_fd); + + pa_xfree(u->device_name); + + pa_xfree(u); +} diff --git a/src/modules/module-pipe-sink.c b/src/modules/module-pipe-sink.c new file mode 100644 index 00000000..e720c8ad --- /dev/null +++ b/src/modules/module-pipe-sink.c @@ -0,0 +1,333 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <sys/ioctl.h> +#include <poll.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> + +#include "module-pipe-sink-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("UNIX pipe sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "file=<path of the FIFO> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate>" + "channel_map=<channel map>"); + +#define DEFAULT_FILE_NAME "/tmp/music.output" +#define DEFAULT_SINK_NAME "fifo_output" + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink *sink; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + char *filename; + int fd; + + pa_memchunk memchunk; + + pa_rtpoll_item *rtpoll_item; +}; + +static const char* const valid_modargs[] = { + "file", + "rate", + "format", + "channels", + "sink_name", + "channel_map", + 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_GET_LATENCY: { + size_t n = 0; + int l; + +#ifdef TIOCINQ + if (ioctl(u->fd, TIOCINQ, &l) >= 0 && l > 0) + n = (size_t) l; +#endif + + n += u->memchunk.length; + + *((pa_usec_t*) data) = pa_bytes_to_usec(n, &u->sink->sample_spec); + break; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int write_type = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + struct pollfd *pollfd; + int ret; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + /* Render some data and write it to the fifo */ + if (u->sink->thread_info.state == PA_SINK_RUNNING && pollfd->revents) { + ssize_t l; + void *p; + + if (u->memchunk.length <= 0) + pa_sink_render(u->sink, PIPE_BUF, &u->memchunk); + + pa_assert(u->memchunk.length > 0); + + p = pa_memblock_acquire(u->memchunk.memblock); + l = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, &write_type); + pa_memblock_release(u->memchunk.memblock); + + pa_assert(l != 0); + + if (l < 0) { + + if (errno == EINTR) + continue; + else if (errno != EAGAIN) { + pa_log("Failed to write data to FIFO: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + + u->memchunk.index += l; + u->memchunk.length -= l; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + pollfd->revents = 0; + } + } + + /* Hmm, nothing to do. Let's sleep */ + pollfd->events = u->sink->thread_info.state == PA_SINK_RUNNING ? POLLOUT : 0; + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~POLLOUT) { + pa_log("FIFO shutdown."); + goto fail; + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + struct userdata *u; + struct stat st; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + char *t; + struct pollfd *pollfd; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + pa_memchunk_reset(&u->memchunk); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); + + mkfifo(u->filename, 0666); + if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { + pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); + goto fail; + } + + pa_make_fd_cloexec(u->fd); + pa_make_fd_nonblock(u->fd); + + if (fstat(u->fd, &st) < 0) { + pa_log("fstat('%s'): %s", u->filename, pa_cstrerror(errno)); + goto fail; + } + + if (!S_ISFIFO(st.st_mode)) { + pa_log("'%s' is not a FIFO.", u->filename); + goto fail; + } + + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map))) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Unix FIFO sink '%s'", u->filename)); + pa_xfree(t); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->fd; + pollfd->events = pollfd->revents = 0; + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + pa_sink_put(u->sink); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->thread) { + pa_asyncmsgq_send(u->thread_mq.inq, NULL, PA_MESSAGE_SHUTDOWN, NULL, 0, NULL); + pa_thread_free(u->thread); + } + + pa_thread_mq_done(&u->thread_mq); + + if (u->sink) + pa_sink_unref(u->sink); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->filename) { + unlink(u->filename); + pa_xfree(u->filename); + } + + if (u->fd >= 0) + pa_assert_se(pa_close(u->fd) == 0); + + pa_xfree(u); +} diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c new file mode 100644 index 00000000..02935649 --- /dev/null +++ b/src/modules/module-pipe-source.c @@ -0,0 +1,309 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <sys/poll.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/source.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> + +#include "module-pipe-source-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("UNIX pipe source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "source_name=<name for the source> " + "file=<path of the FIFO> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "channel_map=<channel map>"); + +#define DEFAULT_FILE_NAME "/tmp/music.input" +#define DEFAULT_SOURCE_NAME "fifo_input" + +struct userdata { + pa_core *core; + pa_module *module; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + char *filename; + int fd; + + pa_memchunk memchunk; + + pa_rtpoll_item *rtpoll_item; +}; + +static const char* const valid_modargs[] = { + "file", + "rate", + "channels", + "format", + "source_name", + "channel_map", + NULL +}; + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + int read_type = 0; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + struct pollfd *pollfd; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + /* Try to read some data and pass it on to the source driver */ + if (u->source->thread_info.state == PA_SOURCE_RUNNING && pollfd->revents) { + ssize_t l; + void *p; + + if (!u->memchunk.memblock) { + u->memchunk.memblock = pa_memblock_new(u->core->mempool, PIPE_BUF); + u->memchunk.index = u->memchunk.length = 0; + } + + pa_assert(pa_memblock_get_length(u->memchunk.memblock) > u->memchunk.index); + + p = pa_memblock_acquire(u->memchunk.memblock); + l = pa_read(u->fd, (uint8_t*) p + u->memchunk.index, pa_memblock_get_length(u->memchunk.memblock) - u->memchunk.index, &read_type); + pa_memblock_release(u->memchunk.memblock); + + pa_assert(l != 0); /* EOF cannot happen, since we opened the fifo for both reading and writing */ + + if (l < 0) { + + if (errno == EINTR) + continue; + else if (errno != EAGAIN) { + pa_log("Faile to read data from FIFO: %s", pa_cstrerror(errno)); + goto fail; + } + + } else { + + u->memchunk.length = l; + pa_source_post(u->source, &u->memchunk); + u->memchunk.index += l; + + if (u->memchunk.index >= pa_memblock_get_length(u->memchunk.memblock)) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + pollfd->revents = 0; + } + } + + /* Hmm, nothing to do. Let's sleep */ + pollfd->events = u->source->thread_info.state == PA_SOURCE_RUNNING ? POLLIN : 0; + + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + if (pollfd->revents & ~POLLIN) { + pa_log("FIFO shutdown."); + goto fail; + } + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +int pa__init(pa_module*m) { + struct userdata *u; + struct stat st; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma; + char *t; + struct pollfd *pollfd; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments."); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("invalid sample format specification or channel map"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + pa_memchunk_reset(&u->memchunk); + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + u->filename = pa_xstrdup(pa_modargs_get_value(ma, "file", DEFAULT_FILE_NAME)); + + mkfifo(u->filename, 0666); + if ((u->fd = open(u->filename, O_RDWR|O_NOCTTY)) < 0) { + pa_log("open('%s'): %s", u->filename, pa_cstrerror(errno)); + goto fail; + } + + pa_make_fd_cloexec(u->fd); + pa_make_fd_nonblock(u->fd); + + if (fstat(u->fd, &st) < 0) { + pa_log("fstat('%s'): %s",u->filename, pa_cstrerror(errno)); + goto fail; + } + + if (!S_ISFIFO(st.st_mode)) { + pa_log("'%s' is not a FIFO.", u->filename); + goto fail; + } + + if (!(u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map))) { + pa_log("Failed to create source."); + goto fail; + } + + u->source->userdata = u; + u->source->flags = 0; + + pa_source_set_module(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_description(u->source, t = pa_sprintf_malloc("Unix FIFO source '%s'", u->filename)); + pa_xfree(t); + + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->fd = u->fd; + pollfd->events = pollfd->revents = 0; + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + pa_source_put(u->source); + + 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->source) + pa_source_unlink(u->source); + + 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->source) + pa_source_unref(u->source); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->filename) { + unlink(u->filename); + pa_xfree(u->filename); + } + + if (u->fd >= 0) + pa_assert_se(pa_close(u->fd) == 0); + + pa_xfree(u); +} diff --git a/src/modules/module-protocol-stub.c b/src/modules/module-protocol-stub.c new file mode 100644 index 00000000..a9bd850b --- /dev/null +++ b/src/modules/module-protocol-stub.c @@ -0,0 +1,376 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <limits.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/winsock.h> +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/socket-server.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/native-common.h> +#include <pulsecore/creds.h> + +#ifdef USE_TCP_SOCKETS +#define SOCKET_DESCRIPTION "(TCP sockets)" +#define SOCKET_USAGE "port=<TCP port number> listen=<address to listen on>" +#else +#define SOCKET_DESCRIPTION "(UNIX sockets)" +#define SOCKET_USAGE "socket=<path to UNIX socket>" +#endif + +#if defined(USE_PROTOCOL_SIMPLE) + #include <pulsecore/protocol-simple.h> + #define protocol_new pa_protocol_simple_new + #define protocol_free pa_protocol_simple_free + #define TCPWRAP_SERVICE "pulseaudio-simple" + #define IPV4_PORT 4711 + #define UNIX_SOCKET "simple" + #define MODULE_ARGUMENTS "rate", "format", "channels", "sink", "source", "playback", "record", + #if defined(USE_TCP_SOCKETS) + #include "module-simple-protocol-tcp-symdef.h" + #else + #include "module-simple-protocol-unix-symdef.h" + #endif +PA_MODULE_DESCRIPTION("Simple protocol "SOCKET_DESCRIPTION); + PA_MODULE_USAGE("rate=<sample rate> " + "format=<sample format> " + "channels=<number of channels> " + "sink=<sink to connect to> " + "source=<source to connect to> " + "playback=<enable playback?> " + "record=<enable record?> " + SOCKET_USAGE); +#elif defined(USE_PROTOCOL_CLI) + #include <pulsecore/protocol-cli.h> + #define protocol_new pa_protocol_cli_new + #define protocol_free pa_protocol_cli_free + #define TCPWRAP_SERVICE "pulseaudio-cli" + #define IPV4_PORT 4712 + #define UNIX_SOCKET "cli" + #define MODULE_ARGUMENTS + #ifdef USE_TCP_SOCKETS + #include "module-cli-protocol-tcp-symdef.h" + #else + #include "module-cli-protocol-unix-symdef.h" + #endif + PA_MODULE_DESCRIPTION("Command line interface protocol "SOCKET_DESCRIPTION); + PA_MODULE_USAGE(SOCKET_USAGE); +#elif defined(USE_PROTOCOL_HTTP) + #include <pulsecore/protocol-http.h> + #define protocol_new pa_protocol_http_new + #define protocol_free pa_protocol_http_free + #define TCPWRAP_SERVICE "pulseaudio-http" + #define IPV4_PORT 4714 + #define UNIX_SOCKET "http" + #define MODULE_ARGUMENTS + #ifdef USE_TCP_SOCKETS + #include "module-http-protocol-tcp-symdef.h" + #else + #include "module-http-protocol-unix-symdef.h" + #endif + PA_MODULE_DESCRIPTION("HTTP "SOCKET_DESCRIPTION); + PA_MODULE_USAGE(SOCKET_USAGE); +#elif defined(USE_PROTOCOL_NATIVE) + #include <pulsecore/protocol-native.h> + #define protocol_new pa_protocol_native_new + #define protocol_free pa_protocol_native_free + #define TCPWRAP_SERVICE "pulseaudio-native" + #define IPV4_PORT PA_NATIVE_DEFAULT_PORT + #define UNIX_SOCKET PA_NATIVE_DEFAULT_UNIX_SOCKET + #define MODULE_ARGUMENTS_COMMON "cookie", "auth-anonymous", + #ifdef USE_TCP_SOCKETS + #include "module-native-protocol-tcp-symdef.h" + #else + #include "module-native-protocol-unix-symdef.h" + #endif + + #if defined(HAVE_CREDS) && !defined(USE_TCP_SOCKETS) + #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON "auth-group", "auth-group-enable", + #define AUTH_USAGE "auth-group=<system group to allow access> auth-group-enable=<enable auth by UNIX group?> " + #elif defined(USE_TCP_SOCKETS) + #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON "auth-ip-acl", + #define AUTH_USAGE "auth-ip-acl=<IP address ACL to allow access> " + #else + #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON + #define AUTH_USAGE + #endif + + PA_MODULE_DESCRIPTION("Native protocol "SOCKET_DESCRIPTION); + PA_MODULE_USAGE("auth-anonymous=<don't check for cookies?> " + "cookie=<path to cookie file> " + AUTH_USAGE + SOCKET_USAGE); +#elif defined(USE_PROTOCOL_ESOUND) + #include <pulsecore/protocol-esound.h> + #include <pulsecore/esound.h> + #define protocol_new pa_protocol_esound_new + #define protocol_free pa_protocol_esound_free + #define TCPWRAP_SERVICE "esound" + #define IPV4_PORT ESD_DEFAULT_PORT + #define MODULE_ARGUMENTS_COMMON "sink", "source", "auth-anonymous", "cookie", + #ifdef USE_TCP_SOCKETS + #include "module-esound-protocol-tcp-symdef.h" + #else + #include "module-esound-protocol-unix-symdef.h" + #endif + + #if defined(USE_TCP_SOCKETS) + #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON "auth-ip-acl", + #define AUTH_USAGE "auth-ip-acl=<IP address ACL to allow access> " + #else + #define MODULE_ARGUMENTS MODULE_ARGUMENTS_COMMON + #define AUTH_USAGE + #endif + + PA_MODULE_DESCRIPTION("ESOUND protocol "SOCKET_DESCRIPTION); + PA_MODULE_USAGE("sink=<sink to connect to> " + "source=<source to connect to> " + "auth-anonymous=<don't verify cookies?> " + "cookie=<path to cookie file> " + AUTH_USAGE + SOCKET_USAGE); +#else + #error "Broken build system" +#endif + +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_VERSION(PACKAGE_VERSION); + +static const char* const valid_modargs[] = { + MODULE_ARGUMENTS +#if defined(USE_TCP_SOCKETS) + "port", + "listen", +#else + "socket", +#endif + NULL +}; + +struct userdata { +#if defined(USE_TCP_SOCKETS) + void *protocol_ipv4; + void *protocol_ipv6; +#else + void *protocol_unix; + char *socket_path; +#endif +}; + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + int ret = -1; + struct userdata *u = NULL; + +#if defined(USE_TCP_SOCKETS) + pa_socket_server *s_ipv4 = NULL, *s_ipv6 = NULL; + uint32_t port = IPV4_PORT; + const char *listen_on; +#else + pa_socket_server *s; + int r; + char tmp[PATH_MAX]; + +#if defined(USE_PROTOCOL_ESOUND) +#if defined(USE_PERUSER_ESOUND_SOCKET) + char esdsocketpath[PATH_MAX]; +#else + const char esdsocketpath[] = "/tmp/.esd/socket"; +#endif +#endif +#endif + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto finish; + } + + u = pa_xnew0(struct userdata, 1); + +#if defined(USE_TCP_SOCKETS) + if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port < 1 || port > 0xFFFF) { + pa_log("port= expects a numerical argument between 1 and 65535."); + goto fail; + } + + listen_on = pa_modargs_get_value(ma, "listen", NULL); + + if (listen_on) { + s_ipv6 = pa_socket_server_new_ipv6_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE); + s_ipv4 = pa_socket_server_new_ipv4_string(m->core->mainloop, listen_on, port, TCPWRAP_SERVICE); + } else { + s_ipv6 = pa_socket_server_new_ipv6_any(m->core->mainloop, port, TCPWRAP_SERVICE); + s_ipv4 = pa_socket_server_new_ipv4_any(m->core->mainloop, port, TCPWRAP_SERVICE); + } + + if (!s_ipv4 && !s_ipv6) + goto fail; + + if (s_ipv4) + if (!(u->protocol_ipv4 = protocol_new(m->core, s_ipv4, m, ma))) + pa_socket_server_unref(s_ipv4); + + if (s_ipv6) + if (!(u->protocol_ipv6 = protocol_new(m->core, s_ipv6, m, ma))) + pa_socket_server_unref(s_ipv6); + + if (!u->protocol_ipv4 && !u->protocol_ipv6) + goto fail; + +#else + +#if defined(USE_PROTOCOL_ESOUND) + +#if defined(USE_PERUSER_ESOUND_SOCKET) + snprintf(esdsocketpath, sizeof(esdsocketpath), "/tmp/.esd-%lu/socket", (unsigned long) getuid()); +#endif + pa_runtime_path(pa_modargs_get_value(ma, "socket", esdsocketpath), tmp, sizeof(tmp)); + u->socket_path = pa_xstrdup(tmp); + + /* This socket doesn't reside in our own runtime dir but in + * /tmp/.esd/, hence we have to create the dir first */ + + if (pa_make_secure_parent_dir(u->socket_path, m->core->is_system_instance ? 0755 : 0700, (uid_t)-1, (gid_t)-1) < 0) { + pa_log("Failed to create socket directory '%s': %s\n", u->socket_path, pa_cstrerror(errno)); + goto fail; + } + +#else + pa_runtime_path(pa_modargs_get_value(ma, "socket", UNIX_SOCKET), tmp, sizeof(tmp)); + u->socket_path = pa_xstrdup(tmp); +#endif + + if ((r = pa_unix_socket_remove_stale(tmp)) < 0) { + pa_log("Failed to remove stale UNIX socket '%s': %s", tmp, pa_cstrerror(errno)); + goto fail; + } + + if (r) + pa_log("Removed stale UNIX socket '%s'.", tmp); + + if (!(s = pa_socket_server_new_unix(m->core->mainloop, tmp))) + goto fail; + + if (!(u->protocol_unix = protocol_new(m->core, s, m, ma))) + goto fail; + +#endif + + m->userdata = u; + + ret = 0; + +finish: + if (ma) + pa_modargs_free(ma); + + return ret; + +fail: + if (u) { +#if defined(USE_TCP_SOCKETS) + if (u->protocol_ipv4) + protocol_free(u->protocol_ipv4); + if (u->protocol_ipv6) + protocol_free(u->protocol_ipv6); +#else + if (u->protocol_unix) + protocol_free(u->protocol_unix); + + if (u->socket_path) + pa_xfree(u->socket_path); +#endif + + pa_xfree(u); + } else { +#if defined(USE_TCP_SOCKETS) + if (s_ipv4) + pa_socket_server_unref(s_ipv4); + if (s_ipv6) + pa_socket_server_unref(s_ipv6); +#else + if (s) + pa_socket_server_unref(s); +#endif + } + + goto finish; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + u = m->userdata; + +#if defined(USE_TCP_SOCKETS) + if (u->protocol_ipv4) + protocol_free(u->protocol_ipv4); + if (u->protocol_ipv6) + protocol_free(u->protocol_ipv6); +#else + if (u->protocol_unix) + protocol_free(u->protocol_unix); + +#if defined(USE_PROTOCOL_ESOUND) + if (u->socket_path) { + char *p = pa_parent_dir(u->socket_path); + rmdir(p); + pa_xfree(p); + } +#endif + + pa_xfree(u->socket_path); +#endif + + pa_xfree(u); +} diff --git a/src/modules/module-remap-sink.c b/src/modules/module-remap-sink.c new file mode 100644 index 00000000..39a9245d --- /dev/null +++ b/src/modules/module-remap-sink.c @@ -0,0 +1,335 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> + +#include "module-remap-sink-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Virtual channel remapping sink"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "master=<name of sink to remap> " + "master_channel_map=<channel map> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "channel_map=<channel map>"); + +struct userdata { + pa_core *core; + pa_module *module; + + pa_sink *sink, *master; + pa_sink_input *sink_input; + + pa_memchunk memchunk; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "master", + "master_channel_map", + "rate", + "format", + "channels", + "channel_map", + NULL +}; + +/* Called from I/O thread context */ +static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK(o)->userdata; + + switch (code) { + + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t usec = 0; + + if (PA_MSGOBJECT(u->master)->process_msg(PA_MSGOBJECT(u->master), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + usec = 0; + + *((pa_usec_t*) data) = usec + pa_bytes_to_usec(u->memchunk.length, &u->sink->sample_spec); + return 0; + } + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +/* Called from main context */ +static int sink_set_state(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + + pa_sink_assert_ref(s); + pa_assert_se(u = s->userdata); + + if (PA_SINK_LINKED(state) && u->sink_input && PA_SINK_INPUT_LINKED(pa_sink_input_get_state(u->sink_input))) + pa_sink_input_cork(u->sink_input, state == PA_SINK_SUSPENDED); + + return 0; +} + +/* Called from I/O thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SINK_INPUT(o)->userdata; + + switch (code) { + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: + *((pa_usec_t*) data) = pa_bytes_to_usec(u->memchunk.length, &u->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + return pa_sink_input_process_msg(o, code, data, offset, chunk); +} + +/* Called from I/O thread context */ +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + if (!u->memchunk.memblock) + pa_sink_render(u->sink, length, &u->memchunk); + + pa_assert(u->memchunk.memblock); + *chunk = u->memchunk; + pa_memblock_ref(chunk->memblock); + return 0; +} + +/* Called from I/O thread context */ +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + pa_assert(length > 0); + + if (u->memchunk.memblock) { + + if (length < u->memchunk.length) { + u->memchunk.index += length; + u->memchunk.length -= length; + return; + } + + pa_memblock_unref(u->memchunk.memblock); + length -= u->memchunk.length; + pa_memchunk_reset(&u->memchunk); + } + + if (length > 0) + pa_sink_skip(u->sink, length); +} + +/* Called from I/O thread context */ +static void sink_input_detach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_detach_within_thread(u->sink); +} + +/* Called from I/O thread context */ +static void sink_input_attach_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_set_asyncmsgq(u->sink, i->sink->asyncmsgq); + pa_sink_set_rtpoll(u->sink, i->sink->rtpoll); + + pa_sink_attach_within_thread(u->sink); +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_sink_input_assert_ref(i); + pa_assert_se(u = i->userdata); + + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + u->sink = NULL; + + pa_module_unload_request(u->module); +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_sample_spec ss; + pa_channel_map sink_map, stream_map; + pa_modargs *ma; + char *t; + pa_sink *master; + pa_sink_input_new_data data; + char *default_sink_name = NULL; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (!(master = pa_namereg_get(m->core, pa_modargs_get_value(ma, "master", NULL), PA_NAMEREG_SINK, 1))) { + pa_log("Master sink not found"); + goto fail; + } + + ss = master->sample_spec; + sink_map = master->channel_map; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &sink_map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("Invalid sample format specification or channel map"); + goto fail; + } + + stream_map = sink_map; + if (pa_modargs_get_channel_map(ma, "master_channel_map", &stream_map) < 0) { + pa_log("Invalid master hannel map"); + goto fail; + } + + if (stream_map.channels != ss.channels) { + pa_log("Number of channels doesn't match"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + m->userdata = u; + u->master = master; + pa_memchunk_reset(&u->memchunk); + + default_sink_name = pa_sprintf_malloc("%s.remapped", master->name); + + /* Create sink */ + if (!(u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", default_sink_name), 0, &ss, &sink_map))) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->set_state = sink_set_state; + u->sink->userdata = u; + u->sink->flags = PA_SINK_LATENCY; + + pa_sink_set_module(u->sink, m); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Remapped %s", master->description)); + pa_xfree(t); + pa_sink_set_asyncmsgq(u->sink, master->asyncmsgq); + pa_sink_set_rtpoll(u->sink, master->rtpoll); + + /* Create sink input */ + pa_sink_input_new_data_init(&data); + data.sink = u->master; + data.driver = __FILE__; + data.name = "Remapped Stream"; + pa_sink_input_new_data_set_sample_spec(&data, &ss); + pa_sink_input_new_data_set_channel_map(&data, &stream_map); + data.module = m; + + if (!(u->sink_input = pa_sink_input_new(m->core, &data, PA_SINK_INPUT_DONT_MOVE))) + goto fail; + + u->sink_input->parent.process_msg = sink_input_process_msg; + u->sink_input->peek = sink_input_peek_cb; + u->sink_input->drop = sink_input_drop_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->attach = sink_input_attach_cb; + u->sink_input->detach = sink_input_detach_cb; + u->sink_input->userdata = u; + + pa_sink_put(u->sink); + pa_sink_input_put(u->sink_input); + + pa_modargs_free(ma); + pa_xfree(default_sink_name); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + pa_xfree(default_sink_name); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sink_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } + + if (u->sink) { + pa_sink_unlink(u->sink); + pa_sink_unref(u->sink); + } + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + pa_xfree(u); +} diff --git a/src/modules/module-rescue-streams.c b/src/modules/module-rescue-streams.c new file mode 100644 index 00000000..12957c9d --- /dev/null +++ b/src/modules/module-rescue-streams.c @@ -0,0 +1,164 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulse/xmalloc.h> + +#include <pulsecore/core.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> + +#include "module-rescue-streams-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("When a sink/source is removed, try to move their streams to the default sink/source"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static const char* const valid_modargs[] = { + NULL, +}; + +struct userdata { + pa_hook_slot *sink_slot, *source_slot; +}; + +static pa_hook_result_t sink_hook_callback(pa_core *c, pa_sink *sink, void* userdata) { + pa_sink_input *i; + pa_sink *target; + + pa_assert(c); + pa_assert(sink); + + if (!pa_idxset_size(sink->inputs)) { + pa_log_debug("No sink inputs to move away."); + return PA_HOOK_OK; + } + + if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SINK, 0)) || target == sink) { + uint32_t idx; + + for (target = pa_idxset_first(c->sinks, &idx); target; target = pa_idxset_next(c->sinks, &idx)) + if (target != sink) + break; + + if (!target) { + pa_log_info("No evacuation sink found."); + return PA_HOOK_OK; + } + } + + while ((i = pa_idxset_first(sink->inputs, NULL))) { + if (pa_sink_input_move_to(i, target, 1) < 0) { + pa_log_warn("Failed to move sink input %u \"%s\" to %s.", i->index, i->name, target->name); + return PA_HOOK_OK; + } + + pa_log_info("Sucessfully moved sink input %u \"%s\" to %s.", i->index, i->name, target->name); + } + + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_hook_callback(pa_core *c, pa_source *source, void* userdata) { + pa_source_output *o; + pa_source *target; + + pa_assert(c); + pa_assert(source); + + if (!pa_idxset_size(source->outputs)) { + pa_log_debug("No source outputs to move away."); + return PA_HOOK_OK; + } + + if (!(target = pa_namereg_get(c, NULL, PA_NAMEREG_SOURCE, 0)) || target == source) { + uint32_t idx; + + for (target = pa_idxset_first(c->sources, &idx); target; target = pa_idxset_next(c->sources, &idx)) + if (target != source && !target->monitor_of == !source->monitor_of) + break; + + if (!target) { + pa_log_info("No evacuation source found."); + return PA_HOOK_OK; + } + } + + pa_assert(target != source); + + while ((o = pa_idxset_first(source->outputs, NULL))) { + if (pa_source_output_move_to(o, target) < 0) { + pa_log_warn("Failed to move source output %u \"%s\" to %s.", o->index, o->name, target->name); + return PA_HOOK_OK; + } + + pa_log_info("Sucessfully moved source output %u \"%s\" to %s.", o->index, o->name, target->name); + } + + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + return -1; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->sink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) sink_hook_callback, NULL); + u->source_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) source_hook_callback, NULL); + + pa_modargs_free(ma); + return 0; +} + +void pa__done(pa_module*m) { + struct userdata *u; + + pa_assert(m); + + if (!m->userdata) + return; + + u = m->userdata; + if (u->sink_slot) + pa_hook_slot_free(u->sink_slot); + if (u->source_slot) + pa_hook_slot_free(u->source_slot); + + pa_xfree(u); +} diff --git a/src/modules/module-sine.c b/src/modules/module-sine.c new file mode 100644 index 00000000..41d9a51c --- /dev/null +++ b/src/modules/module-sine.c @@ -0,0 +1,206 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <math.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> + +#include "module-sine-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Sine wave generator"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("sink=<sink to connect to> frequency=<frequency in Hz>"); + +struct userdata { + pa_core *core; + pa_module *module; + pa_sink_input *sink_input; + pa_memblock *memblock; + size_t peek_index; +}; + +static const char* const valid_modargs[] = { + "sink", + "frequency", + NULL, +}; + +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + struct userdata *u; + + pa_assert(i); + u = i->userdata; + pa_assert(u); + pa_assert(chunk); + + chunk->memblock = pa_memblock_ref(u->memblock); + chunk->index = u->peek_index; + chunk->length = pa_memblock_get_length(u->memblock) - u->peek_index; + + return 0; +} + +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + struct userdata *u; + size_t l; + + pa_assert(i); + u = i->userdata; + pa_assert(u); + pa_assert(length > 0); + + u->peek_index += length; + + l = pa_memblock_get_length(u->memblock); + + while (u->peek_index >= l) + u->peek_index -= l; +} + +static void sink_input_kill_cb(pa_sink_input *i) { + struct userdata *u; + + pa_assert(i); + u = i->userdata; + pa_assert(u); + + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + pa_module_unload_request(u->module); +} + +static void calc_sine(float *f, size_t l, float freq) { + size_t i; + + l /= sizeof(float); + + for (i = 0; i < l; i++) + f[i] = (float) sin((double) i/l*M_PI*2*freq)/2; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + pa_sink *sink; + pa_sample_spec ss; + uint32_t frequency; + char t[256]; + void *p; + pa_sink_input_new_data data; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xnew0(struct userdata, 1); + u->core = m->core; + u->module = m; + u->sink_input = NULL; + u->memblock = NULL; + u->peek_index = 0; + + if (!(sink = pa_namereg_get(m->core, pa_modargs_get_value(ma, "sink", NULL), PA_NAMEREG_SINK, 1))) { + pa_log("No such sink."); + goto fail; + } + + ss.format = PA_SAMPLE_FLOAT32; + ss.rate = sink->sample_spec.rate; + ss.channels = 1; + + frequency = 440; + if (pa_modargs_get_value_u32(ma, "frequency", &frequency) < 0 || frequency < 1 || frequency > ss.rate/2) { + pa_log("Invalid frequency specification"); + goto fail; + } + + u->memblock = pa_memblock_new(m->core->mempool, pa_bytes_per_second(&ss)); + p = pa_memblock_acquire(u->memblock); + calc_sine(p, pa_memblock_get_length(u->memblock), frequency); + pa_memblock_release(u->memblock); + + pa_snprintf(t, sizeof(t), "Sine Generator at %u Hz", frequency); + + pa_sink_input_new_data_init(&data); + data.sink = sink; + data.driver = __FILE__; + data.name = t; + pa_sink_input_new_data_set_sample_spec(&data, &ss); + data.module = m; + + if (!(u->sink_input = pa_sink_input_new(m->core, &data, 0))) + goto fail; + + u->sink_input->peek = sink_input_peek_cb; + u->sink_input->drop = sink_input_drop_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->userdata = u; + + pa_sink_input_put(u->sink_input); + + 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_input) { + pa_sink_input_unlink(u->sink_input); + pa_sink_input_unref(u->sink_input); + } + + if (u->memblock) + pa_memblock_unref(u->memblock); + + pa_xfree(u); +} diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c new file mode 100644 index 00000000..4a5c88e4 --- /dev/null +++ b/src/modules/module-solaris.c @@ -0,0 +1,766 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdio.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <signal.h> +#include <stropts.h> +#include <sys/conf.h> +#include <sys/audio.h> + +#include <pulse/error.h> +#include <pulse/mainloop-signal.h> +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/iochannel.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/module.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/thread.h> + +#include "module-solaris-symdef.h" + +PA_MODULE_AUTHOR("Pierre Ossman") +PA_MODULE_DESCRIPTION("Solaris Sink/Source") +PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "source_name=<name for the source> " + "device=<OSS device> record=<enable source?> " + "playback=<enable sink?> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "buffer_size=<record buffer size> " + "channel_map=<channel map>") + +struct userdata { + pa_core *core; + pa_sink *sink; + pa_source *source; + + pa_thread *thread; + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + + pa_signal_event *sig; + + pa_memchunk memchunk; + + unsigned int page_size; + + uint32_t frame_size; + uint32_t buffer_size; + unsigned int written_bytes, read_bytes; + + int fd; + pa_rtpoll_item *rtpoll_item; + pa_module *module; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "source_name", + "device", + "record", + "playback", + "buffer_size", + "format", + "rate", + "channels", + "channel_map", + NULL +}; + +#define DEFAULT_SINK_NAME "solaris_output" +#define DEFAULT_SOURCE_NAME "solaris_input" +#define DEFAULT_DEVICE "/dev/audio" + +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; + int err; + audio_info_t info; + + switch (code) { + case PA_SINK_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->fd >= 0) { + + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); + + r += pa_bytes_to_usec(u->written_bytes, &PA_SINK(o)->sample_spec); + r -= pa_bytes_to_usec(info.play.samples * u->frame_size, &PA_SINK(o)->sample_spec); + + if (u->memchunk.memblock) + r += pa_bytes_to_usec(u->memchunk.length, &PA_SINK(o)->sample_spec); + } + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SINK_MESSAGE_SET_VOLUME: + if (u->fd >= 0) { + AUDIO_INITINFO(&info); + + info.play.gain = pa_cvolume_avg((pa_cvolume*)data) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + assert(info.play.gain <= AUDIO_MAX_GAIN); + + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { + if (errno == EINVAL) + pa_log("AUDIO_SETINFO: Unsupported volume."); + else + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + } else { + return 0; + } + } + break; + + case PA_SINK_MESSAGE_GET_VOLUME: + if (u->fd >= 0) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + assert(err >= 0); + + pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels, + info.play.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + + return 0; + } + break; + + case PA_SINK_MESSAGE_SET_MUTE: + if (u->fd >= 0) { + AUDIO_INITINFO(&info); + + info.output_muted = !!PA_PTR_TO_UINT(data); + + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + else + return 0; + } + break; + + case PA_SINK_MESSAGE_GET_MUTE: + if (u->fd >= 0) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); + + *(int*)data = !!info.output_muted; + + return 0; + } + break; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + int err; + audio_info_t info; + + switch (code) { + case PA_SOURCE_MESSAGE_GET_LATENCY: { + pa_usec_t r = 0; + + if (u->fd) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); + + r += pa_bytes_to_usec(info.record.samples * u->frame_size, &PA_SOURCE(o)->sample_spec); + r -= pa_bytes_to_usec(u->read_bytes, &PA_SOURCE(o)->sample_spec); + } + + *((pa_usec_t*) data) = r; + + return 0; + } + + case PA_SOURCE_MESSAGE_SET_VOLUME: + if (u->fd >= 0) { + AUDIO_INITINFO(&info); + + info.record.gain = pa_cvolume_avg((pa_cvolume*) data) * AUDIO_MAX_GAIN / PA_VOLUME_NORM; + assert(info.record.gain <= AUDIO_MAX_GAIN); + + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) { + if (errno == EINVAL) + pa_log("AUDIO_SETINFO: Unsupported volume."); + else + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + } else { + return 0; + } + } + break; + + case PA_SOURCE_MESSAGE_GET_VOLUME: + if (u->fd >= 0) { + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); + + pa_cvolume_set((pa_cvolume*) data, ((pa_cvolume*) data)->channels, + info.record.gain * PA_VOLUME_NORM / AUDIO_MAX_GAIN); + + return 0; + } + break; + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static void clear_underflow(struct userdata *u) +{ + audio_info_t info; + + AUDIO_INITINFO(&info); + + info.play.error = 0; + + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); +} + +static void clear_overflow(struct userdata *u) +{ + audio_info_t info; + + AUDIO_INITINFO(&info); + + info.record.error = 0; + + if (ioctl(u->fd, AUDIO_SETINFO, &info) < 0) + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); +} + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + unsigned short revents = 0; + int ret; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + if (u->core->high_priority) + pa_make_realtime(); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + /* Render some data and write it to the dsp */ + + if (u->sink && PA_SINK_OPENED(u->sink->thread_info.state)) { + audio_info_t info; + int err; + size_t len; + + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); + + /* + * Since we cannot modify the size of the output buffer we fake it + * by not filling it more than u->buffer_size. + */ + len = u->buffer_size; + len -= u->written_bytes - (info.play.samples * u->frame_size); + + /* The sample counter can sometimes go backwards :( */ + if (len > u->buffer_size) + len = 0; + + if (info.play.error) { + pa_log_debug("Solaris buffer underflow!"); + clear_underflow(u); + } + + len -= len % u->frame_size; + + while (len) { + void *p; + ssize_t r; + + if (!u->memchunk.length) + pa_sink_render(u->sink, len, &u->memchunk); + + pa_assert(u->memchunk.length); + + p = pa_memblock_acquire(u->memchunk.memblock); + r = pa_write(u->fd, (uint8_t*) p + u->memchunk.index, u->memchunk.length, NULL); + pa_memblock_release(u->memchunk.memblock); + + if (r < 0) { + if (errno == EINTR) + continue; + else if (errno != EAGAIN) { + pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno)); + goto fail; + } + } else { + pa_assert(r % u->frame_size == 0); + + u->memchunk.index += r; + u->memchunk.length -= r; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + len -= r; + u->written_bytes += r; + } + } + } + + /* Try to read some data and pass it on to the source driver */ + + if (u->source && PA_SOURCE_OPENED(u->source->thread_info.state) && ((revents & POLLIN))) { + pa_memchunk memchunk; + int err; + size_t l; + void *p; + ssize_t r; + audio_info_t info; + + err = ioctl(u->fd, AUDIO_GETINFO, &info); + pa_assert(err >= 0); + + if (info.record.error) { + pa_log_debug("Solaris buffer overflow!"); + clear_overflow(u); + } + + err = ioctl(u->fd, I_NREAD, &l); + pa_assert(err >= 0); + + if (l > 0) { + /* This is to make sure it fits in the memory pool. Also, a page + should be the most efficient transfer size. */ + if (l > u->page_size) + l = u->page_size; + + memchunk.memblock = pa_memblock_new(u->core->mempool, l); + pa_assert(memchunk.memblock); + + p = pa_memblock_acquire(memchunk.memblock); + r = pa_read(u->fd, p, l, NULL); + pa_memblock_release(memchunk.memblock); + + if (r < 0) { + pa_memblock_unref(memchunk.memblock); + if (errno != EAGAIN) { + pa_log("Failed to read data from DSP: %s", pa_cstrerror(errno)); + goto fail; + } + } else { + memchunk.index = 0; + memchunk.length = r; + + pa_source_post(u->source, &memchunk); + pa_memblock_unref(memchunk.memblock); + + u->read_bytes += r; + + revents &= ~POLLIN; + } + } + } + + if (u->fd >= 0) { + struct pollfd *pollfd; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + pollfd->events = + ((u->source && PA_SOURCE_OPENED(u->source->thread_info.state)) ? POLLIN : 0); + } + + /* Hmm, nothing to do. Let's sleep */ + if ((ret = pa_rtpoll_run(u->rtpoll, 1)) < 0) + goto fail; + + if (ret == 0) + goto finish; + + if (u->fd >= 0) { + struct pollfd *pollfd; + + pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); + + if (pollfd->revents & ~(POLLOUT|POLLIN)) { + pa_log("DSP shutdown."); + goto fail; + } + + revents = pollfd->revents; + } else + revents = 0; + } + +fail: + /* We have to continue processing messages until we receive the + * SHUTDOWN message */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +static void sig_callback(pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata) { + struct userdata *u = userdata; + + assert(u); + + if (u->sink) { + pa_sink_get_volume(u->sink); + pa_sink_get_mute(u->sink); + } + + if (u->source) + pa_source_get_volume(u->source); +} + +static int pa_solaris_auto_format(int fd, int mode, pa_sample_spec *ss) { + audio_info_t info; + + AUDIO_INITINFO(&info); + + if (mode != O_RDONLY) { + info.play.sample_rate = ss->rate; + info.play.channels = ss->channels; + switch (ss->format) { + case PA_SAMPLE_U8: + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_LINEAR; + break; + case PA_SAMPLE_ALAW: + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_ALAW; + break; + case PA_SAMPLE_ULAW: + info.play.precision = 8; + info.play.encoding = AUDIO_ENCODING_ULAW; + break; + case PA_SAMPLE_S16NE: + info.play.precision = 16; + info.play.encoding = AUDIO_ENCODING_LINEAR; + break; + default: + return -1; + } + } + + if (mode != O_WRONLY) { + info.record.sample_rate = ss->rate; + info.record.channels = ss->channels; + switch (ss->format) { + case PA_SAMPLE_U8: + info.record.precision = 8; + info.record.encoding = AUDIO_ENCODING_LINEAR; + break; + case PA_SAMPLE_ALAW: + info.record.precision = 8; + info.record.encoding = AUDIO_ENCODING_ALAW; + break; + case PA_SAMPLE_ULAW: + info.record.precision = 8; + info.record.encoding = AUDIO_ENCODING_ULAW; + break; + case PA_SAMPLE_S16NE: + info.record.precision = 16; + info.record.encoding = AUDIO_ENCODING_LINEAR; + break; + default: + return -1; + } + } + + if (ioctl(fd, AUDIO_SETINFO, &info) < 0) { + if (errno == EINVAL) + pa_log("AUDIO_SETINFO: Unsupported sample format."); + else + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +static int pa_solaris_set_buffer(int fd, int buffer_size) { + audio_info_t info; + + AUDIO_INITINFO(&info); + + info.play.buffer_size = buffer_size; + info.record.buffer_size = buffer_size; + + if (ioctl(fd, AUDIO_SETINFO, &info) < 0) { + if (errno == EINVAL) + pa_log("AUDIO_SETINFO: Unsupported buffer size."); + else + pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +int pa__init(pa_module *m) { + struct userdata *u = NULL; + const char *p; + int fd = -1; + int buffer_size; + int mode; + int record = 1, playback = 1; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + char *t; + struct pollfd *pollfd; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) { + pa_log("record= and playback= expect numeric argument."); + goto fail; + } + + if (!playback && !record) { + pa_log("neither playback nor record enabled for device."); + goto fail; + } + + mode = (playback&&record) ? O_RDWR : (playback ? O_WRONLY : (record ? O_RDONLY : 0)); + + buffer_size = 16384; + if (pa_modargs_get_value_s32(ma, "buffer_size", &buffer_size) < 0) { + pa_log("failed to parse buffer size argument"); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("failed to parse sample specification"); + goto fail; + } + + if ((fd = open(p = pa_modargs_get_value(ma, "device", DEFAULT_DEVICE), mode | O_NONBLOCK)) < 0) + goto fail; + + pa_log_info("device opened in %s mode.", mode == O_WRONLY ? "O_WRONLY" : (mode == O_RDONLY ? "O_RDONLY" : "O_RDWR")); + + if (pa_solaris_auto_format(fd, mode, &ss) < 0) + goto fail; + + if (pa_solaris_set_buffer(fd, buffer_size) < 0) + goto fail; + + u = pa_xmalloc(sizeof(struct userdata)); + u->core = m->core; + + u->fd = fd; + + pa_memchunk_reset(&u->memchunk); + + /* We use this to get a reasonable chunk size */ + u->page_size = PA_PAGE_SIZE; + + u->frame_size = pa_frame_size(&ss); + u->buffer_size = buffer_size; + + u->written_bytes = 0; + u->read_bytes = 0; + + u->module = m; + m->userdata = u; + + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + pa_rtpoll_set_timer_periodic(u->rtpoll, pa_bytes_to_usec(u->buffer_size / 10, &ss)); + + 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 = fd; + pollfd->events = 0; + pollfd->revents = 0; + + if (mode != O_WRONLY) { + u->source = pa_source_new(m->core, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map); + pa_assert(u->source); + + u->source->userdata = u; + u->source->parent.process_msg = source_process_msg; + + pa_source_set_module(u->source, m); + pa_source_set_description(u->source, t = pa_sprintf_malloc("Solaris PCM on '%s'", p)); + pa_xfree(t); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + + u->source->flags = PA_SOURCE_HARDWARE|PA_SOURCE_LATENCY|PA_SOURCE_HW_VOLUME_CTRL; + u->source->refresh_volume = 1; + } else + u->source = NULL; + + if (mode != O_RDONLY) { + u->sink = pa_sink_new(m->core, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map); + pa_assert(u->sink); + + u->sink->userdata = u; + u->sink->parent.process_msg = sink_process_msg; + + pa_sink_set_module(u->sink, m); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("Solaris PCM on '%s'", p)); + pa_xfree(t); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + + u->sink->flags = PA_SINK_HARDWARE|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL; + u->sink->refresh_volume = 1; + u->sink->refresh_mute = 1; + } else + u->sink = NULL; + + pa_assert(u->source || u->sink); + + u->sig = pa_signal_new(SIGPOLL, sig_callback, u); + pa_assert(u->sig); + ioctl(u->fd, I_SETSIG, S_MSG); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + + /* Read mixer settings */ + if (u->source) + pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->source), PA_SOURCE_MESSAGE_GET_VOLUME, &u->source->volume, 0, NULL); + if (u->sink) { + pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_VOLUME, &u->sink->volume, 0, NULL); + pa_asyncmsgq_send(u->thread_mq.inq, PA_MSGOBJECT(u->sink), PA_SINK_MESSAGE_GET_MUTE, &u->sink->muted, 0, NULL); + } + + if (u->sink) + pa_sink_put(u->sink); + if (u->source) + pa_source_put(u->source); + + pa_modargs_free(ma); + + return 0; + +fail: + if (u) + pa__done(m); + else if (fd >= 0) + close(fd); + + 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; + + ioctl(u->fd, I_SETSIG, 0); + pa_signal_free(u->sig); + + if (u->sink) + pa_sink_unlink(u->sink); + + if (u->source) + pa_source_unlink(u->source); + + 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->source) + pa_source_unref(u->source); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->rtpoll_item) + pa_rtpoll_item_free(u->rtpoll_item); + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->fd >= 0) + close(u->fd); + + pa_xfree(u); +} diff --git a/src/modules/module-suspend-on-idle.c b/src/modules/module-suspend-on-idle.c new file mode 100644 index 00000000..4c260d76 --- /dev/null +++ b/src/modules/module-suspend-on-idle.c @@ -0,0 +1,446 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/core.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> + +#include "module-suspend-on-idle-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("When a sink/source is idle for too long, suspend it"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static const char* const valid_modargs[] = { + "timeout", + NULL, +}; + +struct userdata { + pa_core *core; + pa_usec_t timeout; + pa_hashmap *device_infos; + pa_hook_slot + *sink_new_slot, + *source_new_slot, + *sink_unlink_slot, + *source_unlink_slot, + *sink_state_changed_slot, + *source_state_changed_slot; + + pa_hook_slot + *sink_input_new_slot, + *source_output_new_slot, + *sink_input_unlink_slot, + *source_output_unlink_slot, + *sink_input_move_slot, + *source_output_move_slot, + *sink_input_state_changed_slot, + *source_output_state_changed_slot; +}; + +struct device_info { + struct userdata *userdata; + pa_sink *sink; + pa_source *source; + struct timeval last_use; + pa_time_event *time_event; +}; + +static void timeout_cb(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { + struct device_info *d = userdata; + + pa_assert(d); + + d->userdata->core->mainloop->time_restart(d->time_event, NULL); + + if (d->sink && pa_sink_used_by(d->sink) <= 0 && pa_sink_get_state(d->sink) != PA_SINK_SUSPENDED) { + pa_log_info("Sink %s idle for too long, suspending ...", d->sink->name); + pa_sink_suspend(d->sink, TRUE); + } + + if (d->source && pa_source_used_by(d->source) <= 0 && pa_source_get_state(d->source) != PA_SOURCE_SUSPENDED) { + pa_log_info("Source %s idle for too long, suspending ...", d->source->name); + pa_source_suspend(d->source, TRUE); + } +} + +static void restart(struct device_info *d) { + struct timeval tv; + pa_assert(d); + + pa_gettimeofday(&tv); + d->last_use = tv; + pa_timeval_add(&tv, d->userdata->timeout*1000000); + d->userdata->core->mainloop->time_restart(d->time_event, &tv); + + if (d->sink) + pa_log_debug("Sink %s becomes idle.", d->sink->name); + if (d->source) + pa_log_debug("Source %s becomes idle.", d->source->name); +} + +static void resume(struct device_info *d) { + pa_assert(d); + + d->userdata->core->mainloop->time_restart(d->time_event, NULL); + + if (d->sink) { + pa_sink_suspend(d->sink, FALSE); + + pa_log_debug("Sink %s becomes busy.", d->sink->name); + } + + if (d->source) { + pa_source_suspend(d->source, FALSE); + + pa_log_debug("Source %s becomes busy.", d->source->name); + } +} + +static pa_hook_result_t sink_input_fixate_hook_cb(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_assert(data); + pa_assert(u); + + if ((d = pa_hashmap_get(u->device_infos, data->sink))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_fixate_hook_cb(pa_core *c, pa_source_output_new_data *data, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_assert(data); + pa_assert(u); + + if ((d = pa_hashmap_get(u->device_infos, data->source))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_unlink_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { + pa_assert(c); + pa_sink_input_assert_ref(s); + pa_assert(u); + + if (pa_sink_used_by(s->sink) <= 0) { + struct device_info *d; + if ((d = pa_hashmap_get(u->device_infos, s->sink))) + restart(d); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_unlink_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { + pa_assert(c); + pa_source_output_assert_ref(s); + pa_assert(u); + + if (pa_source_used_by(s->source) <= 0) { + struct device_info *d; + if ((d = pa_hashmap_get(u->device_infos, s->source))) + restart(d); + } + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_move_hook_cb(pa_core *c, pa_sink_input_move_hook_data *data, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_assert(data); + pa_assert(u); + + if ((d = pa_hashmap_get(u->device_infos, data->destination))) + resume(d); + + if (pa_sink_used_by(data->sink_input->sink) <= 1) + if ((d = pa_hashmap_get(u->device_infos, data->sink_input->sink))) + restart(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_move_hook_cb(pa_core *c, pa_source_output_move_hook_data *data, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_assert(data); + pa_assert(u); + + if ((d = pa_hashmap_get(u->device_infos, data->destination))) + resume(d); + + if (pa_source_used_by(data->source_output->source) <= 1) + if ((d = pa_hashmap_get(u->device_infos, data->source_output->source))) + restart(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_state_changed_hook_cb(pa_core *c, pa_sink_input *s, struct userdata *u) { + struct device_info *d; + pa_sink_input_state_t state; + pa_assert(c); + pa_sink_input_assert_ref(s); + pa_assert(u); + + state = pa_sink_input_get_state(s); + if (state == PA_SINK_INPUT_RUNNING || state == PA_SINK_INPUT_DRAINED) + if ((d = pa_hashmap_get(u->device_infos, s->sink))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_state_changed_hook_cb(pa_core *c, pa_source_output *s, struct userdata *u) { + struct device_info *d; + pa_source_output_state_t state; + pa_assert(c); + pa_source_output_assert_ref(s); + pa_assert(u); + + state = pa_source_output_get_state(s); + if (state == PA_SOURCE_OUTPUT_RUNNING) + if ((d = pa_hashmap_get(u->device_infos, s->source))) + resume(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t device_new_hook_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct device_info *d; + pa_source *source; + pa_sink *sink; + + pa_assert(c); + pa_object_assert_ref(o); + pa_assert(u); + + source = pa_source_isinstance(o) ? PA_SOURCE(o) : NULL; + sink = pa_sink_isinstance(o) ? PA_SINK(o) : NULL; + + pa_assert(source || sink); + + d = pa_xnew(struct device_info, 1); + d->userdata = u; + d->source = source ? pa_source_ref(source) : NULL; + d->sink = sink ? pa_sink_ref(sink) : NULL; + d->time_event = c->mainloop->time_new(c->mainloop, NULL, timeout_cb, d); + pa_hashmap_put(u->device_infos, o, d); + + if ((d->sink && pa_sink_used_by(d->sink) <= 0) || + (d->source && pa_source_used_by(d->source) <= 0)) + restart(d); + + return PA_HOOK_OK; +} + +static void device_info_free(struct device_info *d) { + pa_assert(d); + + if (d->source) + pa_source_unref(d->source); + if (d->sink) + pa_sink_unref(d->sink); + + d->userdata->core->mainloop->time_free(d->time_event); + + pa_xfree(d); +} + +static pa_hook_result_t device_unlink_hook_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_object_assert_ref(o); + pa_assert(u); + + if ((d = pa_hashmap_remove(u->device_infos, o))) + device_info_free(d); + + return PA_HOOK_OK; +} + +static pa_hook_result_t device_state_changed_hook_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct device_info *d; + + pa_assert(c); + pa_object_assert_ref(o); + pa_assert(u); + + if (!(d = pa_hashmap_get(u->device_infos, o))) + return PA_HOOK_OK; + + if (pa_sink_isinstance(o)) { + pa_sink *s = PA_SINK(o); + pa_sink_state_t state = pa_sink_get_state(s); + + if (pa_sink_used_by(s) <= 0) { + + if (PA_SINK_OPENED(state)) + restart(d); + + } + + } else if (pa_source_isinstance(o)) { + pa_source *s = PA_SOURCE(o); + pa_source_state_t state = pa_source_get_state(s); + + if (pa_source_used_by(s) <= 0) { + + if (PA_SOURCE_OPENED(state)) + restart(d); + } + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + uint32_t timeout = 1; + uint32_t idx; + pa_sink *sink; + pa_source *source; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "timeout", &timeout) < 0) { + pa_log("Failed to parse timeout value."); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->timeout = timeout; + u->device_infos = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + for (sink = pa_idxset_first(m->core->sinks, &idx); sink; sink = pa_idxset_next(m->core->sinks, &idx)) + device_new_hook_cb(m->core, PA_OBJECT(sink), u); + + for (source = pa_idxset_first(m->core->sources, &idx); source; source = pa_idxset_next(m->core->sources, &idx)) + device_new_hook_cb(m->core, PA_OBJECT(source), u); + + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_hook_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], (pa_hook_cb_t) device_unlink_hook_cb, u); + u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u); + u->source_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], (pa_hook_cb_t) device_state_changed_hook_cb, u); + + u->sink_input_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], (pa_hook_cb_t) sink_input_fixate_hook_cb, u); + u->source_output_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], (pa_hook_cb_t) source_output_fixate_hook_cb, u); + u->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], (pa_hook_cb_t) sink_input_unlink_hook_cb, u); + u->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], (pa_hook_cb_t) source_output_unlink_hook_cb, u); + u->sink_input_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], (pa_hook_cb_t) sink_input_move_hook_cb, u); + u->source_output_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], (pa_hook_cb_t) source_output_move_hook_cb, u); + u->sink_input_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], (pa_hook_cb_t) sink_input_state_changed_hook_cb, u); + u->source_output_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], (pa_hook_cb_t) source_output_state_changed_hook_cb, u); + + pa_modargs_free(ma); + return 0; + +fail: + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + struct device_info *d; + + pa_assert(m); + + if (!m->userdata) + return; + + u = m->userdata; + + if (u->sink_new_slot) + pa_hook_slot_free(u->sink_new_slot); + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + if (u->sink_state_changed_slot) + pa_hook_slot_free(u->sink_state_changed_slot); + + if (u->source_new_slot) + pa_hook_slot_free(u->source_new_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); + if (u->source_state_changed_slot) + pa_hook_slot_free(u->source_state_changed_slot); + + if (u->sink_input_new_slot) + pa_hook_slot_free(u->sink_input_new_slot); + if (u->sink_input_unlink_slot) + pa_hook_slot_free(u->sink_input_unlink_slot); + if (u->sink_input_move_slot) + pa_hook_slot_free(u->sink_input_move_slot); + if (u->sink_input_state_changed_slot) + pa_hook_slot_free(u->sink_input_state_changed_slot); + + if (u->source_output_new_slot) + pa_hook_slot_free(u->source_output_new_slot); + if (u->source_output_unlink_slot) + pa_hook_slot_free(u->source_output_unlink_slot); + if (u->source_output_move_slot) + pa_hook_slot_free(u->source_output_move_slot); + if (u->source_output_state_changed_slot) + pa_hook_slot_free(u->source_output_state_changed_slot); + + while ((d = pa_hashmap_steal_first(u->device_infos))) + device_info_free(d); + + pa_hashmap_free(u->device_infos, NULL, NULL); + + pa_xfree(u); +} diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c new file mode 100644 index 00000000..a53e3932 --- /dev/null +++ b/src/modules/module-tunnel.c @@ -0,0 +1,1509 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + PulseAudio is free software; you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published + by the Free Software Foundation; either version 2 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> + +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/version.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/pstream.h> +#include <pulsecore/pstream-util.h> +#include <pulsecore/authkey.h> +#include <pulsecore/socket-client.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/authkey-prop.h> +#include <pulsecore/time-smoother.h> +#include <pulsecore/thread.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/core-error.h> + +#ifdef TUNNEL_SINK +#include "module-tunnel-sink-symdef.h" +PA_MODULE_DESCRIPTION("Tunnel module for sinks"); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "server=<address> " + "sink=<remote sink name> " + "cookie=<filename> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "sink_name=<name for the local sink> " + "channel_map=<channel map>"); +#else +#include "module-tunnel-source-symdef.h" +PA_MODULE_DESCRIPTION("Tunnel module for sources"); +PA_MODULE_USAGE( + "server=<address> " + "source=<remote source name> " + "cookie=<filename> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "source_name=<name for the local source> " + "channel_map=<channel map>"); +#endif + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_VERSION(PACKAGE_VERSION); + +#define DEFAULT_TLENGTH_MSEC 100 +#define DEFAULT_MINREQ_MSEC 10 +#define DEFAULT_MAXLENGTH_MSEC ((DEFAULT_TLENGTH_MSEC*3)/2) +#define DEFAULT_FRAGSIZE_MSEC 10 + +#define DEFAULT_TIMEOUT 5 + +#define LATENCY_INTERVAL 10 + +static const char* const valid_modargs[] = { + "server", + "cookie", + "format", + "channels", + "rate", +#ifdef TUNNEL_SINK + "sink_name", + "sink", +#else + "source_name", + "source", +#endif + "channel_map", + NULL, +}; + +enum { + SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX +}; + +enum { + SINK_MESSAGE_REQUEST = PA_SINK_MESSAGE_MAX, + SINK_MESSAGE_POST +}; + +#ifdef TUNNEL_SINK +static void command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +#endif +static void command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_overflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); + +static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { +#ifdef TUNNEL_SINK + [PA_COMMAND_REQUEST] = command_request, +#endif + [PA_COMMAND_SUBSCRIBE_EVENT] = command_subscribe_event, + [PA_COMMAND_OVERFLOW] = command_overflow, + [PA_COMMAND_UNDERFLOW] = command_underflow, + [PA_COMMAND_PLAYBACK_STREAM_KILLED] = command_stream_killed, + [PA_COMMAND_RECORD_STREAM_KILLED] = command_stream_killed, + [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = command_suspend, + [PA_COMMAND_RECORD_STREAM_SUSPENDED] = command_suspend, + [PA_COMMAND_PLAYBACK_STREAM_MOVED] = command_moved, + [PA_COMMAND_RECORD_STREAM_MOVED] = command_moved, +}; + +struct userdata { + pa_core *core; + pa_module *module; + + pa_thread_mq thread_mq; + pa_rtpoll *rtpoll; + pa_thread *thread; + + pa_socket_client *client; + pa_pstream *pstream; + pa_pdispatch *pdispatch; + + char *server_name; +#ifdef TUNNEL_SINK + char *sink_name; + pa_sink *sink; + uint32_t requested_bytes; +#else + char *source_name; + pa_source *source; +#endif + + uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH]; + + uint32_t version; + uint32_t ctag; + uint32_t device_index; + uint32_t channel; + + int64_t counter, counter_delta; + + pa_time_event *time_event; + + pa_bool_t auth_cookie_in_property; + + pa_smoother *smoother; + + char *device_description; + char *server_fqdn; + char *user_name; + + uint32_t maxlength; +#ifdef TUNNEL_SINK + uint32_t tlength; + uint32_t minreq; + uint32_t prebuf; +#else + uint32_t fragsize; +#endif +}; + +static void command_stream_killed(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); + + pa_log_warn("Stream killed"); + pa_module_unload_request(u->module); +} + +static void command_overflow(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); + + pa_log_warn("Server signalled buffer overrun."); +} + +static void command_underflow(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); + + pa_log_warn("Server signalled buffer underrun."); +} + +static void command_suspend(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); + + pa_log_debug("Server reports a stream suspension."); +} + +static void command_moved(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); + + pa_log_debug("Server reports a stream move."); +} + +static void stream_cork(struct userdata *u, pa_bool_t cork) { + pa_tagstruct *t; + pa_assert(u); + + if (cork) + pa_smoother_pause(u->smoother, pa_rtclock_usec()); + else + pa_smoother_resume(u->smoother, pa_rtclock_usec()); + + if (!u->pstream) + return; + + t = pa_tagstruct_new(NULL, 0); +#ifdef TUNNEL_SINK + pa_tagstruct_putu32(t, PA_COMMAND_CORK_PLAYBACK_STREAM); +#else + pa_tagstruct_putu32(t, PA_COMMAND_CORK_RECORD_STREAM); +#endif + pa_tagstruct_putu32(t, u->ctag++); + pa_tagstruct_putu32(t, u->channel); + pa_tagstruct_put_boolean(t, !!cork); + pa_pstream_send_tagstruct(u->pstream, t); +} + +#ifdef TUNNEL_SINK + +static void send_data(struct userdata *u) { + pa_assert(u); + + while (u->requested_bytes > 0) { + pa_memchunk memchunk; + pa_sink_render(u->sink, u->requested_bytes, &memchunk); + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_POST, NULL, 0, &memchunk, NULL); + pa_memblock_unref(memchunk.memblock); + u->requested_bytes -= memchunk.length; + } +} + +/* This function is called from IO context -- except when it is not. */ +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: { + int r; + + /* First, change the state, because otherwide pa_sink_render() would fail */ + if ((r = pa_sink_process_msg(o, code, data, offset, chunk)) >= 0) + if (PA_SINK_OPENED((pa_sink_state_t) PA_PTR_TO_UINT(data))) + send_data(u); + + return r; + } + + case SINK_MESSAGE_REQUEST: + + pa_assert(offset > 0); + u->requested_bytes += (size_t) offset; + + if (PA_SINK_OPENED(u->sink->thread_info.state)) + send_data(u); + + return 0; + + case SINK_MESSAGE_POST: + + /* OK, This might be a bit confusing. This message is + * delivered to us from the main context -- NOT from the + * IO thread context where the rest of the messages are + * dispatched. Yeah, ugly, but I am a lazy bastard. */ + + pa_pstream_send_memblock(u->pstream, u->channel, 0, PA_SEEK_RELATIVE, chunk); + u->counter += chunk->length; + u->counter_delta += chunk->length; + return 0; + } + + return pa_sink_process_msg(o, code, data, offset, chunk); +} + +static int sink_set_state(pa_sink *s, pa_sink_state_t state) { + struct userdata *u; + pa_sink_assert_ref(s); + u = s->userdata; + + switch ((pa_sink_state_t) state) { + + case PA_SINK_SUSPENDED: + pa_assert(PA_SINK_OPENED(s->state)); + stream_cork(u, TRUE); + break; + + case PA_SINK_IDLE: + case PA_SINK_RUNNING: + if (s->state == PA_SINK_SUSPENDED) + stream_cork(u, FALSE); + break; + + case PA_SINK_UNLINKED: + case PA_SINK_INIT: + ; + } + + return 0; +} + +#else + +static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u = PA_SOURCE(o)->userdata; + + switch (code) { + case SOURCE_MESSAGE_POST: + + if (PA_SOURCE_OPENED(u->source->thread_info.state)) + pa_source_post(u->source, chunk); + return 0; + } + + return pa_source_process_msg(o, code, data, offset, chunk); +} + +static int source_set_state(pa_source *s, pa_source_state_t state) { + struct userdata *u; + pa_source_assert_ref(s); + u = s->userdata; + + switch ((pa_source_state_t) state) { + + case PA_SOURCE_SUSPENDED: + pa_assert(PA_SOURCE_OPENED(s->state)); + stream_cork(u, TRUE); + break; + + case PA_SOURCE_IDLE: + case PA_SOURCE_RUNNING: + if (s->state == PA_SOURCE_SUSPENDED) + stream_cork(u, FALSE); + break; + + case PA_SOURCE_UNLINKED: + case PA_SOURCE_INIT: + ; + } + + return 0; +} + +#endif + +static void thread_func(void *userdata) { + struct userdata *u = userdata; + + pa_assert(u); + + pa_log_debug("Thread starting up"); + + pa_thread_mq_install(&u->thread_mq); + pa_rtpoll_install(u->rtpoll); + + for (;;) { + int ret; + + if ((ret = pa_rtpoll_run(u->rtpoll, TRUE)) < 0) + goto fail; + + if (ret == 0) + goto finish; + } + +fail: + /* If this was no regular exit from the loop we have to continue + * processing messages until we received PA_MESSAGE_SHUTDOWN */ + pa_asyncmsgq_post(u->thread_mq.outq, PA_MSGOBJECT(u->core), PA_CORE_MESSAGE_UNLOAD_MODULE, u->module, 0, NULL, NULL); + pa_asyncmsgq_wait_for(u->thread_mq.inq, PA_MESSAGE_SHUTDOWN); + +finish: + pa_log_debug("Thread shutting down"); +} + +#ifdef TUNNEL_SINK +static void command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + uint32_t bytes, channel; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_REQUEST); + pa_assert(t); + pa_assert(u); + pa_assert(u->pdispatch == pd); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_getu32(t, &bytes) < 0) { + pa_log("Invalid protocol reply"); + goto fail; + } + + if (channel != u->channel) { + pa_log("Recieved data for invalid channel"); + goto fail; + } + + pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REQUEST, NULL, bytes, NULL); + return; + +fail: + pa_module_unload_request(u->module); +} + +#endif + +static void stream_get_latency_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + pa_usec_t sink_usec, source_usec, transport_usec, host_usec, k; + int playing; + int64_t write_index, read_index; + struct timeval local, remote, now; + + pa_assert(pd); + pa_assert(u); + + if (command != PA_COMMAND_REPLY) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to get latency."); + else + pa_log("Protocol error 1."); + goto fail; + } + + if (pa_tagstruct_get_usec(t, &sink_usec) < 0 || + pa_tagstruct_get_usec(t, &source_usec) < 0 || + pa_tagstruct_get_boolean(t, &playing) < 0 || + pa_tagstruct_get_timeval(t, &local) < 0 || + pa_tagstruct_get_timeval(t, &remote) < 0 || + pa_tagstruct_gets64(t, &write_index) < 0 || + pa_tagstruct_gets64(t, &read_index) < 0) { + pa_log("Invalid reply. (latency)"); + goto fail; + } + + pa_gettimeofday(&now); + + if (pa_timeval_cmp(&local, &remote) < 0 && pa_timeval_cmp(&remote, &now)) { + /* local and remote seem to have synchronized clocks */ +#ifdef TUNNEL_SINK + transport_usec = pa_timeval_diff(&remote, &local); +#else + transport_usec = pa_timeval_diff(&now, &remote); +#endif + } else + transport_usec = pa_timeval_diff(&now, &local)/2; + +#ifdef TUNNEL_SINK + host_usec = sink_usec + transport_usec; +#else + host_usec = source_usec + transport_usec; + if (host_usec > sink_usec) + host_usec -= sink_usec; + else + host_usec = 0; +#endif + +#ifdef TUNNEL_SINK + k = pa_bytes_to_usec(u->counter - u->counter_delta, &u->sink->sample_spec); + + if (k > host_usec) + k -= host_usec; + else + k = 0; +#else + k = pa_bytes_to_usec(u->counter - u->counter_delta, &u->source->sample_spec); + k += host_usec; +#endif + + pa_smoother_put(u->smoother, pa_rtclock_usec(), k); + + return; + +fail: + pa_module_unload_request(u->module); +} + +static void request_latency(struct userdata *u) { + pa_tagstruct *t; + struct timeval now; + uint32_t tag; + pa_assert(u); + + t = pa_tagstruct_new(NULL, 0); +#ifdef TUNNEL_SINK + pa_tagstruct_putu32(t, PA_COMMAND_GET_PLAYBACK_LATENCY); +#else + pa_tagstruct_putu32(t, PA_COMMAND_GET_RECORD_LATENCY); +#endif + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->channel); + + pa_gettimeofday(&now); + pa_tagstruct_put_timeval(t, &now); + + pa_pstream_send_tagstruct(u->pstream, t); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_latency_callback, u, NULL); + + u->counter_delta = 0; +} + +static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + struct timeval ntv; + + pa_assert(m); + pa_assert(e); + pa_assert(u); + + request_latency(u); + + pa_gettimeofday(&ntv); + ntv.tv_sec += LATENCY_INTERVAL; + m->time_restart(e, &ntv); +} + +#ifdef TUNNEL_SINK +static pa_usec_t sink_get_latency(pa_sink *s) { + pa_usec_t t, c; + struct userdata *u = s->userdata; + + pa_sink_assert_ref(s); + + c = pa_bytes_to_usec(u->counter, &s->sample_spec); + t = pa_smoother_get(u->smoother, pa_rtclock_usec()); + + return c > t ? c - t : 0; +} +#else +static pa_usec_t source_get_latency(pa_source *s) { + pa_usec_t t, c; + struct userdata *u = s->userdata; + + pa_source_assert_ref(s); + + c = pa_bytes_to_usec(u->counter, &s->sample_spec); + t = pa_smoother_get(u->smoother, pa_rtclock_usec()); + + return t > c ? t - c : 0; +} +#endif + +static void update_description(struct userdata *u) { + char *d; + char un[128], hn[128]; + pa_tagstruct *t; + + pa_assert(u); + + if (!u->server_fqdn || !u->user_name || !u->device_description) + return; + + d = pa_sprintf_malloc("%s on %s@%s", u->device_description, u->user_name, u->server_fqdn); + +#ifdef TUNNEL_SINK + pa_sink_set_description(u->sink, d); +#else + pa_source_set_description(u->source, d); +#endif + + pa_xfree(d); + + d = pa_sprintf_malloc("%s for %s@%s", u->device_description, + pa_get_user_name(un, sizeof(un)), + pa_get_host_name(hn, sizeof(hn))); + + t = pa_tagstruct_new(NULL, 0); +#ifdef TUNNEL_SINK + pa_tagstruct_putu32(t, PA_COMMAND_SET_PLAYBACK_STREAM_NAME); +#else + pa_tagstruct_putu32(t, PA_COMMAND_SET_RECORD_STREAM_NAME); +#endif + pa_tagstruct_putu32(t, u->ctag++); + pa_tagstruct_putu32(t, u->channel); + pa_tagstruct_puts(t, d); + pa_pstream_send_tagstruct(u->pstream, t); + + pa_xfree(d); +} + +static void server_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + pa_sample_spec ss; + const char *server_name, *server_version, *user_name, *host_name, *default_sink_name, *default_source_name; + uint32_t cookie; + + pa_assert(pd); + pa_assert(u); + + if (command != PA_COMMAND_REPLY) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to get info."); + else + pa_log("Protocol error 6."); + goto fail; + } + + if (pa_tagstruct_gets(t, &server_name) < 0 || + pa_tagstruct_gets(t, &server_version) < 0 || + pa_tagstruct_gets(t, &user_name) < 0 || + pa_tagstruct_gets(t, &host_name) < 0 || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_gets(t, &default_sink_name) < 0 || + pa_tagstruct_gets(t, &default_source_name) < 0 || + pa_tagstruct_getu32(t, &cookie) < 0) { + pa_log("Invalid reply. (get_server_info)"); + goto fail; + } + + pa_xfree(u->server_fqdn); + u->server_fqdn = pa_xstrdup(host_name); + + pa_xfree(u->user_name); + u->user_name = pa_xstrdup(user_name); + + update_description(u); + + return; + +fail: + pa_module_unload_request(u->module); +} + +#ifdef TUNNEL_SINK + +static void sink_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + uint32_t idx, owner_module, monitor_source, flags; + const char *name, *description, *monitor_source_name, *driver; + pa_sample_spec ss; + pa_channel_map cm; + pa_cvolume volume; + int mute; + pa_usec_t latency; + + pa_assert(pd); + pa_assert(u); + + if (command != PA_COMMAND_REPLY) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to get info."); + else + pa_log("Protocol error 5."); + goto fail; + } + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_gets(t, &description) < 0 || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &cm) < 0 || + pa_tagstruct_getu32(t, &owner_module) < 0 || + pa_tagstruct_get_cvolume(t, &volume) < 0 || + pa_tagstruct_get_boolean(t, &mute) < 0 || + pa_tagstruct_getu32(t, &monitor_source) < 0 || + pa_tagstruct_gets(t, &monitor_source_name) < 0 || + pa_tagstruct_get_usec(t, &latency) < 0 || + pa_tagstruct_gets(t, &driver) < 0 || + pa_tagstruct_getu32(t, &flags) < 0) { + pa_log("Invalid reply. (get_sink_info)"); + goto fail; + } + + if (strcmp(name, u->sink_name)) + return; + + pa_xfree(u->device_description); + u->device_description = pa_xstrdup(description); + + update_description(u); + + return; + +fail: + pa_module_unload_request(u->module); +} + +static void sink_input_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + uint32_t idx, owner_module, client, sink; + pa_usec_t buffer_usec, sink_usec; + const char *name, *driver, *resample_method; + int mute; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_cvolume volume; + + pa_assert(pd); + pa_assert(u); + + if (command != PA_COMMAND_REPLY) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to get info."); + else + pa_log("Protocol error 2."); + goto fail; + } + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_getu32(t, &owner_module) < 0 || + pa_tagstruct_getu32(t, &client) < 0 || + pa_tagstruct_getu32(t, &sink) < 0 || + pa_tagstruct_get_sample_spec(t, &sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &channel_map) < 0 || + pa_tagstruct_get_cvolume(t, &volume) < 0 || + pa_tagstruct_get_usec(t, &buffer_usec) < 0 || + pa_tagstruct_get_usec(t, &sink_usec) < 0 || + pa_tagstruct_gets(t, &resample_method) < 0 || + pa_tagstruct_gets(t, &driver) < 0 || + (u->version >= 11 && pa_tagstruct_get_boolean(t, &mute) < 0)) { + pa_log("Invalid reply. (get_info)"); + goto fail; + } + + if (idx != u->device_index) + return; + + pa_assert(u->sink); + + if ((u->version < 11 || !!mute == !!u->sink->muted) && + pa_cvolume_equal(&volume, &u->sink->volume)) + return; + + memcpy(&u->sink->volume, &volume, sizeof(pa_cvolume)); + + if (u->version >= 11) + u->sink->muted = !!mute; + + pa_subscription_post(u->sink->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, u->sink->index); + return; + +fail: + pa_module_unload_request(u->module); +} + +#else + +static void source_info_cb(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + uint32_t idx, owner_module, monitor_of_sink, flags; + const char *name, *description, *monitor_of_sink_name, *driver; + pa_sample_spec ss; + pa_channel_map cm; + pa_cvolume volume; + int mute; + pa_usec_t latency; + + pa_assert(pd); + pa_assert(u); + + if (command != PA_COMMAND_REPLY) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to get info."); + else + pa_log("Protocol error 5."); + goto fail; + } + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_gets(t, &description) < 0 || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &cm) < 0 || + pa_tagstruct_getu32(t, &owner_module) < 0 || + pa_tagstruct_get_cvolume(t, &volume) < 0 || + pa_tagstruct_get_boolean(t, &mute) < 0 || + pa_tagstruct_getu32(t, &monitor_of_sink) < 0 || + pa_tagstruct_gets(t, &monitor_of_sink_name) < 0 || + pa_tagstruct_get_usec(t, &latency) < 0 || + pa_tagstruct_gets(t, &driver) < 0 || + pa_tagstruct_getu32(t, &flags) < 0) { + pa_log("Invalid reply. (get_source_info)"); + goto fail; + } + + if (strcmp(name, u->source_name)) + return; + + pa_xfree(u->device_description); + u->device_description = pa_xstrdup(description); + + update_description(u); + + return; + +fail: + pa_module_unload_request(u->module); +} + +#endif + +static void request_info(struct userdata *u) { + pa_tagstruct *t; + uint32_t tag; + pa_assert(u); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_GET_SERVER_INFO); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_pstream_send_tagstruct(u->pstream, t); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, server_info_cb, u, NULL); + +#ifdef TUNNEL_SINK + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INPUT_INFO); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->device_index); + pa_pstream_send_tagstruct(u->pstream, t); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_input_info_cb, u, NULL); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_GET_SINK_INFO); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, u->sink_name); + pa_pstream_send_tagstruct(u->pstream, t); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, sink_info_cb, u, NULL); +#else + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_GET_SOURCE_INFO); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, u->source_name); + pa_pstream_send_tagstruct(u->pstream, t); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, source_info_cb, u, NULL); +#endif +} + +static void command_subscribe_event(pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + pa_subscription_event_type_t e; + uint32_t idx; + + pa_assert(pd); + pa_assert(t); + pa_assert(u); + pa_assert(command == PA_COMMAND_SUBSCRIBE_EVENT); + + if (pa_tagstruct_getu32(t, &e) < 0 || + pa_tagstruct_getu32(t, &idx) < 0) { + pa_log("Invalid protocol reply"); + pa_module_unload_request(u->module); + return; + } + + if (e != (PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE) && +#ifdef TUNNEL_SINK + e != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && + e != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE) +#else + e != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE) +#endif + ) + return; + + request_info(u); +} + +static void start_subscribe(struct userdata *u) { + pa_tagstruct *t; + uint32_t tag; + pa_assert(u); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, PA_SUBSCRIPTION_MASK_SERVER| +#ifdef TUNNEL_SINK + PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SINK +#else + PA_SUBSCRIPTION_MASK_SOURCE +#endif + ); + + pa_pstream_send_tagstruct(u->pstream, t); +} + +static void create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + struct timeval ntv; +#ifdef TUNNEL_SINK + uint32_t bytes; +#endif + + pa_assert(pd); + pa_assert(u); + pa_assert(u->pdispatch == pd); + + if (command != PA_COMMAND_REPLY) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to create stream."); + else + pa_log("Protocol error 3."); + goto fail; + } + + if (pa_tagstruct_getu32(t, &u->channel) < 0 || + pa_tagstruct_getu32(t, &u->device_index) < 0 +#ifdef TUNNEL_SINK + || pa_tagstruct_getu32(t, &bytes) < 0 +#endif + ) + goto parse_error; + + if (u->version >= 9) { +#ifdef TUNNEL_SINK + uint32_t maxlength, tlength, prebuf, minreq; + + if (pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_getu32(t, &tlength) < 0 || + pa_tagstruct_getu32(t, &prebuf) < 0 || + pa_tagstruct_getu32(t, &minreq) < 0) + goto parse_error; +#else + uint32_t maxlength, fragsize; + + if (pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_getu32(t, &fragsize) < 0) + goto parse_error; +#endif + } + + start_subscribe(u); + request_info(u); + + pa_assert(!u->time_event); + pa_gettimeofday(&ntv); + ntv.tv_sec += LATENCY_INTERVAL; + u->time_event = u->core->mainloop->time_new(u->core->mainloop, &ntv, timeout_callback, u); + + request_latency(u); + + pa_log_debug("Stream created."); + +#ifdef TUNNEL_SINK + pa_asyncmsgq_post(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), SINK_MESSAGE_REQUEST, NULL, bytes, NULL, NULL); +#endif + + return; + +parse_error: + pa_log("Invalid reply. (Create stream)"); + +fail: + pa_module_unload_request(u->module); +} + +static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + struct userdata *u = userdata; + pa_tagstruct *reply; + char name[256], un[128], hn[128]; +#ifdef TUNNEL_SINK + pa_cvolume volume; +#endif + + pa_assert(pd); + pa_assert(u); + pa_assert(u->pdispatch == pd); + + if (command != PA_COMMAND_REPLY || + pa_tagstruct_getu32(t, &u->version) < 0) { + if (command == PA_COMMAND_ERROR) + pa_log("Failed to authenticate"); + else + pa_log("Protocol error 4."); + + goto fail; + } + + /* Minimum supported protocol version */ + if (u->version < 8) { + pa_log("Incompatible protocol version"); + goto fail; + } + +#ifdef TUNNEL_SINK + pa_snprintf(name, sizeof(name), "%s for %s@%s", + u->sink_name, + pa_get_user_name(un, sizeof(un)), + pa_get_host_name(hn, sizeof(hn))); +#else + pa_snprintf(name, sizeof(name), "%s for %s@%s", + u->source_name, + pa_get_user_name(un, sizeof(un)), + pa_get_host_name(hn, sizeof(hn))); +#endif + + reply = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(reply, PA_COMMAND_SET_CLIENT_NAME); + pa_tagstruct_putu32(reply, tag = u->ctag++); + pa_tagstruct_puts(reply, "PulseAudio"); + pa_pstream_send_tagstruct(u->pstream, reply); + /* We ignore the server's reply here */ + + reply = pa_tagstruct_new(NULL, 0); + +#ifdef TUNNEL_SINK + pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_PLAYBACK_STREAM); + pa_tagstruct_putu32(reply, tag = u->ctag++); + pa_tagstruct_puts(reply, name); + pa_tagstruct_put_sample_spec(reply, &u->sink->sample_spec); + pa_tagstruct_put_channel_map(reply, &u->sink->channel_map); + pa_tagstruct_putu32(reply, PA_INVALID_INDEX); + pa_tagstruct_puts(reply, u->sink_name); + pa_tagstruct_putu32(reply, u->maxlength); + pa_tagstruct_put_boolean(reply, !PA_SINK_OPENED(pa_sink_get_state(u->sink))); + pa_tagstruct_putu32(reply, u->tlength); + pa_tagstruct_putu32(reply, u->prebuf); + pa_tagstruct_putu32(reply, u->minreq); + pa_tagstruct_putu32(reply, 0); + pa_cvolume_reset(&volume, u->sink->sample_spec.channels); + pa_tagstruct_put_cvolume(reply, &volume); +#else + pa_tagstruct_putu32(reply, PA_COMMAND_CREATE_RECORD_STREAM); + pa_tagstruct_putu32(reply, tag = u->ctag++); + pa_tagstruct_puts(reply, name); + pa_tagstruct_put_sample_spec(reply, &u->source->sample_spec); + pa_tagstruct_put_channel_map(reply, &u->source->channel_map); + pa_tagstruct_putu32(reply, PA_INVALID_INDEX); + pa_tagstruct_puts(reply, u->source_name); + pa_tagstruct_putu32(reply, u->maxlength); + pa_tagstruct_put_boolean(reply, !PA_SOURCE_OPENED(pa_source_get_state(u->source))); + pa_tagstruct_putu32(reply, u->fragsize); +#endif + + /* New flags added in 0.9.8 */ + if (u->version >= 12) { + /* TODO: set these to useful values */ + pa_tagstruct_put_boolean(reply, FALSE); /*no_remap*/ + pa_tagstruct_put_boolean(reply, FALSE); /*no_remix*/ + pa_tagstruct_put_boolean(reply, FALSE); /*fix_format*/ + pa_tagstruct_put_boolean(reply, FALSE); /*fix_rate*/ + pa_tagstruct_put_boolean(reply, FALSE); /*fix_channels*/ + pa_tagstruct_put_boolean(reply, FALSE); /*no_move*/ + pa_tagstruct_put_boolean(reply, FALSE); /*variable_rate*/ + } + + pa_pstream_send_tagstruct(u->pstream, reply); + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, create_stream_callback, u, NULL); + + pa_log_debug("Connection authenticated, creating stream ..."); + + return; + +fail: + pa_module_unload_request(u->module); +} + +static void pstream_die_callback(pa_pstream *p, void *userdata) { + struct userdata *u = userdata; + + pa_assert(p); + pa_assert(u); + + pa_log_warn("Stream died."); + pa_module_unload_request(u->module); +} + +static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_creds *creds, void *userdata) { + struct userdata *u = userdata; + + pa_assert(p); + pa_assert(packet); + pa_assert(u); + + if (pa_pdispatch_run(u->pdispatch, packet, creds, u) < 0) { + pa_log("Invalid packet"); + pa_module_unload_request(u->module); + return; + } +} + +#ifndef TUNNEL_SINK +static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata) { + struct userdata *u = userdata; + + pa_assert(p); + pa_assert(chunk); + pa_assert(u); + + if (channel != u->channel) { + pa_log("Recieved memory block on bad channel."); + pa_module_unload_request(u->module); + return; + } + + pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), SOURCE_MESSAGE_POST, PA_UINT_TO_PTR(seek), offset, chunk); + + u->counter += chunk->length; + u->counter_delta += chunk->length; +} + +#endif + +static void on_connection(pa_socket_client *sc, pa_iochannel *io, void *userdata) { + struct userdata *u = userdata; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(sc); + pa_assert(u); + pa_assert(u->client == sc); + + pa_socket_client_unref(u->client); + u->client = NULL; + + if (!io) { + pa_log("Connection failed: %s", pa_cstrerror(errno)); + pa_module_unload_request(u->module); + return; + } + + u->pstream = pa_pstream_new(u->core->mainloop, io, u->core->mempool); + u->pdispatch = pa_pdispatch_new(u->core->mainloop, command_table, PA_COMMAND_MAX); + + pa_pstream_set_die_callback(u->pstream, pstream_die_callback, u); + pa_pstream_set_recieve_packet_callback(u->pstream, pstream_packet_callback, u); +#ifndef TUNNEL_SINK + pa_pstream_set_recieve_memblock_callback(u->pstream, pstream_memblock_callback, u); +#endif + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_AUTH); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION); + pa_tagstruct_put_arbitrary(t, u->auth_cookie, sizeof(u->auth_cookie)); + +#ifdef HAVE_CREDS +{ + pa_creds ucred; + + if (pa_iochannel_creds_supported(io)) + pa_iochannel_creds_enable(io); + + ucred.uid = getuid(); + ucred.gid = getgid(); + + pa_pstream_send_tagstruct_with_creds(u->pstream, t, &ucred); +} +#else + pa_pstream_send_tagstruct(u->pstream, t); +#endif + + pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, u, NULL); + + pa_log_debug("Connection established, authenticating ..."); +} + +#ifdef TUNNEL_SINK + +static int sink_get_volume(pa_sink *sink) { + return 0; +} + +static int sink_set_volume(pa_sink *sink) { + struct userdata *u; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(sink); + u = sink->userdata; + pa_assert(u); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_VOLUME); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->device_index); + pa_tagstruct_put_cvolume(t, &sink->volume); + pa_pstream_send_tagstruct(u->pstream, t); + + return 0; +} + +static int sink_get_mute(pa_sink *sink) { + return 0; +} + +static int sink_set_mute(pa_sink *sink) { + struct userdata *u; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(sink); + u = sink->userdata; + pa_assert(u); + + if (u->version < 11) + return -1; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_SET_SINK_INPUT_MUTE); + pa_tagstruct_putu32(t, tag = u->ctag++); + pa_tagstruct_putu32(t, u->device_index); + pa_tagstruct_put_boolean(t, !!sink->muted); + pa_pstream_send_tagstruct(u->pstream, t); + + return 0; +} + +#endif + +static int load_key(struct userdata *u, const char*fn) { + pa_assert(u); + + u->auth_cookie_in_property = FALSE; + + if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) { + pa_log_debug("Using already loaded auth cookie."); + pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME); + u->auth_cookie_in_property = 1; + return 0; + } + + if (!fn) + fn = PA_NATIVE_COOKIE_FILE; + + if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0) + return -1; + + pa_log_debug("Loading cookie from disk."); + + if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) + u->auth_cookie_in_property = TRUE; + + return 0; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u = NULL; + pa_sample_spec ss; + pa_channel_map map; + char *t, *dn = NULL; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments"); + goto fail; + } + + u = pa_xnew0(struct userdata, 1); + m->userdata = u; + u->module = m; + u->core = m->core; + u->client = NULL; + u->pdispatch = NULL; + u->pstream = NULL; + u->server_name = NULL; +#ifdef TUNNEL_SINK + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL));; + u->sink = NULL; + u->requested_bytes = 0; +#else + u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));; + u->source = NULL; +#endif + u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC*2, TRUE); + u->ctag = 1; + u->device_index = u->channel = PA_INVALID_INDEX; + u->auth_cookie_in_property = FALSE; + u->time_event = NULL; + + pa_thread_mq_init(&u->thread_mq, m->core->mainloop); + u->rtpoll = pa_rtpoll_new(); + pa_rtpoll_item_new_asyncmsgq(u->rtpoll, PA_RTPOLL_EARLY, u->thread_mq.inq); + + if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0) + goto fail; + + if (!(u->server_name = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL)))) { + pa_log("no server specified."); + goto fail; + } + + ss = m->core->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_DEFAULT) < 0) { + pa_log("invalid sample format specification"); + goto fail; + } + + if (!(u->client = pa_socket_client_new_string(m->core->mainloop, u->server_name, PA_NATIVE_DEFAULT_PORT))) { + pa_log("failed to connect to server '%s'", u->server_name); + goto fail; + } + + pa_socket_client_set_callback(u->client, on_connection, u); + +#ifdef TUNNEL_SINK + + if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "sink_name", NULL)))) + dn = pa_sprintf_malloc("tunnel.%s", u->server_name); + + if (!(u->sink = pa_sink_new(m->core, __FILE__, dn, 1, &ss, &map))) { + pa_log("Failed to create sink."); + goto fail; + } + + u->sink->parent.process_msg = sink_process_msg; + u->sink->userdata = u; + u->sink->set_state = sink_set_state; + u->sink->get_latency = sink_get_latency; + u->sink->get_volume = sink_get_volume; + u->sink->get_mute = sink_get_mute; + u->sink->set_volume = sink_set_volume; + u->sink->set_mute = sink_set_mute; + u->sink->flags = PA_SINK_NETWORK|PA_SINK_LATENCY|PA_SINK_HW_VOLUME_CTRL; + + pa_sink_set_module(u->sink, m); + pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq); + pa_sink_set_rtpoll(u->sink, u->rtpoll); + pa_sink_set_description(u->sink, t = pa_sprintf_malloc("%s%s%s", u->sink_name ? u->sink_name : "", u->sink_name ? " on " : "", u->server_name)); + pa_xfree(t); + +#else + + if (!(dn = pa_xstrdup(pa_modargs_get_value(ma, "source_name", NULL)))) + dn = pa_sprintf_malloc("tunnel.%s", u->server_name); + + if (!(u->source = pa_source_new(m->core, __FILE__, dn, 1, &ss, &map))) { + pa_log("Failed to create source."); + goto fail; + } + + u->source->parent.process_msg = source_process_msg; + u->source->userdata = u; + u->source->set_state = source_set_state; + u->source->get_latency = source_get_latency; + u->source->flags = PA_SOURCE_NETWORK|PA_SOURCE_LATENCY; + + pa_source_set_module(u->source, m); + pa_source_set_asyncmsgq(u->source, u->thread_mq.inq); + pa_source_set_rtpoll(u->source, u->rtpoll); + pa_source_set_description(u->source, t = pa_sprintf_malloc("%s%s%s", u->source_name ? u->source_name : "", u->source_name ? " on " : "", u->server_name)); + pa_xfree(t); +#endif + + pa_xfree(dn); + + u->time_event = NULL; + + u->maxlength = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MAXLENGTH_MSEC, &ss); +#ifdef TUNNEL_SINK + u->tlength = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_TLENGTH_MSEC, &ss); + u->minreq = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_MINREQ_MSEC, &ss); + u->prebuf = u->tlength; +#else + u->fragsize = pa_usec_to_bytes(PA_USEC_PER_MSEC * DEFAULT_FRAGSIZE_MSEC, &ss); +#endif + + u->counter = u->counter_delta = 0; + pa_smoother_set_time_offset(u->smoother, pa_rtclock_usec()); + + if (!(u->thread = pa_thread_new(thread_func, u))) { + pa_log("Failed to create thread."); + goto fail; + } + +#ifdef TUNNEL_SINK + pa_sink_put(u->sink); +#else + pa_source_put(u->source); +#endif + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + pa_xfree(dn); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + +#ifdef TUNNEL_SINK + if (u->sink) + pa_sink_unlink(u->sink); +#else + if (u->source) + pa_source_unlink(u->source); +#endif + + 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); + +#ifdef TUNNEL_SINK + if (u->sink) + pa_sink_unref(u->sink); +#else + if (u->source) + pa_source_unref(u->source); +#endif + + if (u->rtpoll) + pa_rtpoll_free(u->rtpoll); + + if (u->pstream) { + pa_pstream_unlink(u->pstream); + pa_pstream_unref(u->pstream); + } + + if (u->pdispatch) + pa_pdispatch_unref(u->pdispatch); + + if (u->client) + pa_socket_client_unref(u->client); + + if (u->auth_cookie_in_property) + pa_authkey_prop_unref(m->core, PA_NATIVE_COOKIE_PROPERTY_NAME); + + if (u->smoother) + pa_smoother_free(u->smoother); + + if (u->time_event) + u->core->mainloop->time_free(u->time_event); + +#ifdef TUNNEL_SINK + pa_xfree(u->sink_name); +#else + pa_xfree(u->source_name); +#endif + pa_xfree(u->server_name); + + pa_xfree(u->device_description); + pa_xfree(u->server_fqdn); + pa_xfree(u->user_name); + + pa_xfree(u); +} diff --git a/src/modules/module-volume-restore.c b/src/modules/module-volume-restore.c new file mode 100644 index 00000000..192a2a78 --- /dev/null +++ b/src/modules/module-volume-restore.c @@ -0,0 +1,580 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 of the License, + or (at your option) any later version. + + PulseAudio is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with PulseAudio; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +#include <pulse/xmalloc.h> +#include <pulse/volume.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/log.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/namereg.h> + +#include "module-volume-restore-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Automatically restore the volume and the devices of streams"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE( + "table=<filename> " + "restore_device=<Restore the device for each stream?> " + "restore_volume=<Restore the volume for each stream?>" +); + +#define WHITESPACE "\n\r \t" +#define DEFAULT_VOLUME_TABLE_FILE "volume-restore.table" +#define SAVE_INTERVAL 10 + +static const char* const valid_modargs[] = { + "table", + "restore_device", + "restore_volume", + NULL, +}; + +struct rule { + char* name; + pa_bool_t volume_is_set; + pa_cvolume volume; + char *sink, *source; +}; + +struct userdata { + pa_core *core; + pa_hashmap *hashmap; + pa_subscription *subscription; + pa_hook_slot + *sink_input_new_hook_slot, + *sink_input_fixate_hook_slot, + *source_output_new_hook_slot; + pa_bool_t modified; + char *table_file; + pa_time_event *save_time_event; +}; + +static pa_cvolume* parse_volume(const char *s, pa_cvolume *v) { + char *p; + long k; + unsigned i; + + pa_assert(s); + pa_assert(v); + + if (!isdigit(*s)) + return NULL; + + k = strtol(s, &p, 0); + if (k <= 0 || k > PA_CHANNELS_MAX) + return NULL; + + v->channels = (unsigned) k; + + for (i = 0; i < v->channels; i++) { + p += strspn(p, WHITESPACE); + + if (!isdigit(*p)) + return NULL; + + k = strtol(p, &p, 0); + + if (k < PA_VOLUME_MUTED) + return NULL; + + v->values[i] = (pa_volume_t) k; + } + + if (*p != 0) + return NULL; + + return v; +} + +static int load_rules(struct userdata *u) { + FILE *f; + int n = 0; + int ret = -1; + char buf_name[256], buf_volume[256], buf_sink[256], buf_source[256]; + char *ln = buf_name; + + f = u->table_file ? + fopen(u->table_file, "r") : + pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "r"); + + if (!f) { + if (errno == ENOENT) { + pa_log_info("starting with empty ruleset."); + ret = 0; + } else + pa_log("failed to open file '%s': %s", u->table_file, pa_cstrerror(errno)); + + goto finish; + } + + pa_lock_fd(fileno(f), 1); + + while (!feof(f)) { + struct rule *rule; + pa_cvolume v; + pa_bool_t v_is_set; + + if (!fgets(ln, sizeof(buf_name), f)) + break; + + n++; + + pa_strip_nl(ln); + + if (ln[0] == '#') + continue; + + if (ln == buf_name) { + ln = buf_volume; + continue; + } + + if (ln == buf_volume) { + ln = buf_sink; + continue; + } + + if (ln == buf_sink) { + ln = buf_source; + continue; + } + + pa_assert(ln == buf_source); + + if (buf_volume[0]) { + if (!parse_volume(buf_volume, &v)) { + pa_log("parse failure in %s:%u, stopping parsing", u->table_file, n); + goto finish; + } + + v_is_set = TRUE; + } else + v_is_set = FALSE; + + ln = buf_name; + + if (pa_hashmap_get(u->hashmap, buf_name)) { + pa_log("double entry in %s:%u, ignoring", u->table_file, n); + continue; + } + + rule = pa_xnew(struct rule, 1); + rule->name = pa_xstrdup(buf_name); + if ((rule->volume_is_set = v_is_set)) + rule->volume = v; + rule->sink = buf_sink[0] ? pa_xstrdup(buf_sink) : NULL; + rule->source = buf_source[0] ? pa_xstrdup(buf_source) : NULL; + + pa_hashmap_put(u->hashmap, rule->name, rule); + } + + if (ln != buf_name) { + pa_log("invalid number of lines in %s.", u->table_file); + goto finish; + } + + ret = 0; + +finish: + if (f) { + pa_lock_fd(fileno(f), 0); + fclose(f); + } + + return ret; +} + +static int save_rules(struct userdata *u) { + FILE *f; + int ret = -1; + void *state = NULL; + struct rule *rule; + + if (!u->modified) + return 0; + + pa_log_info("Saving rules..."); + + f = u->table_file ? + fopen(u->table_file, "w") : + pa_open_config_file(NULL, DEFAULT_VOLUME_TABLE_FILE, NULL, &u->table_file, "w"); + + if (!f) { + pa_log("Failed to open file '%s': %s", u->table_file, pa_cstrerror(errno)); + goto finish; + } + + pa_lock_fd(fileno(f), 1); + + while ((rule = pa_hashmap_iterate(u->hashmap, &state, NULL))) { + unsigned i; + + fprintf(f, "%s\n", rule->name); + + if (rule->volume_is_set) { + fprintf(f, "%u", rule->volume.channels); + + for (i = 0; i < rule->volume.channels; i++) + fprintf(f, " %u", rule->volume.values[i]); + } + + fprintf(f, "\n%s\n%s\n", + rule->sink ? rule->sink : "", + rule->source ? rule->source : ""); + } + + ret = 0; + u->modified = FALSE; + pa_log_debug("Successfully saved rules..."); + +finish: + if (f) { + pa_lock_fd(fileno(f), 0); + fclose(f); + } + + return ret; +} + +static char* client_name(pa_client *c) { + char *t, *e; + + if (!c->name || !c->driver) + return NULL; + + t = pa_sprintf_malloc("%s$%s", c->driver, c->name); + t[strcspn(t, "\n\r#")] = 0; + + if (!*t) { + pa_xfree(t); + return NULL; + } + + if ((e = strrchr(t, '('))) { + char *k = e + 1 + strspn(e + 1, "0123456789-"); + + /* Dirty trick: truncate all trailing parens with numbers in + * between, since they are usually used to identify multiple + * sessions of the same application, which is something we + * explicitly don't want. Besides other stuff this makes xmms + * with esound work properly for us. */ + + if (*k == ')' && *(k+1) == 0) + *e = 0; + } + + return t; +} + +static void save_time_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + + pa_assert(a); + pa_assert(e); + pa_assert(tv); + pa_assert(u); + + pa_assert(e == u->save_time_event); + u->core->mainloop->time_free(u->save_time_event); + u->save_time_event = NULL; + + save_rules(u); +} + +static void subscribe_callback(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + struct userdata *u = userdata; + pa_sink_input *si = NULL; + pa_source_output *so = NULL; + struct rule *r; + char *name; + + pa_assert(c); + pa_assert(u); + + if (t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW) && + t != (PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE)) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK_INPUT) { + if (!(si = pa_idxset_get_by_index(c->sink_inputs, idx))) + return; + + if (!si->client || !(name = client_name(si->client))) + return; + } else { + pa_assert((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT); + + if (!(so = pa_idxset_get_by_index(c->source_outputs, idx))) + return; + + if (!so->client || !(name = client_name(so->client))) + return; + } + + if ((r = pa_hashmap_get(u->hashmap, name))) { + pa_xfree(name); + + if (si) { + + if (!r->volume_is_set || !pa_cvolume_equal(pa_sink_input_get_volume(si), &r->volume)) { + pa_log_info("Saving volume for <%s>", r->name); + r->volume = *pa_sink_input_get_volume(si); + r->volume_is_set = TRUE; + u->modified = TRUE; + } + + if (!r->sink || strcmp(si->sink->name, r->sink) != 0) { + pa_log_info("Saving sink for <%s>", r->name); + pa_xfree(r->sink); + r->sink = pa_xstrdup(si->sink->name); + u->modified = TRUE; + } + } else { + pa_assert(so); + + if (!r->source || strcmp(so->source->name, r->source) != 0) { + pa_log_info("Saving source for <%s>", r->name); + pa_xfree(r->source); + r->source = pa_xstrdup(so->source->name); + u->modified = TRUE; + } + } + + } else { + pa_log_info("Creating new entry for <%s>", name); + + r = pa_xnew(struct rule, 1); + r->name = name; + + if (si) { + r->volume = *pa_sink_input_get_volume(si); + r->volume_is_set = TRUE; + r->sink = pa_xstrdup(si->sink->name); + r->source = NULL; + } else { + pa_assert(so); + r->volume_is_set = FALSE; + r->sink = NULL; + r->source = pa_xstrdup(so->source->name); + } + + pa_hashmap_put(u->hashmap, r->name, r); + u->modified = TRUE; + } + + if (u->modified && !u->save_time_event) { + struct timeval tv; + pa_gettimeofday(&tv); + tv.tv_sec += SAVE_INTERVAL; + u->save_time_event = u->core->mainloop->time_new(u->core->mainloop, &tv, save_time_callback, u); + } +} + +static pa_hook_result_t sink_input_new_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { + struct rule *r; + char *name; + + pa_assert(data); + + /* In the NEW hook we only adjust the device. Adjusting the volume + * is left for the FIXATE hook */ + + if (!data->client || !(name = client_name(data->client))) + return PA_HOOK_OK; + + if ((r = pa_hashmap_get(u->hashmap, name))) { + if (!data->sink && r->sink) { + if ((data->sink = pa_namereg_get(c, r->sink, PA_NAMEREG_SINK, 1))) + pa_log_info("Restoring sink for <%s>", r->name); + } + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t sink_input_fixate_hook_callback(pa_core *c, pa_sink_input_new_data *data, struct userdata *u) { + struct rule *r; + char *name; + + pa_assert(data); + + /* In the FIXATE hook we only adjust the volum. Adjusting the device + * is left for the NEW hook */ + + if (!data->client || !(name = client_name(data->client))) + return PA_HOOK_OK; + + if ((r = pa_hashmap_get(u->hashmap, name))) { + + if (r->volume_is_set && data->sample_spec.channels == r->volume.channels) { + pa_log_info("Restoring volume for <%s>", r->name); + pa_sink_input_new_data_set_volume(data, &r->volume); + } + } + + pa_xfree(name); + + return PA_HOOK_OK; +} + +static pa_hook_result_t source_output_new_hook_callback(pa_core *c, pa_source_output_new_data *data, struct userdata *u) { + struct rule *r; + char *name; + + pa_assert(data); + + if (!data->client || !(name = client_name(data->client))) + return PA_HOOK_OK; + + if ((r = pa_hashmap_get(u->hashmap, name))) { + if (!data->source && r->source) { + if ((data->source = pa_namereg_get(c, r->source, PA_NAMEREG_SOURCE, 1))) + pa_log_info("Restoring source for <%s>", r->name); + } + } + + return PA_HOOK_OK; +} + +int pa__init(pa_module*m) { + pa_modargs *ma = NULL; + struct userdata *u; + pa_bool_t restore_device = TRUE, restore_volume = TRUE; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + u->table_file = pa_xstrdup(pa_modargs_get_value(ma, "table", NULL)); + u->modified = FALSE; + u->subscription = NULL; + u->sink_input_new_hook_slot = u->sink_input_fixate_hook_slot = u->source_output_new_hook_slot = NULL; + u->save_time_event = NULL; + + m->userdata = u; + + if (pa_modargs_get_value_boolean(ma, "restore_device", &restore_device) < 0 || + pa_modargs_get_value_boolean(ma, "restore_volume", &restore_volume) < 0) { + pa_log("restore_volume= and restore_device= expect boolean arguments"); + goto fail; + } + + if (!(restore_device || restore_volume)) { + pa_log("Both restrong the volume and restoring the device are disabled. There's no point in using this module at all then, failing."); + goto fail; + } + + if (load_rules(u) < 0) + goto fail; + + u->subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK_INPUT|PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, subscribe_callback, u); + + if (restore_device) { + u->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], (pa_hook_cb_t) sink_input_new_hook_callback, u); + u->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], (pa_hook_cb_t) source_output_new_hook_callback, u); + } + + if (restore_volume) + u->sink_input_fixate_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], (pa_hook_cb_t) sink_input_fixate_hook_callback, u); + + pa_modargs_free(ma); + return 0; + +fail: + pa__done(m); + if (ma) + pa_modargs_free(ma); + + return -1; +} + +static void free_func(void *p, void *userdata) { + struct rule *r = p; + pa_assert(r); + + pa_xfree(r->name); + pa_xfree(r->sink); + pa_xfree(r->source); + pa_xfree(r); +} + +void pa__done(pa_module*m) { + struct userdata* u; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->subscription) + pa_subscription_free(u->subscription); + + if (u->sink_input_new_hook_slot) + pa_hook_slot_free(u->sink_input_new_hook_slot); + if (u->sink_input_fixate_hook_slot) + pa_hook_slot_free(u->sink_input_fixate_hook_slot); + if (u->source_output_new_hook_slot) + pa_hook_slot_free(u->source_output_new_hook_slot); + + if (u->hashmap) { + save_rules(u); + pa_hashmap_free(u->hashmap, free_func, NULL); + } + + if (u->save_time_event) + u->core->mainloop->time_free(u->save_time_event); + + pa_xfree(u->table_file); + pa_xfree(u); +} diff --git a/src/modules/module-waveout.c b/src/modules/module-waveout.c new file mode 100644 index 00000000..f8bae02f --- /dev/null +++ b/src/modules/module-waveout.c @@ -0,0 +1,649 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <windows.h> +#include <mmsystem.h> + +#include <pulse/mainloop-api.h> + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> + +#include "module-waveout-symdef.h" + +PA_MODULE_AUTHOR("Pierre Ossman") +PA_MODULE_DESCRIPTION("Windows waveOut Sink/Source") +PA_MODULE_VERSION(PACKAGE_VERSION) +PA_MODULE_USAGE( + "sink_name=<name for the sink> " + "source_name=<name for the source> " + "device=<device number> " + "record=<enable source?> " + "playback=<enable sink?> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "fragments=<number of fragments> " + "fragment_size=<fragment size> " + "channel_map=<channel map>") + +#define DEFAULT_SINK_NAME "wave_output" +#define DEFAULT_SOURCE_NAME "wave_input" + +#define WAVEOUT_MAX_VOLUME 0xFFFF + +struct userdata { + pa_sink *sink; + pa_source *source; + pa_core *core; + pa_time_event *event; + pa_defer_event *defer; + pa_usec_t poll_timeout; + + uint32_t fragments, fragment_size; + + uint32_t free_ofrags, free_ifrags; + + DWORD written_bytes; + int sink_underflow; + + int cur_ohdr, cur_ihdr; + WAVEHDR *ohdrs, *ihdrs; + + HWAVEOUT hwo; + HWAVEIN hwi; + pa_module *module; + + CRITICAL_SECTION crit; +}; + +static const char* const valid_modargs[] = { + "sink_name", + "source_name", + "device", + "record", + "playback", + "fragments", + "fragment_size", + "format", + "rate", + "channels", + "channel_map", + NULL +}; + +static void update_usage(struct userdata *u) { + pa_module_set_used(u->module, + (u->sink ? pa_sink_used_by(u->sink) : 0) + + (u->source ? pa_source_used_by(u->source) : 0)); +} + +static void do_write(struct userdata *u) +{ + uint32_t free_frags; + pa_memchunk memchunk; + WAVEHDR *hdr; + MMRESULT res; + + if (!u->sink) + return; + + EnterCriticalSection(&u->crit); + free_frags = u->free_ofrags; + LeaveCriticalSection(&u->crit); + + if (!u->sink_underflow && (free_frags == u->fragments)) + pa_log_debug("WaveOut underflow!"); + + while (free_frags) { + hdr = &u->ohdrs[u->cur_ohdr]; + if (hdr->dwFlags & WHDR_PREPARED) + waveOutUnprepareHeader(u->hwo, hdr, sizeof(WAVEHDR)); + + hdr->dwBufferLength = 0; + while (hdr->dwBufferLength < u->fragment_size) { + size_t len; + + len = u->fragment_size - hdr->dwBufferLength; + + if (pa_sink_render(u->sink, len, &memchunk) < 0) + break; + + assert(memchunk.memblock); + assert(memchunk.memblock->data); + assert(memchunk.length); + + if (memchunk.length < len) + len = memchunk.length; + + memcpy(hdr->lpData + hdr->dwBufferLength, + (char*)memchunk.memblock->data + memchunk.index, len); + + hdr->dwBufferLength += len; + + pa_memblock_unref(memchunk.memblock); + memchunk.memblock = NULL; + } + + /* Insufficient data in sink buffer? */ + if (hdr->dwBufferLength == 0) { + u->sink_underflow = 1; + break; + } + + u->sink_underflow = 0; + + res = waveOutPrepareHeader(u->hwo, hdr, sizeof(WAVEHDR)); + if (res != MMSYSERR_NOERROR) { + pa_log_error(__FILE__ ": ERROR: Unable to prepare waveOut block: %d", + res); + } + res = waveOutWrite(u->hwo, hdr, sizeof(WAVEHDR)); + if (res != MMSYSERR_NOERROR) { + pa_log_error(__FILE__ ": ERROR: Unable to write waveOut block: %d", + res); + } + + u->written_bytes += hdr->dwBufferLength; + + EnterCriticalSection(&u->crit); + u->free_ofrags--; + LeaveCriticalSection(&u->crit); + + free_frags--; + u->cur_ohdr++; + u->cur_ohdr %= u->fragments; + } +} + +static void do_read(struct userdata *u) +{ + uint32_t free_frags; + pa_memchunk memchunk; + WAVEHDR *hdr; + MMRESULT res; + + if (!u->source) + return; + + EnterCriticalSection(&u->crit); + + free_frags = u->free_ifrags; + u->free_ifrags = 0; + + LeaveCriticalSection(&u->crit); + + if (free_frags == u->fragments) + pa_log_debug("WaveIn overflow!"); + + while (free_frags) { + hdr = &u->ihdrs[u->cur_ihdr]; + if (hdr->dwFlags & WHDR_PREPARED) + waveInUnprepareHeader(u->hwi, hdr, sizeof(WAVEHDR)); + + if (hdr->dwBytesRecorded) { + memchunk.memblock = pa_memblock_new(u->core->mempool, hdr->dwBytesRecorded); + assert(memchunk.memblock); + + memcpy((char*)memchunk.memblock->data, hdr->lpData, hdr->dwBytesRecorded); + + memchunk.length = memchunk.memblock->length = hdr->dwBytesRecorded; + memchunk.index = 0; + + pa_source_post(u->source, &memchunk); + pa_memblock_unref(memchunk.memblock); + } + + res = waveInPrepareHeader(u->hwi, hdr, sizeof(WAVEHDR)); + if (res != MMSYSERR_NOERROR) { + pa_log_error(__FILE__ ": ERROR: Unable to prepare waveIn block: %d", + res); + } + res = waveInAddBuffer(u->hwi, hdr, sizeof(WAVEHDR)); + if (res != MMSYSERR_NOERROR) { + pa_log_error(__FILE__ ": ERROR: Unable to add waveIn block: %d", + res); + } + + free_frags--; + u->cur_ihdr++; + u->cur_ihdr %= u->fragments; + } +} + +static void poll_cb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + struct timeval ntv; + + assert(u); + + update_usage(u); + + do_write(u); + do_read(u); + + pa_gettimeofday(&ntv); + pa_timeval_add(&ntv, u->poll_timeout); + + a->time_restart(e, &ntv); +} + +static void defer_cb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) { + struct userdata *u = userdata; + + assert(u); + + a->defer_enable(e, 0); + + do_write(u); + do_read(u); +} + +static void CALLBACK chunk_done_cb(HWAVEOUT hwo, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) { + struct userdata *u = (struct userdata *)inst; + + if (msg != WOM_DONE) + return; + + EnterCriticalSection(&u->crit); + + u->free_ofrags++; + assert(u->free_ofrags <= u->fragments); + + LeaveCriticalSection(&u->crit); +} + +static void CALLBACK chunk_ready_cb(HWAVEIN hwi, UINT msg, DWORD_PTR inst, DWORD param1, DWORD param2) { + struct userdata *u = (struct userdata *)inst; + + if (msg != WIM_DATA) + return; + + EnterCriticalSection(&u->crit); + + u->free_ifrags++; + assert(u->free_ifrags <= u->fragments); + + LeaveCriticalSection(&u->crit); +} + +static pa_usec_t sink_get_latency_cb(pa_sink *s) { + struct userdata *u = s->userdata; + uint32_t free_frags; + MMTIME mmt; + assert(s && u && u->sink); + + memset(&mmt, 0, sizeof(mmt)); + mmt.wType = TIME_BYTES; + if (waveOutGetPosition(u->hwo, &mmt, sizeof(mmt)) == MMSYSERR_NOERROR) + return pa_bytes_to_usec(u->written_bytes - mmt.u.cb, &s->sample_spec); + else { + EnterCriticalSection(&u->crit); + + free_frags = u->free_ofrags; + + LeaveCriticalSection(&u->crit); + + return pa_bytes_to_usec((u->fragments - free_frags) * u->fragment_size, + &s->sample_spec); + } +} + +static pa_usec_t source_get_latency_cb(pa_source *s) { + pa_usec_t r = 0; + struct userdata *u = s->userdata; + uint32_t free_frags; + assert(s && u && u->sink); + + EnterCriticalSection(&u->crit); + + free_frags = u->free_ifrags; + + LeaveCriticalSection(&u->crit); + + r += pa_bytes_to_usec((free_frags + 1) * u->fragment_size, &s->sample_spec); + + return r; +} + +static void notify_sink_cb(pa_sink *s) { + struct userdata *u = s->userdata; + assert(u); + + u->core->mainloop->defer_enable(u->defer, 1); +} + +static void notify_source_cb(pa_source *s) { + struct userdata *u = s->userdata; + assert(u); + + u->core->mainloop->defer_enable(u->defer, 1); +} + +static int sink_get_hw_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + DWORD vol; + pa_volume_t left, right; + + if (waveOutGetVolume(u->hwo, &vol) != MMSYSERR_NOERROR) + return -1; + + left = (vol & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME; + right = ((vol >> 16) & 0xFFFF) * PA_VOLUME_NORM / WAVEOUT_MAX_VOLUME; + + /* Windows supports > 2 channels, except for volume control */ + if (s->hw_volume.channels > 2) + pa_cvolume_set(&s->hw_volume, s->hw_volume.channels, (left + right)/2); + + s->hw_volume.values[0] = left; + if (s->hw_volume.channels > 1) + s->hw_volume.values[1] = right; + + return 0; +} + +static int sink_set_hw_volume_cb(pa_sink *s) { + struct userdata *u = s->userdata; + DWORD vol; + + vol = s->hw_volume.values[0] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM; + if (s->hw_volume.channels > 1) + vol |= (s->hw_volume.values[0] * WAVEOUT_MAX_VOLUME / PA_VOLUME_NORM) << 16; + + if (waveOutSetVolume(u->hwo, vol) != MMSYSERR_NOERROR) + return -1; + + return 0; +} + +static int ss_to_waveformat(pa_sample_spec *ss, LPWAVEFORMATEX wf) { + wf->wFormatTag = WAVE_FORMAT_PCM; + + if (ss->channels > 2) { + pa_log_error("ERROR: More than two channels not supported."); + return -1; + } + + wf->nChannels = ss->channels; + + switch (ss->rate) { + case 8000: + case 11025: + case 22005: + case 44100: + break; + default: + pa_log_error("ERROR: Unsupported sample rate."); + return -1; + } + + wf->nSamplesPerSec = ss->rate; + + if (ss->format == PA_SAMPLE_U8) + wf->wBitsPerSample = 8; + else if (ss->format == PA_SAMPLE_S16NE) + wf->wBitsPerSample = 16; + else { + pa_log_error("ERROR: Unsupported sample format."); + return -1; + } + + wf->nBlockAlign = wf->nChannels * wf->wBitsPerSample/8; + wf->nAvgBytesPerSec = wf->nSamplesPerSec * wf->nBlockAlign; + + wf->cbSize = 0; + + return 0; +} + +int pa__init(pa_core *c, pa_module*m) { + struct userdata *u = NULL; + HWAVEOUT hwo = INVALID_HANDLE_VALUE; + HWAVEIN hwi = INVALID_HANDLE_VALUE; + WAVEFORMATEX wf; + int nfrags, frag_size; + int record = 1, playback = 1; + unsigned int device; + pa_sample_spec ss; + pa_channel_map map; + pa_modargs *ma = NULL; + unsigned int i; + struct timeval tv; + + assert(c && m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "record", &record) < 0 || pa_modargs_get_value_boolean(ma, "playback", &playback) < 0) { + pa_log("record= and playback= expect boolean argument."); + goto fail; + } + + if (!playback && !record) { + pa_log("neither playback nor record enabled for device."); + goto fail; + } + + device = WAVE_MAPPER; + if (pa_modargs_get_value_u32(ma, "device", &device) < 0) { + pa_log("failed to parse device argument"); + goto fail; + } + + nfrags = 5; + frag_size = 8192; + if (pa_modargs_get_value_s32(ma, "fragments", &nfrags) < 0 || pa_modargs_get_value_s32(ma, "fragment_size", &frag_size) < 0) { + pa_log("failed to parse fragments arguments"); + goto fail; + } + + ss = c->default_sample_spec; + if (pa_modargs_get_sample_spec_and_channel_map(ma, &ss, &map, PA_CHANNEL_MAP_WAVEEX) < 0) { + pa_log("failed to parse sample specification"); + goto fail; + } + + if (ss_to_waveformat(&ss, &wf) < 0) + goto fail; + + u = pa_xmalloc(sizeof(struct userdata)); + + if (record) { + if (waveInOpen(&hwi, device, &wf, (DWORD_PTR)chunk_ready_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) { + pa_log("failed to open waveIn"); + goto fail; + } + if (waveInStart(hwi) != MMSYSERR_NOERROR) { + pa_log("failed to start waveIn"); + goto fail; + } + pa_log_debug("Opened waveIn subsystem."); + } + + if (playback) { + if (waveOutOpen(&hwo, device, &wf, (DWORD_PTR)chunk_done_cb, (DWORD_PTR)u, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) { + pa_log("failed to open waveOut"); + goto fail; + } + pa_log_debug("Opened waveOut subsystem."); + } + + InitializeCriticalSection(&u->crit); + + if (hwi != INVALID_HANDLE_VALUE) { + u->source = pa_source_new(c, __FILE__, pa_modargs_get_value(ma, "source_name", DEFAULT_SOURCE_NAME), 0, &ss, &map); + assert(u->source); + u->source->userdata = u; + u->source->notify = notify_source_cb; + u->source->get_latency = source_get_latency_cb; + pa_source_set_owner(u->source, m); + pa_source_set_description(u->source, "Windows waveIn PCM"); + u->source->is_hardware = 1; + } else + u->source = NULL; + + if (hwo != INVALID_HANDLE_VALUE) { + u->sink = pa_sink_new(c, __FILE__, pa_modargs_get_value(ma, "sink_name", DEFAULT_SINK_NAME), 0, &ss, &map); + assert(u->sink); + u->sink->notify = notify_sink_cb; + u->sink->get_latency = sink_get_latency_cb; + u->sink->get_hw_volume = sink_get_hw_volume_cb; + u->sink->set_hw_volume = sink_set_hw_volume_cb; + u->sink->userdata = u; + pa_sink_set_owner(u->sink, m); + pa_sink_set_description(u->sink, "Windows waveOut PCM"); + u->sink->is_hardware = 1; + } else + u->sink = NULL; + + assert(u->source || u->sink); + + u->core = c; + u->hwi = hwi; + u->hwo = hwo; + + u->fragments = nfrags; + u->free_ifrags = u->fragments; + u->free_ofrags = u->fragments; + u->fragment_size = frag_size - (frag_size % pa_frame_size(&ss)); + + u->written_bytes = 0; + u->sink_underflow = 1; + + u->poll_timeout = pa_bytes_to_usec(u->fragments * u->fragment_size / 10, &ss); + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, u->poll_timeout); + + u->event = c->mainloop->time_new(c->mainloop, &tv, poll_cb, u); + assert(u->event); + + u->defer = c->mainloop->defer_new(c->mainloop, defer_cb, u); + assert(u->defer); + c->mainloop->defer_enable(u->defer, 0); + + u->cur_ihdr = 0; + u->cur_ohdr = 0; + u->ihdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments); + assert(u->ihdrs); + u->ohdrs = pa_xmalloc0(sizeof(WAVEHDR) * u->fragments); + assert(u->ohdrs); + for (i = 0;i < u->fragments;i++) { + u->ihdrs[i].dwBufferLength = u->fragment_size; + u->ohdrs[i].dwBufferLength = u->fragment_size; + u->ihdrs[i].lpData = pa_xmalloc(u->fragment_size); + assert(u->ihdrs); + u->ohdrs[i].lpData = pa_xmalloc(u->fragment_size); + assert(u->ohdrs); + } + + u->module = m; + m->userdata = u; + + pa_modargs_free(ma); + + /* Read mixer settings */ + if (u->sink) + sink_get_hw_volume_cb(u->sink); + + return 0; + +fail: + if (hwi != INVALID_HANDLE_VALUE) + waveInClose(hwi); + + if (hwo != INVALID_HANDLE_VALUE) + waveOutClose(hwo); + + if (u) + pa_xfree(u); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_core *c, pa_module*m) { + struct userdata *u; + unsigned int i; + + assert(c && m); + + if (!(u = m->userdata)) + return; + + if (u->event) + c->mainloop->time_free(u->event); + + if (u->defer) + c->mainloop->defer_free(u->defer); + + if (u->sink) { + pa_sink_disconnect(u->sink); + pa_sink_unref(u->sink); + } + + if (u->source) { + pa_source_disconnect(u->source); + pa_source_unref(u->source); + } + + if (u->hwi != INVALID_HANDLE_VALUE) { + waveInReset(u->hwi); + waveInClose(u->hwi); + } + + if (u->hwo != INVALID_HANDLE_VALUE) { + waveOutReset(u->hwo); + waveOutClose(u->hwo); + } + + for (i = 0;i < u->fragments;i++) { + pa_xfree(u->ihdrs[i].lpData); + pa_xfree(u->ohdrs[i].lpData); + } + + pa_xfree(u->ihdrs); + pa_xfree(u->ohdrs); + + DeleteCriticalSection(&u->crit); + + pa_xfree(u); +} diff --git a/src/modules/module-x11-bell.c b/src/modules/module-x11-bell.c new file mode 100644 index 00000000..87c6849d --- /dev/null +++ b/src/modules/module-x11-bell.c @@ -0,0 +1,171 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <X11/Xlib.h> +#include <X11/XKBlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/iochannel.h> +#include <pulsecore/sink.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/x11wrap.h> + +#include "module-x11-bell-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("X11 Bell interceptor"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("sink=<sink to connect to> sample=<sample name> display=<X11 display>"); + +struct userdata { + pa_core *core; + int xkb_event_base; + char *sink_name; + char *scache_item; + + pa_x11_wrapper *x11_wrapper; + pa_x11_client *x11_client; +}; + +static const char* const valid_modargs[] = { + "sink", + "sample", + "display", + NULL +}; + +static int x11_event_callback(pa_x11_wrapper *w, XEvent *e, void *userdata) { + XkbBellNotifyEvent *bne; + struct userdata *u = userdata; + + pa_assert(w); + pa_assert(e); + pa_assert(u); + pa_assert(u->x11_wrapper == w); + + if (((XkbEvent*) e)->any.xkb_type != XkbBellNotify) + return 0; + + bne = (XkbBellNotifyEvent*) e; + + if (pa_scache_play_item_by_name(u->core, u->scache_item, u->sink_name, (bne->percent*PA_VOLUME_NORM)/100, 1) < 0) { + pa_log_info("Ringing bell failed, reverting to X11 device bell."); + XkbForceDeviceBell(pa_x11_wrapper_get_display(w), bne->device, bne->bell_class, bne->bell_id, bne->percent); + } + + return 1; +} + +int pa__init(pa_module*m) { + + struct userdata *u = NULL; + pa_modargs *ma = NULL; + int major, minor; + unsigned int auto_ctrls, auto_values; + + pa_assert(m); + + 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->scache_item = pa_xstrdup(pa_modargs_get_value(ma, "sample", "x11-bell")); + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + u->x11_client = NULL; + + if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) + goto fail; + + major = XkbMajorVersion; + minor = XkbMinorVersion; + + if (!XkbLibraryVersion(&major, &minor)) { + pa_log("XkbLibraryVersion() failed"); + goto fail; + } + + major = XkbMajorVersion; + minor = XkbMinorVersion; + + if (!XkbQueryExtension(pa_x11_wrapper_get_display(u->x11_wrapper), NULL, &u->xkb_event_base, NULL, &major, &minor)) { + pa_log("XkbQueryExtension() failed"); + goto fail; + } + + XkbSelectEvents(pa_x11_wrapper_get_display(u->x11_wrapper), XkbUseCoreKbd, XkbBellNotifyMask, XkbBellNotifyMask); + auto_ctrls = auto_values = XkbAudibleBellMask; + XkbSetAutoResetControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbAudibleBellMask, &auto_ctrls, &auto_values); + XkbChangeEnabledControls(pa_x11_wrapper_get_display(u->x11_wrapper), XkbUseCoreKbd, XkbAudibleBellMask, 0); + + u->x11_client = pa_x11_client_new(u->x11_wrapper, x11_event_callback, u); + + 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 (!m->userdata) + return; + + u = m->userdata; + + pa_xfree(u->scache_item); + pa_xfree(u->sink_name); + + if (u->x11_client) + pa_x11_client_free(u->x11_client); + + if (u->x11_wrapper) + pa_x11_wrapper_unref(u->x11_wrapper); + + pa_xfree(u); +} diff --git a/src/modules/module-x11-publish.c b/src/modules/module-x11-publish.c new file mode 100644 index 00000000..429c2a69 --- /dev/null +++ b/src/modules/module-x11-publish.c @@ -0,0 +1,198 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <X11/Xlib.h> +#include <X11/Xatom.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/sink.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/x11wrap.h> +#include <pulsecore/core-util.h> +#include <pulsecore/native-common.h> +#include <pulsecore/authkey-prop.h> +#include <pulsecore/authkey.h> +#include <pulsecore/x11prop.h> +#include <pulsecore/strlist.h> +#include <pulsecore/props.h> + +#include "module-x11-publish-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("X11 Credential Publisher"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE("display=<X11 display>"); + +static const char* const valid_modargs[] = { + "display", + "sink", + "source", + "cookie", + NULL +}; + +struct userdata { + pa_core *core; + pa_x11_wrapper *x11_wrapper; + char *id; + uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH]; + int auth_cookie_in_property; +}; + +static int load_key(struct userdata *u, const char*fn) { + pa_assert(u); + + u->auth_cookie_in_property = 0; + + if (!fn && pa_authkey_prop_get(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) { + pa_log_debug("using already loaded auth cookie."); + pa_authkey_prop_ref(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME); + u->auth_cookie_in_property = 1; + return 0; + } + + if (!fn) + fn = PA_NATIVE_COOKIE_FILE; + + if (pa_authkey_load_auto(fn, u->auth_cookie, sizeof(u->auth_cookie)) < 0) + return -1; + + pa_log_debug("Loading cookie from disk."); + + if (pa_authkey_prop_put(u->core, PA_NATIVE_COOKIE_PROPERTY_NAME, u->auth_cookie, sizeof(u->auth_cookie)) >= 0) + u->auth_cookie_in_property = 1; + + return 0; +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_modargs *ma = NULL; + char hn[256], un[128]; + char hx[PA_NATIVE_COOKIE_LENGTH*2+1]; + const char *t; + char *s; + pa_strlist *l; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments"); + goto fail; + } + + m->userdata = u = pa_xmalloc(sizeof(struct userdata)); + u->core = m->core; + u->id = NULL; + u->auth_cookie_in_property = 0; + + if (load_key(u, pa_modargs_get_value(ma, "cookie", NULL)) < 0) + goto fail; + + if (!(u->x11_wrapper = pa_x11_wrapper_get(m->core, pa_modargs_get_value(ma, "display", NULL)))) + goto fail; + + if (!(l = pa_property_get(m->core, PA_NATIVE_SERVER_PROPERTY_NAME))) + goto fail; + + l = pa_strlist_reverse(l); + s = pa_strlist_tostring(l); + l = pa_strlist_reverse(l); + + pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER", s); + pa_xfree(s); + + if (!pa_get_fqdn(hn, sizeof(hn)) || !pa_get_user_name(un, sizeof(un))) + goto fail; + + u->id = pa_sprintf_malloc("%s@%s/%u", un, hn, (unsigned) getpid()); + pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", u->id); + + if ((t = pa_modargs_get_value(ma, "source", NULL))) + pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SOURCE", t); + + if ((t = pa_modargs_get_value(ma, "sink", NULL))) + pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SINK", t); + + pa_x11_set_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE", pa_hexstr(u->auth_cookie, sizeof(u->auth_cookie), hx, sizeof(hx))); + + 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->x11_wrapper) { + char t[256]; + + /* Yes, here is a race condition */ + if (!pa_x11_get_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID", t, sizeof(t)) || strcmp(t, u->id)) + pa_log_warn("PulseAudio information vanished from X11!"); + else { + pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_ID"); + pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SERVER"); + pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SINK"); + pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_SOURCE"); + pa_x11_del_prop(pa_x11_wrapper_get_display(u->x11_wrapper), "PULSE_COOKIE"); + XSync(pa_x11_wrapper_get_display(u->x11_wrapper), False); + } + } + + if (u->x11_wrapper) + pa_x11_wrapper_unref(u->x11_wrapper); + + if (u->auth_cookie_in_property) + pa_authkey_prop_unref(m->core, PA_NATIVE_COOKIE_PROPERTY_NAME); + + pa_xfree(u->id); + pa_xfree(u); +} diff --git a/src/modules/module-x11-xsmp.c b/src/modules/module-x11-xsmp.c new file mode 100644 index 00000000..e9efa096 --- /dev/null +++ b/src/modules/module-x11-xsmp.c @@ -0,0 +1,196 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <X11/Xlib.h> +#include <X11/SM/SMlib.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/iochannel.h> +#include <pulsecore/sink.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> + +#include "module-x11-xsmp-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("X11 session management"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +static int ice_in_use = 0; + +static const char* const valid_modargs[] = { + NULL +}; + +static void die_cb(SmcConn connection, SmPointer client_data){ + pa_core *c = PA_CORE(client_data); + + pa_log_debug("Got die message from XSM. Exiting..."); + + pa_core_assert_ref(c); + c->mainloop->quit(c->mainloop, 0); +} + +static void save_complete_cb(SmcConn connection, SmPointer client_data) { +} + +static void shutdown_cancelled_cb(SmcConn connection, SmPointer client_data) { + SmcSaveYourselfDone(connection, True); +} + +static void save_yourself_cb(SmcConn connection, SmPointer client_data, int save_type, Bool _shutdown, int interact_style, Bool fast) { + SmcSaveYourselfDone(connection, True); +} + +static void ice_io_cb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { + IceConn connection = userdata; + + if (IceProcessMessages(connection, NULL, NULL) == IceProcessMessagesIOError) { + IceSetShutdownNegotiation(connection, False); + IceCloseConnection(connection); + } +} + +static void new_ice_connection(IceConn connection, IcePointer client_data, Bool opening, IcePointer *watch_data) { + pa_core *c = client_data; + + pa_assert(c); + + if (opening) + *watch_data = c->mainloop->io_new(c->mainloop, IceConnectionNumber(connection), PA_IO_EVENT_INPUT, ice_io_cb, connection); + else + c->mainloop->io_free(*watch_data); +} + +int pa__init(pa_module*m) { + + pa_modargs *ma = NULL; + char t[256], *vendor, *client_id; + SmcCallbacks callbacks; + SmProp prop_program, prop_user; + SmProp *prop_list[2]; + SmPropValue val_program, val_user; + SmcConn connection; + + pa_assert(m); + + if (ice_in_use) { + pa_log("module-x11-xsmp may no be loaded twice."); + return -1; + } + + IceAddConnectionWatch(new_ice_connection, m->core); + ice_in_use = 1; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (!getenv("SESSION_MANAGER")) { + pa_log("X11 session manager not running."); + goto fail; + } + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.die.callback = die_cb; + callbacks.die.client_data = m->core; + callbacks.save_yourself.callback = save_yourself_cb; + callbacks.save_yourself.client_data = m->core; + callbacks.save_complete.callback = save_complete_cb; + callbacks.save_complete.client_data = m->core; + callbacks.shutdown_cancelled.callback = shutdown_cancelled_cb; + callbacks.shutdown_cancelled.client_data = m->core; + + if (!(m->userdata = connection = SmcOpenConnection( + NULL, m->core, + SmProtoMajor, SmProtoMinor, + SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask, + &callbacks, NULL, &client_id, + sizeof(t), t))) { + + pa_log("Failed to open connection to session manager: %s", t); + goto fail; + } + + prop_program.name = (char*) SmProgram; + prop_program.type = (char*) SmARRAY8; + val_program.value = (char*) PACKAGE_NAME; + val_program.length = strlen(val_program.value); + prop_program.num_vals = 1; + prop_program.vals = &val_program; + prop_list[0] = &prop_program; + + prop_user.name = (char*) SmUserID; + prop_user.type = (char*) SmARRAY8; + pa_get_user_name(t, sizeof(t)); + val_user.value = t; + val_user.length = strlen(val_user.value); + prop_user.num_vals = 1; + prop_user.vals = &val_user; + prop_list[1] = &prop_user; + + SmcSetProperties(connection, PA_ELEMENTSOF(prop_list), prop_list); + + pa_log_info("Connected to session manager '%s' as '%s'.", vendor = SmcVendor(connection), client_id); + free(vendor); + free(client_id); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + pa__done(m); + + return -1; +} + +void pa__done(pa_module*m) { + pa_assert(m); + + if (m->userdata) + SmcCloseConnection(m->userdata, 0, NULL); + + if (ice_in_use) { + IceRemoveConnectionWatch(new_ice_connection, m->core); + ice_in_use = 0; + } +} diff --git a/src/modules/module-zeroconf-discover.c b/src/modules/module-zeroconf-discover.c new file mode 100644 index 00000000..4e76f448 --- /dev/null +++ b/src/modules/module-zeroconf-discover.c @@ -0,0 +1,443 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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-zeroconf-discover-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Discovery"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); + +#define SERVICE_TYPE_SINK "_pulse-sink._tcp" +#define SERVICE_TYPE_SOURCE "_non-monitor._sub._pulse-source._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 *source_browser, *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, *module_name, *args; + const char *t; + char at[AVAHI_ADDRESS_STR_MAX], cmt[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_sample_spec ss; + pa_channel_map cm; + AvahiStringList *l; + pa_bool_t channel_map_set = FALSE; + pa_module *m; + + ss = u->core->default_sample_spec; + pa_assert_se(pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_AUX)); + pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); + + for (l = txt; l; l = l->next) { + char *key, *value; + pa_assert_se(avahi_string_list_get_pair(l, &key, &value, NULL) == 0); + + if (strcmp(key, "device") == 0) { + pa_xfree(device); + device = value; + value = NULL; + } else if (strcmp(key, "rate") == 0) + ss.rate = atoi(value); + else if (strcmp(key, "channels") == 0) + ss.channels = atoi(value); + else if (strcmp(key, "format") == 0) + ss.format = pa_parse_sample_format(value); + else if (strcmp(key, "channel_map") == 0) { + pa_channel_map_parse(&cm, value); + channel_map_set = TRUE; + } + + avahi_free(key); + avahi_free(value); + } + + if (!channel_map_set && cm.channels != ss.channels) { + pa_assert_se(pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_AUX)); + pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_DEFAULT); + } + + if (!pa_sample_spec_valid(&ss)) { + pa_log("Service '%s' contains an invalid sample specification.", name); + avahi_free(device); + goto finish; + } + + if (!pa_channel_map_valid(&cm) || cm.channels != ss.channels) { + pa_log("Service '%s' contains an invalid channel map.", name); + avahi_free(device); + goto finish; + } + + if (device) + dname = pa_sprintf_malloc("tunnel.%s.%s", host_name, device); + else + dname = pa_sprintf_malloc("tunnel.%s", host_name); + + if (!pa_namereg_is_valid_name(dname)) { + pa_log("Cannot construct valid device name from credentials of service '%s'.", dname); + avahi_free(device); + pa_xfree(dname); + goto finish; + } + + t = strstr(type, "sink") ? "sink" : "source"; + + module_name = pa_sprintf_malloc("module-tunnel-%s", t); + args = pa_sprintf_malloc("server=[%s]:%u " + "%s=%s " + "format=%s " + "channels=%u " + "rate=%u " + "%s_name=%s " + "channel_map=%s", + avahi_address_snprint(at, sizeof(at), a), port, + t, device, + pa_sample_format_to_string(ss.format), + ss.channels, + ss.rate, + t, dname, + pa_channel_map_snprint(cmt, sizeof(cmt), &cm)); + + pa_log_debug("Loading module-tunnel-%s with arguments '%s'", module_name, args); + + if ((m = pa_module_load(u->core, module_name, args))) { + tnl->module_index = m->index; + pa_hashmap_put(u->tunnels, tnl, tnl); + tnl = NULL; + } + + pa_xfree(module_name); + pa_xfree(dname); + pa_xfree(args); + avahi_free(device); + } + +finish: + + avahi_service_resolver_free(r); + + if (tnl) + tunnel_free(tnl); +} + +static void browser_cb( + AvahiServiceBrowser *b, + AvahiIfIndex interface, AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, const char *type, const char *domain, + AvahiLookupResultFlags flags, + void *userdata) { + + struct userdata *u = userdata; + struct tunnel *t; + + pa_assert(u); + + if (flags & AVAHI_LOOKUP_RESULT_LOCAL) + return; + + t = tunnel_new(interface, protocol, name, type, domain); + + if (event == AVAHI_BROWSER_NEW) { + + if (!pa_hashmap_get(u->tunnels, t)) + if (!(avahi_service_resolver_new(u->client, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolver_cb, u))) + pa_log("avahi_service_resolver_new() failed: %s", avahi_strerror(avahi_client_errno(u->client))); + + /* We ignore the returned resolver object here, since the we don't + * need to attach any special data to it, and we can still destory + * it from the callback */ + + } else if (event == AVAHI_BROWSER_REMOVE) { + struct tunnel *t2; + + if ((t2 = pa_hashmap_get(u->tunnels, t))) { + pa_module_unload_by_index(u->core, t2->module_index); + pa_hashmap_remove(u->tunnels, t2); + tunnel_free(t2); + } + } + + tunnel_free(t); +} + +static void client_callback(AvahiClient *c, AvahiClientState state, void *userdata) { + struct userdata *u = userdata; + + pa_assert(c); + pa_assert(u); + + u->client = c; + + switch (state) { + case AVAHI_CLIENT_S_REGISTERING: + case AVAHI_CLIENT_S_RUNNING: + case AVAHI_CLIENT_S_COLLISION: + + if (!u->sink_browser) { + + if (!(u->sink_browser = avahi_service_browser_new( + c, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + SERVICE_TYPE_SINK, + NULL, + 0, + browser_cb, u))) { + + pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c))); + pa_module_unload_request(u->module); + } + } + + if (!u->source_browser) { + + if (!(u->source_browser = avahi_service_browser_new( + c, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + SERVICE_TYPE_SOURCE, + NULL, + 0, + browser_cb, u))) { + + pa_log("avahi_service_browser_new() failed: %s", avahi_strerror(avahi_client_errno(c))); + pa_module_unload_request(u->module); + } + } + + break; + + case AVAHI_CLIENT_FAILURE: + if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { + int error; + + pa_log_debug("Avahi daemon disconnected."); + + if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { + pa_log("avahi_client_new() failed: %s", avahi_strerror(error)); + pa_module_unload_request(u->module); + } + } + + /* Fall through */ + + case AVAHI_CLIENT_CONNECTING: + + if (u->sink_browser) { + avahi_service_browser_free(u->sink_browser); + u->sink_browser = NULL; + } + + if (u->source_browser) { + avahi_service_browser_free(u->source_browser); + u->source_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 = u->source_browser = NULL; + + u->tunnels = pa_hashmap_new(tunnel_hash, tunnel_compare); + + u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); + + if (!(u->client = avahi_client_new(u->avahi_poll, AVAHI_CLIENT_NO_FAIL, client_callback, u, &error))) { + pa_log("pa_avahi_client_new() failed: %s", avahi_strerror(error)); + goto fail; + } + + pa_modargs_free(ma); + + return 0; + +fail: + pa__done(m); + + if (ma) + pa_modargs_free(ma); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata*u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->client) + avahi_client_free(u->client); + + if (u->avahi_poll) + pa_avahi_poll_free(u->avahi_poll); + + if (u->tunnels) { + struct tunnel *t; + + while ((t = pa_hashmap_steal_first(u->tunnels))) { + pa_module_unload_by_index(u->core, t->module_index); + tunnel_free(t); + } + + pa_hashmap_free(u->tunnels, NULL, NULL); + } + + pa_xfree(u); +} diff --git a/src/modules/module-zeroconf-publish.c b/src/modules/module-zeroconf-publish.c new file mode 100644 index 00000000..46969a24 --- /dev/null +++ b/src/modules/module-zeroconf-publish.c @@ -0,0 +1,650 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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/publish.h> +#include <avahi-common/alternative.h> +#include <avahi-common/error.h> +#include <avahi-common/domain.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/dynarray.h> +#include <pulsecore/modargs.h> +#include <pulsecore/avahi-wrap.h> +#include <pulsecore/endianmacros.h> + +#include "module-zeroconf-publish-symdef.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("mDNS/DNS-SD Service Publisher"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(TRUE); +PA_MODULE_USAGE("port=<IP port number>"); + +#define SERVICE_TYPE_SINK "_pulse-sink._tcp" +#define SERVICE_TYPE_SOURCE "_pulse-source._tcp" +#define SERVICE_TYPE_SERVER "_pulse-server._tcp" +#define SERVICE_SUBTYPE_SINK_HARDWARE "_hardware._sub."SERVICE_TYPE_SINK +#define SERVICE_SUBTYPE_SINK_VIRTUAL "_virtual._sub."SERVICE_TYPE_SINK +#define SERVICE_SUBTYPE_SOURCE_HARDWARE "_hardware._sub."SERVICE_TYPE_SOURCE +#define SERVICE_SUBTYPE_SOURCE_VIRTUAL "_virtual._sub."SERVICE_TYPE_SOURCE +#define SERVICE_SUBTYPE_SOURCE_MONITOR "_monitor._sub."SERVICE_TYPE_SOURCE +#define SERVICE_SUBTYPE_SOURCE_NON_MONITOR "_non-monitor._sub."SERVICE_TYPE_SOURCE + +static const char* const valid_modargs[] = { + "port", + NULL +}; + +enum service_subtype { + SUBTYPE_HARDWARE, + SUBTYPE_VIRTUAL, + SUBTYPE_MONITOR +}; + +struct service { + struct userdata *userdata; + AvahiEntryGroup *entry_group; + char *service_name; + pa_object *device; + enum service_subtype subtype; +}; + +struct userdata { + pa_core *core; + pa_module *module; + AvahiPoll *avahi_poll; + AvahiClient *client; + + pa_hashmap *services; + char *service_name; + + AvahiEntryGroup *main_entry_group; + + uint16_t port; + + pa_hook_slot *sink_new_slot, *source_new_slot, *sink_unlink_slot, *source_unlink_slot, *sink_changed_slot, *source_changed_slot; +}; + +static void get_service_data(struct service *s, pa_sample_spec *ret_ss, pa_channel_map *ret_map, const char **ret_name, const char **ret_description, enum service_subtype *ret_subtype) { + pa_assert(s); + pa_assert(ret_ss); + pa_assert(ret_description); + pa_assert(ret_subtype); + + if (pa_sink_isinstance(s->device)) { + pa_sink *sink = PA_SINK(s->device); + + *ret_ss = sink->sample_spec; + *ret_map = sink->channel_map; + *ret_name = sink->name; + *ret_description = sink->description; + *ret_subtype = sink->flags & PA_SINK_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL; + + } else if (pa_source_isinstance(s->device)) { + pa_source *source = PA_SOURCE(s->device); + + *ret_ss = source->sample_spec; + *ret_map = source->channel_map; + *ret_name = source->name; + *ret_description = source->description; + *ret_subtype = source->monitor_of ? SUBTYPE_MONITOR : (source->flags & PA_SOURCE_HARDWARE ? SUBTYPE_HARDWARE : SUBTYPE_VIRTUAL); + + } else + pa_assert_not_reached(); +} + +static AvahiStringList* txt_record_server_data(pa_core *c, AvahiStringList *l) { + char s[128]; + + pa_assert(c); + + l = avahi_string_list_add_pair(l, "server-version", PACKAGE_NAME" "PACKAGE_VERSION); + l = avahi_string_list_add_pair(l, "user-name", pa_get_user_name(s, sizeof(s))); + l = avahi_string_list_add_pair(l, "fqdn", pa_get_fqdn(s, sizeof(s))); + l = avahi_string_list_add_printf(l, "cookie=0x%08x", c->cookie); + + return l; +} + +static int publish_service(struct service *s); + +static void service_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { + struct service *s = userdata; + + pa_assert(s); + + switch (state) { + + case AVAHI_ENTRY_GROUP_ESTABLISHED: + pa_log_info("Successfully established service %s.", s->service_name); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: { + char *t; + + t = avahi_alternative_service_name(s->service_name); + pa_log_info("Name collision, renaming %s to %s.", s->service_name, t); + pa_xfree(s->service_name); + s->service_name = t; + + publish_service(s); + break; + } + + case AVAHI_ENTRY_GROUP_FAILURE: { + pa_log("Failed to register service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + + avahi_entry_group_free(g); + s->entry_group = NULL; + + break; + } + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + ; + } +} + +static void service_free(struct service *s); + +static int publish_service(struct service *s) { + int r = -1; + AvahiStringList *txt = NULL; + const char *description = NULL, *name = NULL; + pa_sample_spec ss; + pa_channel_map map; + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + enum service_subtype subtype; + + const char * const subtype_text[] = { + [SUBTYPE_HARDWARE] = "hardware", + [SUBTYPE_VIRTUAL] = "virtual", + [SUBTYPE_MONITOR] = "monitor" + }; + + pa_assert(s); + + if (!s->userdata->client || avahi_client_get_state(s->userdata->client) != AVAHI_CLIENT_S_RUNNING) + return 0; + + if (!s->entry_group) { + if (!(s->entry_group = avahi_entry_group_new(s->userdata->client, service_entry_group_callback, s))) { + pa_log("avahi_entry_group_new(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; + } + } else + avahi_entry_group_reset(s->entry_group); + + txt = txt_record_server_data(s->userdata->core, txt); + + get_service_data(s, &ss, &map, &name, &description, &subtype); + txt = avahi_string_list_add_pair(txt, "device", name); + txt = avahi_string_list_add_printf(txt, "rate=%u", ss.rate); + txt = avahi_string_list_add_printf(txt, "channels=%u", ss.channels); + txt = avahi_string_list_add_pair(txt, "format", pa_sample_format_to_string(ss.format)); + txt = avahi_string_list_add_pair(txt, "channel_map", pa_channel_map_snprint(cm, sizeof(cm), &map)); + txt = avahi_string_list_add_pair(txt, "subtype", subtype_text[subtype]); + + if (avahi_entry_group_add_service_strlst( + s->entry_group, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + 0, + s->service_name, + pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, + NULL, + NULL, + s->userdata->port, + txt) < 0) { + + pa_log("avahi_entry_group_add_service_strlst(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; + } + + if (avahi_entry_group_add_service_subtype( + s->entry_group, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + 0, + s->service_name, + pa_sink_isinstance(s->device) ? SERVICE_TYPE_SINK : SERVICE_TYPE_SOURCE, + NULL, + pa_sink_isinstance(s->device) ? (subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SINK_HARDWARE : SERVICE_SUBTYPE_SINK_VIRTUAL) : + (subtype == SUBTYPE_HARDWARE ? SERVICE_SUBTYPE_SOURCE_HARDWARE : (subtype == SUBTYPE_VIRTUAL ? SERVICE_SUBTYPE_SOURCE_VIRTUAL : SERVICE_SUBTYPE_SOURCE_MONITOR))) < 0) { + + pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; + } + + if (pa_source_isinstance(s->device) && subtype != SUBTYPE_MONITOR) { + if (avahi_entry_group_add_service_subtype( + s->entry_group, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + 0, + s->service_name, + SERVICE_TYPE_SOURCE, + NULL, + SERVICE_SUBTYPE_SOURCE_NON_MONITOR) < 0) { + + pa_log("avahi_entry_group_add_service_subtype(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; + } + } + + if (avahi_entry_group_commit(s->entry_group) < 0) { + pa_log("avahi_entry_group_commit(): %s", avahi_strerror(avahi_client_errno(s->userdata->client))); + goto finish; + } + + r = 0; + pa_log_debug("Successfully created entry group for %s.", s->service_name); + +finish: + + /* Remove this service */ + if (r < 0) + service_free(s); + + avahi_string_list_free(txt); + + return r; +} + +static struct service *get_service(struct userdata *u, pa_object *device) { + struct service *s; + char hn[64], un[64]; + const char *n; + + pa_assert(u); + pa_object_assert_ref(device); + + if ((s = pa_hashmap_get(u->services, device))) + return s; + + s = pa_xnew(struct service, 1); + s->userdata = u; + s->entry_group = NULL; + s->device = device; + + if (pa_sink_isinstance(device)) { + if (!(n = PA_SINK(device)->description)) + n = PA_SINK(device)->name; + } else { + if (!(n = PA_SOURCE(device)->description)) + n = PA_SOURCE(device)->name; + } + + s->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s: %s", + pa_get_user_name(un, sizeof(un)), + pa_get_host_name(hn, sizeof(hn)), + n), + AVAHI_LABEL_MAX-1); + + pa_hashmap_put(u->services, s->device, s); + + return s; +} + +static void service_free(struct service *s) { + pa_assert(s); + + pa_hashmap_remove(s->userdata->services, s->device); + + if (s->entry_group) { + pa_log_debug("Removing entry group for %s.", s->service_name); + avahi_entry_group_free(s->entry_group); + } + + pa_xfree(s->service_name); + pa_xfree(s); +} + +static pa_bool_t shall_ignore(pa_object *o) { + pa_object_assert_ref(o); + + if (pa_sink_isinstance(o)) + return !!(PA_SINK(o)->flags & PA_SINK_NETWORK); + + if (pa_source_isinstance(o)) + return PA_SOURCE(o)->monitor_of || (PA_SOURCE(o)->flags & PA_SOURCE_NETWORK); + + pa_assert_not_reached(); +} + +static pa_hook_result_t device_new_or_changed_cb(pa_core *c, pa_object *o, struct userdata *u) { + pa_assert(c); + pa_object_assert_ref(o); + + if (!shall_ignore(o)) + publish_service(get_service(u, o)); + + return PA_HOOK_OK; +} + +static pa_hook_result_t device_unlink_cb(pa_core *c, pa_object *o, struct userdata *u) { + struct service *s; + + pa_assert(c); + pa_object_assert_ref(o); + + if ((s = pa_hashmap_get(u->services, o))) + service_free(s); + + return PA_HOOK_OK; +} + +static int publish_main_service(struct userdata *u); + +static void main_entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *userdata) { + struct userdata *u = userdata; + pa_assert(u); + + switch (state) { + + case AVAHI_ENTRY_GROUP_ESTABLISHED: + pa_log_info("Successfully established main service."); + break; + + case AVAHI_ENTRY_GROUP_COLLISION: { + char *t; + + t = avahi_alternative_service_name(u->service_name); + pa_log_info("Name collision: renaming main service %s to %s.", u->service_name, t); + pa_xfree(u->service_name); + u->service_name = t; + + publish_main_service(u); + break; + } + + case AVAHI_ENTRY_GROUP_FAILURE: { + pa_log("Failed to register main service: %s", avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(g)))); + + avahi_entry_group_free(g); + u->main_entry_group = NULL; + break; + } + + case AVAHI_ENTRY_GROUP_UNCOMMITED: + case AVAHI_ENTRY_GROUP_REGISTERING: + break; + } +} + +static int publish_main_service(struct userdata *u) { + AvahiStringList *txt = NULL; + int r = -1; + + pa_assert(u); + + if (!u->main_entry_group) { + if (!(u->main_entry_group = avahi_entry_group_new(u->client, main_entry_group_callback, u))) { + pa_log("avahi_entry_group_new() failed: %s", avahi_strerror(avahi_client_errno(u->client))); + goto fail; + } + } else + avahi_entry_group_reset(u->main_entry_group); + + txt = txt_record_server_data(u->core, txt); + + if (avahi_entry_group_add_service_strlst( + u->main_entry_group, + AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, + 0, + u->service_name, + SERVICE_TYPE_SERVER, + NULL, + NULL, + u->port, + txt) < 0) { + + pa_log("avahi_entry_group_add_service_strlst() failed: %s", avahi_strerror(avahi_client_errno(u->client))); + goto fail; + } + + if (avahi_entry_group_commit(u->main_entry_group) < 0) { + pa_log("avahi_entry_group_commit() failed: %s", avahi_strerror(avahi_client_errno(u->client))); + goto fail; + } + + r = 0; + +fail: + avahi_string_list_free(txt); + + return r; +} + +static int publish_all_services(struct userdata *u) { + pa_sink *sink; + pa_source *source; + int r = -1; + uint32_t idx; + + pa_assert(u); + + pa_log_debug("Publishing services in Zeroconf"); + + for (sink = PA_SINK(pa_idxset_first(u->core->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(u->core->sinks, &idx))) + if (!shall_ignore(PA_OBJECT(sink))) + publish_service(get_service(u, PA_OBJECT(sink))); + + for (source = PA_SOURCE(pa_idxset_first(u->core->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(u->core->sources, &idx))) + if (!shall_ignore(PA_OBJECT(source))) + publish_service(get_service(u, PA_OBJECT(source))); + + if (publish_main_service(u) < 0) + goto fail; + + r = 0; + +fail: + return r; +} + +static void unpublish_all_services(struct userdata *u, pa_bool_t rem) { + void *state = NULL; + struct service *s; + + pa_assert(u); + + pa_log_debug("Unpublishing services in Zeroconf"); + + while ((s = pa_hashmap_iterate(u->services, &state, NULL))) { + if (s->entry_group) { + if (rem) { + pa_log_debug("Removing entry group for %s.", s->service_name); + avahi_entry_group_free(s->entry_group); + s->entry_group = NULL; + } else { + avahi_entry_group_reset(s->entry_group); + pa_log_debug("Resetting entry group for %s.", s->service_name); + } + } + } + + if (u->main_entry_group) { + if (rem) { + pa_log_debug("Removing main entry group."); + avahi_entry_group_free(u->main_entry_group); + u->main_entry_group = NULL; + } else { + avahi_entry_group_reset(u->main_entry_group); + pa_log_debug("Resetting main entry group."); + } + } +} + +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_RUNNING: + publish_all_services(u); + break; + + case AVAHI_CLIENT_S_COLLISION: + pa_log_debug("Host name collision"); + unpublish_all_services(u, FALSE); + break; + + case AVAHI_CLIENT_FAILURE: + if (avahi_client_errno(c) == AVAHI_ERR_DISCONNECTED) { + int error; + + pa_log_debug("Avahi daemon disconnected."); + + unpublish_all_services(u, TRUE); + avahi_client_free(u->client); + + 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); + } + } + + break; + + default: ; + } +} + +int pa__init(pa_module*m) { + + struct userdata *u; + uint32_t port = PA_NATIVE_DEFAULT_PORT; + pa_modargs *ma = NULL; + char hn[256], un[256]; + int error; + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments."); + goto fail; + } + + if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port <= 0 || port > 0xFFFF) { + pa_log("Invalid port specified."); + goto fail; + } + + m->userdata = u = pa_xnew(struct userdata, 1); + u->core = m->core; + u->module = m; + u->port = (uint16_t) port; + + u->avahi_poll = pa_avahi_poll_new(m->core->mainloop); + + u->services = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + + u->sink_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u); + u->sink_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_UNLINK], (pa_hook_cb_t) device_unlink_cb, u); + u->source_new_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED], (pa_hook_cb_t) device_new_or_changed_cb, u); + u->source_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], (pa_hook_cb_t) device_unlink_cb, u); + + u->main_entry_group = NULL; + + u->service_name = pa_truncate_utf8(pa_sprintf_malloc("%s@%s", pa_get_user_name(un, sizeof(un)), pa_get_host_name(hn, sizeof(hn))), AVAHI_LABEL_MAX); + + 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)); + 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->services) { + struct service *s; + + while ((s = pa_hashmap_get_first(u->services))) + service_free(s); + + pa_hashmap_free(u->services, NULL, NULL); + } + + if (u->sink_new_slot) + pa_hook_slot_free(u->sink_new_slot); + if (u->source_new_slot) + pa_hook_slot_free(u->source_new_slot); + if (u->sink_changed_slot) + pa_hook_slot_free(u->sink_changed_slot); + if (u->source_changed_slot) + pa_hook_slot_free(u->source_changed_slot); + if (u->sink_unlink_slot) + pa_hook_slot_free(u->sink_unlink_slot); + if (u->source_unlink_slot) + pa_hook_slot_free(u->source_unlink_slot); + + if (u->main_entry_group) + avahi_entry_group_free(u->main_entry_group); + + if (u->client) + avahi_client_free(u->client); + + if (u->avahi_poll) + pa_avahi_poll_free(u->avahi_poll); + + pa_xfree(u->service_name); + pa_xfree(u); +} diff --git a/src/modules/oss-util.c b/src/modules/oss-util.c new file mode 100644 index 00000000..9598feee --- /dev/null +++ b/src/modules/oss-util.c @@ -0,0 +1,419 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/soundcard.h> +#include <sys/ioctl.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "oss-util.h" + +int pa_oss_open(const char *device, int *mode, int* pcaps) { + int fd = -1; + int caps; + + pa_assert(device); + pa_assert(mode); + pa_assert(*mode == O_RDWR || *mode == O_RDONLY || *mode == O_WRONLY); + + if(!pcaps) + pcaps = ∩︀ + + if (*mode == O_RDWR) { + if ((fd = open(device, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) { + ioctl(fd, SNDCTL_DSP_SETDUPLEX, 0); + + if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { + pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); + goto fail; + } + + if (*pcaps & DSP_CAP_DUPLEX) + goto success; + + pa_log_warn("'%s' doesn't support full duplex", device); + + pa_close(fd); + } + + if ((fd = open(device, (*mode = O_WRONLY)|O_NDELAY|O_NOCTTY)) < 0) { + if ((fd = open(device, (*mode = O_RDONLY)|O_NDELAY|O_NOCTTY)) < 0) { + pa_log("open('%s'): %s", device, pa_cstrerror(errno)); + goto fail; + } + } + } else { + if ((fd = open(device, *mode|O_NDELAY|O_NOCTTY)) < 0) { + pa_log("open('%s'): %s", device, pa_cstrerror(errno)); + goto fail; + } + } + + *pcaps = 0; + + if (ioctl(fd, SNDCTL_DSP_GETCAPS, pcaps) < 0) { + pa_log("SNDCTL_DSP_GETCAPS: %s", pa_cstrerror(errno)); + goto fail; + } + +success: + + pa_log_debug("capabilities:%s%s%s%s%s%s%s%s%s%s%s%s%s%s", + *pcaps & DSP_CAP_BATCH ? " BATCH" : "", +#ifdef DSP_CAP_BIND + *pcaps & DSP_CAP_BIND ? " BIND" : "", +#else + "", +#endif + *pcaps & DSP_CAP_COPROC ? " COPROC" : "", + *pcaps & DSP_CAP_DUPLEX ? " DUPLEX" : "", +#ifdef DSP_CAP_FREERATE + *pcaps & DSP_CAP_FREERATE ? " FREERATE" : "", +#else + "", +#endif +#ifdef DSP_CAP_INPUT + *pcaps & DSP_CAP_INPUT ? " INPUT" : "", +#else + "", +#endif + *pcaps & DSP_CAP_MMAP ? " MMAP" : "", +#ifdef DSP_CAP_MODEM + *pcaps & DSP_CAP_MODEM ? " MODEM" : "", +#else + "", +#endif +#ifdef DSP_CAP_MULTI + *pcaps & DSP_CAP_MULTI ? " MULTI" : "", +#else + "", +#endif +#ifdef DSP_CAP_OUTPUT + *pcaps & DSP_CAP_OUTPUT ? " OUTPUT" : "", +#else + "", +#endif + *pcaps & DSP_CAP_REALTIME ? " REALTIME" : "", +#ifdef DSP_CAP_SHADOW + *pcaps & DSP_CAP_SHADOW ? " SHADOW" : "", +#else + "", +#endif +#ifdef DSP_CAP_VIRTUAL + *pcaps & DSP_CAP_VIRTUAL ? " VIRTUAL" : "", +#else + "", +#endif + *pcaps & DSP_CAP_TRIGGER ? " TRIGGER" : ""); + + pa_make_fd_cloexec(fd); + + return fd; + +fail: + if (fd >= 0) + pa_close(fd); + return -1; +} + +int pa_oss_auto_format(int fd, pa_sample_spec *ss) { + int format, channels, speed, reqformat; + pa_sample_format_t orig_format; + + static const int format_trans[PA_SAMPLE_MAX] = { + [PA_SAMPLE_U8] = AFMT_U8, + [PA_SAMPLE_ALAW] = AFMT_A_LAW, + [PA_SAMPLE_ULAW] = AFMT_MU_LAW, + [PA_SAMPLE_S16LE] = AFMT_S16_LE, + [PA_SAMPLE_S16BE] = AFMT_S16_BE, + [PA_SAMPLE_FLOAT32LE] = AFMT_QUERY, /* not supported */ + [PA_SAMPLE_FLOAT32BE] = AFMT_QUERY, /* not supported */ + [PA_SAMPLE_S32LE] = AFMT_QUERY, /* not supported */ + [PA_SAMPLE_S32BE] = AFMT_QUERY, /* not supported */ + }; + + pa_assert(fd >= 0); + pa_assert(ss); + + orig_format = ss->format; + + reqformat = format = format_trans[ss->format]; + if (reqformat == AFMT_QUERY || ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != reqformat) { + format = AFMT_S16_NE; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_S16_NE) { + int f = AFMT_S16_NE == AFMT_S16_LE ? AFMT_S16_BE : AFMT_S16_LE; + format = f; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != f) { + format = AFMT_U8; + if (ioctl(fd, SNDCTL_DSP_SETFMT, &format) < 0 || format != AFMT_U8) { + pa_log("SNDCTL_DSP_SETFMT: %s", format != AFMT_U8 ? "No supported sample format" : pa_cstrerror(errno)); + return -1; + } else + ss->format = PA_SAMPLE_U8; + } else + ss->format = f == AFMT_S16_LE ? PA_SAMPLE_S16LE : PA_SAMPLE_S16BE; + } else + ss->format = PA_SAMPLE_S16NE; + } + + if (orig_format != ss->format) + pa_log_warn("device doesn't support sample format %s, changed to %s.", + pa_sample_format_to_string(orig_format), + pa_sample_format_to_string(ss->format)); + + channels = ss->channels; + if (ioctl(fd, SNDCTL_DSP_CHANNELS, &channels) < 0) { + pa_log("SNDCTL_DSP_CHANNELS: %s", pa_cstrerror(errno)); + return -1; + } + pa_assert(channels > 0); + + if (ss->channels != channels) { + pa_log_warn("device doesn't support %i channels, using %i channels.", ss->channels, channels); + ss->channels = channels; + } + + speed = ss->rate; + if (ioctl(fd, SNDCTL_DSP_SPEED, &speed) < 0) { + pa_log("SNDCTL_DSP_SPEED: %s", pa_cstrerror(errno)); + return -1; + } + pa_assert(speed > 0); + + if (ss->rate != (unsigned) speed) { + pa_log_warn("device doesn't support %i Hz, changed to %i Hz.", ss->rate, speed); + + /* If the sample rate deviates too much, we need to resample */ + if (speed < ss->rate*.95 || speed > ss->rate*1.05) + ss->rate = speed; + } + + return 0; +} + +static int simple_log2(int v) { + int k = 0; + + for (;;) { + v >>= 1; + if (!v) break; + k++; + } + + return k; +} + +int pa_oss_set_fragments(int fd, int nfrags, int frag_size) { + int arg; + arg = ((int) nfrags << 16) | simple_log2(frag_size); + + if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &arg) < 0) { + pa_log("SNDCTL_DSP_SETFRAGMENT: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume) { + char cv[PA_CVOLUME_SNPRINT_MAX]; + unsigned vol; + + pa_assert(fd >= 0); + pa_assert(ss); + pa_assert(volume); + + if (ioctl(fd, mixer, &vol) < 0) + return -1; + + pa_cvolume_reset(volume, ss->channels); + + volume->values[0] = ((vol & 0xFF) * PA_VOLUME_NORM) / 100; + + if (volume->channels >= 2) + volume->values[1] = (((vol >> 8) & 0xFF) * PA_VOLUME_NORM) / 100; + + pa_log_debug("Read mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); + return 0; +} + +int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume) { + char cv[PA_CVOLUME_SNPRINT_MAX]; + unsigned vol; + pa_volume_t l, r; + + l = volume->values[0] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[0]; + + vol = (l*100)/PA_VOLUME_NORM; + + if (ss->channels >= 2) { + r = volume->values[1] > PA_VOLUME_NORM ? PA_VOLUME_NORM : volume->values[1]; + vol |= ((r*100)/PA_VOLUME_NORM) << 8; + } + + if (ioctl(fd, mixer, &vol) < 0) + return -1; + + pa_log_debug("Wrote mixer settings: %s", pa_cvolume_snprint(cv, sizeof(cv), volume)); + return 0; +} + +static int get_device_number(const char *dev) { + const char *p, *e; + char *rp = NULL; + int r; + + if (!(p = rp = pa_readlink(dev))) { + if (errno != EINVAL && errno != ENOLINK) { + r = -1; + goto finish; + } + + p = dev; + } + + if ((e = strrchr(p, '/'))) + p = e+1; + + if (p == 0) { + r = 0; + goto finish; + } + + p = strchr(p, 0) -1; + + if (*p >= '0' && *p <= '9') { + r = *p - '0'; + goto finish; + } + + r = -1; + +finish: + pa_xfree(rp); + return r; +} + +int pa_oss_get_hw_description(const char *dev, char *name, size_t l) { + FILE *f; + int n, r = -1; + int b = 0; + + if ((n = get_device_number(dev)) < 0) + return -1; + + if (!(f = fopen("/dev/sndstat", "r")) && + !(f = fopen("/proc/sndstat", "r")) && + !(f = fopen("/proc/asound/oss/sndstat", "r"))) { + + if (errno != ENOENT) + pa_log_warn("failed to open OSS sndstat device: %s", pa_cstrerror(errno)); + + return -1; + } + + while (!feof(f)) { + char line[64]; + int device; + + if (!fgets(line, sizeof(line), f)) + break; + + line[strcspn(line, "\r\n")] = 0; + + if (!b) { + b = strcmp(line, "Audio devices:") == 0; + continue; + } + + if (line[0] == 0) + break; + + if (sscanf(line, "%i: ", &device) != 1) + continue; + + if (device == n) { + char *k = strchr(line, ':'); + pa_assert(k); + k++; + k += strspn(k, " "); + + if (pa_endswith(k, " (DUPLEX)")) + k[strlen(k)-9] = 0; + + pa_strlcpy(name, k, l); + r = 0; + break; + } + } + + fclose(f); + return r; +} + +static int open_mixer(const char *mixer) { + int fd; + + if ((fd = open(mixer, O_RDWR|O_NDELAY|O_NOCTTY)) >= 0) + return fd; + + return -1; +} + +int pa_oss_open_mixer_for_device(const char *device) { + int n; + char *fn; + int fd; + + if ((n = get_device_number(device)) < 0) + return -1; + + if (n == 0) + if ((fd = open_mixer("/dev/mixer")) >= 0) + return fd; + + fn = pa_sprintf_malloc("/dev/mixer%i", n); + fd = open_mixer(fn); + pa_xfree(fn); + + if (fd < 0) + pa_log_warn("Failed to open mixer '%s': %s", device, pa_cstrerror(errno)); + + return fd; +} diff --git a/src/modules/oss-util.h b/src/modules/oss-util.h new file mode 100644 index 00000000..259a622a --- /dev/null +++ b/src/modules/oss-util.h @@ -0,0 +1,43 @@ +#ifndef fooossutilhfoo +#define fooossutilhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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/sample.h> +#include <pulse/volume.h> + +int pa_oss_open(const char *device, int *mode, int* pcaps); +int pa_oss_auto_format(int fd, pa_sample_spec *ss); + +int pa_oss_set_fragments(int fd, int frags, int frag_size); + +int pa_oss_set_volume(int fd, long mixer, const pa_sample_spec *ss, const pa_cvolume *volume); +int pa_oss_get_volume(int fd, int mixer, const pa_sample_spec *ss, pa_cvolume *volume); + +int pa_oss_get_hw_description(const char *dev, char *name, size_t l); + +int pa_oss_open_mixer_for_device(const char *device); + +#endif diff --git a/src/modules/rtp/Makefile b/src/modules/rtp/Makefile new file mode 100644 index 00000000..316beb72 --- /dev/null +++ b/src/modules/rtp/Makefile @@ -0,0 +1,13 @@ +# This is a dirty trick just to ease compilation with emacs +# +# This file is not intended to be distributed or anything +# +# So: don't touch it, even better ignore it! + +all: + $(MAKE) -C ../.. + +clean: + $(MAKE) -C ../.. clean + +.PHONY: all clean diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c new file mode 100644 index 00000000..d8e7a781 --- /dev/null +++ b/src/modules/rtp/module-rtp-recv.c @@ -0,0 +1,600 @@ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <poll.h> + +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/llist.h> +#include <pulsecore/sink.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/atomic.h> + +#include "module-rtp-recv-symdef.h" + +#include "rtp.h" +#include "sdp.h" +#include "sap.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Recieve data from a network via RTP/SAP/SDP"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "sink=<name of the sink> " + "sap_address=<multicast address to listen on> " +); + +#define SAP_PORT 9875 +#define DEFAULT_SAP_ADDRESS "224.0.0.56" +#define MEMBLOCKQ_MAXLENGTH (1024*170) +#define MAX_SESSIONS 16 +#define DEATH_TIMEOUT 20 + +static const char* const valid_modargs[] = { + "sink", + "sap_address", + NULL +}; + +struct session { + struct userdata *userdata; + PA_LLIST_FIELDS(struct session); + + pa_sink_input *sink_input; + pa_memblockq *memblockq; + + pa_bool_t first_packet; + uint32_t ssrc; + uint32_t offset; + + struct pa_sdp_info sdp_info; + + pa_rtp_context rtp_context; + + pa_rtpoll_item *rtpoll_item; + + pa_atomic_t timestamp; +}; + +struct userdata { + pa_module *module; + + pa_sap_context sap_context; + pa_io_event* sap_event; + + pa_time_event *check_death_event; + + char *sink_name; + + PA_LLIST_HEAD(struct session, sessions); + pa_hashmap *by_origin; + int n_sessions; +}; + +static void session_free(struct session *s); + +/* Called from I/O thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct session *s = PA_SINK_INPUT(o)->userdata; + + switch (code) { + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: + *((pa_usec_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &s->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + return pa_sink_input_process_msg(o, code, data, offset, chunk); +} + +/* Called from I/O thread context */ +static int sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + struct session *s; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + return pa_memblockq_peek(s->memblockq, chunk); +} + +/* Called from I/O thread context */ +static void sink_input_drop(pa_sink_input *i, size_t length) { + struct session *s; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + pa_memblockq_drop(s->memblockq, length); +} + +/* Called from main context */ +static void sink_input_kill(pa_sink_input* i) { + struct session *s; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + session_free(s); +} + +/* Called from I/O thread context */ +static int rtpoll_work_cb(pa_rtpoll_item *i) { + pa_memchunk chunk; + int64_t k, j, delta; + struct timeval now; + struct session *s; + struct pollfd *p; + + pa_assert_se(s = pa_rtpoll_item_get_userdata(i)); + + p = pa_rtpoll_item_get_pollfd(i, NULL); + + if (p->revents & (POLLERR|POLLNVAL|POLLHUP|POLLOUT)) { + pa_log("poll() signalled bad revents."); + return -1; + } + + if ((p->revents & POLLIN) == 0) + return 0; + + p->revents = 0; + + if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->module->core->mempool) < 0) + return 0; + + if (s->sdp_info.payload != s->rtp_context.payload) { + pa_memblock_unref(chunk.memblock); + return 0; + } + + if (!s->first_packet) { + s->first_packet = TRUE; + + s->ssrc = s->rtp_context.ssrc; + s->offset = s->rtp_context.timestamp; + + if (s->ssrc == s->userdata->module->core->cookie) + pa_log_warn("Detected RTP packet loop!"); + } else { + if (s->ssrc != s->rtp_context.ssrc) { + pa_memblock_unref(chunk.memblock); + return 0; + } + } + + /* Check wheter there was a timestamp overflow */ + k = (int64_t) s->rtp_context.timestamp - (int64_t) s->offset; + j = (int64_t) 0x100000000LL - (int64_t) s->offset + (int64_t) s->rtp_context.timestamp; + + if ((k < 0 ? -k : k) < (j < 0 ? -j : j)) + delta = k; + else + delta = j; + + pa_memblockq_seek(s->memblockq, delta * s->rtp_context.frame_size, PA_SEEK_RELATIVE); + + if (pa_memblockq_push(s->memblockq, &chunk) < 0) { + /* queue overflow, let's flush it and try again */ + pa_memblockq_flush(s->memblockq); + pa_memblockq_push(s->memblockq, &chunk); + } + + /* The next timestamp we expect */ + s->offset = s->rtp_context.timestamp + (chunk.length / s->rtp_context.frame_size); + + pa_memblock_unref(chunk.memblock); + + pa_rtclock_get(&now); + pa_atomic_store(&s->timestamp, now.tv_sec); + + return 1; +} + +/* Called from I/O thread context */ +static void sink_input_attach(pa_sink_input *i) { + struct session *s; + struct pollfd *p; + + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + pa_assert(!s->rtpoll_item); + s->rtpoll_item = pa_rtpoll_item_new(i->sink->rtpoll, PA_RTPOLL_LATE, 1); + + p = pa_rtpoll_item_get_pollfd(s->rtpoll_item, NULL); + p->fd = s->rtp_context.fd; + p->events = POLLIN; + p->revents = 0; + + pa_rtpoll_item_set_work_callback(s->rtpoll_item, rtpoll_work_cb); + pa_rtpoll_item_set_userdata(s->rtpoll_item, s); +} + +/* Called from I/O thread context */ +static void sink_input_detach(pa_sink_input *i) { + struct session *s; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + pa_assert(s->rtpoll_item); + pa_rtpoll_item_free(s->rtpoll_item); + s->rtpoll_item = NULL; +} + +static int mcast_socket(const struct sockaddr* sa, socklen_t salen) { + int af, fd = -1, r, one; + + pa_assert(sa); + pa_assert(salen > 0); + + af = sa->sa_family; + if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) { + pa_log("Failed to create socket: %s", pa_cstrerror(errno)); + goto fail; + } + + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { + pa_log("SO_REUSEADDR failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (af == AF_INET) { + struct ip_mreq mr4; + memset(&mr4, 0, sizeof(mr4)); + mr4.imr_multiaddr = ((const struct sockaddr_in*) sa)->sin_addr; + r = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mr4, sizeof(mr4)); + } else { + struct ipv6_mreq mr6; + memset(&mr6, 0, sizeof(mr6)); + mr6.ipv6mr_multiaddr = ((const struct sockaddr_in6*) sa)->sin6_addr; + r = setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mr6, sizeof(mr6)); + } + + if (r < 0) { + pa_log_info("Joining mcast group failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (bind(fd, sa, salen) < 0) { + pa_log("bind() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + return fd; + +fail: + if (fd >= 0) + close(fd); + + return -1; +} + +static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_info) { + struct session *s = NULL; + char *c; + pa_sink *sink; + int fd = -1; + pa_memblock *silence; + pa_sink_input_new_data data; + struct timeval now; + + pa_assert(u); + pa_assert(sdp_info); + + if (u->n_sessions >= MAX_SESSIONS) { + pa_log("Session limit reached."); + goto fail; + } + + if (!(sink = pa_namereg_get(u->module->core, u->sink_name, PA_NAMEREG_SINK, 1))) { + pa_log("Sink does not exist."); + goto fail; + } + + s = pa_xnew0(struct session, 1); + s->userdata = u; + s->first_packet = FALSE; + s->sdp_info = *sdp_info; + s->rtpoll_item = NULL; + + pa_rtclock_get(&now); + pa_atomic_store(&s->timestamp, now.tv_sec); + + if ((fd = mcast_socket((const struct sockaddr*) &sdp_info->sa, sdp_info->salen)) < 0) + goto fail; + + c = pa_sprintf_malloc("RTP Stream%s%s%s", + sdp_info->session_name ? " (" : "", + sdp_info->session_name ? sdp_info->session_name : "", + sdp_info->session_name ? ")" : ""); + + pa_sink_input_new_data_init(&data); + data.sink = sink; + data.driver = __FILE__; + data.name = c; + data.module = u->module; + pa_sink_input_new_data_set_sample_spec(&data, &sdp_info->sample_spec); + + s->sink_input = pa_sink_input_new(u->module->core, &data, 0); + pa_xfree(c); + + if (!s->sink_input) { + pa_log("Failed to create sink input."); + goto fail; + } + + s->sink_input->userdata = s; + + s->sink_input->parent.process_msg = sink_input_process_msg; + s->sink_input->peek = sink_input_peek; + s->sink_input->drop = sink_input_drop; + s->sink_input->kill = sink_input_kill; + s->sink_input->attach = sink_input_attach; + s->sink_input->detach = sink_input_detach; + + silence = pa_silence_memblock_new( + s->userdata->module->core->mempool, + &s->sink_input->sample_spec, + pa_frame_align(pa_bytes_per_second(&s->sink_input->sample_spec)/128, &s->sink_input->sample_spec)); + + s->memblockq = pa_memblockq_new( + 0, + MEMBLOCKQ_MAXLENGTH, + MEMBLOCKQ_MAXLENGTH, + pa_frame_size(&s->sink_input->sample_spec), + pa_bytes_per_second(&s->sink_input->sample_spec)/10+1, + 0, + silence); + + pa_memblock_unref(silence); + + pa_rtp_context_init_recv(&s->rtp_context, fd, pa_frame_size(&s->sdp_info.sample_spec)); + + pa_hashmap_put(s->userdata->by_origin, s->sdp_info.origin, s); + u->n_sessions++; + PA_LLIST_PREPEND(struct session, s->userdata->sessions, s); + + pa_sink_input_put(s->sink_input); + + pa_log_info("New session '%s'", s->sdp_info.session_name); + + return s; + +fail: + pa_xfree(s); + + if (fd >= 0) + pa_close(fd); + + return NULL; +} + +static void session_free(struct session *s) { + pa_assert(s); + + pa_log_info("Freeing session '%s'", s->sdp_info.session_name); + + pa_sink_input_unlink(s->sink_input); + pa_sink_input_unref(s->sink_input); + + PA_LLIST_REMOVE(struct session, s->userdata->sessions, s); + pa_assert(s->userdata->n_sessions >= 1); + s->userdata->n_sessions--; + pa_hashmap_remove(s->userdata->by_origin, s->sdp_info.origin); + + pa_memblockq_free(s->memblockq); + pa_sdp_info_destroy(&s->sdp_info); + pa_rtp_context_destroy(&s->rtp_context); + + pa_xfree(s); +} + +static void sap_event_cb(pa_mainloop_api *m, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { + struct userdata *u = userdata; + int goodbye; + pa_sdp_info info; + struct session *s; + + pa_assert(m); + pa_assert(e); + pa_assert(u); + pa_assert(fd == u->sap_context.fd); + pa_assert(flags == PA_IO_EVENT_INPUT); + + if (pa_sap_recv(&u->sap_context, &goodbye) < 0) + return; + + if (!pa_sdp_parse(u->sap_context.sdp_data, &info, goodbye)) + return; + + if (goodbye) { + + if ((s = pa_hashmap_get(u->by_origin, info.origin))) + session_free(s); + + pa_sdp_info_destroy(&info); + } else { + + if (!(s = pa_hashmap_get(u->by_origin, info.origin))) { + if (!(s = session_new(u, &info))) + pa_sdp_info_destroy(&info); + + } else { + struct timeval now; + pa_rtclock_get(&now); + pa_atomic_store(&s->timestamp, now.tv_sec); + + pa_sdp_info_destroy(&info); + } + } +} + +static void check_death_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *ptv, void *userdata) { + struct session *s, *n; + struct userdata *u = userdata; + struct timeval now; + struct timeval tv; + + pa_assert(m); + pa_assert(t); + pa_assert(ptv); + pa_assert(u); + + pa_rtclock_get(&now); + + pa_log_debug("Checking for dead streams ..."); + + for (s = u->sessions; s; s = n) { + int k; + n = s->next; + + k = pa_atomic_load(&s->timestamp); + + if (k + DEATH_TIMEOUT < now.tv_sec) + session_free(s); + } + + /* Restart timer */ + pa_gettimeofday(&tv); + pa_timeval_add(&tv, DEATH_TIMEOUT*PA_USEC_PER_SEC); + m->time_restart(t, &tv); +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_modargs *ma = NULL; + struct sockaddr_in sa4; + struct sockaddr_in6 sa6; + struct sockaddr *sa; + socklen_t salen; + const char *sap_address; + int fd = -1; + struct timeval tv; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("failed to parse module arguments"); + goto fail; + } + + sap_address = pa_modargs_get_value(ma, "sap_address", DEFAULT_SAP_ADDRESS); + + if (inet_pton(AF_INET6, sap_address, &sa6.sin6_addr) > 0) { + sa6.sin6_family = AF_INET6; + sa6.sin6_port = htons(SAP_PORT); + sa = (struct sockaddr*) &sa6; + salen = sizeof(sa6); + } else if (inet_pton(AF_INET, sap_address, &sa4.sin_addr) > 0) { + sa4.sin_family = AF_INET; + sa4.sin_port = htons(SAP_PORT); + sa = (struct sockaddr*) &sa4; + salen = sizeof(sa4); + } else { + pa_log("Invalid SAP address '%s'", sap_address); + goto fail; + } + + if ((fd = mcast_socket(sa, salen)) < 0) + goto fail; + + u = pa_xnew(struct userdata, 1); + m->userdata = u; + u->module = m; + u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + + u->sap_event = m->core->mainloop->io_new(m->core->mainloop, fd, PA_IO_EVENT_INPUT, sap_event_cb, u); + pa_sap_context_init_recv(&u->sap_context, fd); + + PA_LLIST_HEAD_INIT(struct session, u->sessions); + u->n_sessions = 0; + u->by_origin = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, DEATH_TIMEOUT * PA_USEC_PER_SEC); + u->check_death_event = m->core->mainloop->time_new(m->core->mainloop, &tv, check_death_event_cb, u); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + if (fd >= 0) + pa_close(fd); + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + struct session *s; + + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sap_event) + m->core->mainloop->io_free(u->sap_event); + + if (u->check_death_event) + m->core->mainloop->time_free(u->check_death_event); + + pa_sap_context_destroy(&u->sap_context); + + if (u->by_origin) { + while ((s = pa_hashmap_get_first(u->by_origin))) + session_free(s); + + pa_hashmap_free(u->by_origin, NULL, NULL); + } + + pa_xfree(u->sink_name); + pa_xfree(u); +} diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c new file mode 100644 index 00000000..95ff15de --- /dev/null +++ b/src/modules/rtp/module-rtp-send.c @@ -0,0 +1,397 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> + +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/module.h> +#include <pulsecore/llist.h> +#include <pulsecore/source.h> +#include <pulsecore/source-output.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/modargs.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/socket-util.h> + +#include "module-rtp-send-symdef.h" + +#include "rtp.h" +#include "sdp.h" +#include "sap.h" + +PA_MODULE_AUTHOR("Lennart Poettering"); +PA_MODULE_DESCRIPTION("Read data from source and send it to the network via RTP/SAP/SDP"); +PA_MODULE_VERSION(PACKAGE_VERSION); +PA_MODULE_LOAD_ONCE(FALSE); +PA_MODULE_USAGE( + "source=<name of the source> " + "format=<sample format> " + "channels=<number of channels> " + "rate=<sample rate> " + "destination=<destination IP address> " + "port=<port number> " + "mtu=<maximum transfer unit> " + "loop=<loopback to local host?>" +); + +#define DEFAULT_PORT 46000 +#define SAP_PORT 9875 +#define DEFAULT_DESTINATION "224.0.0.56" +#define MEMBLOCKQ_MAXLENGTH (1024*170) +#define DEFAULT_MTU 1280 +#define SAP_INTERVAL 5 + +static const char* const valid_modargs[] = { + "source", + "format", + "channels", + "rate", + "destination", + "port", + "mtu" , + "loop", + NULL +}; + +struct userdata { + pa_module *module; + + pa_source_output *source_output; + pa_memblockq *memblockq; + + pa_rtp_context rtp_context; + pa_sap_context sap_context; + size_t mtu; + + pa_time_event *sap_event; +}; + +/* Called from I/O thread context */ +static int source_output_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { + struct userdata *u; + pa_assert_se(u = PA_SOURCE_OUTPUT(o)->userdata); + + switch (code) { + case PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY: + *((pa_usec_t*) data) = pa_bytes_to_usec(pa_memblockq_get_length(u->memblockq), &u->source_output->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + + return pa_source_output_process_msg(o, code, data, offset, chunk); +} + +/* Called from I/O thread context */ +static void source_output_push(pa_source_output *o, const pa_memchunk *chunk) { + struct userdata *u; + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); + + if (pa_memblockq_push(u->memblockq, chunk) < 0) { + pa_log_warn("Failed to push chunk into memblockq."); + return; + } + + pa_rtp_send(&u->rtp_context, u->mtu, u->memblockq); +} + +/* Called from main context */ +static void source_output_kill(pa_source_output* o) { + struct userdata *u; + pa_source_output_assert_ref(o); + pa_assert_se(u = o->userdata); + + pa_module_unload_request(u->module); + + pa_source_output_unlink(u->source_output); + pa_source_output_unref(u->source_output); + u->source_output = NULL; +} + +static void sap_event_cb(pa_mainloop_api *m, pa_time_event *t, const struct timeval *tv, void *userdata) { + struct userdata *u = userdata; + struct timeval next; + + pa_assert(m); + pa_assert(t); + pa_assert(tv); + pa_assert(u); + + pa_sap_send(&u->sap_context, 0); + + pa_gettimeofday(&next); + pa_timeval_add(&next, SAP_INTERVAL * PA_USEC_PER_SEC); + m->time_restart(t, &next); +} + +int pa__init(pa_module*m) { + struct userdata *u; + pa_modargs *ma = NULL; + const char *dest; + uint32_t port = DEFAULT_PORT, mtu; + int af, fd = -1, sap_fd = -1; + pa_source *s; + pa_sample_spec ss; + pa_channel_map cm; + struct sockaddr_in sa4, sap_sa4; + struct sockaddr_in6 sa6, sap_sa6; + struct sockaddr_storage sa_dst; + pa_source_output *o = NULL; + uint8_t payload; + char *p; + int r, j; + socklen_t k; + struct timeval tv; + char hn[128], *n; + pa_bool_t loop = FALSE; + pa_source_output_new_data data; + + pa_assert(m); + + if (!(ma = pa_modargs_new(m->argument, valid_modargs))) { + pa_log("Failed to parse module arguments"); + goto fail; + } + + if (!(s = pa_namereg_get(m->core, pa_modargs_get_value(ma, "source", NULL), PA_NAMEREG_SOURCE, 1))) { + pa_log("Source does not exist."); + goto fail; + } + + if (pa_modargs_get_value_boolean(ma, "loop", &loop) < 0) { + pa_log("Failed to parse \"loop\" parameter."); + goto fail; + } + + ss = s->sample_spec; + pa_rtp_sample_spec_fixup(&ss); + cm = s->channel_map; + if (pa_modargs_get_sample_spec(ma, &ss) < 0) { + pa_log("Failed to parse sample specification"); + goto fail; + } + + if (!pa_rtp_sample_spec_valid(&ss)) { + pa_log("Specified sample type not compatible with RTP"); + goto fail; + } + + if (ss.channels != cm.channels) + pa_channel_map_init_auto(&cm, ss.channels, PA_CHANNEL_MAP_AIFF); + + payload = pa_rtp_payload_from_sample_spec(&ss); + + mtu = pa_frame_align(DEFAULT_MTU, &ss); + + if (pa_modargs_get_value_u32(ma, "mtu", &mtu) < 0 || mtu < 1 || mtu % pa_frame_size(&ss) != 0) { + pa_log("Invalid MTU."); + goto fail; + } + + port = DEFAULT_PORT + ((rand() % 512) << 1); + if (pa_modargs_get_value_u32(ma, "port", &port) < 0 || port < 1 || port > 0xFFFF) { + pa_log("port= expects a numerical argument between 1 and 65535."); + goto fail; + } + + if (port & 1) + pa_log_warn("Port number not even as suggested in RFC3550!"); + + dest = pa_modargs_get_value(ma, "destination", DEFAULT_DESTINATION); + + if (inet_pton(AF_INET6, dest, &sa6.sin6_addr) > 0) { + sa6.sin6_family = af = AF_INET6; + sa6.sin6_port = htons(port); + sap_sa6 = sa6; + sap_sa6.sin6_port = htons(SAP_PORT); + } else if (inet_pton(AF_INET, dest, &sa4.sin_addr) > 0) { + sa4.sin_family = af = AF_INET; + sa4.sin_port = htons(port); + sap_sa4 = sa4; + sap_sa4.sin_port = htons(SAP_PORT); + } else { + pa_log("Invalid destination '%s'", dest); + goto fail; + } + + if ((fd = socket(af, SOCK_DGRAM, 0)) < 0) { + pa_log("socket() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (connect(fd, af == AF_INET ? (struct sockaddr*) &sa4 : (struct sockaddr*) &sa6, af == AF_INET ? sizeof(sa4) : sizeof(sa6)) < 0) { + pa_log("connect() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if ((sap_fd = socket(af, SOCK_DGRAM, 0)) < 0) { + pa_log("socket() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (connect(sap_fd, af == AF_INET ? (struct sockaddr*) &sap_sa4 : (struct sockaddr*) &sap_sa6, af == AF_INET ? sizeof(sap_sa4) : sizeof(sap_sa6)) < 0) { + pa_log("connect() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + j = !!loop; + if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP, &j, sizeof(j)) < 0 || + setsockopt(sap_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &j, sizeof(j)) < 0) { + pa_log("IP_MULTICAST_LOOP failed: %s", pa_cstrerror(errno)); + goto fail; + } + + /* If the socket queue is full, let's drop packets */ + pa_make_fd_nonblock(fd); + pa_make_udp_socket_low_delay(fd); + pa_make_fd_cloexec(fd); + pa_make_fd_cloexec(sap_fd); + + pa_source_output_new_data_init(&data); + data.name = "RTP Monitor Stream"; + data.driver = __FILE__; + data.module = m; + data.source = s; + pa_source_output_new_data_set_sample_spec(&data, &ss); + pa_source_output_new_data_set_channel_map(&data, &cm); + + if (!(o = pa_source_output_new(m->core, &data, 0))) { + pa_log("failed to create source output."); + goto fail; + } + + o->parent.process_msg = source_output_process_msg; + o->push = source_output_push; + o->kill = source_output_kill; + + u = pa_xnew(struct userdata, 1); + m->userdata = u; + o->userdata = u; + + u->module = m; + u->source_output = o; + + u->memblockq = pa_memblockq_new( + 0, + MEMBLOCKQ_MAXLENGTH, + MEMBLOCKQ_MAXLENGTH, + pa_frame_size(&ss), + 1, + 0, + NULL); + + u->mtu = mtu; + + k = sizeof(sa_dst); + pa_assert_se((r = getsockname(fd, (struct sockaddr*) &sa_dst, &k)) >= 0); + + n = pa_sprintf_malloc("PulseAudio RTP Stream on %s", pa_get_fqdn(hn, sizeof(hn))); + + p = pa_sdp_build(af, + af == AF_INET ? (void*) &((struct sockaddr_in*) &sa_dst)->sin_addr : (void*) &((struct sockaddr_in6*) &sa_dst)->sin6_addr, + af == AF_INET ? (void*) &sa4.sin_addr : (void*) &sa6.sin6_addr, + n, port, payload, &ss); + + pa_xfree(n); + + pa_rtp_context_init_send(&u->rtp_context, fd, m->core->cookie, payload, pa_frame_size(&ss)); + pa_sap_context_init_send(&u->sap_context, sap_fd, p); + + pa_log_info("RTP stream initialized with mtu %u on %s:%u, SSRC=0x%08x, payload=%u, initial sequence #%u", mtu, dest, port, u->rtp_context.ssrc, payload, u->rtp_context.sequence); + pa_log_info("SDP-Data:\n%s\nEOF", p); + + pa_sap_send(&u->sap_context, 0); + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, SAP_INTERVAL * PA_USEC_PER_SEC); + u->sap_event = m->core->mainloop->time_new(m->core->mainloop, &tv, sap_event_cb, u); + + pa_source_output_put(u->source_output); + + pa_modargs_free(ma); + + return 0; + +fail: + if (ma) + pa_modargs_free(ma); + + if (fd >= 0) + pa_close(fd); + + if (sap_fd >= 0) + pa_close(sap_fd); + + if (o) { + pa_source_output_unlink(o); + pa_source_output_unref(o); + } + + return -1; +} + +void pa__done(pa_module*m) { + struct userdata *u; + pa_assert(m); + + if (!(u = m->userdata)) + return; + + if (u->sap_event) + m->core->mainloop->time_free(u->sap_event); + + if (u->source_output) { + pa_source_output_unlink(u->source_output); + pa_source_output_unref(u->source_output); + } + + pa_rtp_context_destroy(&u->rtp_context); + + pa_sap_send(&u->sap_context, 1); + pa_sap_context_destroy(&u->sap_context); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + pa_xfree(u); +} diff --git a/src/modules/rtp/rfc2327.txt b/src/modules/rtp/rfc2327.txt new file mode 100644 index 00000000..ce77de61 --- /dev/null +++ b/src/modules/rtp/rfc2327.txt @@ -0,0 +1,2355 @@ + + + + + + +Network Working Group M. Handley +Request for Comments: 2327 V. Jacobson +Category: Standards Track ISI/LBNL + April 1998 + + + SDP: Session Description Protocol + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +Abstract + + This document defines the Session Description Protocol, SDP. SDP is + intended for describing multimedia sessions for the purposes of + session announcement, session invitation, and other forms of + multimedia session initiation. + + This document is a product of the Multiparty Multimedia Session + Control (MMUSIC) working group of the Internet Engineering Task + Force. Comments are solicited and should be addressed to the working + group's mailing list at confctrl@isi.edu and/or the authors. + +1. Introduction + + On the Internet multicast backbone (Mbone), a session directory tool + is used to advertise multimedia conferences and communicate the + conference addresses and conference tool-specific information + necessary for participation. This document defines a session + description protocol for this purpose, and for general real-time + multimedia session description purposes. This memo does not describe + multicast address allocation or the distribution of SDP messages in + detail. These are described in accompanying memos. SDP is not + intended for negotiation of media encodings. + + + + + + + + +Handley & Jacobson Standards Track [Page 1] + +RFC 2327 SDP April 1998 + + +2. Background + + The Mbone is the part of the internet that supports IP multicast, and + thus permits efficient many-to-many communication. It is used + extensively for multimedia conferencing. Such conferences usually + have the property that tight coordination of conference membership is + not necessary; to receive a conference, a user at an Mbone site only + has to know the conference's multicast group address and the UDP + ports for the conference data streams. + + Session directories assist the advertisement of conference sessions + and communicate the relevant conference setup information to + prospective participants. SDP is designed to convey such information + to recipients. SDP is purely a format for session description - it + does not incorporate a transport protocol, and is intended to use + different transport protocols as appropriate including the Session + Announcement Protocol [4], Session Initiation Protocol [11], Real- + Time Streaming Protocol [12], electronic mail using the MIME + extensions, and the Hypertext Transport Protocol. + + SDP is intended to be general purpose so that it can be used for a + wider range of network environments and applications than just + multicast session directories. However, it is not intended to + support negotiation of session content or media encodings - this is + viewed as outside the scope of session description. + +3. Glossary of Terms + + The following terms are used in this document, and have specific + meaning within the context of this document. + + Conference + A multimedia conference is a set of two or more communicating users + along with the software they are using to communicate. + + Session + A multimedia session is a set of multimedia senders and receivers + and the data streams flowing from senders to receivers. A + multimedia conference is an example of a multimedia session. + + Session Advertisement + See session announcement. + + Session Announcement + A session announcement is a mechanism by which a session + description is conveyed to users in a proactive fashion, i.e., the + session description was not explicitly requested by the user. + + + + +Handley & Jacobson Standards Track [Page 2] + +RFC 2327 SDP April 1998 + + + Session Description + A well defined format for conveying sufficient information to + discover and participate in a multimedia session. + +3.1. Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in RFC 2119. + +4. SDP Usage + +4.1. Multicast Announcements + + SDP is a session description protocol for multimedia sessions. A + common mode of usage is for a client to announce a conference session + by periodically multicasting an announcement packet to a well known + multicast address and port using the Session Announcement Protocol + (SAP). + + SAP packets are UDP packets with the following format: + + |--------------------| + | SAP header | + |--------------------| + | text payload | + |////////// + + + The header is the Session Announcement Protocol header. SAP is + described in more detail in a companion memo [4] + + The text payload is an SDP session description, as described in this + memo. The text payload should be no greater than 1 Kbyte in length. + If announced by SAP, only one session announcement is permitted in a + single packet. + +4.2. Email and WWW Announcements + + Alternative means of conveying session descriptions include + electronic mail and the World Wide Web. For both email and WWW + distribution, the use of the MIME content type "application/sdp" + should be used. This enables the automatic launching of applications + for participation in the session from the WWW client or mail reader + in a standard manner. + + + + + + +Handley & Jacobson Standards Track [Page 3] + +RFC 2327 SDP April 1998 + + + Note that announcements of multicast sessions made only via email or + the World Wide Web (WWW) do not have the property that the receiver + of a session announcement can necessarily receive the session because + the multicast sessions may be restricted in scope, and access to the + WWW server or reception of email is possible outside this scope. SAP + announcements do not suffer from this mismatch. + +5. Requirements and Recommendations + + The purpose of SDP is to convey information about media streams in + multimedia sessions to allow the recipients of a session description + to participate in the session. SDP is primarily intended for use in + an internetwork, although it is sufficiently general that it can + describe conferences in other network environments. + + A multimedia session, for these purposes, is defined as a set of + media streams that exist for some duration of time. Media streams + can be many-to-many. The times during which the session is active + need not be continuous. + + Thus far, multicast based sessions on the Internet have differed from + many other forms of conferencing in that anyone receiving the traffic + can join the session (unless the session traffic is encrypted). In + such an environment, SDP serves two primary purposes. It is a means + to communicate the existence of a session, and is a means to convey + sufficient information to enable joining and participating in the + session. In a unicast environment, only the latter purpose is likely + to be relevant. + + Thus SDP includes: + + o Session name and purpose + + o Time(s) the session is active + + o The media comprising the session + + o Information to receive those media (addresses, ports, formats and + so on) + + As resources necessary to participate in a session may be limited, + some additional information may also be desirable: + + o Information about the bandwidth to be used by the conference + + o Contact information for the person responsible for the session + + + + + +Handley & Jacobson Standards Track [Page 4] + +RFC 2327 SDP April 1998 + + + In general, SDP must convey sufficient information to be able to join + a session (with the possible exception of encryption keys) and to + announce the resources to be used to non-participants that may need + to know. + +5.1. Media Information + + SDP includes: + + o The type of media (video, audio, etc) + + o The transport protocol (RTP/UDP/IP, H.320, etc) + + o The format of the media (H.261 video, MPEG video, etc) + + For an IP multicast session, the following are also conveyed: + + o Multicast address for media + + o Transport Port for media + + This address and port are the destination address and destination + port of the multicast stream, whether being sent, received, or both. + + For an IP unicast session, the following are conveyed: + + o Remote address for media + + o Transport port for contact address + + The semantics of this address and port depend on the media and + transport protocol defined. By default, this is the remote address + and remote port to which data is sent, and the remote address and + local port on which to receive data. However, some media may define + to use these to establish a control channel for the actual media + flow. + +5.2. Timing Information + + Sessions may either be bounded or unbounded in time. Whether or not + they are bounded, they may be only active at specific times. + + SDP can convey: + + o An arbitrary list of start and stop times bounding the session + + o For each bound, repeat times such as "every Wednesday at 10am for + one hour" + + + +Handley & Jacobson Standards Track [Page 5] + +RFC 2327 SDP April 1998 + + + This timing information is globally consistent, irrespective of local + time zone or daylight saving time. + +5.3. Private Sessions + + It is possible to create both public sessions and private sessions. + Private sessions will typically be conveyed by encrypting the session + description to distribute it. The details of how encryption is + performed are dependent on the mechanism used to convey SDP - see [4] + for how this is done for session announcements. + + If a session announcement is private it is possible to use that + private announcement to convey encryption keys necessary to decode + each of the media in a conference, including enough information to + know which encryption scheme is used for each media. + +5.4. Obtaining Further Information about a Session + + A session description should convey enough information to decide + whether or not to participate in a session. SDP may include + additional pointers in the form of Universal Resources Identifiers + (URIs) for more information about the session. + +5.5. Categorisation + + When many session descriptions are being distributed by SAP or any + other advertisement mechanism, it may be desirable to filter + announcements that are of interest from those that are not. SDP + supports a categorisation mechanism for sessions that is capable of + being automated. + +5.6. Internationalization + + The SDP specification recommends the use of the ISO 10646 character + sets in the UTF-8 encoding (RFC 2044) to allow many different + languages to be represented. However, to assist in compact + representations, SDP also allows other character sets such as ISO + 8859-1 to be used when desired. Internationalization only applies to + free-text fields (session name and background information), and not + to SDP as a whole. + +6. SDP Specification + + SDP session descriptions are entirely textual using the ISO 10646 + character set in UTF-8 encoding. SDP field names and attributes names + use only the US-ASCII subset of UTF-8, but textual fields and + attribute values may use the full ISO 10646 character set. The + textual form, as opposed to a binary encoding such as ASN/1 or XDR, + + + +Handley & Jacobson Standards Track [Page 6] + +RFC 2327 SDP April 1998 + + + was chosen to enhance portability, to enable a variety of transports + to be used (e.g, session description in a MIME email message) and to + allow flexible, text-based toolkits (e.g., Tcl/Tk ) to be used to + generate and to process session descriptions. However, since the + total bandwidth allocated to all SAP announcements is strictly + limited, the encoding is deliberately compact. Also, since + announcements may be transported via very unreliable means (e.g., + email) or damaged by an intermediate caching server, the encoding was + designed with strict order and formatting rules so that most errors + would result in malformed announcements which could be detected + easily and discarded. This also allows rapid discarding of encrypted + announcements for which a receiver does not have the correct key. + + An SDP session description consists of a number of lines of text of + the form <type>=<value> <type> is always exactly one character and is + case-significant. <value> is a structured text string whose format + depends on <type>. It also will be case-significant unless a + specific field defines otherwise. Whitespace is not permitted either + side of the `=' sign. In general <value> is either a number of fields + delimited by a single space character or a free format string. + + A session description consists of a session-level description + (details that apply to the whole session and all media streams) and + optionally several media-level descriptions (details that apply onto + to a single media stream). + + An announcement consists of a session-level section followed by zero + or more media-level sections. The session-level part starts with a + `v=' line and continues to the first media-level section. The media + description starts with an `m=' line and continues to the next media + description or end of the whole session description. In general, + session-level values are the default for all media unless overridden + by an equivalent media-level value. + + When SDP is conveyed by SAP, only one session description is allowed + per packet. When SDP is conveyed by other means, many SDP session + descriptions may be concatenated together (the `v=' line indicating + the start of a session description terminates the previous + description). Some lines in each description are required and some + are optional but all must appear in exactly the order given here (the + fixed order greatly enhances error detection and allows for a simple + parser). Optional items are marked with a `*'. + +Session description + v= (protocol version) + o= (owner/creator and session identifier). + s= (session name) + i=* (session information) + + + +Handley & Jacobson Standards Track [Page 7] + +RFC 2327 SDP April 1998 + + + u=* (URI of description) + e=* (email address) + p=* (phone number) + c=* (connection information - not required if included in all media) + b=* (bandwidth information) + One or more time descriptions (see below) + z=* (time zone adjustments) + k=* (encryption key) + a=* (zero or more session attribute lines) + Zero or more media descriptions (see below) + +Time description + t= (time the session is active) + r=* (zero or more repeat times) + +Media description + m= (media name and transport address) + i=* (media title) + c=* (connection information - optional if included at session-level) + b=* (bandwidth information) + k=* (encryption key) + a=* (zero or more media attribute lines) + + The set of `type' letters is deliberately small and not intended to + be extensible -- SDP parsers must completely ignore any announcement + that contains a `type' letter that it does not understand. The + `attribute' mechanism ("a=" described below) is the primary means for + extending SDP and tailoring it to particular applications or media. + Some attributes (the ones listed in this document) have a defined + meaning but others may be added on an application-, media- or + session-specific basis. A session directory must ignore any + attribute it doesn't understand. + + The connection (`c=') and attribute (`a=') information in the + session-level section applies to all the media of that session unless + overridden by connection information or an attribute of the same name + in the media description. For instance, in the example below, each + media behaves as if it were given a `recvonly' attribute. + + An example SDP description is: + + v=0 + o=mhandley 2890844526 2890842807 IN IP4 126.16.64.4 + s=SDP Seminar + i=A Seminar on the session description protocol + u=http://www.cs.ucl.ac.uk/staff/M.Handley/sdp.03.ps + e=mjh@isi.edu (Mark Handley) + c=IN IP4 224.2.17.12/127 + + + +Handley & Jacobson Standards Track [Page 8] + +RFC 2327 SDP April 1998 + + + t=2873397496 2873404696 + a=recvonly + m=audio 49170 RTP/AVP 0 + m=video 51372 RTP/AVP 31 + m=application 32416 udp wb + a=orient:portrait + + Text records such as the session name and information are bytes + strings which may contain any byte with the exceptions of 0x00 (Nul), + 0x0a (ASCII newline) and 0x0d (ASCII carriage return). The sequence + CRLF (0x0d0a) is used to end a record, although parsers should be + tolerant and also accept records terminated with a single newline + character. By default these byte strings contain ISO-10646 + characters in UTF-8 encoding, but this default may be changed using + the `charset' attribute. + + Protocol Version + + v=0 + + The "v=" field gives the version of the Session Description Protocol. + There is no minor version number. + + Origin + + o=<username> <session id> <version> <network type> <address type> + <address> + + The "o=" field gives the originator of the session (their username + and the address of the user's host) plus a session id and session + version number. + + <username> is the user's login on the originating host, or it is "-" + if the originating host does not support the concept of user ids. + <username> must not contain spaces. <session id> is a numeric string + such that the tuple of <username>, <session id>, <network type>, + <address type> and <address> form a globally unique identifier for + the session. + + The method of <session id> allocation is up to the creating tool, but + it has been suggested that a Network Time Protocol (NTP) timestamp be + used to ensure uniqueness [1]. + + <version> is a version number for this announcement. It is needed + for proxy announcements to detect which of several announcements for + the same session is the most recent. Again its usage is up to the + + + + + +Handley & Jacobson Standards Track [Page 9] + +RFC 2327 SDP April 1998 + + + creating tool, so long as <version> is increased when a modification + is made to the session data. Again, it is recommended (but not + mandatory) that an NTP timestamp is used. + + <network type> is a text string giving the type of network. + Initially "IN" is defined to have the meaning "Internet". <address + type> is a text string giving the type of the address that follows. + Initially "IP4" and "IP6" are defined. <address> is the globally + unique address of the machine from which the session was created. + For an address type of IP4, this is either the fully-qualified domain + name of the machine, or the dotted-decimal representation of the IP + version 4 address of the machine. For an address type of IP6, this + is either the fully-qualified domain name of the machine, or the + compressed textual representation of the IP version 6 address of the + machine. For both IP4 and IP6, the fully-qualified domain name is + the form that SHOULD be given unless this is unavailable, in which + case the globally unique address may be substituted. A local IP + address MUST NOT be used in any context where the SDP description + might leave the scope in which the address is meaningful. + + In general, the "o=" field serves as a globally unique identifier for + this version of this session description, and the subfields excepting + the version taken together identify the session irrespective of any + modifications. + + Session Name + + s=<session name> + + The "s=" field is the session name. There must be one and only one + "s=" field per session description, and it must contain ISO 10646 + characters (but see also the `charset' attribute below). + + Session and Media Information + + i=<session description> + + The "i=" field is information about the session. There may be at + most one session-level "i=" field per session description, and at + most one "i=" field per media. Although it may be omitted, this is + discouraged for session announcements, and user interfaces for + composing sessions should require text to be entered. If it is + present it must contain ISO 10646 characters (but see also the + `charset' attribute below). + + A single "i=" field can also be used for each media definition. In + media definitions, "i=" fields are primarily intended for labeling + media streams. As such, they are most likely to be useful when a + + + +Handley & Jacobson Standards Track [Page 10] + +RFC 2327 SDP April 1998 + + + single session has more than one distinct media stream of the same + media type. An example would be two different whiteboards, one for + slides and one for feedback and questions. + + URI + + u=<URI> + + o A URI is a Universal Resource Identifier as used by WWW clients + + o The URI should be a pointer to additional information about the + conference + + o This field is optional, but if it is present it should be specified + before the first media field + + o No more than one URI field is allowed per session description + + + Email Address and Phone Number + + e=<email address> + p=<phone number> + + o These specify contact information for the person responsible for + the conference. This is not necessarily the same person that + created the conference announcement. + + o Either an email field or a phone field must be specified. + Additional email and phone fields are allowed. + + o If these are present, they should be specified before the first + media field. + + o More than one email or phone field can be given for a session + description. + + o Phone numbers should be given in the conventional international + + format - preceded by a "+ and the international country code. + There must be a space or a hyphen ("-") between the country code + and the rest of the phone number. Spaces and hyphens may be used + to split up a phone field to aid readability if desired. For + example: + + p=+44-171-380-7777 or p=+1 617 253 6011 + + + + + +Handley & Jacobson Standards Track [Page 11] + +RFC 2327 SDP April 1998 + + + o Both email addresses and phone numbers can have an optional free + text string associated with them, normally giving the name of the + person who may be contacted. This should be enclosed in + parenthesis if it is present. For example: + + e=mjh@isi.edu (Mark Handley) + + The alternative RFC822 name quoting convention is also allowed for + both email addresses and phone numbers. For example, + + e=Mark Handley <mjh@isi.edu> + + The free text string should be in the ISO-10646 character set with + UTF-8 encoding, or alternatively in ISO-8859-1 or other encodings + if the appropriate charset session-level attribute is set. + + Connection Data + + c=<network type> <address type> <connection address> + + The "c=" field contains connection data. + + A session announcement must contain one "c=" field in each media + description (see below) or a "c=" field at the session-level. It may + contain a session-level "c=" field and one additional "c=" field per + media description, in which case the per-media values override the + session-level settings for the relevant media. + + The first sub-field is the network type, which is a text string + giving the type of network. Initially "IN" is defined to have the + meaning "Internet". + + The second sub-field is the address type. This allows SDP to be used + for sessions that are not IP based. Currently only IP4 is defined. + + The third sub-field is the connection address. Optional extra + subfields may be added after the connection address depending on the + value of the <address type> field. + + For IP4 addresses, the connection address is defined as follows: + + o Typically the connection address will be a class-D IP multicast + + group address. If the session is not multicast, then the + connection address contains the fully-qualified domain name or the + unicast IP address of the expected data source or data relay or + data sink as determined by additional attribute fields. It is not + expected that fully-qualified domain names or unicast addresses + + + +Handley & Jacobson Standards Track [Page 12] + +RFC 2327 SDP April 1998 + + + will be given in a session description that is communicated by a + multicast announcement, though this is not prohibited. If a + unicast data stream is to pass through a network address + translator, the use of a fully-qualified domain name rather than an + unicast IP address is RECOMMENDED. In other cases, the use of an + IP address to specify a particular interface on a multi-homed host + might be required. Thus this specification leaves the decision as + to which to use up to the individual application, but all + applications MUST be able to cope with receiving both formats. + + o Conferences using an IP multicast connection address must also have + a time to live (TTL) value present in addition to the multicast + address. The TTL and the address together define the scope with + which multicast packets sent in this conference will be sent. TTL + values must be in the range 0-255. + + The TTL for the session is appended to the address using a slash as + a separator. An example is: + + c=IN IP4 224.2.1.1/127 + + Hierarchical or layered encoding schemes are data streams where the + encoding from a single media source is split into a number of + layers. The receiver can choose the desired quality (and hence + bandwidth) by only subscribing to a subset of these layers. Such + layered encodings are normally transmitted in multiple multicast + groups to allow multicast pruning. This technique keeps unwanted + traffic from sites only requiring certain levels of the hierarchy. + For applications requiring multiple multicast groups, we allow the + following notation to be used for the connection address: + + <base multicast address>/<ttl>/<number of addresses> + + If the number of addresses is not given it is assumed to be one. + Multicast addresses so assigned are contiguously allocated above + the base address, so that, for example: + + c=IN IP4 224.2.1.1/127/3 + + would state that addresses 224.2.1.1, 224.2.1.2 and 224.2.1.3 are + to be used at a ttl of 127. This is semantically identical to + including multiple "c=" lines in a media description: + + c=IN IP4 224.2.1.1/127 + c=IN IP4 224.2.1.2/127 + c=IN IP4 224.2.1.3/127 + + + + + +Handley & Jacobson Standards Track [Page 13] + +RFC 2327 SDP April 1998 + + + Multiple addresses or "c=" lines can only be specified on a per- + media basis, and not for a session-level "c=" field. + + It is illegal for the slash notation described above to be used for + IP unicast addresses. + + Bandwidth + + b=<modifier>:<bandwidth-value> + + o This specifies the proposed bandwidth to be used by the session or + media, and is optional. + + o <bandwidth-value> is in kilobits per second + + o <modifier> is a single alphanumeric word giving the meaning of the + bandwidth figure. + + o Two modifiers are initially defined: + + CT Conference Total: An implicit maximum bandwidth is associated with + each TTL on the Mbone or within a particular multicast + administrative scope region (the Mbone bandwidth vs. TTL limits are + given in the MBone FAQ). If the bandwidth of a session or media in + a session is different from the bandwidth implicit from the scope, + a `b=CT:...' line should be supplied for the session giving the + proposed upper limit to the bandwidth used. The primary purpose of + this is to give an approximate idea as to whether two or more + conferences can co-exist simultaneously. + + AS Application-Specific Maximum: The bandwidth is interpreted to be + application-specific, i.e., will be the application's concept of + maximum bandwidth. Normally this will coincide with what is set on + the application's "maximum bandwidth" control if applicable. + + Note that CT gives a total bandwidth figure for all the media at + all sites. AS gives a bandwidth figure for a single media at a + single site, although there may be many sites sending + simultaneously. + + o Extension Mechanism: Tool writers can define experimental bandwidth + modifiers by prefixing their modifier with "X-". For example: + + b=X-YZ:128 + + SDP parsers should ignore bandwidth fields with unknown modifiers. + Modifiers should be alpha-numeric and, although no length limit is + given, they are recommended to be short. + + + +Handley & Jacobson Standards Track [Page 14] + +RFC 2327 SDP April 1998 + + + Times, Repeat Times and Time Zones + + t=<start time> <stop time> + + o "t=" fields specify the start and stop times for a conference + session. Multiple "t=" fields may be used if a session is active + at multiple irregularly spaced times; each additional "t=" field + specifies an additional period of time for which the session will + be active. If the session is active at regular times, an "r=" + field (see below) should be used in addition to and following a + "t=" field - in which case the "t=" field specifies the start and + stop times of the repeat sequence. + + o The first and second sub-fields give the start and stop times for + the conference respectively. These values are the decimal + representation of Network Time Protocol (NTP) time values in + seconds [1]. To convert these values to UNIX time, subtract + decimal 2208988800. + + o If the stop-time is set to zero, then the session is not bounded, + though it will not become active until after the start-time. If + the start-time is also zero, the session is regarded as permanent. + + User interfaces should strongly discourage the creation of + unbounded and permanent sessions as they give no information about + when the session is actually going to terminate, and so make + scheduling difficult. + + The general assumption may be made, when displaying unbounded + sessions that have not timed out to the user, that an unbounded + session will only be active until half an hour from the current + time or the session start time, whichever is the later. If + behaviour other than this is required, an end-time should be given + and modified as appropriate when new information becomes available + about when the session should really end. + + Permanent sessions may be shown to the user as never being active + unless there are associated repeat times which state precisely when + the session will be active. In general, permanent sessions should + not be created for any session expected to have a duration of less + than 2 months, and should be discouraged for sessions expected to + have a duration of less than 6 months. + + r=<repeat interval> <active duration> <list of offsets from start- + time> + + o "r=" fields specify repeat times for a session. For example, if + a session is active at 10am on Monday and 11am on Tuesday for one + + + +Handley & Jacobson Standards Track [Page 15] + +RFC 2327 SDP April 1998 + + + hour each week for three months, then the <start time> in the + corresponding "t=" field would be the NTP representation of 10am on + the first Monday, the <repeat interval> would be 1 week, the + <active duration> would be 1 hour, and the offsets would be zero + and 25 hours. The corresponding "t=" field stop time would be the + NTP representation of the end of the last session three months + later. By default all fields are in seconds, so the "r=" and "t=" + fields might be: + + t=3034423619 3042462419 + r=604800 3600 0 90000 + + To make announcements more compact, times may also be given in units + of days, hours or minutes. The syntax for these is a number + immediately followed by a single case-sensitive character. + Fractional units are not allowed - a smaller unit should be used + instead. The following unit specification characters are allowed: + + d - days (86400 seconds) + h - minutes (3600 seconds) + m - minutes (60 seconds) + s - seconds (allowed for completeness but not recommended) + + Thus, the above announcement could also have been written: + + r=7d 1h 0 25h + + Monthly and yearly repeats cannot currently be directly specified + with a single SDP repeat time - instead separate "t" fields should + be used to explicitly list the session times. + + z=<adjustment time> <offset> <adjustment time> <offset> .... + + o To schedule a repeated session which spans a change from daylight- + saving time to standard time or vice-versa, it is necessary to + specify offsets from the base repeat times. This is required + because different time zones change time at different times of day, + different countries change to or from daylight time on different + dates, and some countries do not have daylight saving time at all. + + Thus in order to schedule a session that is at the same time winter + and summer, it must be possible to specify unambiguously by whose + time zone a session is scheduled. To simplify this task for + receivers, we allow the sender to specify the NTP time that a time + zone adjustment happens and the offset from the time when the + session was first scheduled. The "z" field allows the sender to + specify a list of these adjustment times and offsets from the base + time. + + + +Handley & Jacobson Standards Track [Page 16] + +RFC 2327 SDP April 1998 + + + An example might be: + + z=2882844526 -1h 2898848070 0 + + This specifies that at time 2882844526 the time base by which the + session's repeat times are calculated is shifted back by 1 hour, + and that at time 2898848070 the session's original time base is + restored. Adjustments are always relative to the specified start + time - they are not cumulative. + + o If a session is likely to last several years, it is expected + that + the session announcement will be modified periodically rather than + transmit several years worth of adjustments in one announcement. + + Encryption Keys + + k=<method> + k=<method>:<encryption key> + + o The session description protocol may be used to convey encryption + keys. A key field is permitted before the first media entry (in + which case it applies to all media in the session), or for each + media entry as required. + + o The format of keys and their usage is outside the scope of this + document, but see [3]. + + o The method indicates the mechanism to be used to obtain a usable + key by external means, or from the encoded encryption key given. + + The following methods are defined: + + k=clear:<encryption key> + The encryption key (as described in [3] for RTP media streams + under the AV profile) is included untransformed in this key + field. + + k=base64:<encoded encryption key> + The encryption key (as described in [3] for RTP media streams + under the AV profile) is included in this key field but has been + base64 encoded because it includes characters that are + prohibited in SDP. + + k=uri:<URI to obtain key> + A Universal Resource Identifier as used by WWW clients is + included in this key field. The URI refers to the data + containing the key, and may require additional authentication + + + +Handley & Jacobson Standards Track [Page 17] + +RFC 2327 SDP April 1998 + + + before the key can be returned. When a request is made to the + given URI, the MIME content-type of the reply specifies the + encoding for the key in the reply. The key should not be + obtained until the user wishes to join the session to reduce + synchronisation of requests to the WWW server(s). + + k=prompt + No key is included in this SDP description, but the session or + media stream referred to by this key field is encrypted. The + user should be prompted for the key when attempting to join the + session, and this user-supplied key should then be used to + decrypt the media streams. + + Attributes + + a=<attribute> + a=<attribute>:<value> + + Attributes are the primary means for extending SDP. Attributes may + be defined to be used as "session-level" attributes, "media-level" + attributes, or both. + + A media description may have any number of attributes ("a=" fields) + which are media specific. These are referred to as "media-level" + attributes and add information about the media stream. Attribute + fields can also be added before the first media field; these + "session-level" attributes convey additional information that applies + to the conference as a whole rather than to individual media; an + example might be the conference's floor control policy. + + Attribute fields may be of two forms: + + o property attributes. A property attribute is simply of the form + "a=<flag>". These are binary attributes, and the presence of the + attribute conveys that the attribute is a property of the session. + An example might be "a=recvonly". + + o value attributes. A value attribute is of the form + "a=<attribute>:<value>". An example might be that a whiteboard + could have the value attribute "a=orient:landscape" + + Attribute interpretation depends on the media tool being invoked. + Thus receivers of session descriptions should be configurable in + their interpretation of announcements in general and of attributes in + particular. + + Attribute names must be in the US-ASCII subset of ISO-10646/UTF-8. + + + + +Handley & Jacobson Standards Track [Page 18] + +RFC 2327 SDP April 1998 + + + Attribute values are byte strings, and MAY use any byte value except + 0x00 (Nul), 0x0A (LF), and 0x0D (CR). By default, attribute values + are to be interpreted as in ISO-10646 character set with UTF-8 + encoding. Unlike other text fields, attribute values are NOT + normally affected by the `charset' attribute as this would make + comparisons against known values problematic. However, when an + attribute is defined, it can be defined to be charset-dependent, in + which case it's value should be interpreted in the session charset + rather than in ISO-10646. + + Attributes that will be commonly used can be registered with IANA + (see Appendix B). Unregistered attributes should begin with "X-" to + prevent inadvertent collision with registered attributes. In either + case, if an attribute is received that is not understood, it should + simply be ignored by the receiver. + + Media Announcements + + m=<media> <port> <transport> <fmt list> + + A session description may contain a number of media descriptions. + Each media description starts with an "m=" field, and is terminated + by either the next "m=" field or by the end of the session + description. A media field also has several sub-fields: + + o The first sub-field is the media type. Currently defined media are + "audio", "video", "application", "data" and "control", though this + list may be extended as new communication modalities emerge (e.g., + telepresense). The difference between "application" and "data" is + that the former is a media flow such as whiteboard information, and + the latter is bulk-data transfer such as multicasting of program + executables which will not typically be displayed to the user. + "control" is used to specify an additional conference control + channel for the session. + + o The second sub-field is the transport port to which the media + stream will be sent. The meaning of the transport port depends on + the network being used as specified in the relevant "c" field and + on the transport protocol defined in the third sub-field. Other + ports used by the media application (such as the RTCP port, see + [2]) should be derived algorithmically from the base media port. + + Note: For transports based on UDP, the value should be in the range + 1024 to 65535 inclusive. For RTP compliance it should be an even + number. + + + + + + +Handley & Jacobson Standards Track [Page 19] + +RFC 2327 SDP April 1998 + + + For applications where hierarchically encoded streams are being + sent to a unicast address, it may be necessary to specify multiple + transport ports. This is done using a similar notation to that + used for IP multicast addresses in the "c=" field: + + m=<media> <port>/<number of ports> <transport> <fmt list> + + In such a case, the ports used depend on the transport protocol. + For RTP, only the even ports are used for data and the + corresponding one-higher odd port is used for RTCP. For example: + + m=video 49170/2 RTP/AVP 31 + + would specify that ports 49170 and 49171 form one RTP/RTCP pair and + 49172 and 49173 form the second RTP/RTCP pair. RTP/AVP is the + transport protocol and 31 is the format (see below). + + It is illegal for both multiple addresses to be specified in the + "c=" field and for multiple ports to be specified in the "m=" field + in the same session description. + + o The third sub-field is the transport protocol. The transport + protocol values are dependent on the address-type field in the "c=" + fields. Thus a "c=" field of IP4 defines that the transport + protocol runs over IP4. For IP4, it is normally expected that most + media traffic will be carried as RTP over UDP. The following + transport protocols are preliminarily defined, but may be extended + through registration of new protocols with IANA: + + - RTP/AVP - the IETF's Realtime Transport Protocol using the + Audio/Video profile carried over UDP. + + - udp - User Datagram Protocol + + If an application uses a single combined proprietary media format + and transport protocol over UDP, then simply specifying the + transport protocol as udp and using the format field to distinguish + the combined protocol is recommended. If a transport protocol is + used over UDP to carry several distinct media types that need to be + distinguished by a session directory, then specifying the transport + protocol and media format separately is necessary. RTP is an + example of a transport-protocol that carries multiple payload + formats that must be distinguished by the session directory for it + to know how to start appropriate tools, relays, mixers or + recorders. + + + + + + +Handley & Jacobson Standards Track [Page 20] + +RFC 2327 SDP April 1998 + + + The main reason to specify the transport-protocol in addition to + the media format is that the same standard media formats may be + carried over different transport protocols even when the network + protocol is the same - a historical example is vat PCM audio and + RTP PCM audio. In addition, relays and monitoring tools that are + transport-protocol-specific but format-independent are possible. + + For RTP media streams operating under the RTP Audio/Video Profile + [3], the protocol field is "RTP/AVP". Should other RTP profiles be + defined in the future, their profiles will be specified in the same + way. For example, the protocol field "RTP/XYZ" would specify RTP + operating under a profile whose short name is "XYZ". + + o The fourth and subsequent sub-fields are media formats. For audio + and video, these will normally be a media payload type as defined + in the RTP Audio/Video Profile. + + When a list of payload formats is given, this implies that all of + these formats may be used in the session, but the first of these + formats is the default format for the session. + + For media whose transport protocol is not RTP or UDP the format + field is protocol specific. Such formats should be defined in an + additional specification document. + + For media whose transport protocol is RTP, SDP can be used to + provide a dynamic binding of media encoding to RTP payload type. + The encoding names in the RTP AV Profile do not specify unique + audio encodings (in terms of clock rate and number of audio + channels), and so they are not used directly in SDP format fields. + Instead, the payload type number should be used to specify the + format for static payload types and the payload type number along + with additional encoding information should be used for dynamically + allocated payload types. + + An example of a static payload type is u-law PCM coded single + channel audio sampled at 8KHz. This is completely defined in the + RTP Audio/Video profile as payload type 0, so the media field for + such a stream sent to UDP port 49232 is: + + m=video 49232 RTP/AVP 0 + + An example of a dynamic payload type is 16 bit linear encoded + stereo audio sampled at 16KHz. If we wish to use dynamic RTP/AVP + payload type 98 for such a stream, additional information is + required to decode it: + + m=video 49232 RTP/AVP 98 + + + +Handley & Jacobson Standards Track [Page 21] + +RFC 2327 SDP April 1998 + + + a=rtpmap:98 L16/16000/2 + + The general form of an rtpmap attribute is: + + a=rtpmap:<payload type> <encoding name>/<clock rate>[/<encoding + parameters>] + + For audio streams, <encoding parameters> may specify the number of + audio channels. This parameter may be omitted if the number of + channels is one provided no additional parameters are needed. For + video streams, no encoding parameters are currently specified. + + Additional parameters may be defined in the future, but + codecspecific parameters should not be added. Parameters added to + an rtpmap attribute should only be those required for a session + directory to make the choice of appropriate media too to + participate in a session. Codec-specific parameters should be + added in other attributes. + + Up to one rtpmap attribute can be defined for each media format + specified. Thus we might have: + + m=audio 49230 RTP/AVP 96 97 98 + a=rtpmap:96 L8/8000 + a=rtpmap:97 L16/8000 + a=rtpmap:98 L16/11025/2 + + RTP profiles that specify the use of dynamic payload types must + define the set of valid encoding names and/or a means to register + encoding names if that profile is to be used with SDP. + + Experimental encoding formats can also be specified using rtpmap. + RTP formats that are not registered as standard format names must + be preceded by "X-". Thus a new experimental redundant audio + stream called GSMLPC using dynamic payload type 99 could be + specified as: + + m=video 49232 RTP/AVP 99 + a=rtpmap:99 X-GSMLPC/8000 + + Such an experimental encoding requires that any site wishing to + receive the media stream has relevant configured state in its + session directory to know which tools are appropriate. + + Note that RTP audio formats typically do not include information + about the number of samples per packet. If a non-default (as + defined in the RTP Audio/Video Profile) packetisation is required, + the "ptime" attribute is used as given below. + + + +Handley & Jacobson Standards Track [Page 22] + +RFC 2327 SDP April 1998 + + + For more details on RTP audio and video formats, see [3]. + + o Formats for non-RTP media should be registered as MIME content + types as described in Appendix B. For example, the LBL whiteboard + application might be registered as MIME content-type application/wb + with encoding considerations specifying that it operates over UDP, + with no appropriate file format. In SDP this would then be + expressed using a combination of the "media" field and the "fmt" + field, as follows: + + m=application 32416 udp wb + + Suggested Attributes + + The following attributes are suggested. Since application writers + may add new attributes as they are required, this list is not + exhaustive. + + a=cat:<category> + This attribute gives the dot-separated hierarchical category of + the session. This is to enable a receiver to filter unwanted + sessions by category. It would probably have been a compulsory + separate field, except for its experimental nature at this time. + It is a session-level attribute, and is not dependent on charset. + + a=keywds:<keywords> + Like the cat attribute, this is to assist identifying wanted + sessions at the receiver. This allows a receiver to select + interesting session based on keywords describing the purpose of + the session. It is a session-level attribute. It is a charset + dependent attribute, meaning that its value should be interpreted + in the charset specified for the session description if one is + specified, or by default in ISO 10646/UTF-8. + + a=tool:<name and version of tool> + This gives the name and version number of the tool used to create + the session description. It is a session-level attribute, and is + not dependent on charset. + + a=ptime:<packet time> + This gives the length of time in milliseconds represented by the + media in a packet. This is probably only meaningful for audio + data. It should not be necessary to know ptime to decode RTP or + vat audio, and it is intended as a recommendation for the + encoding/packetisation of audio. It is a media attribute, and is + not dependent on charset. + + + + + +Handley & Jacobson Standards Track [Page 23] + +RFC 2327 SDP April 1998 + + + a=recvonly + This specifies that the tools should be started in receive-only + mode where applicable. It can be either a session or media + attribute, and is not dependent on charset. + + a=sendrecv + This specifies that the tools should be started in send and + receive mode. This is necessary for interactive conferences with + tools such as wb which defaults to receive only mode. It can be + either a session or media attribute, and is not dependent on + charset. + + a=sendonly + This specifies that the tools should be started in send-only + mode. An example may be where a different unicast address is to + be used for a traffic destination than for a traffic source. In + such a case, two media descriptions may be use, one sendonly and + one recvonly. It can be either a session or media attribute, but + would normally only be used as a media attribute, and is not + dependent on charset. + + a=orient:<whiteboard orientation> + Normally this is only used in a whiteboard media specification. + It specifies the orientation of a the whiteboard on the screen. + It is a media attribute. Permitted values are `portrait', + `landscape' and `seascape' (upside down landscape). It is not + dependent on charset + + a=type:<conference type> + This specifies the type of the conference. Suggested values are + `broadcast', `meeting', `moderated', `test' and `H332'. + `recvonly' should be the default for `type:broadcast' sessions, + `type:meeting' should imply `sendrecv' and `type:moderated' + should indicate the use of a floor control tool and that the + media tools are started so as to "mute" new sites joining the + conference. + + Specifying the attribute type:H332 indicates that this loosely + coupled session is part of a H.332 session as defined in the ITU + H.332 specification [10]. Media tools should be started + `recvonly'. + + Specifying the attribute type:test is suggested as a hint that, + unless explicitly requested otherwise, receivers can safely avoid + displaying this session description to users. + + The type attribute is a session-level attribute, and is not + dependent on charset. + + + +Handley & Jacobson Standards Track [Page 24] + +RFC 2327 SDP April 1998 + + + a=charset:<character set> + This specifies the character set to be used to display the + session name and information data. By default, the ISO-10646 + character set in UTF-8 encoding is used. If a more compact + representation is required, other character sets may be used such + as ISO-8859-1 for Northern European languages. In particular, + the ISO 8859-1 is specified with the following SDP attribute: + + a=charset:ISO-8859-1 + + This is a session-level attribute; if this attribute is present, + it must be before the first media field. The charset specified + MUST be one of those registered with IANA, such as ISO-8859-1. + The character set identifier is a US-ASCII string and MUST be + compared against the IANA identifiers using a case-insensitive + comparison. If the identifier is not recognised or not + supported, all strings that are affected by it SHOULD be regarded + as byte strings. + + Note that a character set specified MUST still prohibit the use + of bytes 0x00 (Nul), 0x0A (LF) and 0x0d (CR). Character sets + requiring the use of these characters MUST define a quoting + mechanism that prevents these bytes appearing within text fields. + + a=sdplang:<language tag> + This can be a session level attribute or a media level attribute. + As a session level attribute, it specifies the language for the + session description. As a media level attribute, it specifies + the language for any media-level SDP information field associated + with that media. Multiple sdplang attributes can be provided + either at session or media level if multiple languages in the + session description or media use multiple languages, in which + case the order of the attributes indicates the order of + importance of the various languages in the session or media from + most important to least important. + + In general, sending session descriptions consisting of multiple + languages should be discouraged. Instead, multiple descriptions + should be sent describing the session, one in each language. + However this is not possible with all transport mechanisms, and + so multiple sdplang attributes are allowed although not + recommended. + + The sdplang attribute value must be a single RFC 1766 language + tag in US-ASCII. It is not dependent on the charset attribute. + An sdplang attribute SHOULD be specified when a session is of + + + + + +Handley & Jacobson Standards Track [Page 25] + +RFC 2327 SDP April 1998 + + + sufficient scope to cross geographic boundaries where the + language of recipients cannot be assumed, or where the session is + in a different language from the locally assumed norm. + + a=lang:<language tag> + This can be a session level attribute or a media level attribute. + As a session level attribute, it specifies the default language + for the session being described. As a media level attribute, it + specifies the language for that media, overriding any session- + level language specified. Multiple lang attributes can be + provided either at session or media level if multiple languages + if the session description or media use multiple languages, in + which case the order of the attributes indicates the order of + importance of the various languages in the session or media from + most important to least important. + + The lang attribute value must be a single RFC 1766 language tag + in US-ASCII. It is not dependent on the charset attribute. A + lang attribute SHOULD be specified when a session is of + sufficient scope to cross geographic boundaries where the + language of recipients cannot be assumed, or where the session is + in a different language from the locally assumed norm. + + a=framerate:<frame rate> + This gives the maximum video frame rate in frames/sec. It is + intended as a recommendation for the encoding of video data. + Decimal representations of fractional values using the notation + "<integer>.<fraction>" are allowed. It is a media attribute, is + only defined for video media, and is not dependent on charset. + + a=quality:<quality> + This gives a suggestion for the quality of the encoding as an + integer value. + + The intention of the quality attribute for video is to specify a + non-default trade-off between frame-rate and still-image quality. + For video, the value in the range 0 to 10, with the following + suggested meaning: + + 10 - the best still-image quality the compression scheme can + give. + + 5 - the default behaviour given no quality suggestion. + + 0 - the worst still-image quality the codec designer thinks is + still usable. + + It is a media attribute, and is not dependent on charset. + + + +Handley & Jacobson Standards Track [Page 26] + +RFC 2327 SDP April 1998 + + + a=fmtp:<format> <format specific parameters> + This attribute allows parameters that are specific to a + particular format to be conveyed in a way that SDP doesn't have + to understand them. The format must be one of the formats + specified for the media. Format-specific parameters may be any + set of parameters required to be conveyed by SDP and given + unchanged to the media tool that will use this format. + + It is a media attribute, and is not dependent on charset. + +6.1. Communicating Conference Control Policy + + There is some debate over the way conference control policy should be + communicated. In general, the authors believe that an implicit + declarative style of specifying conference control is desirable where + possible. + + A simple declarative style uses a single conference attribute field + before the first media field, possibly supplemented by properties + such as `recvonly' for some of the media tools. This conference + attribute conveys the conference control policy. An example might be: + + a=type:moderated + + In some cases, however, it is possible that this may be insufficient + to communicate the details of an unusual conference control policy. + If this is the case, then a conference attribute specifying external + control might be set, and then one or more "media" fields might be + used to specify the conference control tools and configuration data + for those tools. An example is an ITU H.332 session: + + c=IN IP4 224.5.6.7 + a=type:H332 + m=audio 49230 RTP/AVP 0 + m=video 49232 RTP/AVP 31 + m=application 12349 udp wb + m=control 49234 H323 mc + c=IN IP4 134.134.157.81 + + In this example, a general conference attribute (type:H332) is + specified stating that conference control will be provided by an + external H.332 tool, and a contact addresses for the H.323 session + multipoint controller is given. + + In this document, only the declarative style of conference control + declaration is specified. Other forms of conference control should + specify an appropriate type attribute, and should define the + implications this has for control media. + + + +Handley & Jacobson Standards Track [Page 27] + +RFC 2327 SDP April 1998 + + +7. Security Considerations + + SDP is a session description format that describes multimedia + sessions. A session description should not be trusted unless it has + been obtained by an authenticated transport protocol from a trusted + source. Many different transport protocols may be used to distribute + session description, and the nature of the authentication will differ + from transport to transport. + + One transport that will frequently be used to distribute session + descriptions is the Session Announcement Protocol (SAP). SAP + provides both encryption and authentication mechanisms but due to the + nature of session announcements it is likely that there are many + occasions where the originator of a session announcement cannot be + authenticated because they are previously unknown to the receiver of + the announcement and because no common public key infrastructure is + available. + + On receiving a session description over an unauthenticated transport + mechanism or from an untrusted party, software parsing the session + should take a few precautions. Session description contain + information required to start software on the receivers system. + Software that parses a session description MUST not be able to start + other software except that which is specifically configured as + appropriate software to participate in multimedia sessions. It is + normally considered INAPPROPRIATE for software parsing a session + description to start, on a user's system, software that is + appropriate to participate in multimedia sessions, without the user + first being informed that such software will be started and giving + their consent. Thus a session description arriving by session + announcement, email, session invitation, or WWW page SHOULD not + deliver the user into an {it interactive} multimedia session without + the user being aware that this will happen. As it is not always + simple to tell whether a session is interactive or not, applications + that are unsure should assume sessions are interactive. + + In this specification, there are no attributes which would allow the + recipient of a session description to be informed to start multimedia + tools in a mode where they default to transmitting. Under some + circumstances it might be appropriate to define such attributes. If + this is done an application parsing a session description containing + such attributes SHOULD either ignore them, or inform the user that + joining this session will result in the automatic transmission of + multimedia data. The default behaviour for an unknown attribute is + to ignore it. + + + + + + +Handley & Jacobson Standards Track [Page 28] + +RFC 2327 SDP April 1998 + + + Session descriptions may be parsed at intermediate systems such as + firewalls for the purposes of opening a hole in the firewall to allow + the participation in multimedia sessions. It is considered + INAPPROPRIATE for a firewall to open such holes for unicast data + streams unless the session description comes in a request from inside + the firewall. + + For multicast sessions, it is likely that local administrators will + apply their own policies, but the exclusive use of "local" or "site- + local" administrative scope within the firewall and the refusal of + the firewall to open a hole for such scopes will provide separation + of global multicast sessions from local ones. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Handley & Jacobson Standards Track [Page 29] + +RFC 2327 SDP April 1998 + + +Appendix A: SDP Grammar + + This appendix provides an Augmented BNF grammar for SDP. ABNF is + defined in RFC 2234. + + + announcement = proto-version + origin-field + session-name-field + information-field + uri-field + email-fields + phone-fields + connection-field + bandwidth-fields + time-fields + key-field + attribute-fields + media-descriptions + + proto-version = "v=" 1*DIGIT CRLF + ;this memo describes version 0 + + origin-field = "o=" username space + sess-id space sess-version space + nettype space addrtype space + addr CRLF + + session-name-field = "s=" text CRLF + + information-field = ["i=" text CRLF] + + uri-field = ["u=" uri CRLF] + + email-fields = *("e=" email-address CRLF) + + phone-fields = *("p=" phone-number CRLF) + + + connection-field = ["c=" nettype space addrtype space + connection-address CRLF] + ;a connection field must be present + ;in every media description or at the + ;session-level + + + bandwidth-fields = *("b=" bwtype ":" bandwidth CRLF) + + + + +Handley & Jacobson Standards Track [Page 30] + +RFC 2327 SDP April 1998 + + + time-fields = 1*( "t=" start-time space stop-time + *(CRLF repeat-fields) CRLF) + [zone-adjustments CRLF] + + + repeat-fields = "r=" repeat-interval space typed-time + 1*(space typed-time) + + + zone-adjustments = time space ["-"] typed-time + *(space time space ["-"] typed-time) + + + key-field = ["k=" key-type CRLF] + + + key-type = "prompt" | + "clear:" key-data | + "base64:" key-data | + "uri:" uri + + + key-data = email-safe | "~" | " + + + attribute-fields = *("a=" attribute CRLF) + + + media-descriptions = *( media-field + information-field + *(connection-field) + bandwidth-fields + key-field + attribute-fields ) + + + media-field = "m=" media space port ["/" integer] + space proto 1*(space fmt) CRLF + + + media = 1*(alpha-numeric) + ;typically "audio", "video", "application" + ;or "data" + + fmt = 1*(alpha-numeric) + ;typically an RTP payload type for audio + ;and video media + + + + +Handley & Jacobson Standards Track [Page 31] + +RFC 2327 SDP April 1998 + + + proto = 1*(alpha-numeric) + ;typically "RTP/AVP" or "udp" for IP4 + + + port = 1*(DIGIT) + ;should in the range "1024" to "65535" inclusive + ;for UDP based media + + + attribute = (att-field ":" att-value) | att-field + + + att-field = 1*(alpha-numeric) + + + att-value = byte-string + + + sess-id = 1*(DIGIT) + ;should be unique for this originating username/host + + + sess-version = 1*(DIGIT) + ;0 is a new session + + + connection-address = multicast-address + | addr + + + multicast-address = 3*(decimal-uchar ".") decimal-uchar "/" ttl + [ "/" integer ] + ;multicast addresses may be in the range + ;224.0.0.0 to 239.255.255.255 + + ttl = decimal-uchar + + start-time = time | "0" + + stop-time = time | "0" + + time = POS-DIGIT 9*(DIGIT) + ;sufficient for 2 more centuries + + + repeat-interval = typed-time + + + + + +Handley & Jacobson Standards Track [Page 32] + +RFC 2327 SDP April 1998 + + + typed-time = 1*(DIGIT) [fixed-len-time-unit] + + + fixed-len-time-unit = "d" | "h" | "m" | "s" + + + bwtype = 1*(alpha-numeric) + + bandwidth = 1*(DIGIT) + + + username = safe + ;pretty wide definition, but doesn't include space + + + email-address = email | email "(" email-safe ")" | + email-safe "<" email ">" + + + email = ;defined in RFC822 + + + uri= ;defined in RFC1630 + + + phone-number = phone | phone "(" email-safe ")" | + email-safe "<" phone ">" + + + phone = "+" POS-DIGIT 1*(space | "-" | DIGIT) + ;there must be a space or hyphen between the + ;international code and the rest of the number. + + + nettype = "IN" + ;list to be extended + + + addrtype = "IP4" | "IP6" + ;list to be extended + + + addr = FQDN | unicast-address + + + FQDN = 4*(alpha-numeric|"-"|".") + ;fully qualified domain name as specified in RFC1035 + + + + +Handley & Jacobson Standards Track [Page 33] + +RFC 2327 SDP April 1998 + + + unicast-address = IP4-address | IP6-address + + + IP4-address = b1 "." decimal-uchar "." decimal-uchar "." b4 + b1 = decimal-uchar + ;less than "224"; not "0" or "127" + b4 = decimal-uchar + ;not "0" + + IP6-address = ;to be defined + + + text = byte-string + ;default is to interpret this as IS0-10646 UTF8 + ;ISO 8859-1 requires a "a=charset:ISO-8859-1" + ;session-level attribute to be used + + + byte-string = 1*(0x01..0x09|0x0b|0x0c|0x0e..0xff) + ;any byte except NUL, CR or LF + + + decimal-uchar = DIGIT + | POS-DIGIT DIGIT + | ("1" 2*(DIGIT)) + | ("2" ("0"|"1"|"2"|"3"|"4") DIGIT) + | ("2" "5" ("0"|"1"|"2"|"3"|"4"|"5")) + + + integer = POS-DIGIT *(DIGIT) + + + alpha-numeric = ALPHA | DIGIT + + + DIGIT = "0" | POS-DIGIT + + + POS-DIGIT = "1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9" + + + ALPHA = "a"|"b"|"c"|"d"|"e"|"f"|"g"|"h"|"i"|"j"|"k"| + "l"|"m"|"n"|"o "|"p"|"q"|"r"|"s"|"t"|"u"|"v"| + "w"|"x"|"y"|"z"|"A"|"B"|"C "|"D"|"E"|"F"|"G"| + "H"|"I"|"J"|"K"|"L"|"M"|"N"|"O"|"P"|" Q"|"R"| + "S"|"T"|"U"|"V"|"W"|"X"|"Y"|"Z" + + + + + +Handley & Jacobson Standards Track [Page 34] + +RFC 2327 SDP April 1998 + + + email-safe = safe | space | tab + + + safe = alpha-numeric | + "'" | "'" | "-" | "." | "/" | ":" | "?" | """ | + "#" | "$" | "&" | "*" | ";" | "=" | "@" | "[" | + "]" | "^" | "_" | "`" | "{" | "|" | "}" | "+" | + "~" | " + + + space = %d32 + tab = %d9 + CRLF = %d13.10 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Handley & Jacobson Standards Track [Page 35] + +RFC 2327 SDP April 1998 + + +Appendix B: Guidelines for registering SDP names with IANA + + There are seven field names that may be registered with IANA. Using + the terminology in the SDP specification BNF, they are "media", + "proto", "fmt", "att-field", "bwtype", "nettype" and "addrtype". + + "media" (eg, audio, video, application, data). + + Packetized media types, such as those used by RTP, share the + namespace used by media types registry [RFC 2048] (i.e. "MIME + types"). The list of valid media names is the set of top-level + MIME content types. The set of media is intended to be small and + not to be extended except under rare circumstances. (The MIME + subtype corresponds to the "fmt" parameter below). + + "proto" + + In general this should be an IETF standards-track transport + protocol identifier such as RTP/AVP (rfc 1889 under the rfc 1890 + profile). + + However, people will want to invent their own proprietary + transport protocols. Some of these should be registered as a + "fmt" using "udp" as the protocol and some of which probably + can't be. + + Where the protocol and the application are intimately linked, + such as with the LBL whiteboard wb which used a proprietary and + special purpose protocol over UDP, the protocol name should be + "udp" and the format name that should be registered is "wb". The + rules for formats (see below) apply to such registrations. + + Where the proprietary transport protocol really carries many + different data formats, it is possible to register a new protocol + name with IANA. In such a case, an RFC MUST be produced + describing the protocol and referenced in the registration. Such + an RFC MAY be informational, although it is preferable if it is + standards-track. + + "fmt" + + The format namespace is dependent on the context of the "proto" + field, so a format cannot be registered without specifying one or + more transport protocols that it applies to. + + Formats cover all the possible encodings that might want to be + transported in a multimedia session. + + + + +Handley & Jacobson Standards Track [Page 36] + +RFC 2327 SDP April 1998 + + + For RTP formats that have been assigned static payload types, the + payload type number is used. For RTP formats using a dynamic + payload type number, the dynamic payload type number is given as + the format and an additional "rtpmap" attribute specifies the + format and parameters. + + For non-RTP formats, any unregistered format name may be + registered through the MIME-type registration process [RFC 2048]. + The type given here is the MIME subtype only (the top-level MIME + content type is specified by the media parameter). The MIME type + registration SHOULD reference a standards-track RFC which + describes the transport protocol for this media type. If there + is an existing MIME type for this format, the MIME registration + should be augmented to reference the transport specification for + this media type. If there is not an existing MIME type for this + format, and there exists no appropriate file format, this should + be noted in the encoding considerations as "no appropriate file + format". + + "att-field" (Attribute names) + + Attribute field names MAY be registered with IANA, although this + is not compulsory, and unknown attributes are simply ignored. + + When an attribute is registered, it must be accompanied by a + brief specification stating the following: + + o contact name, email address and telephone number + + o attribute-name (as it will appear in SDP) + + o long-form attribute name in English + + o type of attribute (session level, media level, or both) + + o whether the attribute value is subject to the charset + attribute. + + o a one paragraph explanation of the purpose of the attribute. + + o a specification of appropriate attribute values for this + attribute. + + IANA will not sanity check such attribute registrations except to + ensure that they do not clash with existing registrations. + + + + + + +Handley & Jacobson Standards Track [Page 37] + +RFC 2327 SDP April 1998 + + + Although the above is the minimum that IANA will accept, if the + attribute is expected to see widespread use and interoperability + is an issue, authors are encouraged to produce a standards-track + RFC that specifies the attribute more precisely. + + Submitters of registrations should ensure that the specification + is in the spirit of SDP attributes, most notably that the + attribute is platform independent in the sense that it makes no + implicit assumptions about operating systems and does not name + specific pieces of software in a manner that might inhibit + interoperability. + + "bwtype" (bandwidth specifiers) + + A proliferation of bandwidth specifiers is strongly discouraged. + + New bandwidth specifiers may be registered with IANA. The + submission MUST reference a standards-track RFC specifying the + semantics of the bandwidth specifier precisely, and indicating + when it should be used, and why the existing registered bandwidth + specifiers do not suffice. + + "nettype" (Network Type) + + New network types may be registered with IANA if SDP needs to be + used in the context of non-internet environments. Whilst these + are not normally the preserve of IANA, there may be circumstances + when an Internet application needs to interoperate with a non- + internet application, such as when gatewaying an internet + telephony call into the PSTN. The number of network types should + be small and should be rarely extended. A new network type + cannot be registered without registering at least one address + type to be used with that network type. A new network type + registration MUST reference an RFC which gives details of the + network type and address type and specifies how and when they + would be used. Such an RFC MAY be Informational. + + "addrtype" (Address Type) + + New address types may be registered with IANA. An address type + is only meaningful in the context of a network type, and any + registration of an address type MUST specify a registered network + type, or be submitted along with a network type registration. A + new address type registration MUST reference an RFC giving + details of the syntax of the address type. Such an RFC MAY be + Informational. Address types are not expected to be registered + frequently. + + + + +Handley & Jacobson Standards Track [Page 38] + +RFC 2327 SDP April 1998 + + + Registration Procedure + + To register a name the above guidelines should be followed regarding + the required level of documentation that is required. The + registration itself should be sent to IANA. Attribute registrations + should include the information given above. Other registrations + should include the following additional information: + + o contact name, email address and telephone number + + o name being registered (as it will appear in SDP) + + o long-form name in English + + o type of name ("media", "proto", "fmt", "bwtype", "nettype", or + "addrtype") + + o a one paragraph explanation of the purpose of the registered name. + + o a reference to the specification (eg RFC number) of the registered + name. + + IANA may refer any registration to the IESG or to any appropriate + IETF working group for review, and may request revisions to be made + before a registration will be made. + + + + + + + + + + + + + + + + + + + + + + + + + + +Handley & Jacobson Standards Track [Page 39] + +RFC 2327 SDP April 1998 + + +Appendix C: Authors' Addresses + + Mark Handley + Information Sciences Institute + c/o MIT Laboratory for Computer Science + 545 Technology Square + Cambridge, MA 02139 + United States + electronic mail: mjh@isi.edu + + Van Jacobson + MS 46a-1121 + Lawrence Berkeley Laboratory + Berkeley, CA 94720 + United States + electronic mail: van@ee.lbl.gov + +Acknowledgments + + Many people in the IETF MMUSIC working group have made comments and + suggestions contributing to this document. In particular, we would + like to thank Eve Schooler, Steve Casner, Bill Fenner, Allison + Mankin, Ross Finlayson, Peter Parnes, Joerg Ott, Carsten Bormann, Rob + Lanphier and Steve Hanna. + +References + + [1] Mills, D., "Network Time Protocol (version 3) specification and + implementation", RFC 1305, March 1992. + + [2] Schulzrinne, H., Casner, S., Frederick, R. and V. Jacobson, "RTP: + A Transport Protocol for Real-Time Applications", RFC 1889, January + 1996. + + [3] Schulzrinne, H., "RTP Profile for Audio and Video Conferences + with Minimal Control", RFC 1890, January 1996 + + [4] Handley, M., "SAP - Session Announcement Protocol", Work in + Progress. + + [5] V. Jacobson, S. McCanne, "vat - X11-based audio teleconferencing + tool" vat manual page, Lawrence Berkeley Laboratory, 1994. + + [6] The Unicode Consortium, "The Unicode Standard -- Version 2.0", + Addison-Wesley, 1996. + + + + + + +Handley & Jacobson Standards Track [Page 40] + +RFC 2327 SDP April 1998 + + + [7] ISO/IEC 10646-1:1993. International Standard -- Information + technol- ogy -- Universal Multiple-Octet Coded Character Set (UCS) -- + Part 1: Architecture and Basic Multilingual Plane. Five amendments + and a techn- ical corrigendum have been published up to now. UTF-8 + is described in Annex R, published as Amendment 2. + + [8] Goldsmith, D., and M. Davis, "Using Unicode with MIME", RFC 1641, + July 1994. + + [9] Yergeau, F., "UTF-8, a transformation format of Unicode and ISO + 10646", RFC 2044, October 1996. + + [10] ITU-T Recommendation H.332 (1998): "Multimedia Terminal for + Receiving Internet-based H.323 Conferences", ITU, Geneva. + + [11] Handley, M., Schooler, E., and H. Schulzrinne, "Session + Initiation Protocol (SIP)", Work in Progress. + + [12] Schulzrinne, H., Rao, A., and R. Lanphier, "Real Time Streaming + Protocol (RTSP)", RFC 2326, April 1998. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Handley & Jacobson Standards Track [Page 41] + +RFC 2327 SDP April 1998 + + +Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Handley & Jacobson Standards Track [Page 42] + diff --git a/src/modules/rtp/rfc2974.txt b/src/modules/rtp/rfc2974.txt new file mode 100644 index 00000000..4a5aa626 --- /dev/null +++ b/src/modules/rtp/rfc2974.txt @@ -0,0 +1,1011 @@ + + + + + + +Network Working Group M. Handley +Request for Comments: 2974 ACIRI +Category: Experimental C. Perkins + USC/ISI + E. Whelan + UCL + October 2000 + + + Session Announcement Protocol + +Status of this Memo + + This memo defines an Experimental Protocol for the Internet + community. It does not specify an Internet standard of any kind. + Discussion and suggestions for improvement are requested. + Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2000). All Rights Reserved. + +Abstract + + This document describes version 2 of the multicast session directory + announcement protocol, Session Announcement Protocol (SAP), and the + related issues affecting security and scalability that should be + taken into account by implementors. + +1 Introduction + + In order to assist the advertisement of multicast multimedia + conferences and other multicast sessions, and to communicate the + relevant session setup information to prospective participants, a + distributed session directory may be used. An instance of such a + session directory periodically multicasts packets containing a + description of the session, and these advertisements are received by + other session directories such that potential remote participants can + use the session description to start the tools required to + participate in the session. + + This memo describes the issues involved in the multicast announcement + of session description information and defines an announcement + protocol to be used. Sessions are described using the session + description protocol which is described in a companion memo [4]. + + + + + + +Handley, et al. Experimental [Page 1] + +RFC 2974 Session Announcement Protocol October 2000 + + +2 Terminology + + A SAP announcer periodically multicasts an announcement packet to a + well known multicast address and port. The announcement is multicast + with the same scope as the session it is announcing, ensuring that + the recipients of the announcement are within the scope of the + session the announcement describes (bandwidth and other such + constraints permitting). This is also important for the scalability + of the protocol, as it keeps local session announcements local. + + A SAP listener learns of the multicast scopes it is within (for + example, using the Multicast-Scope Zone Announcement Protocol [5]) + and listens on the well known SAP address and port for those scopes. + In this manner, it will eventually learn of all the sessions being + announced, allowing those sessions to be joined. + + The key words `MUST', `MUST NOT', `REQUIRED', `SHALL', `SHALL NOT', + `SHOULD', `SHOULD NOT', `RECOMMENDED', `MAY', and `OPTIONAL' in this + document are to be interpreted as described in [1]. + +3 Session Announcement + + As noted previously, a SAP announcer periodically sends an + announcement packet to a well known multicast address and port. + There is no rendezvous mechanism - the SAP announcer is not aware of + the presence or absence of any SAP listeners - and no additional + reliability is provided over the standard best-effort UDP/IP + semantics. + + That announcement contains a session description and SHOULD contain + an authentication header. The session description MAY be encrypted + although this is NOT RECOMMENDED (see section 7). + + A SAP announcement is multicast with the same scope as the session it + is announcing, ensuring that the recipients of the announcement are + within the scope of the session the announcement describes. There are + a number of possibilities: + + IPv4 global scope sessions use multicast addresses in the range + 224.2.128.0 - 224.2.255.255 with SAP announcements being sent to + 224.2.127.254 (note that 224.2.127.255 is used by the obsolete + SAPv0 and MUST NOT be used). + + + + + + + + + +Handley, et al. Experimental [Page 2] + +RFC 2974 Session Announcement Protocol October 2000 + + + IPv4 administrative scope sessions using administratively scoped IP + multicast as defined in [7]. The multicast address to be used for + announcements is the highest multicast address in the relevant + administrative scope zone. For example, if the scope range is + 239.16.32.0 - 239.16.33.255, then 239.16.33.255 is used for SAP + announcements. + + IPv6 sessions are announced on the address FF0X:0:0:0:0:0:2:7FFE + where X is the 4-bit scope value. For example, an announcement + for a link-local session assigned the address + FF02:0:0:0:0:0:1234:5678, should be advertised on SAP address + FF02:0:0:0:0:0:2:7FFE. + + Ensuring that a description is not used by a potential participant + outside the session scope is not addressed in this memo. + + SAP announcements MUST be sent on port 9875 and SHOULD be sent with + an IP time-to-live of 255 (the use of TTL scoping for multicast is + discouraged [7]). + + If a session uses addresses in multiple administrative scope ranges, + it is necessary for the announcer to send identical copies of the + announcement to each administrative scope range. It is up to the + listeners to parse such multiple announcements as the same session + (as identified by the SDP origin field, for example). The + announcement rate for each administrative scope range MUST be + calculated separately, as if the multiple announcements were + separate. + + Multiple announcers may announce a single session, as an aid to + robustness in the face of packet loss and failure of one or more + announcers. The rate at which each announcer repeats its + announcement MUST be scaled back such that the total announcement + rate is equal to that which a single server would choose. + Announcements made in this manner MUST be identical. + + If multiple announcements are being made for a session, then each + announcement MUST carry an authentication header signed by the same + key, or be treated as a completely separate announcement by + listeners. + + An IPv4 SAP listener SHOULD listen on the IPv4 global scope SAP + address and on the SAP addresses for each IPv4 administrative scope + zone it is within. The discovery of administrative scope zones is + outside the scope of this memo, but it is assumed that each SAP + listener within a particular scope zone is aware of that scope zone. + A SAP listener which supports IPv6 SHOULD also listen to the IPv6 SAP + addresses. + + + +Handley, et al. Experimental [Page 3] + +RFC 2974 Session Announcement Protocol October 2000 + + +3.1 Announcement Interval + + The time period between repetitions of an announcement is chosen such + that the total bandwidth used by all announcements on a single SAP + group remains below a preconfigured limit. If not otherwise + specified, the bandwidth limit SHOULD be assumed to be 4000 bits per + second. + + Each announcer is expected to listen to other announcements in order + to determine the total number of sessions being announced on a + particular group. Sessions are uniquely identified by the + combination of the message identifier hash and originating source + fields of the SAP header (note that SAP v0 announcers always set the + message identifier hash to zero, and if such an announcement is + received the entire message MUST be compared to determine + uniqueness). + + Announcements are made by periodic multicast to the group. The base + interval between announcements is derived from the number of + announcements being made in that group, the size of the announcement + and the configured bandwidth limit. The actual transmission time is + derived from this base interval as follows: + + 1. The announcer initializes the variable tp to be the last time a + particular announcement was transmitted (or the current time if + this is the first time this announcement is to be made). + + 2. Given a configured bandwidth limit in bits/second and an + announcement of ad_size bytes, the base announcement interval + in seconds is + + interval =max(300; (8*no_of_ads*ad_size)/limit) + + 3. An offset is calculated based on the base announcement interval + + offset= rand(interval* 2/3)-(interval/3) + + 4. The next transmission time for an announcement derived as + + tn =tp+ interval+ offset + + The announcer then sets a timer to expire at tn and waits. At time + tn the announcer SHOULD recalculate the next transmission time. If + the new value of tn is before the current time, the announcement is + sent immediately. Otherwise the transmission is rescheduled for the + new tn. This reconsideration prevents transient packet bursts on + startup and when a network partition heals. + + + + +Handley, et al. Experimental [Page 4] + +RFC 2974 Session Announcement Protocol October 2000 + + +4 Session Deletion + + Sessions may be deleted in one of several ways: + + Explicit Timeout The session description payload may contain + timestamp information specifying the start- and end-times of the + session. If the current time is later than the end-time of the + session, then the session SHOULD be deleted from the receiver's + session cache. + + Implicit Timeout A session announcement message should be received + periodically for each session description in a receiver's session + cache. The announcement period can be predicted by the receiver + from the set of sessions currently being announced. If a session + announcement message has not been received for ten times the + announcement period, or one hour, whichever is the greater, then + the session is deleted from the receiver's session cache. The one + hour minimum is to allow for transient network partitionings. + + Explicit Deletion A session deletion packet is received specifying + the session to be deleted. Session deletion packets SHOULD have a + valid authentication header, matching that used to authenticate + previous announcement packets. If this authentication is missing, + the deletion message SHOULD be ignored. + +5 Session Modification + + A pre-announced session can be modified by simply announcing the + modified session description. In this case, the version hash in the + SAP header MUST be changed to indicate to receivers that the packet + contents should be parsed (or decrypted and parsed if it is + encrypted). The session itself, as distinct from the session + announcement, is uniquely identified by the payload and not by the + message identifier hash in the header. + + The same rules apply for session modification as for session + deletion: + + o Either the modified announcement must contain an authentication + header signed by the same key as the cached session announcement + it is modifying, or: + + o The cached session announcement must not contain an authentication + header, and the session modification announcement must originate + from the same host as the session it is modifying. + + + + + + +Handley, et al. Experimental [Page 5] + +RFC 2974 Session Announcement Protocol October 2000 + + + If an announcement is received containing an authentication header + and the cached announcement did not contain an authentication header, + or it contained a different authentication header, then the modified + announcement MUST be treated as a new and different announcement, and + displayed in addition to the un-authenticated announcement. The same + should happen if a modified packet without an authentication header + is received from a different source than the original announcement. + + These rules prevent an announcement having an authentication header + added by a malicious user and then being deleted using that header, + and it also prevents a denial-of-service attack by someone putting + out a spoof announcement which, due to packet loss, reaches some + participants before the original announcement. Note that under such + circumstances, being able to authenticate the message originator is + the only way to discover which session is the correct session. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | V=1 |A|R|T|E|C| auth len | msg id hash | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | + : originating source (32 or 128 bits) : + : : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | optional authentication data | + : .... : + *-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-* + | optional payload type | + + +-+- - - - - - - - - -+ + | |0| | + + - - - - - - - - - - - - - - - - - - - - +-+ | + | | + : payload : + | | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 1: Packet format + +6 Packet Format + + SAP data packets have the format described in figure 1. + + V: Version Number. The version number field MUST be set to 1 (SAPv2 + announcements which use only SAPv1 features are backwards + compatible, those which use new features can be detected by other + means, so the SAP version number doesn't need to change). + + + + +Handley, et al. Experimental [Page 6] + +RFC 2974 Session Announcement Protocol October 2000 + + + A: Address type. If the A bit is 0, the originating source field + contains a 32-bit IPv4 address. If the A bit is 1, the + originating source contains a 128-bit IPv6 address. + + R: Reserved. SAP announcers MUST set this to 0, SAP listeners MUST + ignore the contents of this field. + + T: Message Type. If the T field is set to 0 this is a session + announcement packet, if 1 this is a session deletion packet. + + E: Encryption Bit. If the encryption bit is set to 1, the payload of + the SAP packet is encrypted. If this bit is 0 the packet is not + encrypted. See section 7 for details of the encryption process. + + C: Compressed bit. If the compressed bit is set to 1, the payload is + compressed using the zlib compression algorithm [3]. If the + payload is to be compressed and encrypted, the compression MUST be + performed first. + + Authentication Length. An 8 bit unsigned quantity giving the number + of 32 bit words following the main SAP header that contain + authentication data. If it is zero, no authentication header is + present. + + Authentication data containing a digital signature of the packet, + with length as specified by the authentication length header + field. See section 8 for details of the authentication process. + + Message Identifier Hash. A 16 bit quantity that, used in combination + with the originating source, provides a globally unique identifier + indicating the precise version of this announcement. The choice + of value for this field is not specified here, except that it MUST + be unique for each session announced by a particular SAP announcer + and it MUST be changed if the session description is modified (and + a session deletion message SHOULD be sent for the old version of + the session). + + Earlier versions of SAP used a value of zero to mean that the hash + should be ignored and the payload should always be parsed. This + had the unfortunate side-effect that SAP announcers had to study + the payload data to determine how many unique sessions were being + advertised, making the calculation of the announcement interval + more complex that necessary. In order to decouple the session + announcement process from the contents of those announcements, SAP + announcers SHOULD NOT set the message identifier hash to zero. + + SAP listeners MAY silently discard messages if the message + identifier hash is set to zero. + + + +Handley, et al. Experimental [Page 7] + +RFC 2974 Session Announcement Protocol October 2000 + + + Originating Source. This gives the IP address of the original source + of the message. This is an IPv4 address if the A field is set to + zero, else it is an IPv6 address. The address is stored in + network byte order. + + SAPv0 permitted the originating source to be zero if the message + identifier hash was also zero. This practise is no longer legal, + and SAP announcers SHOULD NOT set the originating source to zero. + SAP listeners MAY silently discard packets with the originating + source set to zero. + + The header is followed by an optional payload type field and the + payload data itself. If the E or C bits are set in the header both + the payload type and payload are encrypted and/or compressed. + + The payload type field is a MIME content type specifier, describing + the format of the payload. This is a variable length ASCII text + string, followed by a single zero byte (ASCII NUL). The payload type + SHOULD be included in all packets. If the payload type is + `application/sdp' both the payload type and its terminating zero byte + MAY be omitted, although this is intended for backwards compatibility + with SAP v1 listeners only. + + The absence of a payload type field may be noted since the payload + section of such a packet will start with an SDP `v=0' field, which is + not a legal MIME content type specifier. + + All implementations MUST support payloads of type `application/sdp' + [4]. Other formats MAY be supported although since there is no + negotiation in SAP an announcer which chooses to use a session + description format other than SDP cannot know that the listeners are + able to understand the announcement. A proliferation of payload + types in announcements has the potential to lead to severe + interoperability problems, and for this reason, the use of non-SDP + payloads is NOT RECOMMENDED. + + If the packet is an announcement packet, the payload contains a + session description. + + If the packet is a session deletion packet, the payload contains a + session deletion message. If the payload format is `application/sdp' + the deletion message is a single SDP line consisting of the origin + field of the announcement to be deleted. + + It is desirable for the payload to be sufficiently small that SAP + packets do not get fragmented by the underlying network. + Fragmentation has a loss multiplier effect, which is known to + significantly affect the reliability of announcements. It is + + + +Handley, et al. Experimental [Page 8] + +RFC 2974 Session Announcement Protocol October 2000 + + + RECOMMENDED that SAP packets are smaller than 1kByte in length, + although if it is known that announcements will use a network with a + smaller MTU than this, then that SHOULD be used as the maximum + recommended packet size. + +7 Encrypted Announcements + + An announcement is received by all listeners in the scope to which it + is sent. If an announcement is encrypted, and many of the receivers + do not have the encryption key, there is a considerable waste of + bandwidth since those receivers cannot use the announcement they have + received. For this reason, the use of encrypted SAP announcements is + NOT RECOMMENDED on the global scope SAP group or on administrative + scope groups which may have many receivers which cannot decrypt those + announcements. + + The opinion of the authors is that encrypted SAP is useful in special + cases only, and that the vast majority of scenarios where encrypted + SAP has been proposed may be better served by distributing session + details using another mechanism. There are, however, certain + scenarios where encrypted announcements may be useful. For this + reason, the encryption bit is included in the SAP header to allow + experimentation with encrypted announcements. + + This memo does not specify details of the encryption algorithm to be + used or the means by which keys are generated and distributed. An + additional specification should define these, if it is desired to use + encrypted SAP. + + Note that if an encrypted announcement is being announced via a + proxy, then there may be no way for the proxy to discover that the + announcement has been superseded, and so it may continue to relay the + old announcement in addition to the new announcement. SAP provides + no mechanism to chain modified encrypted announcements, so it is + advisable to announce the unmodified session as deleted for a short + time after the modification has occurred. This does not guarantee + that all proxies have deleted the session, and so receivers of + encrypted sessions should be prepared to discard old versions of + session announcements that they may receive. In most cases however, + the only stateful proxy will be local to (and known to) the sender, + and an additional (local-area) protocol involving a handshake for + such session modifications can be used to avoid this problem. + + Session announcements that are encrypted with a symmetric algorithm + may allow a degree of privacy in the announcement of a session, but + it should be recognized that a user in possession of such a key can + pass it on to other users who should not be in possession of such a + key. Thus announcements to such a group of key holders cannot be + + + +Handley, et al. Experimental [Page 9] + +RFC 2974 Session Announcement Protocol October 2000 + + + assumed to have come from an authorized key holder unless there is an + appropriate authentication header signed by an authorized key holder. + In addition the recipients of such encrypted announcements cannot be + assumed to only be authorized key holders. Such encrypted + announcements do not provide any real security unless all of the + authorized key holders are trusted to maintain security of such + session directory keys. This property is shared by the multicast + session tools themselves, where it is possible for an un-trustworthy + member of the session to pass on encryption keys to un-authorized + users. However it is likely that keys used for the session tools + will be more short lived than those used for session directories. + + Similar considerations should apply when session announcements are + encrypted with an asymmetric algorithm, but then it is possible to + restrict the possessor(s) of the private key, so that announcements + to a key-holder group can not be made, even if one of the untrusted + members of the group proves to be un-trustworthy. + + 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | V=1 |P| Auth | | + +-+-+-+-+-+-+-+-+ | + | Format specific authentication subheader | + : .................. : + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 2: Format of the authentication data in the SAP header + +8 Authenticated Announcements + + The authentication header can be used for two purposes: + + o Verification that changes to a session description or deletion of + a session are permitted. + + o Authentication of the identity of the session creator. + + In some circumstances only verification is possible because a + certificate signed by a mutually trusted person or authority is not + available. However, under such circumstances, the session originator + may still be authenticated to be the same as the session originator + of previous sessions claiming to be from the same person. This may + or may not be sufficient depending on the purpose of the session and + the people involved. + + + + + + +Handley, et al. Experimental [Page 10] + +RFC 2974 Session Announcement Protocol October 2000 + + + Clearly the key used for the authentication should not be trusted to + belong to the session originator unless it has been separately + authenticated by some other means, such as being certified by a + trusted third party. Such certificates are not normally included in + an SAP header because they take more space than can normally be + afforded in an SAP packet, and such verification must therefore take + place by some other mechanism. However, as certified public keys are + normally locally cached, authentication of a particular key only has + to take place once, rather than every time the session directory + retransmits the announcement. + + SAP is not tied to any single authentication mechanism. + Authentication data in the header is self-describing, but the precise + format depends on the authentication mechanism in use. The generic + format of the authentication data is given in figure 2. The + structure of the format specific authentication subheader, using both + the PGP and the CMS formats, is discussed in sections 8.1 and 8.2 + respectively. Additional formats may be added in future. + + Version Number, V: The version number of the authentication format + specified by this memo is 1. + + Padding Bit, P: If necessary the authentication data is padded to be + a multiple of 32 bits and the padding bit is set. In this case + the last byte of the authentication data contains the number of + padding bytes (including the last byte) that must be discarded. + + Authentication Type, Auth: The authentication type is a 4 bit + encoded field that denotes the authentication infrastructure the + sender expects the recipients to use to check the authenticity and + integrity of the information. This defines the format of the + authentication subheader and can take the values: 0 = PGP format, + 1 = CMS format. All other values are undefined and SHOULD be + ignored. + + If a SAP packet is to be compressed or encrypted, this MUST be done + before the authentication is added. + + The digital signature in the authentication data MUST be calculated + over the entire packet, including the header. The authentication + length MUST be set to zero and the authentication data excluded when + calculating the digital signature. + + It is to be expected that sessions may be announced by a number of + different mechanisms, not only SAP. For example, a session + description may placed on a web page, sent by email or conveyed in a + + + + + +Handley, et al. Experimental [Page 11] + +RFC 2974 Session Announcement Protocol October 2000 + + + session initiation protocol. To ease interoperability with these + other mechanisms, application level security is employed, rather than + using IPsec authentication headers. + +8.1 PGP Authentication + + A full description of the PGP protocol can be found in [2]. When + using PGP for SAP authentication the basic format specific + authentication subheader comprises a digital signature packet as + described in [2]. The signature type MUST be 0x01 which means the + signature is that of a canonical text document. + +8.2 CMS Authentication + + A full description of the Cryptographic Message Syntax can be found + in [6]. The format specific authentication subheader will, in the + CMS case, have an ASN.1 ContentInfo type with the ContentType being + signedData. + + Use is made of the option available in PKCS#7 to leave the content + itself blank as the content which is signed is already present in the + packet. Inclusion of it within the SignedData type would duplicate + this data and increase the packet length unnecessarily. In addition + this allows recipients with either no interest in the authentication, + or with no mechanism for checking it, to more easily skip the + authentication information. + + There SHOULD be only one signerInfo and related fields corresponding + to the originator of the SAP announcement. The signingTime SHOULD be + present as a signedAttribute. However, due to the strict size + limitations on the size of SAP packets, certificates and CRLs SHOULD + NOT be included in the signedData structure. It is expected that + users of the protocol will have other methods for certificate and CRL + distribution. + +9 Scalability and caching + + SAP is intended to announce the existence of long-lived wide-area + multicast sessions. It is not an especially timely protocol: + sessions are announced by periodic multicast with a repeat rate on + the order of tens of minutes, and no enhanced reliability over UDP. + This leads to a long startup delay before a complete set of + announcements is heard by a listener. This delay is clearly + undesirable for interactive browsing of announced sessions. + + In order to reduce the delays inherent in SAP, it is recommended that + proxy caches are deployed. A SAP proxy cache is expected to listen + to all SAP groups in its scope, and to maintain an up-to-date list of + + + +Handley, et al. Experimental [Page 12] + +RFC 2974 Session Announcement Protocol October 2000 + + + all announced sessions along with the time each announcement was last + received. When a new SAP listeners starts, it should contact its + local proxy to download this information, which is then sufficient + for it to process future announcements directly, as if it has been + continually listening. + + The protocol by which a SAP listener contacts its local proxy cache + is not specified here. + +10 Security Considerations + + SAP contains mechanisms for ensuring integrity of session + announcements, for authenticating the origin of an announcement and + for encrypting such announcements (sections 7 and 8). + + As stated in section 5, if a session modification announcement is + received that contains a valid authentication header, but which is + not signed by the original creator of the session, then the session + must be treated as a new session in addition to the original session + with the same SDP origin information unless the originator of one of + the session descriptions can be authenticated using a certificate + signed by a trusted third party. If this were not done, there would + be a possible denial of service attack whereby a party listens for + new announcements, strips off the original authentication header, + modifies the session description, adds a new authentication header + and re-announces the session. If a rule was imposed that such spoof + announcements were ignored, then if packet loss or late starting of a + session directory instance caused the original announcement to fail + to arrive at a site, but the spoof announcement did so, this would + then prevent the original announcement from being accepted at that + site. + + A similar denial-of-service attack is possible if a session + announcement receiver relies completely on the originating source and + hash fields to indicate change, and fails to parse the remainder of + announcements for which it has seen the origin/hash combination + before. + + A denial of service attack is possible from a malicious site close to + a legitimate site which is making a session announcement. This can + happen if the malicious site floods the legitimate site with huge + numbers of (illegal) low TTL announcements describing high TTL + sessions. This may reduce the session announcement rate of the + legitimate announcement to below a tenth of the rate expected at + remote sites and therefore cause the session to time out. Such an + attack is likely to be easily detectable, and we do not provide any + mechanism here to prevent it. + + + + +Handley, et al. Experimental [Page 13] + +RFC 2974 Session Announcement Protocol October 2000 + + +A. Summary of differences between SAPv0 and SAPv1 + + For this purpose SAPv0 is defined as the protocol in use by version + 2.2 of the session directory tool, sdr. SAPv1 is the protocol + described in the 19 November 1996 version of this memo. The packet + headers of SAP messages are the same in V0 and V1 in that a V1 tool + can parse a V0 announcement header but not vice-versa. In SAPv0, the + fields have the following values: + + o Version Number: 0 + + o Message Type: 0 (Announcement) + + o Authentication Type: 0 (No Authentication) + + o Encryption Bit: 0 (No Encryption) + + o Compression Bit: 0 (No compression) + + o Message Id Hash: 0 (No Hash Specified) + + o Originating Source: 0 (No source specified, announcement has + not been relayed) + +B. Summary of differences between SAPv1 and SAPv2 + + The packet headers of SAP messages are the same in V1 and V2 in that + a V2 tool can parse a V1 announcement header but not necessarily + vice-versa. + + o The A bit has been added to the SAP header, replacing one of the + bits of the SAPv1 message type field. If set to zero the + announcement is of an IPv4 session, and the packet is backwards + compatible with SAPv1. If set to one the announcement is of an + IPv6 session, and SAPv1 listeners (which do not support IPv6) will + see this as an illegal message type (MT) field. + + o The second bit of the message type field in SAPv1 has been + replaced by a reserved, must-be-zero, bit. This bit was unused in + SAPv1, so this change just codifies existing usage. + + o SAPv1 specified encryption of the payload. SAPv2 includes the E + bit in the SAP header to indicate that the payload is encrypted, + but does not specify any details of the encryption. + + o SAPv1 allowed the message identifier hash and originating source + fields to be set to zero, for backwards compatibility. This is no + longer legal. + + + +Handley, et al. Experimental [Page 14] + +RFC 2974 Session Announcement Protocol October 2000 + + + o SAPv1 specified gzip compression. SAPv2 uses zlib (the only known + implementation of SAP compression used zlib, and gzip compression + was a mistake). + + o SAPv2 provides a more complete specification for authentication. + + o SAPv2 allows for non-SDP payloads to be transported. SAPv1 + required that the payload was SDP. + + o SAPv1 included a timeout field for encrypted announcement, SAPv2 + does not (and relies of explicit deletion messages or implicit + timeouts). + +C. Acknowledgements + + SAP and SDP were originally based on the protocol used by the sd + session directory from Van Jacobson at LBNL. Version 1 of SAP was + designed by Mark Handley as part of the European Commission MICE + (Esprit 7602) and MERCI (Telematics 1007) projects. Version 2 + includes authentication features developed by Edmund Whelan, Goli + Montasser-Kohsari and Peter Kirstein as part of the European + Commission ICE-TEL project (Telematics 1005), and support for IPv6 + developed by Maryann P. Maher and Colin Perkins. + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Handley, et al. Experimental [Page 15] + +RFC 2974 Session Announcement Protocol October 2000 + + +D. Authors' Addresses + + Mark Handley + AT&T Center for Internet Research at ICSI, + International Computer Science Institute, + 1947 Center Street, Suite 600, + Berkeley, CA 94704, USA + + EMail: mjh@aciri.org + + + Colin Perkins + USC Information Sciences Institute + 4350 N. Fairfax Drive, Suite 620 + Arlington, VA 22203, USA + + EMail: csp@isi.edu + + + Edmund Whelan + Department of Computer Science, + University College London, + Gower Street, + London, WC1E 6BT, UK + + EMail: e.whelan@cs.ucl.ac.uk + + + + + + + + + + + + + + + + + + + + + + + + + +Handley, et al. Experimental [Page 16] + +RFC 2974 Session Announcement Protocol October 2000 + + +References + + [1] Bradner, S., "Key words for use in RFCs to indicate requirement + levels", BCP 14, RFC 2119, March 1997. + + [2] Callas, J., Donnerhacke, L., Finney, H. and R. Thayer. "OpenPGP + message format", RFC 2440, November 1998. + + [3] Deutsch, P. and J.-L. Gailly, "Zlib compressed data format + specification version 3.3", RFC 1950, May 1996. + + [4] Handley, M. and V. Jacobson, "SDP: Session Description Protocol", + RFC 2327, April 1998. + + [5] Handley, M., Thaler, D. and R. Kermode, "Multicast-scope zone + announcement protocol (MZAP)", RFC 2776, February 2000. + + [6] Housley, R., "Cryptographic message syntax", RFC 2630, June 1999. + + [7] Mayer, D., "Administratively scoped IP multicast", RFC 2365, July + 1998. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Handley, et al. Experimental [Page 17] + +RFC 2974 Session Announcement Protocol October 2000 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2000). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Handley, et al. Experimental [Page 18] + diff --git a/src/modules/rtp/rfc3550.txt b/src/modules/rtp/rfc3550.txt new file mode 100644 index 00000000..165736cf --- /dev/null +++ b/src/modules/rtp/rfc3550.txt @@ -0,0 +1,5827 @@ + + + + + + +Network Working Group H. Schulzrinne +Request for Comments: 3550 Columbia University +Obsoletes: 1889 S. Casner +Category: Standards Track Packet Design + R. Frederick + Blue Coat Systems Inc. + V. Jacobson + Packet Design + July 2003 + + + RTP: A Transport Protocol for Real-Time Applications + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2003). All Rights Reserved. + +Abstract + + This memorandum describes RTP, the real-time transport protocol. RTP + provides end-to-end network transport functions suitable for + applications transmitting real-time data, such as audio, video or + simulation data, over multicast or unicast network services. RTP + does not address resource reservation and does not guarantee + quality-of-service for real-time services. The data transport is + augmented by a control protocol (RTCP) to allow monitoring of the + data delivery in a manner scalable to large multicast networks, and + to provide minimal control and identification functionality. RTP and + RTCP are designed to be independent of the underlying transport and + network layers. The protocol supports the use of RTP-level + translators and mixers. + + Most of the text in this memorandum is identical to RFC 1889 which it + obsoletes. There are no changes in the packet formats on the wire, + only changes to the rules and algorithms governing how the protocol + is used. The biggest change is an enhancement to the scalable timer + algorithm for calculating when to send RTCP packets in order to + minimize transmission in excess of the intended rate when many + participants join a session simultaneously. + + + + +Schulzrinne, et al. Standards Track [Page 1] + +RFC 3550 RTP July 2003 + + +Table of Contents + + 1. Introduction ................................................ 4 + 1.1 Terminology ............................................ 5 + 2. RTP Use Scenarios ........................................... 5 + 2.1 Simple Multicast Audio Conference ...................... 6 + 2.2 Audio and Video Conference ............................. 7 + 2.3 Mixers and Translators ................................. 7 + 2.4 Layered Encodings ...................................... 8 + 3. Definitions ................................................. 8 + 4. Byte Order, Alignment, and Time Format ...................... 12 + 5. RTP Data Transfer Protocol .................................. 13 + 5.1 RTP Fixed Header Fields ................................ 13 + 5.2 Multiplexing RTP Sessions .............................. 16 + 5.3 Profile-Specific Modifications to the RTP Header ....... 18 + 5.3.1 RTP Header Extension ............................ 18 + 6. RTP Control Protocol -- RTCP ................................ 19 + 6.1 RTCP Packet Format ..................................... 21 + 6.2 RTCP Transmission Interval ............................. 24 + 6.2.1 Maintaining the Number of Session Members ....... 28 + 6.3 RTCP Packet Send and Receive Rules ..................... 28 + 6.3.1 Computing the RTCP Transmission Interval ........ 29 + 6.3.2 Initialization .................................. 30 + 6.3.3 Receiving an RTP or Non-BYE RTCP Packet ......... 31 + 6.3.4 Receiving an RTCP BYE Packet .................... 31 + 6.3.5 Timing Out an SSRC .............................. 32 + 6.3.6 Expiration of Transmission Timer ................ 32 + 6.3.7 Transmitting a BYE Packet ....................... 33 + 6.3.8 Updating we_sent ................................ 34 + 6.3.9 Allocation of Source Description Bandwidth ...... 34 + 6.4 Sender and Receiver Reports ............................ 35 + 6.4.1 SR: Sender Report RTCP Packet ................... 36 + 6.4.2 RR: Receiver Report RTCP Packet ................. 42 + 6.4.3 Extending the Sender and Receiver Reports ....... 42 + 6.4.4 Analyzing Sender and Receiver Reports ........... 43 + 6.5 SDES: Source Description RTCP Packet ................... 45 + 6.5.1 CNAME: Canonical End-Point Identifier SDES Item . 46 + 6.5.2 NAME: User Name SDES Item ....................... 48 + 6.5.3 EMAIL: Electronic Mail Address SDES Item ........ 48 + 6.5.4 PHONE: Phone Number SDES Item ................... 49 + 6.5.5 LOC: Geographic User Location SDES Item ......... 49 + 6.5.6 TOOL: Application or Tool Name SDES Item ........ 49 + 6.5.7 NOTE: Notice/Status SDES Item ................... 50 + 6.5.8 PRIV: Private Extensions SDES Item .............. 50 + 6.6 BYE: Goodbye RTCP Packet ............................... 51 + 6.7 APP: Application-Defined RTCP Packet ................... 52 + 7. RTP Translators and Mixers .................................. 53 + 7.1 General Description .................................... 53 + + + +Schulzrinne, et al. Standards Track [Page 2] + +RFC 3550 RTP July 2003 + + + 7.2 RTCP Processing in Translators ......................... 55 + 7.3 RTCP Processing in Mixers .............................. 57 + 7.4 Cascaded Mixers ........................................ 58 + 8. SSRC Identifier Allocation and Use .......................... 59 + 8.1 Probability of Collision ............................... 59 + 8.2 Collision Resolution and Loop Detection ................ 60 + 8.3 Use with Layered Encodings ............................. 64 + 9. Security .................................................... 65 + 9.1 Confidentiality ........................................ 65 + 9.2 Authentication and Message Integrity ................... 67 + 10. Congestion Control .......................................... 67 + 11. RTP over Network and Transport Protocols .................... 68 + 12. Summary of Protocol Constants ............................... 69 + 12.1 RTCP Packet Types ...................................... 70 + 12.2 SDES Types ............................................. 70 + 13. RTP Profiles and Payload Format Specifications .............. 71 + 14. Security Considerations ..................................... 73 + 15. IANA Considerations ......................................... 73 + 16. Intellectual Property Rights Statement ...................... 74 + 17. Acknowledgments ............................................. 74 + Appendix A. Algorithms ........................................ 75 + Appendix A.1 RTP Data Header Validity Checks ................... 78 + Appendix A.2 RTCP Header Validity Checks ....................... 82 + Appendix A.3 Determining Number of Packets Expected and Lost ... 83 + Appendix A.4 Generating RTCP SDES Packets ...................... 84 + Appendix A.5 Parsing RTCP SDES Packets ......................... 85 + Appendix A.6 Generating a Random 32-bit Identifier ............. 85 + Appendix A.7 Computing the RTCP Transmission Interval .......... 87 + Appendix A.8 Estimating the Interarrival Jitter ................ 94 + Appendix B. Changes from RFC 1889 ............................. 95 + References ...................................................... 100 + Normative References ............................................ 100 + Informative References .......................................... 100 + Authors' Addresses .............................................. 103 + Full Copyright Statement ........................................ 104 + + + + + + + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 3] + +RFC 3550 RTP July 2003 + + +1. Introduction + + This memorandum specifies the real-time transport protocol (RTP), + which provides end-to-end delivery services for data with real-time + characteristics, such as interactive audio and video. Those services + include payload type identification, sequence numbering, timestamping + and delivery monitoring. Applications typically run RTP on top of + UDP to make use of its multiplexing and checksum services; both + protocols contribute parts of the transport protocol functionality. + However, RTP may be used with other suitable underlying network or + transport protocols (see Section 11). RTP supports data transfer to + multiple destinations using multicast distribution if provided by the + underlying network. + + Note that RTP itself does not provide any mechanism to ensure timely + delivery or provide other quality-of-service guarantees, but relies + on lower-layer services to do so. It does not guarantee delivery or + prevent out-of-order delivery, nor does it assume that the underlying + network is reliable and delivers packets in sequence. The sequence + numbers included in RTP allow the receiver to reconstruct the + sender's packet sequence, but sequence numbers might also be used to + determine the proper location of a packet, for example in video + decoding, without necessarily decoding packets in sequence. + + While RTP is primarily designed to satisfy the needs of multi- + participant multimedia conferences, it is not limited to that + particular application. Storage of continuous data, interactive + distributed simulation, active badge, and control and measurement + applications may also find RTP applicable. + + This document defines RTP, consisting of two closely-linked parts: + + o the real-time transport protocol (RTP), to carry data that has + real-time properties. + + o the RTP control protocol (RTCP), to monitor the quality of service + and to convey information about the participants in an on-going + session. The latter aspect of RTCP may be sufficient for "loosely + controlled" sessions, i.e., where there is no explicit membership + control and set-up, but it is not necessarily intended to support + all of an application's control communication requirements. This + functionality may be fully or partially subsumed by a separate + session control protocol, which is beyond the scope of this + document. + + RTP represents a new style of protocol following the principles of + application level framing and integrated layer processing proposed by + Clark and Tennenhouse [10]. That is, RTP is intended to be malleable + + + +Schulzrinne, et al. Standards Track [Page 4] + +RFC 3550 RTP July 2003 + + + to provide the information required by a particular application and + will often be integrated into the application processing rather than + being implemented as a separate layer. RTP is a protocol framework + that is deliberately not complete. This document specifies those + functions expected to be common across all the applications for which + RTP would be appropriate. Unlike conventional protocols in which + additional functions might be accommodated by making the protocol + more general or by adding an option mechanism that would require + parsing, RTP is intended to be tailored through modifications and/or + additions to the headers as needed. Examples are given in Sections + 5.3 and 6.4.3. + + Therefore, in addition to this document, a complete specification of + RTP for a particular application will require one or more companion + documents (see Section 13): + + o a profile specification document, which defines a set of payload + type codes and their mapping to payload formats (e.g., media + encodings). A profile may also define extensions or modifications + to RTP that are specific to a particular class of applications. + Typically an application will operate under only one profile. A + profile for audio and video data may be found in the companion RFC + 3551 [1]. + + o payload format specification documents, which define how a + particular payload, such as an audio or video encoding, is to be + carried in RTP. + + A discussion of real-time services and algorithms for their + implementation as well as background discussion on some of the RTP + design decisions can be found in [11]. + +1.1 Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in BCP 14, RFC 2119 [2] + and indicate requirement levels for compliant RTP implementations. + +2. RTP Use Scenarios + + The following sections describe some aspects of the use of RTP. The + examples were chosen to illustrate the basic operation of + applications using RTP, not to limit what RTP may be used for. In + these examples, RTP is carried on top of IP and UDP, and follows the + conventions established by the profile for audio and video specified + in the companion RFC 3551. + + + + +Schulzrinne, et al. Standards Track [Page 5] + +RFC 3550 RTP July 2003 + + +2.1 Simple Multicast Audio Conference + + A working group of the IETF meets to discuss the latest protocol + document, using the IP multicast services of the Internet for voice + communications. Through some allocation mechanism the working group + chair obtains a multicast group address and pair of ports. One port + is used for audio data, and the other is used for control (RTCP) + packets. This address and port information is distributed to the + intended participants. If privacy is desired, the data and control + packets may be encrypted as specified in Section 9.1, in which case + an encryption key must also be generated and distributed. The exact + details of these allocation and distribution mechanisms are beyond + the scope of RTP. + + The audio conferencing application used by each conference + participant sends audio data in small chunks of, say, 20 ms duration. + Each chunk of audio data is preceded by an RTP header; RTP header and + data are in turn contained in a UDP packet. The RTP header indicates + what type of audio encoding (such as PCM, ADPCM or LPC) is contained + in each packet so that senders can change the encoding during a + conference, for example, to accommodate a new participant that is + connected through a low-bandwidth link or react to indications of + network congestion. + + The Internet, like other packet networks, occasionally loses and + reorders packets and delays them by variable amounts of time. To + cope with these impairments, the RTP header contains timing + information and a sequence number that allow the receivers to + reconstruct the timing produced by the source, so that in this + example, chunks of audio are contiguously played out the speaker + every 20 ms. This timing reconstruction is performed separately for + each source of RTP packets in the conference. The sequence number + can also be used by the receiver to estimate how many packets are + being lost. + + Since members of the working group join and leave during the + conference, it is useful to know who is participating at any moment + and how well they are receiving the audio data. For that purpose, + each instance of the audio application in the conference periodically + multicasts a reception report plus the name of its user on the RTCP + (control) port. The reception report indicates how well the current + speaker is being received and may be used to control adaptive + encodings. In addition to the user name, other identifying + information may also be included subject to control bandwidth limits. + A site sends the RTCP BYE packet (Section 6.6) when it leaves the + conference. + + + + + +Schulzrinne, et al. Standards Track [Page 6] + +RFC 3550 RTP July 2003 + + +2.2 Audio and Video Conference + + If both audio and video media are used in a conference, they are + transmitted as separate RTP sessions. That is, separate RTP and RTCP + packets are transmitted for each medium using two different UDP port + pairs and/or multicast addresses. There is no direct coupling at the + RTP level between the audio and video sessions, except that a user + participating in both sessions should use the same distinguished + (canonical) name in the RTCP packets for both so that the sessions + can be associated. + + One motivation for this separation is to allow some participants in + the conference to receive only one medium if they choose. Further + explanation is given in Section 5.2. Despite the separation, + synchronized playback of a source's audio and video can be achieved + using timing information carried in the RTCP packets for both + sessions. + +2.3 Mixers and Translators + + So far, we have assumed that all sites want to receive media data in + the same format. However, this may not always be appropriate. + Consider the case where participants in one area are connected + through a low-speed link to the majority of the conference + participants who enjoy high-speed network access. Instead of forcing + everyone to use a lower-bandwidth, reduced-quality audio encoding, an + RTP-level relay called a mixer may be placed near the low-bandwidth + area. This mixer resynchronizes incoming audio packets to + reconstruct the constant 20 ms spacing generated by the sender, mixes + these reconstructed audio streams into a single stream, translates + the audio encoding to a lower-bandwidth one and forwards the lower- + bandwidth packet stream across the low-speed link. These packets + might be unicast to a single recipient or multicast on a different + address to multiple recipients. The RTP header includes a means for + mixers to identify the sources that contributed to a mixed packet so + that correct talker indication can be provided at the receivers. + + Some of the intended participants in the audio conference may be + connected with high bandwidth links but might not be directly + reachable via IP multicast. For example, they might be behind an + application-level firewall that will not let any IP packets pass. + For these sites, mixing may not be necessary, in which case another + type of RTP-level relay called a translator may be used. Two + translators are installed, one on either side of the firewall, with + the outside one funneling all multicast packets received through a + secure connection to the translator inside the firewall. The + translator inside the firewall sends them again as multicast packets + to a multicast group restricted to the site's internal network. + + + +Schulzrinne, et al. Standards Track [Page 7] + +RFC 3550 RTP July 2003 + + + Mixers and translators may be designed for a variety of purposes. An + example is a video mixer that scales the images of individual people + in separate video streams and composites them into one video stream + to simulate a group scene. Other examples of translation include the + connection of a group of hosts speaking only IP/UDP to a group of + hosts that understand only ST-II, or the packet-by-packet encoding + translation of video streams from individual sources without + resynchronization or mixing. Details of the operation of mixers and + translators are given in Section 7. + +2.4 Layered Encodings + + Multimedia applications should be able to adjust the transmission + rate to match the capacity of the receiver or to adapt to network + congestion. Many implementations place the responsibility of rate- + adaptivity at the source. This does not work well with multicast + transmission because of the conflicting bandwidth requirements of + heterogeneous receivers. The result is often a least-common + denominator scenario, where the smallest pipe in the network mesh + dictates the quality and fidelity of the overall live multimedia + "broadcast". + + Instead, responsibility for rate-adaptation can be placed at the + receivers by combining a layered encoding with a layered transmission + system. In the context of RTP over IP multicast, the source can + stripe the progressive layers of a hierarchically represented signal + across multiple RTP sessions each carried on its own multicast group. + Receivers can then adapt to network heterogeneity and control their + reception bandwidth by joining only the appropriate subset of the + multicast groups. + + Details of the use of RTP with layered encodings are given in + Sections 6.3.9, 8.3 and 11. + +3. Definitions + + RTP payload: The data transported by RTP in a packet, for + example audio samples or compressed video data. The payload + format and interpretation are beyond the scope of this document. + + RTP packet: A data packet consisting of the fixed RTP header, a + possibly empty list of contributing sources (see below), and the + payload data. Some underlying protocols may require an + encapsulation of the RTP packet to be defined. Typically one + packet of the underlying protocol contains a single RTP packet, + but several RTP packets MAY be contained if permitted by the + encapsulation method (see Section 11). + + + + +Schulzrinne, et al. Standards Track [Page 8] + +RFC 3550 RTP July 2003 + + + RTCP packet: A control packet consisting of a fixed header part + similar to that of RTP data packets, followed by structured + elements that vary depending upon the RTCP packet type. The + formats are defined in Section 6. Typically, multiple RTCP + packets are sent together as a compound RTCP packet in a single + packet of the underlying protocol; this is enabled by the length + field in the fixed header of each RTCP packet. + + Port: The "abstraction that transport protocols use to + distinguish among multiple destinations within a given host + computer. TCP/IP protocols identify ports using small positive + integers." [12] The transport selectors (TSEL) used by the OSI + transport layer are equivalent to ports. RTP depends upon the + lower-layer protocol to provide some mechanism such as ports to + multiplex the RTP and RTCP packets of a session. + + Transport address: The combination of a network address and port + that identifies a transport-level endpoint, for example an IP + address and a UDP port. Packets are transmitted from a source + transport address to a destination transport address. + + RTP media type: An RTP media type is the collection of payload + types which can be carried within a single RTP session. The RTP + Profile assigns RTP media types to RTP payload types. + + Multimedia session: A set of concurrent RTP sessions among a + common group of participants. For example, a videoconference + (which is a multimedia session) may contain an audio RTP session + and a video RTP session. + + RTP session: An association among a set of participants + communicating with RTP. A participant may be involved in multiple + RTP sessions at the same time. In a multimedia session, each + medium is typically carried in a separate RTP session with its own + RTCP packets unless the the encoding itself multiplexes multiple + media into a single data stream. A participant distinguishes + multiple RTP sessions by reception of different sessions using + different pairs of destination transport addresses, where a pair + of transport addresses comprises one network address plus a pair + of ports for RTP and RTCP. All participants in an RTP session may + share a common destination transport address pair, as in the case + of IP multicast, or the pairs may be different for each + participant, as in the case of individual unicast network + addresses and port pairs. In the unicast case, a participant may + receive from all other participants in the session using the same + pair of ports, or may use a distinct pair of ports for each. + + + + + +Schulzrinne, et al. Standards Track [Page 9] + +RFC 3550 RTP July 2003 + + + The distinguishing feature of an RTP session is that each + maintains a full, separate space of SSRC identifiers (defined + next). The set of participants included in one RTP session + consists of those that can receive an SSRC identifier transmitted + by any one of the participants either in RTP as the SSRC or a CSRC + (also defined below) or in RTCP. For example, consider a three- + party conference implemented using unicast UDP with each + participant receiving from the other two on separate port pairs. + If each participant sends RTCP feedback about data received from + one other participant only back to that participant, then the + conference is composed of three separate point-to-point RTP + sessions. If each participant provides RTCP feedback about its + reception of one other participant to both of the other + participants, then the conference is composed of one multi-party + RTP session. The latter case simulates the behavior that would + occur with IP multicast communication among the three + participants. + + The RTP framework allows the variations defined here, but a + particular control protocol or application design will usually + impose constraints on these variations. + + Synchronization source (SSRC): The source of a stream of RTP + packets, identified by a 32-bit numeric SSRC identifier carried in + the RTP header so as not to be dependent upon the network address. + All packets from a synchronization source form part of the same + timing and sequence number space, so a receiver groups packets by + synchronization source for playback. Examples of synchronization + sources include the sender of a stream of packets derived from a + signal source such as a microphone or a camera, or an RTP mixer + (see below). A synchronization source may change its data format, + e.g., audio encoding, over time. The SSRC identifier is a + randomly chosen value meant to be globally unique within a + particular RTP session (see Section 8). A participant need not + use the same SSRC identifier for all the RTP sessions in a + multimedia session; the binding of the SSRC identifiers is + provided through RTCP (see Section 6.5.1). If a participant + generates multiple streams in one RTP session, for example from + separate video cameras, each MUST be identified as a different + SSRC. + + Contributing source (CSRC): A source of a stream of RTP packets + that has contributed to the combined stream produced by an RTP + mixer (see below). The mixer inserts a list of the SSRC + identifiers of the sources that contributed to the generation of a + particular packet into the RTP header of that packet. This list + is called the CSRC list. An example application is audio + conferencing where a mixer indicates all the talkers whose speech + + + +Schulzrinne, et al. Standards Track [Page 10] + +RFC 3550 RTP July 2003 + + + was combined to produce the outgoing packet, allowing the receiver + to indicate the current talker, even though all the audio packets + contain the same SSRC identifier (that of the mixer). + + End system: An application that generates the content to be sent + in RTP packets and/or consumes the content of received RTP + packets. An end system can act as one or more synchronization + sources in a particular RTP session, but typically only one. + + Mixer: An intermediate system that receives RTP packets from one + or more sources, possibly changes the data format, combines the + packets in some manner and then forwards a new RTP packet. Since + the timing among multiple input sources will not generally be + synchronized, the mixer will make timing adjustments among the + streams and generate its own timing for the combined stream. + Thus, all data packets originating from a mixer will be identified + as having the mixer as their synchronization source. + + Translator: An intermediate system that forwards RTP packets + with their synchronization source identifier intact. Examples of + translators include devices that convert encodings without mixing, + replicators from multicast to unicast, and application-level + filters in firewalls. + + Monitor: An application that receives RTCP packets sent by + participants in an RTP session, in particular the reception + reports, and estimates the current quality of service for + distribution monitoring, fault diagnosis and long-term statistics. + The monitor function is likely to be built into the application(s) + participating in the session, but may also be a separate + application that does not otherwise participate and does not send + or receive the RTP data packets (since they are on a separate + port). These are called third-party monitors. It is also + acceptable for a third-party monitor to receive the RTP data + packets but not send RTCP packets or otherwise be counted in the + session. + + Non-RTP means: Protocols and mechanisms that may be needed in + addition to RTP to provide a usable service. In particular, for + multimedia conferences, a control protocol may distribute + multicast addresses and keys for encryption, negotiate the + encryption algorithm to be used, and define dynamic mappings + between RTP payload type values and the payload formats they + represent for formats that do not have a predefined payload type + value. Examples of such protocols include the Session Initiation + Protocol (SIP) (RFC 3261 [13]), ITU Recommendation H.323 [14] and + applications using SDP (RFC 2327 [15]), such as RTSP (RFC 2326 + [16]). For simple + + + +Schulzrinne, et al. Standards Track [Page 11] + +RFC 3550 RTP July 2003 + + + applications, electronic mail or a conference database may also be + used. The specification of such protocols and mechanisms is + outside the scope of this document. + +4. Byte Order, Alignment, and Time Format + + All integer fields are carried in network byte order, that is, most + significant byte (octet) first. This byte order is commonly known as + big-endian. The transmission order is described in detail in [3]. + Unless otherwise noted, numeric constants are in decimal (base 10). + + All header data is aligned to its natural length, i.e., 16-bit fields + are aligned on even offsets, 32-bit fields are aligned at offsets + divisible by four, etc. Octets designated as padding have the value + zero. + + Wallclock time (absolute date and time) is represented using the + timestamp format of the Network Time Protocol (NTP), which is in + seconds relative to 0h UTC on 1 January 1900 [4]. The full + resolution NTP timestamp is a 64-bit unsigned fixed-point number with + the integer part in the first 32 bits and the fractional part in the + last 32 bits. In some fields where a more compact representation is + appropriate, only the middle 32 bits are used; that is, the low 16 + bits of the integer part and the high 16 bits of the fractional part. + The high 16 bits of the integer part must be determined + independently. + + An implementation is not required to run the Network Time Protocol in + order to use RTP. Other time sources, or none at all, may be used + (see the description of the NTP timestamp field in Section 6.4.1). + However, running NTP may be useful for synchronizing streams + transmitted from separate hosts. + + The NTP timestamp will wrap around to zero some time in the year + 2036, but for RTP purposes, only differences between pairs of NTP + timestamps are used. So long as the pairs of timestamps can be + assumed to be within 68 years of each other, using modular arithmetic + for subtractions and comparisons makes the wraparound irrelevant. + + + + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 12] + +RFC 3550 RTP July 2003 + + +5. RTP Data Transfer Protocol + +5.1 RTP Fixed Header Fields + + The RTP header has the following format: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P|X| CC |M| PT | sequence number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | timestamp | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | synchronization source (SSRC) identifier | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | contributing source (CSRC) identifiers | + | .... | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The first twelve octets are present in every RTP packet, while the + list of CSRC identifiers is present only when inserted by a mixer. + The fields have the following meaning: + + version (V): 2 bits + This field identifies the version of RTP. The version defined by + this specification is two (2). (The value 1 is used by the first + draft version of RTP and the value 0 is used by the protocol + initially implemented in the "vat" audio tool.) + + padding (P): 1 bit + If the padding bit is set, the packet contains one or more + additional padding octets at the end which are not part of the + payload. The last octet of the padding contains a count of how + many padding octets should be ignored, including itself. Padding + may be needed by some encryption algorithms with fixed block sizes + or for carrying several RTP packets in a lower-layer protocol data + unit. + + extension (X): 1 bit + If the extension bit is set, the fixed header MUST be followed by + exactly one header extension, with a format defined in Section + 5.3.1. + + CSRC count (CC): 4 bits + The CSRC count contains the number of CSRC identifiers that follow + the fixed header. + + + + + +Schulzrinne, et al. Standards Track [Page 13] + +RFC 3550 RTP July 2003 + + + marker (M): 1 bit + The interpretation of the marker is defined by a profile. It is + intended to allow significant events such as frame boundaries to + be marked in the packet stream. A profile MAY define additional + marker bits or specify that there is no marker bit by changing the + number of bits in the payload type field (see Section 5.3). + + payload type (PT): 7 bits + This field identifies the format of the RTP payload and determines + its interpretation by the application. A profile MAY specify a + default static mapping of payload type codes to payload formats. + Additional payload type codes MAY be defined dynamically through + non-RTP means (see Section 3). A set of default mappings for + audio and video is specified in the companion RFC 3551 [1]. An + RTP source MAY change the payload type during a session, but this + field SHOULD NOT be used for multiplexing separate media streams + (see Section 5.2). + + A receiver MUST ignore packets with payload types that it does not + understand. + + sequence number: 16 bits + The sequence number increments by one for each RTP data packet + sent, and may be used by the receiver to detect packet loss and to + restore packet sequence. The initial value of the sequence number + SHOULD be random (unpredictable) to make known-plaintext attacks + on encryption more difficult, even if the source itself does not + encrypt according to the method in Section 9.1, because the + packets may flow through a translator that does. Techniques for + choosing unpredictable numbers are discussed in [17]. + + timestamp: 32 bits + The timestamp reflects the sampling instant of the first octet in + the RTP data packet. The sampling instant MUST be derived from a + clock that increments monotonically and linearly in time to allow + synchronization and jitter calculations (see Section 6.4.1). The + resolution of the clock MUST be sufficient for the desired + synchronization accuracy and for measuring packet arrival jitter + (one tick per video frame is typically not sufficient). The clock + frequency is dependent on the format of data carried as payload + and is specified statically in the profile or payload format + specification that defines the format, or MAY be specified + dynamically for payload formats defined through non-RTP means. If + RTP packets are generated periodically, the nominal sampling + instant as determined from the sampling clock is to be used, not a + reading of the system clock. As an example, for fixed-rate audio + the timestamp clock would likely increment by one for each + sampling period. If an audio application reads blocks covering + + + +Schulzrinne, et al. Standards Track [Page 14] + +RFC 3550 RTP July 2003 + + + 160 sampling periods from the input device, the timestamp would be + increased by 160 for each such block, regardless of whether the + block is transmitted in a packet or dropped as silent. + + The initial value of the timestamp SHOULD be random, as for the + sequence number. Several consecutive RTP packets will have equal + timestamps if they are (logically) generated at once, e.g., belong + to the same video frame. Consecutive RTP packets MAY contain + timestamps that are not monotonic if the data is not transmitted + in the order it was sampled, as in the case of MPEG interpolated + video frames. (The sequence numbers of the packets as transmitted + will still be monotonic.) + + RTP timestamps from different media streams may advance at + different rates and usually have independent, random offsets. + Therefore, although these timestamps are sufficient to reconstruct + the timing of a single stream, directly comparing RTP timestamps + from different media is not effective for synchronization. + Instead, for each medium the RTP timestamp is related to the + sampling instant by pairing it with a timestamp from a reference + clock (wallclock) that represents the time when the data + corresponding to the RTP timestamp was sampled. The reference + clock is shared by all media to be synchronized. The timestamp + pairs are not transmitted in every data packet, but at a lower + rate in RTCP SR packets as described in Section 6.4. + + The sampling instant is chosen as the point of reference for the + RTP timestamp because it is known to the transmitting endpoint and + has a common definition for all media, independent of encoding + delays or other processing. The purpose is to allow synchronized + presentation of all media sampled at the same time. + + Applications transmitting stored data rather than data sampled in + real time typically use a virtual presentation timeline derived + from wallclock time to determine when the next frame or other unit + of each medium in the stored data should be presented. In this + case, the RTP timestamp would reflect the presentation time for + each unit. That is, the RTP timestamp for each unit would be + related to the wallclock time at which the unit becomes current on + the virtual presentation timeline. Actual presentation occurs + some time later as determined by the receiver. + + An example describing live audio narration of prerecorded video + illustrates the significance of choosing the sampling instant as + the reference point. In this scenario, the video would be + presented locally for the narrator to view and would be + simultaneously transmitted using RTP. The "sampling instant" of a + video frame transmitted in RTP would be established by referencing + + + +Schulzrinne, et al. Standards Track [Page 15] + +RFC 3550 RTP July 2003 + + + its timestamp to the wallclock time when that video frame was + presented to the narrator. The sampling instant for the audio RTP + packets containing the narrator's speech would be established by + referencing the same wallclock time when the audio was sampled. + The audio and video may even be transmitted by different hosts if + the reference clocks on the two hosts are synchronized by some + means such as NTP. A receiver can then synchronize presentation + of the audio and video packets by relating their RTP timestamps + using the timestamp pairs in RTCP SR packets. + + SSRC: 32 bits + The SSRC field identifies the synchronization source. This + identifier SHOULD be chosen randomly, with the intent that no two + synchronization sources within the same RTP session will have the + same SSRC identifier. An example algorithm for generating a + random identifier is presented in Appendix A.6. Although the + probability of multiple sources choosing the same identifier is + low, all RTP implementations must be prepared to detect and + resolve collisions. Section 8 describes the probability of + collision along with a mechanism for resolving collisions and + detecting RTP-level forwarding loops based on the uniqueness of + the SSRC identifier. If a source changes its source transport + address, it must also choose a new SSRC identifier to avoid being + interpreted as a looped source (see Section 8.2). + + CSRC list: 0 to 15 items, 32 bits each + The CSRC list identifies the contributing sources for the payload + contained in this packet. The number of identifiers is given by + the CC field. If there are more than 15 contributing sources, + only 15 can be identified. CSRC identifiers are inserted by + mixers (see Section 7.1), using the SSRC identifiers of + contributing sources. For example, for audio packets the SSRC + identifiers of all sources that were mixed together to create a + packet are listed, allowing correct talker indication at the + receiver. + +5.2 Multiplexing RTP Sessions + + For efficient protocol processing, the number of multiplexing points + should be minimized, as described in the integrated layer processing + design principle [10]. In RTP, multiplexing is provided by the + destination transport address (network address and port number) which + is different for each RTP session. For example, in a teleconference + composed of audio and video media encoded separately, each medium + SHOULD be carried in a separate RTP session with its own destination + transport address. + + + + + +Schulzrinne, et al. Standards Track [Page 16] + +RFC 3550 RTP July 2003 + + + Separate audio and video streams SHOULD NOT be carried in a single + RTP session and demultiplexed based on the payload type or SSRC + fields. Interleaving packets with different RTP media types but + using the same SSRC would introduce several problems: + + 1. If, say, two audio streams shared the same RTP session and the + same SSRC value, and one were to change encodings and thus acquire + a different RTP payload type, there would be no general way of + identifying which stream had changed encodings. + + 2. An SSRC is defined to identify a single timing and sequence number + space. Interleaving multiple payload types would require + different timing spaces if the media clock rates differ and would + require different sequence number spaces to tell which payload + type suffered packet loss. + + 3. The RTCP sender and receiver reports (see Section 6.4) can only + describe one timing and sequence number space per SSRC and do not + carry a payload type field. + + 4. An RTP mixer would not be able to combine interleaved streams of + incompatible media into one stream. + + 5. Carrying multiple media in one RTP session precludes: the use of + different network paths or network resource allocations if + appropriate; reception of a subset of the media if desired, for + example just audio if video would exceed the available bandwidth; + and receiver implementations that use separate processes for the + different media, whereas using separate RTP sessions permits + either single- or multiple-process implementations. + + Using a different SSRC for each medium but sending them in the same + RTP session would avoid the first three problems but not the last + two. + + On the other hand, multiplexing multiple related sources of the same + medium in one RTP session using different SSRC values is the norm for + multicast sessions. The problems listed above don't apply: an RTP + mixer can combine multiple audio sources, for example, and the same + treatment is applicable for all of them. It may also be appropriate + to multiplex streams of the same medium using different SSRC values + in other scenarios where the last two problems do not apply. + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 17] + +RFC 3550 RTP July 2003 + + +5.3 Profile-Specific Modifications to the RTP Header + + The existing RTP data packet header is believed to be complete for + the set of functions required in common across all the application + classes that RTP might support. However, in keeping with the ALF + design principle, the header MAY be tailored through modifications or + additions defined in a profile specification while still allowing + profile-independent monitoring and recording tools to function. + + o The marker bit and payload type field carry profile-specific + information, but they are allocated in the fixed header since many + applications are expected to need them and might otherwise have to + add another 32-bit word just to hold them. The octet containing + these fields MAY be redefined by a profile to suit different + requirements, for example with more or fewer marker bits. If + there are any marker bits, one SHOULD be located in the most + significant bit of the octet since profile-independent monitors + may be able to observe a correlation between packet loss patterns + and the marker bit. + + o Additional information that is required for a particular payload + format, such as a video encoding, SHOULD be carried in the payload + section of the packet. This might be in a header that is always + present at the start of the payload section, or might be indicated + by a reserved value in the data pattern. + + o If a particular class of applications needs additional + functionality independent of payload format, the profile under + which those applications operate SHOULD define additional fixed + fields to follow immediately after the SSRC field of the existing + fixed header. Those applications will be able to quickly and + directly access the additional fields while profile-independent + monitors or recorders can still process the RTP packets by + interpreting only the first twelve octets. + + If it turns out that additional functionality is needed in common + across all profiles, then a new version of RTP should be defined to + make a permanent change to the fixed header. + +5.3.1 RTP Header Extension + + An extension mechanism is provided to allow individual + implementations to experiment with new payload-format-independent + functions that require additional information to be carried in the + RTP data packet header. This mechanism is designed so that the + header extension may be ignored by other interoperating + implementations that have not been extended. + + + + +Schulzrinne, et al. Standards Track [Page 18] + +RFC 3550 RTP July 2003 + + + Note that this header extension is intended only for limited use. + Most potential uses of this mechanism would be better done another + way, using the methods described in the previous section. For + example, a profile-specific extension to the fixed header is less + expensive to process because it is not conditional nor in a variable + location. Additional information required for a particular payload + format SHOULD NOT use this header extension, but SHOULD be carried in + the payload section of the packet. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | defined by profile | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | header extension | + | .... | + + If the X bit in the RTP header is one, a variable-length header + extension MUST be appended to the RTP header, following the CSRC list + if present. The header extension contains a 16-bit length field that + counts the number of 32-bit words in the extension, excluding the + four-octet extension header (therefore zero is a valid length). Only + a single extension can be appended to the RTP data header. To allow + multiple interoperating implementations to each experiment + independently with different header extensions, or to allow a + particular implementation to experiment with more than one type of + header extension, the first 16 bits of the header extension are left + open for distinguishing identifiers or parameters. The format of + these 16 bits is to be defined by the profile specification under + which the implementations are operating. This RTP specification does + not define any header extensions itself. + +6. RTP Control Protocol -- RTCP + + The RTP control protocol (RTCP) is based on the periodic transmission + of control packets to all participants in the session, using the same + distribution mechanism as the data packets. The underlying protocol + MUST provide multiplexing of the data and control packets, for + example using separate port numbers with UDP. RTCP performs four + functions: + + 1. The primary function is to provide feedback on the quality of the + data distribution. This is an integral part of the RTP's role as + a transport protocol and is related to the flow and congestion + control functions of other transport protocols (see Section 10 on + the requirement for congestion control). The feedback may be + directly useful for control of adaptive encodings [18,19], but + experiments with IP multicasting have shown that it is also + + + +Schulzrinne, et al. Standards Track [Page 19] + +RFC 3550 RTP July 2003 + + + critical to get feedback from the receivers to diagnose faults in + the distribution. Sending reception feedback reports to all + participants allows one who is observing problems to evaluate + whether those problems are local or global. With a distribution + mechanism like IP multicast, it is also possible for an entity + such as a network service provider who is not otherwise involved + in the session to receive the feedback information and act as a + third-party monitor to diagnose network problems. This feedback + function is performed by the RTCP sender and receiver reports, + described below in Section 6.4. + + 2. RTCP carries a persistent transport-level identifier for an RTP + source called the canonical name or CNAME, Section 6.5.1. Since + the SSRC identifier may change if a conflict is discovered or a + program is restarted, receivers require the CNAME to keep track of + each participant. Receivers may also require the CNAME to + associate multiple data streams from a given participant in a set + of related RTP sessions, for example to synchronize audio and + video. Inter-media synchronization also requires the NTP and RTP + timestamps included in RTCP packets by data senders. + + 3. The first two functions require that all participants send RTCP + packets, therefore the rate must be controlled in order for RTP to + scale up to a large number of participants. By having each + participant send its control packets to all the others, each can + independently observe the number of participants. This number is + used to calculate the rate at which the packets are sent, as + explained in Section 6.2. + + 4. A fourth, OPTIONAL function is to convey minimal session control + information, for example participant identification to be + displayed in the user interface. This is most likely to be useful + in "loosely controlled" sessions where participants enter and + leave without membership control or parameter negotiation. RTCP + serves as a convenient channel to reach all the participants, but + it is not necessarily expected to support all the control + communication requirements of an application. A higher-level + session control protocol, which is beyond the scope of this + document, may be needed. + + Functions 1-3 SHOULD be used in all environments, but particularly in + the IP multicast environment. RTP application designers SHOULD avoid + mechanisms that can only work in unicast mode and will not scale to + larger numbers. Transmission of RTCP MAY be controlled separately + for senders and receivers, as described in Section 6.2, for cases + such as unidirectional links where feedback from receivers is not + possible. + + + + +Schulzrinne, et al. Standards Track [Page 20] + +RFC 3550 RTP July 2003 + + + Non-normative note: In the multicast routing approach + called Source-Specific Multicast (SSM), there is only one sender + per "channel" (a source address, group address pair), and + receivers (except for the channel source) cannot use multicast to + communicate directly with other channel members. The + recommendations here accommodate SSM only through Section 6.2's + option of turning off receivers' RTCP entirely. Future work will + specify adaptation of RTCP for SSM so that feedback from receivers + can be maintained. + +6.1 RTCP Packet Format + + This specification defines several RTCP packet types to carry a + variety of control information: + + SR: Sender report, for transmission and reception statistics from + participants that are active senders + + RR: Receiver report, for reception statistics from participants + that are not active senders and in combination with SR for + active senders reporting on more than 31 sources + + SDES: Source description items, including CNAME + + BYE: Indicates end of participation + + APP: Application-specific functions + + Each RTCP packet begins with a fixed part similar to that of RTP data + packets, followed by structured elements that MAY be of variable + length according to the packet type but MUST end on a 32-bit + boundary. The alignment requirement and a length field in the fixed + part of each packet are included to make RTCP packets "stackable". + Multiple RTCP packets can be concatenated without any intervening + separators to form a compound RTCP packet that is sent in a single + packet of the lower layer protocol, for example UDP. There is no + explicit count of individual RTCP packets in the compound packet + since the lower layer protocols are expected to provide an overall + length to determine the end of the compound packet. + + Each individual RTCP packet in the compound packet may be processed + independently with no requirements upon the order or combination of + packets. However, in order to perform the functions of the protocol, + the following constraints are imposed: + + + + + + + +Schulzrinne, et al. Standards Track [Page 21] + +RFC 3550 RTP July 2003 + + + o Reception statistics (in SR or RR) should be sent as often as + bandwidth constraints will allow to maximize the resolution of the + statistics, therefore each periodically transmitted compound RTCP + packet MUST include a report packet. + + o New receivers need to receive the CNAME for a source as soon as + possible to identify the source and to begin associating media for + purposes such as lip-sync, so each compound RTCP packet MUST also + include the SDES CNAME except when the compound RTCP packet is + split for partial encryption as described in Section 9.1. + + o The number of packet types that may appear first in the compound + packet needs to be limited to increase the number of constant bits + in the first word and the probability of successfully validating + RTCP packets against misaddressed RTP data packets or other + unrelated packets. + + Thus, all RTCP packets MUST be sent in a compound packet of at least + two individual packets, with the following format: + + Encryption prefix: If and only if the compound packet is to be + encrypted according to the method in Section 9.1, it MUST be + prefixed by a random 32-bit quantity redrawn for every compound + packet transmitted. If padding is required for the encryption, it + MUST be added to the last packet of the compound packet. + + SR or RR: The first RTCP packet in the compound packet MUST + always be a report packet to facilitate header validation as + described in Appendix A.2. This is true even if no data has been + sent or received, in which case an empty RR MUST be sent, and even + if the only other RTCP packet in the compound packet is a BYE. + + Additional RRs: If the number of sources for which reception + statistics are being reported exceeds 31, the number that will fit + into one SR or RR packet, then additional RR packets SHOULD follow + the initial report packet. + + SDES: An SDES packet containing a CNAME item MUST be included + in each compound RTCP packet, except as noted in Section 9.1. + Other source description items MAY optionally be included if + required by a particular application, subject to bandwidth + constraints (see Section 6.3.9). + + BYE or APP: Other RTCP packet types, including those yet to be + defined, MAY follow in any order, except that BYE SHOULD be the + last packet sent with a given SSRC/CSRC. Packet types MAY appear + more than once. + + + + +Schulzrinne, et al. Standards Track [Page 22] + +RFC 3550 RTP July 2003 + + + An individual RTP participant SHOULD send only one compound RTCP + packet per report interval in order for the RTCP bandwidth per + participant to be estimated correctly (see Section 6.2), except when + the compound RTCP packet is split for partial encryption as described + in Section 9.1. If there are too many sources to fit all the + necessary RR packets into one compound RTCP packet without exceeding + the maximum transmission unit (MTU) of the network path, then only + the subset that will fit into one MTU SHOULD be included in each + interval. The subsets SHOULD be selected round-robin across multiple + intervals so that all sources are reported. + + It is RECOMMENDED that translators and mixers combine individual RTCP + packets from the multiple sources they are forwarding into one + compound packet whenever feasible in order to amortize the packet + overhead (see Section 7). An example RTCP compound packet as might + be produced by a mixer is shown in Fig. 1. If the overall length of + a compound packet would exceed the MTU of the network path, it SHOULD + be segmented into multiple shorter compound packets to be transmitted + in separate packets of the underlying protocol. This does not impair + the RTCP bandwidth estimation because each compound packet represents + at least one distinct participant. Note that each of the compound + packets MUST begin with an SR or RR packet. + + An implementation SHOULD ignore incoming RTCP packets with types + unknown to it. Additional RTCP packet types may be registered with + the Internet Assigned Numbers Authority (IANA) as described in + Section 15. + + if encrypted: random 32-bit integer + | + |[--------- packet --------][---------- packet ----------][-packet-] + | + | receiver chunk chunk + V reports item item item item + -------------------------------------------------------------------- + R[SR #sendinfo #site1#site2][SDES #CNAME PHONE #CNAME LOC][BYE##why] + -------------------------------------------------------------------- + | | + |<----------------------- compound packet ----------------------->| + |<-------------------------- UDP packet ------------------------->| + + #: SSRC/CSRC identifier + + Figure 1: Example of an RTCP compound packet + + + + + + + +Schulzrinne, et al. Standards Track [Page 23] + +RFC 3550 RTP July 2003 + + +6.2 RTCP Transmission Interval + + RTP is designed to allow an application to scale automatically over + session sizes ranging from a few participants to thousands. For + example, in an audio conference the data traffic is inherently self- + limiting because only one or two people will speak at a time, so with + multicast distribution the data rate on any given link remains + relatively constant independent of the number of participants. + However, the control traffic is not self-limiting. If the reception + reports from each participant were sent at a constant rate, the + control traffic would grow linearly with the number of participants. + Therefore, the rate must be scaled down by dynamically calculating + the interval between RTCP packet transmissions. + + For each session, it is assumed that the data traffic is subject to + an aggregate limit called the "session bandwidth" to be divided among + the participants. This bandwidth might be reserved and the limit + enforced by the network. If there is no reservation, there may be + other constraints, depending on the environment, that establish the + "reasonable" maximum for the session to use, and that would be the + session bandwidth. The session bandwidth may be chosen based on some + cost or a priori knowledge of the available network bandwidth for the + session. It is somewhat independent of the media encoding, but the + encoding choice may be limited by the session bandwidth. Often, the + session bandwidth is the sum of the nominal bandwidths of the senders + expected to be concurrently active. For teleconference audio, this + number would typically be one sender's bandwidth. For layered + encodings, each layer is a separate RTP session with its own session + bandwidth parameter. + + The session bandwidth parameter is expected to be supplied by a + session management application when it invokes a media application, + but media applications MAY set a default based on the single-sender + data bandwidth for the encoding selected for the session. The + application MAY also enforce bandwidth limits based on multicast + scope rules or other criteria. All participants MUST use the same + value for the session bandwidth so that the same RTCP interval will + be calculated. + + Bandwidth calculations for control and data traffic include lower- + layer transport and network protocols (e.g., UDP and IP) since that + is what the resource reservation system would need to know. The + application can also be expected to know which of these protocols are + in use. Link level headers are not included in the calculation since + the packet will be encapsulated with different link level headers as + it travels. + + + + + +Schulzrinne, et al. Standards Track [Page 24] + +RFC 3550 RTP July 2003 + + + The control traffic should be limited to a small and known fraction + of the session bandwidth: small so that the primary function of the + transport protocol to carry data is not impaired; known so that the + control traffic can be included in the bandwidth specification given + to a resource reservation protocol, and so that each participant can + independently calculate its share. The control traffic bandwidth is + in addition to the session bandwidth for the data traffic. It is + RECOMMENDED that the fraction of the session bandwidth added for RTCP + be fixed at 5%. It is also RECOMMENDED that 1/4 of the RTCP + bandwidth be dedicated to participants that are sending data so that + in sessions with a large number of receivers but a small number of + senders, newly joining participants will more quickly receive the + CNAME for the sending sites. When the proportion of senders is + greater than 1/4 of the participants, the senders get their + proportion of the full RTCP bandwidth. While the values of these and + other constants in the interval calculation are not critical, all + participants in the session MUST use the same values so the same + interval will be calculated. Therefore, these constants SHOULD be + fixed for a particular profile. + + A profile MAY specify that the control traffic bandwidth may be a + separate parameter of the session rather than a strict percentage of + the session bandwidth. Using a separate parameter allows rate- + adaptive applications to set an RTCP bandwidth consistent with a + "typical" data bandwidth that is lower than the maximum bandwidth + specified by the session bandwidth parameter. + + The profile MAY further specify that the control traffic bandwidth + may be divided into two separate session parameters for those + participants which are active data senders and those which are not; + let us call the parameters S and R. Following the recommendation + that 1/4 of the RTCP bandwidth be dedicated to data senders, the + RECOMMENDED default values for these two parameters would be 1.25% + and 3.75%, respectively. When the proportion of senders is greater + than S/(S+R) of the participants, the senders get their proportion of + the sum of these parameters. Using two parameters allows RTCP + reception reports to be turned off entirely for a particular session + by setting the RTCP bandwidth for non-data-senders to zero while + keeping the RTCP bandwidth for data senders non-zero so that sender + reports can still be sent for inter-media synchronization. Turning + off RTCP reception reports is NOT RECOMMENDED because they are needed + for the functions listed at the beginning of Section 6, particularly + reception quality feedback and congestion control. However, doing so + may be appropriate for systems operating on unidirectional links or + for sessions that don't require feedback on the quality of reception + or liveness of receivers and that have other means to avoid + congestion. + + + + +Schulzrinne, et al. Standards Track [Page 25] + +RFC 3550 RTP July 2003 + + + The calculated interval between transmissions of compound RTCP + packets SHOULD also have a lower bound to avoid having bursts of + packets exceed the allowed bandwidth when the number of participants + is small and the traffic isn't smoothed according to the law of large + numbers. It also keeps the report interval from becoming too small + during transient outages like a network partition such that + adaptation is delayed when the partition heals. At application + startup, a delay SHOULD be imposed before the first compound RTCP + packet is sent to allow time for RTCP packets to be received from + other participants so the report interval will converge to the + correct value more quickly. This delay MAY be set to half the + minimum interval to allow quicker notification that the new + participant is present. The RECOMMENDED value for a fixed minimum + interval is 5 seconds. + + An implementation MAY scale the minimum RTCP interval to a smaller + value inversely proportional to the session bandwidth parameter with + the following limitations: + + o For multicast sessions, only active data senders MAY use the + reduced minimum value to calculate the interval for transmission + of compound RTCP packets. + + o For unicast sessions, the reduced value MAY be used by + participants that are not active data senders as well, and the + delay before sending the initial compound RTCP packet MAY be zero. + + o For all sessions, the fixed minimum SHOULD be used when + calculating the participant timeout interval (see Section 6.3.5) + so that implementations which do not use the reduced value for + transmitting RTCP packets are not timed out by other participants + prematurely. + + o The RECOMMENDED value for the reduced minimum in seconds is 360 + divided by the session bandwidth in kilobits/second. This minimum + is smaller than 5 seconds for bandwidths greater than 72 kb/s. + + The algorithm described in Section 6.3 and Appendix A.7 was designed + to meet the goals outlined in this section. It calculates the + interval between sending compound RTCP packets to divide the allowed + control traffic bandwidth among the participants. This allows an + application to provide fast response for small sessions where, for + example, identification of all participants is important, yet + automatically adapt to large sessions. The algorithm incorporates + the following characteristics: + + + + + + +Schulzrinne, et al. Standards Track [Page 26] + +RFC 3550 RTP July 2003 + + + o The calculated interval between RTCP packets scales linearly with + the number of members in the group. It is this linear factor + which allows for a constant amount of control traffic when summed + across all members. + + o The interval between RTCP packets is varied randomly over the + range [0.5,1.5] times the calculated interval to avoid unintended + synchronization of all participants [20]. The first RTCP packet + sent after joining a session is also delayed by a random variation + of half the minimum RTCP interval. + + o A dynamic estimate of the average compound RTCP packet size is + calculated, including all those packets received and sent, to + automatically adapt to changes in the amount of control + information carried. + + o Since the calculated interval is dependent on the number of + observed group members, there may be undesirable startup effects + when a new user joins an existing session, or many users + simultaneously join a new session. These new users will initially + have incorrect estimates of the group membership, and thus their + RTCP transmission interval will be too short. This problem can be + significant if many users join the session simultaneously. To + deal with this, an algorithm called "timer reconsideration" is + employed. This algorithm implements a simple back-off mechanism + which causes users to hold back RTCP packet transmission if the + group sizes are increasing. + + o When users leave a session, either with a BYE or by timeout, the + group membership decreases, and thus the calculated interval + should decrease. A "reverse reconsideration" algorithm is used to + allow members to more quickly reduce their intervals in response + to group membership decreases. + + o BYE packets are given different treatment than other RTCP packets. + When a user leaves a group, and wishes to send a BYE packet, it + may do so before its next scheduled RTCP packet. However, + transmission of BYEs follows a back-off algorithm which avoids + floods of BYE packets should a large number of members + simultaneously leave the session. + + This algorithm may be used for sessions in which all participants are + allowed to send. In that case, the session bandwidth parameter is + the product of the individual sender's bandwidth times the number of + participants, and the RTCP bandwidth is 5% of that. + + Details of the algorithm's operation are given in the sections that + follow. Appendix A.7 gives an example implementation. + + + +Schulzrinne, et al. Standards Track [Page 27] + +RFC 3550 RTP July 2003 + + +6.2.1 Maintaining the Number of Session Members + + Calculation of the RTCP packet interval depends upon an estimate of + the number of sites participating in the session. New sites are + added to the count when they are heard, and an entry for each SHOULD + be created in a table indexed by the SSRC or CSRC identifier (see + Section 8.2) to keep track of them. New entries MAY be considered + not valid until multiple packets carrying the new SSRC have been + received (see Appendix A.1), or until an SDES RTCP packet containing + a CNAME for that SSRC has been received. Entries MAY be deleted from + the table when an RTCP BYE packet with the corresponding SSRC + identifier is received, except that some straggler data packets might + arrive after the BYE and cause the entry to be recreated. Instead, + the entry SHOULD be marked as having received a BYE and then deleted + after an appropriate delay. + + A participant MAY mark another site inactive, or delete it if not yet + valid, if no RTP or RTCP packet has been received for a small number + of RTCP report intervals (5 is RECOMMENDED). This provides some + robustness against packet loss. All sites must have the same value + for this multiplier and must calculate roughly the same value for the + RTCP report interval in order for this timeout to work properly. + Therefore, this multiplier SHOULD be fixed for a particular profile. + + For sessions with a very large number of participants, it may be + impractical to maintain a table to store the SSRC identifier and + state information for all of them. An implementation MAY use SSRC + sampling, as described in [21], to reduce the storage requirements. + An implementation MAY use any other algorithm with similar + performance. A key requirement is that any algorithm considered + SHOULD NOT substantially underestimate the group size, although it + MAY overestimate. + +6.3 RTCP Packet Send and Receive Rules + + The rules for how to send, and what to do when receiving an RTCP + packet are outlined here. An implementation that allows operation in + a multicast environment or a multipoint unicast environment MUST meet + the requirements in Section 6.2. Such an implementation MAY use the + algorithm defined in this section to meet those requirements, or MAY + use some other algorithm so long as it provides equivalent or better + performance. An implementation which is constrained to two-party + unicast operation SHOULD still use randomization of the RTCP + transmission interval to avoid unintended synchronization of multiple + instances operating in the same environment, but MAY omit the "timer + reconsideration" and "reverse reconsideration" algorithms in Sections + 6.3.3, 6.3.6 and 6.3.7. + + + + +Schulzrinne, et al. Standards Track [Page 28] + +RFC 3550 RTP July 2003 + + + To execute these rules, a session participant must maintain several + pieces of state: + + tp: the last time an RTCP packet was transmitted; + + tc: the current time; + + tn: the next scheduled transmission time of an RTCP packet; + + pmembers: the estimated number of session members at the time tn + was last recomputed; + + members: the most current estimate for the number of session + members; + + senders: the most current estimate for the number of senders in + the session; + + rtcp_bw: The target RTCP bandwidth, i.e., the total bandwidth + that will be used for RTCP packets by all members of this session, + in octets per second. This will be a specified fraction of the + "session bandwidth" parameter supplied to the application at + startup. + + we_sent: Flag that is true if the application has sent data + since the 2nd previous RTCP report was transmitted. + + avg_rtcp_size: The average compound RTCP packet size, in octets, + over all RTCP packets sent and received by this participant. The + size includes lower-layer transport and network protocol headers + (e.g., UDP and IP) as explained in Section 6.2. + + initial: Flag that is true if the application has not yet sent + an RTCP packet. + + Many of these rules make use of the "calculated interval" between + packet transmissions. This interval is described in the following + section. + +6.3.1 Computing the RTCP Transmission Interval + + To maintain scalability, the average interval between packets from a + session participant should scale with the group size. This interval + is called the calculated interval. It is obtained by combining a + number of the pieces of state described above. The calculated + interval T is then determined as follows: + + + + + +Schulzrinne, et al. Standards Track [Page 29] + +RFC 3550 RTP July 2003 + + + 1. If the number of senders is less than or equal to 25% of the + membership (members), the interval depends on whether the + participant is a sender or not (based on the value of we_sent). + If the participant is a sender (we_sent true), the constant C is + set to the average RTCP packet size (avg_rtcp_size) divided by 25% + of the RTCP bandwidth (rtcp_bw), and the constant n is set to the + number of senders. If we_sent is not true, the constant C is set + to the average RTCP packet size divided by 75% of the RTCP + bandwidth. The constant n is set to the number of receivers + (members - senders). If the number of senders is greater than + 25%, senders and receivers are treated together. The constant C + is set to the average RTCP packet size divided by the total RTCP + bandwidth and n is set to the total number of members. As stated + in Section 6.2, an RTP profile MAY specify that the RTCP bandwidth + may be explicitly defined by two separate parameters (call them S + and R) for those participants which are senders and those which + are not. In that case, the 25% fraction becomes S/(S+R) and the + 75% fraction becomes R/(S+R). Note that if R is zero, the + percentage of senders is never greater than S/(S+R), and the + implementation must avoid division by zero. + + 2. If the participant has not yet sent an RTCP packet (the variable + initial is true), the constant Tmin is set to 2.5 seconds, else it + is set to 5 seconds. + + 3. The deterministic calculated interval Td is set to max(Tmin, n*C). + + 4. The calculated interval T is set to a number uniformly distributed + between 0.5 and 1.5 times the deterministic calculated interval. + + 5. The resulting value of T is divided by e-3/2=1.21828 to compensate + for the fact that the timer reconsideration algorithm converges to + a value of the RTCP bandwidth below the intended average. + + This procedure results in an interval which is random, but which, on + average, gives at least 25% of the RTCP bandwidth to senders and the + rest to receivers. If the senders constitute more than one quarter + of the membership, this procedure splits the bandwidth equally among + all participants, on average. + +6.3.2 Initialization + + Upon joining the session, the participant initializes tp to 0, tc to + 0, senders to 0, pmembers to 1, members to 1, we_sent to false, + rtcp_bw to the specified fraction of the session bandwidth, initial + to true, and avg_rtcp_size to the probable size of the first RTCP + packet that the application will later construct. The calculated + interval T is then computed, and the first packet is scheduled for + + + +Schulzrinne, et al. Standards Track [Page 30] + +RFC 3550 RTP July 2003 + + + time tn = T. This means that a transmission timer is set which + expires at time T. Note that an application MAY use any desired + approach for implementing this timer. + + The participant adds its own SSRC to the member table. + +6.3.3 Receiving an RTP or Non-BYE RTCP Packet + + When an RTP or RTCP packet is received from a participant whose SSRC + is not in the member table, the SSRC is added to the table, and the + value for members is updated once the participant has been validated + as described in Section 6.2.1. The same processing occurs for each + CSRC in a validated RTP packet. + + When an RTP packet is received from a participant whose SSRC is not + in the sender table, the SSRC is added to the table, and the value + for senders is updated. + + For each compound RTCP packet received, the value of avg_rtcp_size is + updated: + + avg_rtcp_size = (1/16) * packet_size + (15/16) * avg_rtcp_size + + where packet_size is the size of the RTCP packet just received. + +6.3.4 Receiving an RTCP BYE Packet + + Except as described in Section 6.3.7 for the case when an RTCP BYE is + to be transmitted, if the received packet is an RTCP BYE packet, the + SSRC is checked against the member table. If present, the entry is + removed from the table, and the value for members is updated. The + SSRC is then checked against the sender table. If present, the entry + is removed from the table, and the value for senders is updated. + + Furthermore, to make the transmission rate of RTCP packets more + adaptive to changes in group membership, the following "reverse + reconsideration" algorithm SHOULD be executed when a BYE packet is + received that reduces members to a value less than pmembers: + + o The value for tn is updated according to the following formula: + + tn = tc + (members/pmembers) * (tn - tc) + + o The value for tp is updated according the following formula: + + tp = tc - (members/pmembers) * (tc - tp). + + + + + +Schulzrinne, et al. Standards Track [Page 31] + +RFC 3550 RTP July 2003 + + + o The next RTCP packet is rescheduled for transmission at time tn, + which is now earlier. + + o The value of pmembers is set equal to members. + + This algorithm does not prevent the group size estimate from + incorrectly dropping to zero for a short time due to premature + timeouts when most participants of a large session leave at once but + some remain. The algorithm does make the estimate return to the + correct value more rapidly. This situation is unusual enough and the + consequences are sufficiently harmless that this problem is deemed + only a secondary concern. + +6.3.5 Timing Out an SSRC + + At occasional intervals, the participant MUST check to see if any of + the other participants time out. To do this, the participant + computes the deterministic (without the randomization factor) + calculated interval Td for a receiver, that is, with we_sent false. + Any other session member who has not sent an RTP or RTCP packet since + time tc - MTd (M is the timeout multiplier, and defaults to 5) is + timed out. This means that its SSRC is removed from the member list, + and members is updated. A similar check is performed on the sender + list. Any member on the sender list who has not sent an RTP packet + since time tc - 2T (within the last two RTCP report intervals) is + removed from the sender list, and senders is updated. + + If any members time out, the reverse reconsideration algorithm + described in Section 6.3.4 SHOULD be performed. + + The participant MUST perform this check at least once per RTCP + transmission interval. + +6.3.6 Expiration of Transmission Timer + + When the packet transmission timer expires, the participant performs + the following operations: + + o The transmission interval T is computed as described in Section + 6.3.1, including the randomization factor. + + o If tp + T is less than or equal to tc, an RTCP packet is + transmitted. tp is set to tc, then another value for T is + calculated as in the previous step and tn is set to tc + T. The + transmission timer is set to expire again at time tn. If tp + T + is greater than tc, tn is set to tp + T. No RTCP packet is + transmitted. The transmission timer is set to expire at time tn. + + + + +Schulzrinne, et al. Standards Track [Page 32] + +RFC 3550 RTP July 2003 + + + o pmembers is set to members. + + If an RTCP packet is transmitted, the value of initial is set to + FALSE. Furthermore, the value of avg_rtcp_size is updated: + + avg_rtcp_size = (1/16) * packet_size + (15/16) * avg_rtcp_size + + where packet_size is the size of the RTCP packet just transmitted. + +6.3.7 Transmitting a BYE Packet + + When a participant wishes to leave a session, a BYE packet is + transmitted to inform the other participants of the event. In order + to avoid a flood of BYE packets when many participants leave the + system, a participant MUST execute the following algorithm if the + number of members is more than 50 when the participant chooses to + leave. This algorithm usurps the normal role of the members variable + to count BYE packets instead: + + o When the participant decides to leave the system, tp is reset to + tc, the current time, members and pmembers are initialized to 1, + initial is set to 1, we_sent is set to false, senders is set to 0, + and avg_rtcp_size is set to the size of the compound BYE packet. + The calculated interval T is computed. The BYE packet is then + scheduled for time tn = tc + T. + + o Every time a BYE packet from another participant is received, + members is incremented by 1 regardless of whether that participant + exists in the member table or not, and when SSRC sampling is in + use, regardless of whether or not the BYE SSRC would be included + in the sample. members is NOT incremented when other RTCP packets + or RTP packets are received, but only for BYE packets. Similarly, + avg_rtcp_size is updated only for received BYE packets. senders + is NOT updated when RTP packets arrive; it remains 0. + + o Transmission of the BYE packet then follows the rules for + transmitting a regular RTCP packet, as above. + + This allows BYE packets to be sent right away, yet controls their + total bandwidth usage. In the worst case, this could cause RTCP + control packets to use twice the bandwidth as normal (10%) -- 5% for + non-BYE RTCP packets and 5% for BYE. + + A participant that does not want to wait for the above mechanism to + allow transmission of a BYE packet MAY leave the group without + sending a BYE at all. That participant will eventually be timed out + by the other group members. + + + + +Schulzrinne, et al. Standards Track [Page 33] + +RFC 3550 RTP July 2003 + + + If the group size estimate members is less than 50 when the + participant decides to leave, the participant MAY send a BYE packet + immediately. Alternatively, the participant MAY choose to execute + the above BYE backoff algorithm. + + In either case, a participant which never sent an RTP or RTCP packet + MUST NOT send a BYE packet when they leave the group. + +6.3.8 Updating we_sent + + The variable we_sent contains true if the participant has sent an RTP + packet recently, false otherwise. This determination is made by + using the same mechanisms as for managing the set of other + participants listed in the senders table. If the participant sends + an RTP packet when we_sent is false, it adds itself to the sender + table and sets we_sent to true. The reverse reconsideration + algorithm described in Section 6.3.4 SHOULD be performed to possibly + reduce the delay before sending an SR packet. Every time another RTP + packet is sent, the time of transmission of that packet is maintained + in the table. The normal sender timeout algorithm is then applied to + the participant -- if an RTP packet has not been transmitted since + time tc - 2T, the participant removes itself from the sender table, + decrements the sender count, and sets we_sent to false. + +6.3.9 Allocation of Source Description Bandwidth + + This specification defines several source description (SDES) items in + addition to the mandatory CNAME item, such as NAME (personal name) + and EMAIL (email address). It also provides a means to define new + application-specific RTCP packet types. Applications should exercise + caution in allocating control bandwidth to this additional + information because it will slow down the rate at which reception + reports and CNAME are sent, thus impairing the performance of the + protocol. It is RECOMMENDED that no more than 20% of the RTCP + bandwidth allocated to a single participant be used to carry the + additional information. Furthermore, it is not intended that all + SDES items will be included in every application. Those that are + included SHOULD be assigned a fraction of the bandwidth according to + their utility. Rather than estimate these fractions dynamically, it + is recommended that the percentages be translated statically into + report interval counts based on the typical length of an item. + + For example, an application may be designed to send only CNAME, NAME + and EMAIL and not any others. NAME might be given much higher + priority than EMAIL because the NAME would be displayed continuously + in the application's user interface, whereas EMAIL would be displayed + only when requested. At every RTCP interval, an RR packet and an + SDES packet with the CNAME item would be sent. For a small session + + + +Schulzrinne, et al. Standards Track [Page 34] + +RFC 3550 RTP July 2003 + + + operating at the minimum interval, that would be every 5 seconds on + the average. Every third interval (15 seconds), one extra item would + be included in the SDES packet. Seven out of eight times this would + be the NAME item, and every eighth time (2 minutes) it would be the + EMAIL item. + + When multiple applications operate in concert using cross-application + binding through a common CNAME for each participant, for example in a + multimedia conference composed of an RTP session for each medium, the + additional SDES information MAY be sent in only one RTP session. The + other sessions would carry only the CNAME item. In particular, this + approach should be applied to the multiple sessions of a layered + encoding scheme (see Section 2.4). + +6.4 Sender and Receiver Reports + + RTP receivers provide reception quality feedback using RTCP report + packets which may take one of two forms depending upon whether or not + the receiver is also a sender. The only difference between the + sender report (SR) and receiver report (RR) forms, besides the packet + type code, is that the sender report includes a 20-byte sender + information section for use by active senders. The SR is issued if a + site has sent any data packets during the interval since issuing the + last report or the previous one, otherwise the RR is issued. + + Both the SR and RR forms include zero or more reception report + blocks, one for each of the synchronization sources from which this + receiver has received RTP data packets since the last report. + Reports are not issued for contributing sources listed in the CSRC + list. Each reception report block provides statistics about the data + received from the particular source indicated in that block. Since a + maximum of 31 reception report blocks will fit in an SR or RR packet, + additional RR packets SHOULD be stacked after the initial SR or RR + packet as needed to contain the reception reports for all sources + heard during the interval since the last report. If there are too + many sources to fit all the necessary RR packets into one compound + RTCP packet without exceeding the MTU of the network path, then only + the subset that will fit into one MTU SHOULD be included in each + interval. The subsets SHOULD be selected round-robin across multiple + intervals so that all sources are reported. + + The next sections define the formats of the two reports, how they may + be extended in a profile-specific manner if an application requires + additional feedback information, and how the reports may be used. + Details of reception reporting by translators and mixers is given in + Section 7. + + + + + +Schulzrinne, et al. Standards Track [Page 35] + +RFC 3550 RTP July 2003 + + +6.4.1 SR: Sender Report RTCP Packet + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| RC | PT=SR=200 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of sender | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +sender | NTP timestamp, most significant word | +info +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NTP timestamp, least significant word | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RTP timestamp | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | sender's packet count | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | sender's octet count | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_1 (SSRC of first source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 1 | fraction lost | cumulative number of packets lost | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | extended highest sequence number received | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | interarrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | last SR (LSR) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last SR (DLSR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_2 (SSRC of second source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 2 : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | profile-specific extensions | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The sender report packet consists of three sections, possibly + followed by a fourth profile-specific extension section if defined. + The first section, the header, is 8 octets long. The fields have the + following meaning: + + version (V): 2 bits + Identifies the version of RTP, which is the same in RTCP packets + as in RTP data packets. The version defined by this specification + is two (2). + + + + +Schulzrinne, et al. Standards Track [Page 36] + +RFC 3550 RTP July 2003 + + + padding (P): 1 bit + If the padding bit is set, this individual RTCP packet contains + some additional padding octets at the end which are not part of + the control information but are included in the length field. The + last octet of the padding is a count of how many padding octets + should be ignored, including itself (it will be a multiple of + four). Padding may be needed by some encryption algorithms with + fixed block sizes. In a compound RTCP packet, padding is only + required on one individual packet because the compound packet is + encrypted as a whole for the method in Section 9.1. Thus, padding + MUST only be added to the last individual packet, and if padding + is added to that packet, the padding bit MUST be set only on that + packet. This convention aids the header validity checks described + in Appendix A.2 and allows detection of packets from some early + implementations that incorrectly set the padding bit on the first + individual packet and add padding to the last individual packet. + + reception report count (RC): 5 bits + The number of reception report blocks contained in this packet. A + value of zero is valid. + + packet type (PT): 8 bits + Contains the constant 200 to identify this as an RTCP SR packet. + + length: 16 bits + The length of this RTCP packet in 32-bit words minus one, + including the header and any padding. (The offset of one makes + zero a valid length and avoids a possible infinite loop in + scanning a compound RTCP packet, while counting 32-bit words + avoids a validity check for a multiple of 4.) + + SSRC: 32 bits + The synchronization source identifier for the originator of this + SR packet. + + The second section, the sender information, is 20 octets long and is + present in every sender report packet. It summarizes the data + transmissions from this sender. The fields have the following + meaning: + + NTP timestamp: 64 bits + Indicates the wallclock time (see Section 4) when this report was + sent so that it may be used in combination with timestamps + returned in reception reports from other receivers to measure + round-trip propagation to those receivers. Receivers should + expect that the measurement accuracy of the timestamp may be + limited to far less than the resolution of the NTP timestamp. The + measurement uncertainty of the timestamp is not indicated as it + + + +Schulzrinne, et al. Standards Track [Page 37] + +RFC 3550 RTP July 2003 + + + may not be known. On a system that has no notion of wallclock + time but does have some system-specific clock such as "system + uptime", a sender MAY use that clock as a reference to calculate + relative NTP timestamps. It is important to choose a commonly + used clock so that if separate implementations are used to produce + the individual streams of a multimedia session, all + implementations will use the same clock. Until the year 2036, + relative and absolute timestamps will differ in the high bit so + (invalid) comparisons will show a large difference; by then one + hopes relative timestamps will no longer be needed. A sender that + has no notion of wallclock or elapsed time MAY set the NTP + timestamp to zero. + + RTP timestamp: 32 bits + Corresponds to the same time as the NTP timestamp (above), but in + the same units and with the same random offset as the RTP + timestamps in data packets. This correspondence may be used for + intra- and inter-media synchronization for sources whose NTP + timestamps are synchronized, and may be used by media-independent + receivers to estimate the nominal RTP clock frequency. Note that + in most cases this timestamp will not be equal to the RTP + timestamp in any adjacent data packet. Rather, it MUST be + calculated from the corresponding NTP timestamp using the + relationship between the RTP timestamp counter and real time as + maintained by periodically checking the wallclock time at a + sampling instant. + + sender's packet count: 32 bits + The total number of RTP data packets transmitted by the sender + since starting transmission up until the time this SR packet was + generated. The count SHOULD be reset if the sender changes its + SSRC identifier. + + sender's octet count: 32 bits + The total number of payload octets (i.e., not including header or + padding) transmitted in RTP data packets by the sender since + starting transmission up until the time this SR packet was + generated. The count SHOULD be reset if the sender changes its + SSRC identifier. This field can be used to estimate the average + payload data rate. + + The third section contains zero or more reception report blocks + depending on the number of other sources heard by this sender since + the last report. Each reception report block conveys statistics on + the reception of RTP packets from a single synchronization source. + Receivers SHOULD NOT carry over statistics when a source changes its + SSRC identifier due to a collision. These statistics are: + + + + +Schulzrinne, et al. Standards Track [Page 38] + +RFC 3550 RTP July 2003 + + + SSRC_n (source identifier): 32 bits + The SSRC identifier of the source to which the information in this + reception report block pertains. + + fraction lost: 8 bits + The fraction of RTP data packets from source SSRC_n lost since the + previous SR or RR packet was sent, expressed as a fixed point + number with the binary point at the left edge of the field. (That + is equivalent to taking the integer part after multiplying the + loss fraction by 256.) This fraction is defined to be the number + of packets lost divided by the number of packets expected, as + defined in the next paragraph. An implementation is shown in + Appendix A.3. If the loss is negative due to duplicates, the + fraction lost is set to zero. Note that a receiver cannot tell + whether any packets were lost after the last one received, and + that there will be no reception report block issued for a source + if all packets from that source sent during the last reporting + interval have been lost. + + cumulative number of packets lost: 24 bits + The total number of RTP data packets from source SSRC_n that have + been lost since the beginning of reception. This number is + defined to be the number of packets expected less the number of + packets actually received, where the number of packets received + includes any which are late or duplicates. Thus, packets that + arrive late are not counted as lost, and the loss may be negative + if there are duplicates. The number of packets expected is + defined to be the extended last sequence number received, as + defined next, less the initial sequence number received. This may + be calculated as shown in Appendix A.3. + + extended highest sequence number received: 32 bits + The low 16 bits contain the highest sequence number received in an + RTP data packet from source SSRC_n, and the most significant 16 + bits extend that sequence number with the corresponding count of + sequence number cycles, which may be maintained according to the + algorithm in Appendix A.1. Note that different receivers within + the same session will generate different extensions to the + sequence number if their start times differ significantly. + + interarrival jitter: 32 bits + An estimate of the statistical variance of the RTP data packet + interarrival time, measured in timestamp units and expressed as an + unsigned integer. The interarrival jitter J is defined to be the + mean deviation (smoothed absolute value) of the difference D in + packet spacing at the receiver compared to the sender for a pair + of packets. As shown in the equation below, this is equivalent to + the difference in the "relative transit time" for the two packets; + + + +Schulzrinne, et al. Standards Track [Page 39] + +RFC 3550 RTP July 2003 + + + the relative transit time is the difference between a packet's RTP + timestamp and the receiver's clock at the time of arrival, + measured in the same units. + + If Si is the RTP timestamp from packet i, and Ri is the time of + arrival in RTP timestamp units for packet i, then for two packets + i and j, D may be expressed as + + D(i,j) = (Rj - Ri) - (Sj - Si) = (Rj - Sj) - (Ri - Si) + + The interarrival jitter SHOULD be calculated continuously as each + data packet i is received from source SSRC_n, using this + difference D for that packet and the previous packet i-1 in order + of arrival (not necessarily in sequence), according to the formula + + J(i) = J(i-1) + (|D(i-1,i)| - J(i-1))/16 + + Whenever a reception report is issued, the current value of J is + sampled. + + The jitter calculation MUST conform to the formula specified here + in order to allow profile-independent monitors to make valid + interpretations of reports coming from different implementations. + This algorithm is the optimal first-order estimator and the gain + parameter 1/16 gives a good noise reduction ratio while + maintaining a reasonable rate of convergence [22]. A sample + implementation is shown in Appendix A.8. See Section 6.4.4 for a + discussion of the effects of varying packet duration and delay + before transmission. + + last SR timestamp (LSR): 32 bits + The middle 32 bits out of 64 in the NTP timestamp (as explained in + Section 4) received as part of the most recent RTCP sender report + (SR) packet from source SSRC_n. If no SR has been received yet, + the field is set to zero. + + delay since last SR (DLSR): 32 bits + The delay, expressed in units of 1/65536 seconds, between + receiving the last SR packet from source SSRC_n and sending this + reception report block. If no SR packet has been received yet + from SSRC_n, the DLSR field is set to zero. + + Let SSRC_r denote the receiver issuing this receiver report. + Source SSRC_n can compute the round-trip propagation delay to + SSRC_r by recording the time A when this reception report block is + received. It calculates the total round-trip time A-LSR using the + last SR timestamp (LSR) field, and then subtracting this field to + leave the round-trip propagation delay as (A - LSR - DLSR). This + + + +Schulzrinne, et al. Standards Track [Page 40] + +RFC 3550 RTP July 2003 + + + is illustrated in Fig. 2. Times are shown in both a hexadecimal + representation of the 32-bit fields and the equivalent floating- + point decimal representation. Colons indicate a 32-bit field + divided into a 16-bit integer part and 16-bit fraction part. + + This may be used as an approximate measure of distance to cluster + receivers, although some links have very asymmetric delays. + + [10 Nov 1995 11:33:25.125 UTC] [10 Nov 1995 11:33:36.5 UTC] + n SR(n) A=b710:8000 (46864.500 s) + ----------------------------------------------------------------> + v ^ + ntp_sec =0xb44db705 v ^ dlsr=0x0005:4000 ( 5.250s) + ntp_frac=0x20000000 v ^ lsr =0xb705:2000 (46853.125s) + (3024992005.125 s) v ^ + r v ^ RR(n) + ----------------------------------------------------------------> + |<-DLSR->| + (5.250 s) + + A 0xb710:8000 (46864.500 s) + DLSR -0x0005:4000 ( 5.250 s) + LSR -0xb705:2000 (46853.125 s) + ------------------------------- + delay 0x0006:2000 ( 6.125 s) + + Figure 2: Example for round-trip time computation + + + + + + + + + + + + + + + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 41] + +RFC 3550 RTP July 2003 + + +6.4.2 RR: Receiver Report RTCP Packet + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| RC | PT=RR=201 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC of packet sender | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_1 (SSRC of first source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 1 | fraction lost | cumulative number of packets lost | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | extended highest sequence number received | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | interarrival jitter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | last SR (LSR) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | delay since last SR (DLSR) | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +report | SSRC_2 (SSRC of second source) | +block +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + 2 : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + | profile-specific extensions | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The format of the receiver report (RR) packet is the same as that of + the SR packet except that the packet type field contains the constant + 201 and the five words of sender information are omitted (these are + the NTP and RTP timestamps and sender's packet and octet counts). + The remaining fields have the same meaning as for the SR packet. + + An empty RR packet (RC = 0) MUST be put at the head of a compound + RTCP packet when there is no data transmission or reception to + report. + +6.4.3 Extending the Sender and Receiver Reports + + A profile SHOULD define profile-specific extensions to the sender + report and receiver report if there is additional information that + needs to be reported regularly about the sender or receivers. This + method SHOULD be used in preference to defining another RTCP packet + type because it requires less overhead: + + o fewer octets in the packet (no RTCP header or SSRC field); + + + + +Schulzrinne, et al. Standards Track [Page 42] + +RFC 3550 RTP July 2003 + + + o simpler and faster parsing because applications running under that + profile would be programmed to always expect the extension fields + in the directly accessible location after the reception reports. + + The extension is a fourth section in the sender- or receiver-report + packet which comes at the end after the reception report blocks, if + any. If additional sender information is required, then for sender + reports it would be included first in the extension section, but for + receiver reports it would not be present. If information about + receivers is to be included, that data SHOULD be structured as an + array of blocks parallel to the existing array of reception report + blocks; that is, the number of blocks would be indicated by the RC + field. + +6.4.4 Analyzing Sender and Receiver Reports + + It is expected that reception quality feedback will be useful not + only for the sender but also for other receivers and third-party + monitors. The sender may modify its transmissions based on the + feedback; receivers can determine whether problems are local, + regional or global; network managers may use profile-independent + monitors that receive only the RTCP packets and not the corresponding + RTP data packets to evaluate the performance of their networks for + multicast distribution. + + Cumulative counts are used in both the sender information and + receiver report blocks so that differences may be calculated between + any two reports to make measurements over both short and long time + periods, and to provide resilience against the loss of a report. The + difference between the last two reports received can be used to + estimate the recent quality of the distribution. The NTP timestamp + is included so that rates may be calculated from these differences + over the interval between two reports. Since that timestamp is + independent of the clock rate for the data encoding, it is possible + to implement encoding- and profile-independent quality monitors. + + An example calculation is the packet loss rate over the interval + between two reception reports. The difference in the cumulative + number of packets lost gives the number lost during that interval. + The difference in the extended last sequence numbers received gives + the number of packets expected during the interval. The ratio of + these two is the packet loss fraction over the interval. This ratio + should equal the fraction lost field if the two reports are + consecutive, but otherwise it may not. The loss rate per second can + be obtained by dividing the loss fraction by the difference in NTP + timestamps, expressed in seconds. The number of packets received is + the number of packets expected minus the number lost. The number of + + + + +Schulzrinne, et al. Standards Track [Page 43] + +RFC 3550 RTP July 2003 + + + packets expected may also be used to judge the statistical validity + of any loss estimates. For example, 1 out of 5 packets lost has a + lower significance than 200 out of 1000. + + From the sender information, a third-party monitor can calculate the + average payload data rate and the average packet rate over an + interval without receiving the data. Taking the ratio of the two + gives the average payload size. If it can be assumed that packet + loss is independent of packet size, then the number of packets + received by a particular receiver times the average payload size (or + the corresponding packet size) gives the apparent throughput + available to that receiver. + + In addition to the cumulative counts which allow long-term packet + loss measurements using differences between reports, the fraction + lost field provides a short-term measurement from a single report. + This becomes more important as the size of a session scales up enough + that reception state information might not be kept for all receivers + or the interval between reports becomes long enough that only one + report might have been received from a particular receiver. + + The interarrival jitter field provides a second short-term measure of + network congestion. Packet loss tracks persistent congestion while + the jitter measure tracks transient congestion. The jitter measure + may indicate congestion before it leads to packet loss. The + interarrival jitter field is only a snapshot of the jitter at the + time of a report and is not intended to be taken quantitatively. + Rather, it is intended for comparison across a number of reports from + one receiver over time or from multiple receivers, e.g., within a + single network, at the same time. To allow comparison across + receivers, it is important the the jitter be calculated according to + the same formula by all receivers. + + Because the jitter calculation is based on the RTP timestamp which + represents the instant when the first data in the packet was sampled, + any variation in the delay between that sampling instant and the time + the packet is transmitted will affect the resulting jitter that is + calculated. Such a variation in delay would occur for audio packets + of varying duration. It will also occur for video encodings because + the timestamp is the same for all the packets of one frame but those + packets are not all transmitted at the same time. The variation in + delay until transmission does reduce the accuracy of the jitter + calculation as a measure of the behavior of the network by itself, + but it is appropriate to include considering that the receiver buffer + must accommodate it. When the jitter calculation is used as a + comparative measure, the (constant) component due to variation in + delay until transmission subtracts out so that a change in the + + + + +Schulzrinne, et al. Standards Track [Page 44] + +RFC 3550 RTP July 2003 + + + network jitter component can then be observed unless it is relatively + small. If the change is small, then it is likely to be + inconsequential. + +6.5 SDES: Source Description RTCP Packet + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +header |V=2|P| SC | PT=SDES=202 | length | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +chunk | SSRC/CSRC_1 | + 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SDES items | + | ... | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +chunk | SSRC/CSRC_2 | + 2 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SDES items | + | ... | + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ + + The SDES packet is a three-level structure composed of a header and + zero or more chunks, each of which is composed of items describing + the source identified in that chunk. The items are described + individually in subsequent sections. + + version (V), padding (P), length: + As described for the SR packet (see Section 6.4.1). + + packet type (PT): 8 bits + Contains the constant 202 to identify this as an RTCP SDES packet. + + source count (SC): 5 bits + The number of SSRC/CSRC chunks contained in this SDES packet. A + value of zero is valid but useless. + + Each chunk consists of an SSRC/CSRC identifier followed by a list of + zero or more items, which carry information about the SSRC/CSRC. + Each chunk starts on a 32-bit boundary. Each item consists of an 8- + bit type field, an 8-bit octet count describing the length of the + text (thus, not including this two-octet header), and the text + itself. Note that the text can be no longer than 255 octets, but + this is consistent with the need to limit RTCP bandwidth consumption. + + + + + + + +Schulzrinne, et al. Standards Track [Page 45] + +RFC 3550 RTP July 2003 + + + The text is encoded according to the UTF-8 encoding specified in RFC + 2279 [5]. US-ASCII is a subset of this encoding and requires no + additional encoding. The presence of multi-octet encodings is + indicated by setting the most significant bit of a character to a + value of one. + + Items are contiguous, i.e., items are not individually padded to a + 32-bit boundary. Text is not null terminated because some multi- + octet encodings include null octets. The list of items in each chunk + MUST be terminated by one or more null octets, the first of which is + interpreted as an item type of zero to denote the end of the list. + No length octet follows the null item type octet, but additional null + octets MUST be included if needed to pad until the next 32-bit + boundary. Note that this padding is separate from that indicated by + the P bit in the RTCP header. A chunk with zero items (four null + octets) is valid but useless. + + End systems send one SDES packet containing their own source + identifier (the same as the SSRC in the fixed RTP header). A mixer + sends one SDES packet containing a chunk for each contributing source + from which it is receiving SDES information, or multiple complete + SDES packets in the format above if there are more than 31 such + sources (see Section 7). + + The SDES items currently defined are described in the next sections. + Only the CNAME item is mandatory. Some items shown here may be + useful only for particular profiles, but the item types are all + assigned from one common space to promote shared use and to simplify + profile-independent applications. Additional items may be defined in + a profile by registering the type numbers with IANA as described in + Section 15. + +6.5.1 CNAME: Canonical End-Point Identifier SDES Item + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | CNAME=1 | length | user and domain name ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The CNAME identifier has the following properties: + + o Because the randomly allocated SSRC identifier may change if a + conflict is discovered or if a program is restarted, the CNAME + item MUST be included to provide the binding from the SSRC + identifier to an identifier for the source (sender or receiver) + that remains constant. + + + + +Schulzrinne, et al. Standards Track [Page 46] + +RFC 3550 RTP July 2003 + + + o Like the SSRC identifier, the CNAME identifier SHOULD also be + unique among all participants within one RTP session. + + o To provide a binding across multiple media tools used by one + participant in a set of related RTP sessions, the CNAME SHOULD be + fixed for that participant. + + o To facilitate third-party monitoring, the CNAME SHOULD be suitable + for either a program or a person to locate the source. + + Therefore, the CNAME SHOULD be derived algorithmically and not + entered manually, when possible. To meet these requirements, the + following format SHOULD be used unless a profile specifies an + alternate syntax or semantics. The CNAME item SHOULD have the format + "user@host", or "host" if a user name is not available as on single- + user systems. For both formats, "host" is either the fully qualified + domain name of the host from which the real-time data originates, + formatted according to the rules specified in RFC 1034 [6], RFC 1035 + [7] and Section 2.1 of RFC 1123 [8]; or the standard ASCII + representation of the host's numeric address on the interface used + for the RTP communication. For example, the standard ASCII + representation of an IP Version 4 address is "dotted decimal", also + known as dotted quad, and for IP Version 6, addresses are textually + represented as groups of hexadecimal digits separated by colons (with + variations as detailed in RFC 3513 [23]). Other address types are + expected to have ASCII representations that are mutually unique. The + fully qualified domain name is more convenient for a human observer + and may avoid the need to send a NAME item in addition, but it may be + difficult or impossible to obtain reliably in some operating + environments. Applications that may be run in such environments + SHOULD use the ASCII representation of the address instead. + + Examples are "doe@sleepy.example.com", "doe@192.0.2.89" or + "doe@2201:056D::112E:144A:1E24" for a multi-user system. On a system + with no user name, examples would be "sleepy.example.com", + "192.0.2.89" or "2201:056D::112E:144A:1E24". + + The user name SHOULD be in a form that a program such as "finger" or + "talk" could use, i.e., it typically is the login name rather than + the personal name. The host name is not necessarily identical to the + one in the participant's electronic mail address. + + This syntax will not provide unique identifiers for each source if an + application permits a user to generate multiple sources from one + host. Such an application would have to rely on the SSRC to further + identify the source, or the profile for that application would have + to specify additional syntax for the CNAME identifier. + + + + +Schulzrinne, et al. Standards Track [Page 47] + +RFC 3550 RTP July 2003 + + + If each application creates its CNAME independently, the resulting + CNAMEs may not be identical as would be required to provide a binding + across multiple media tools belonging to one participant in a set of + related RTP sessions. If cross-media binding is required, it may be + necessary for the CNAME of each tool to be externally configured with + the same value by a coordination tool. + + Application writers should be aware that private network address + assignments such as the Net-10 assignment proposed in RFC 1918 [24] + may create network addresses that are not globally unique. This + would lead to non-unique CNAMEs if hosts with private addresses and + no direct IP connectivity to the public Internet have their RTP + packets forwarded to the public Internet through an RTP-level + translator. (See also RFC 1627 [25].) To handle this case, + applications MAY provide a means to configure a unique CNAME, but the + burden is on the translator to translate CNAMEs from private + addresses to public addresses if necessary to keep private addresses + from being exposed. + +6.5.2 NAME: User Name SDES Item + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NAME=2 | length | common name of source ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + This is the real name used to describe the source, e.g., "John Doe, + Bit Recycler". It may be in any form desired by the user. For + applications such as conferencing, this form of name may be the most + desirable for display in participant lists, and therefore might be + sent most frequently of those items other than CNAME. Profiles MAY + establish such priorities. The NAME value is expected to remain + constant at least for the duration of a session. It SHOULD NOT be + relied upon to be unique among all participants in the session. + +6.5.3 EMAIL: Electronic Mail Address SDES Item + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | EMAIL=3 | length | email address of source ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The email address is formatted according to RFC 2822 [9], for + example, "John.Doe@example.com". The EMAIL value is expected to + remain constant for the duration of a session. + + + + +Schulzrinne, et al. Standards Track [Page 48] + +RFC 3550 RTP July 2003 + + +6.5.4 PHONE: Phone Number SDES Item + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PHONE=4 | length | phone number of source ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The phone number SHOULD be formatted with the plus sign replacing the + international access code. For example, "+1 908 555 1212" for a + number in the United States. + +6.5.5 LOC: Geographic User Location SDES Item + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | LOC=5 | length | geographic location of site ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Depending on the application, different degrees of detail are + appropriate for this item. For conference applications, a string + like "Murray Hill, New Jersey" may be sufficient, while, for an + active badge system, strings like "Room 2A244, AT&T BL MH" might be + appropriate. The degree of detail is left to the implementation + and/or user, but format and content MAY be prescribed by a profile. + The LOC value is expected to remain constant for the duration of a + session, except for mobile hosts. + +6.5.6 TOOL: Application or Tool Name SDES Item + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | TOOL=6 | length |name/version of source appl. ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + A string giving the name and possibly version of the application + generating the stream, e.g., "videotool 1.2". This information may + be useful for debugging purposes and is similar to the Mailer or + Mail-System-Version SMTP headers. The TOOL value is expected to + remain constant for the duration of the session. + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 49] + +RFC 3550 RTP July 2003 + + +6.5.7 NOTE: Notice/Status SDES Item + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | NOTE=7 | length | note about the source ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The following semantics are suggested for this item, but these or + other semantics MAY be explicitly defined by a profile. The NOTE + item is intended for transient messages describing the current state + of the source, e.g., "on the phone, can't talk". Or, during a + seminar, this item might be used to convey the title of the talk. It + should be used only to carry exceptional information and SHOULD NOT + be included routinely by all participants because this would slow + down the rate at which reception reports and CNAME are sent, thus + impairing the performance of the protocol. In particular, it SHOULD + NOT be included as an item in a user's configuration file nor + automatically generated as in a quote-of-the-day. + + Since the NOTE item may be important to display while it is active, + the rate at which other non-CNAME items such as NAME are transmitted + might be reduced so that the NOTE item can take that part of the RTCP + bandwidth. When the transient message becomes inactive, the NOTE + item SHOULD continue to be transmitted a few times at the same + repetition rate but with a string of length zero to signal the + receivers. However, receivers SHOULD also consider the NOTE item + inactive if it is not received for a small multiple of the repetition + rate, or perhaps 20-30 RTCP intervals. + +6.5.8 PRIV: Private Extensions SDES Item + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | PRIV=8 | length | prefix length |prefix string... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + ... | value string ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + This item is used to define experimental or application-specific SDES + extensions. The item contains a prefix consisting of a length-string + pair, followed by the value string filling the remainder of the item + and carrying the desired information. The prefix length field is 8 + bits long. The prefix string is a name chosen by the person defining + the PRIV item to be unique with respect to other PRIV items this + application might receive. The application creator might choose to + use the application name plus an additional subtype identification if + + + +Schulzrinne, et al. Standards Track [Page 50] + +RFC 3550 RTP July 2003 + + + needed. Alternatively, it is RECOMMENDED that others choose a name + based on the entity they represent, then coordinate the use of the + name within that entity. + + Note that the prefix consumes some space within the item's total + length of 255 octets, so the prefix should be kept as short as + possible. This facility and the constrained RTCP bandwidth SHOULD + NOT be overloaded; it is not intended to satisfy all the control + communication requirements of all applications. + + SDES PRIV prefixes will not be registered by IANA. If some form of + the PRIV item proves to be of general utility, it SHOULD instead be + assigned a regular SDES item type registered with IANA so that no + prefix is required. This simplifies use and increases transmission + efficiency. + +6.6 BYE: Goodbye RTCP Packet + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| SC | PT=BYE=203 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + : ... : + +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +(opt) | length | reason for leaving ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The BYE packet indicates that one or more sources are no longer + active. + + version (V), padding (P), length: + As described for the SR packet (see Section 6.4.1). + + packet type (PT): 8 bits + Contains the constant 203 to identify this as an RTCP BYE packet. + + source count (SC): 5 bits + The number of SSRC/CSRC identifiers included in this BYE packet. + A count value of zero is valid, but useless. + + The rules for when a BYE packet should be sent are specified in + Sections 6.3.7 and 8.2. + + + + + + +Schulzrinne, et al. Standards Track [Page 51] + +RFC 3550 RTP July 2003 + + + If a BYE packet is received by a mixer, the mixer SHOULD forward the + BYE packet with the SSRC/CSRC identifier(s) unchanged. If a mixer + shuts down, it SHOULD send a BYE packet listing all contributing + sources it handles, as well as its own SSRC identifier. Optionally, + the BYE packet MAY include an 8-bit octet count followed by that many + octets of text indicating the reason for leaving, e.g., "camera + malfunction" or "RTP loop detected". The string has the same + encoding as that described for SDES. If the string fills the packet + to the next 32-bit boundary, the string is not null terminated. If + not, the BYE packet MUST be padded with null octets to the next 32- + bit boundary. This padding is separate from that indicated by the P + bit in the RTCP header. + +6.7 APP: Application-Defined RTCP Packet + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |V=2|P| subtype | PT=APP=204 | length | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | SSRC/CSRC | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | name (ASCII) | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | application-dependent data ... + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + The APP packet is intended for experimental use as new applications + and new features are developed, without requiring packet type value + registration. APP packets with unrecognized names SHOULD be ignored. + After testing and if wider use is justified, it is RECOMMENDED that + each APP packet be redefined without the subtype and name fields and + registered with IANA using an RTCP packet type. + + version (V), padding (P), length: + As described for the SR packet (see Section 6.4.1). + + subtype: 5 bits + May be used as a subtype to allow a set of APP packets to be + defined under one unique name, or for any application-dependent + data. + + packet type (PT): 8 bits + Contains the constant 204 to identify this as an RTCP APP packet. + + + + + + + +Schulzrinne, et al. Standards Track [Page 52] + +RFC 3550 RTP July 2003 + + + name: 4 octets + A name chosen by the person defining the set of APP packets to be + unique with respect to other APP packets this application might + receive. The application creator might choose to use the + application name, and then coordinate the allocation of subtype + values to others who want to define new packet types for the + application. Alternatively, it is RECOMMENDED that others choose + a name based on the entity they represent, then coordinate the use + of the name within that entity. The name is interpreted as a + sequence of four ASCII characters, with uppercase and lowercase + characters treated as distinct. + + application-dependent data: variable length + Application-dependent data may or may not appear in an APP packet. + It is interpreted by the application and not RTP itself. It MUST + be a multiple of 32 bits long. + +7. RTP Translators and Mixers + + In addition to end systems, RTP supports the notion of "translators" + and "mixers", which could be considered as "intermediate systems" at + the RTP level. Although this support adds some complexity to the + protocol, the need for these functions has been clearly established + by experiments with multicast audio and video applications in the + Internet. Example uses of translators and mixers given in Section + 2.3 stem from the presence of firewalls and low bandwidth + connections, both of which are likely to remain. + +7.1 General Description + + An RTP translator/mixer connects two or more transport-level + "clouds". Typically, each cloud is defined by a common network and + transport protocol (e.g., IP/UDP) plus a multicast address and + transport level destination port or a pair of unicast addresses and + ports. (Network-level protocol translators, such as IP version 4 to + IP version 6, may be present within a cloud invisibly to RTP.) One + system may serve as a translator or mixer for a number of RTP + sessions, but each is considered a logically separate entity. + + In order to avoid creating a loop when a translator or mixer is + installed, the following rules MUST be observed: + + o Each of the clouds connected by translators and mixers + participating in one RTP session either MUST be distinct from all + the others in at least one of these parameters (protocol, address, + port), or MUST be isolated at the network level from the others. + + + + + +Schulzrinne, et al. Standards Track [Page 53] + +RFC 3550 RTP July 2003 + + + o A derivative of the first rule is that there MUST NOT be multiple + translators or mixers connected in parallel unless by some + arrangement they partition the set of sources to be forwarded. + + Similarly, all RTP end systems that can communicate through one or + more RTP translators or mixers share the same SSRC space, that is, + the SSRC identifiers MUST be unique among all these end systems. + Section 8.2 describes the collision resolution algorithm by which + SSRC identifiers are kept unique and loops are detected. + + There may be many varieties of translators and mixers designed for + different purposes and applications. Some examples are to add or + remove encryption, change the encoding of the data or the underlying + protocols, or replicate between a multicast address and one or more + unicast addresses. The distinction between translators and mixers is + that a translator passes through the data streams from different + sources separately, whereas a mixer combines them to form one new + stream: + + Translator: Forwards RTP packets with their SSRC identifier + intact; this makes it possible for receivers to identify + individual sources even though packets from all the sources pass + through the same translator and carry the translator's network + source address. Some kinds of translators will pass through the + data untouched, but others MAY change the encoding of the data and + thus the RTP data payload type and timestamp. If multiple data + packets are re-encoded into one, or vice versa, a translator MUST + assign new sequence numbers to the outgoing packets. Losses in + the incoming packet stream may induce corresponding gaps in the + outgoing sequence numbers. Receivers cannot detect the presence + of a translator unless they know by some other means what payload + type or transport address was used by the original source. + + Mixer: Receives streams of RTP data packets from one or more + sources, possibly changes the data format, combines the streams in + some manner and then forwards the combined stream. Since the + timing among multiple input sources will not generally be + synchronized, the mixer will make timing adjustments among the + streams and generate its own timing for the combined stream, so it + is the synchronization source. Thus, all data packets forwarded + by a mixer MUST be marked with the mixer's own SSRC identifier. + In order to preserve the identity of the original sources + contributing to the mixed packet, the mixer SHOULD insert their + SSRC identifiers into the CSRC identifier list following the fixed + RTP header of the packet. A mixer that is also itself a + contributing source for some packet SHOULD explicitly include its + own SSRC identifier in the CSRC list for that packet. + + + + +Schulzrinne, et al. Standards Track [Page 54] + +RFC 3550 RTP July 2003 + + + For some applications, it MAY be acceptable for a mixer not to + identify sources in the CSRC list. However, this introduces the + danger that loops involving those sources could not be detected. + + The advantage of a mixer over a translator for applications like + audio is that the output bandwidth is limited to that of one source + even when multiple sources are active on the input side. This may be + important for low-bandwidth links. The disadvantage is that + receivers on the output side don't have any control over which + sources are passed through or muted, unless some mechanism is + implemented for remote control of the mixer. The regeneration of + synchronization information by mixers also means that receivers can't + do inter-media synchronization of the original streams. A multi- + media mixer could do it. + + [E1] [E6] + | | + E1:17 | E6:15 | + | | E6:15 + V M1:48 (1,17) M1:48 (1,17) V M1:48 (1,17) + (M1)-------------><T1>-----------------><T2>-------------->[E7] + ^ ^ E4:47 ^ E4:47 + E2:1 | E4:47 | | M3:89 (64,45) + | | | + [E2] [E4] M3:89 (64,45) | + | legend: + [E3] --------->(M2)----------->(M3)------------| [End system] + E3:64 M2:12 (64) ^ (Mixer) + | E5:45 <Translator> + | + [E5] source: SSRC (CSRCs) + -------------------> + + Figure 3: Sample RTP network with end systems, mixers and translators + + A collection of mixers and translators is shown in Fig. 3 to + illustrate their effect on SSRC and CSRC identifiers. In the figure, + end systems are shown as rectangles (named E), translators as + triangles (named T) and mixers as ovals (named M). The notation "M1: + 48(1,17)" designates a packet originating a mixer M1, identified by + M1's (random) SSRC value of 48 and two CSRC identifiers, 1 and 17, + copied from the SSRC identifiers of packets from E1 and E2. + +7.2 RTCP Processing in Translators + + In addition to forwarding data packets, perhaps modified, translators + and mixers MUST also process RTCP packets. In many cases, they will + take apart the compound RTCP packets received from end systems to + + + +Schulzrinne, et al. Standards Track [Page 55] + +RFC 3550 RTP July 2003 + + + aggregate SDES information and to modify the SR or RR packets. + Retransmission of this information may be triggered by the packet + arrival or by the RTCP interval timer of the translator or mixer + itself. + + A translator that does not modify the data packets, for example one + that just replicates between a multicast address and a unicast + address, MAY simply forward RTCP packets unmodified as well. A + translator that transforms the payload in some way MUST make + corresponding transformations in the SR and RR information so that it + still reflects the characteristics of the data and the reception + quality. These translators MUST NOT simply forward RTCP packets. In + general, a translator SHOULD NOT aggregate SR and RR packets from + different sources into one packet since that would reduce the + accuracy of the propagation delay measurements based on the LSR and + DLSR fields. + + SR sender information: A translator does not generate its own + sender information, but forwards the SR packets received from one + cloud to the others. The SSRC is left intact but the sender + information MUST be modified if required by the translation. If a + translator changes the data encoding, it MUST change the "sender's + byte count" field. If it also combines several data packets into + one output packet, it MUST change the "sender's packet count" + field. If it changes the timestamp frequency, it MUST change the + "RTP timestamp" field in the SR packet. + + SR/RR reception report blocks: A translator forwards reception + reports received from one cloud to the others. Note that these + flow in the direction opposite to the data. The SSRC is left + intact. If a translator combines several data packets into one + output packet, and therefore changes the sequence numbers, it MUST + make the inverse manipulation for the packet loss fields and the + "extended last sequence number" field. This may be complex. In + the extreme case, there may be no meaningful way to translate the + reception reports, so the translator MAY pass on no reception + report at all or a synthetic report based on its own reception. + The general rule is to do what makes sense for a particular + translation. + + A translator does not require an SSRC identifier of its own, but + MAY choose to allocate one for the purpose of sending reports + about what it has received. These would be sent to all the + connected clouds, each corresponding to the translation of the + data stream as sent to that cloud, since reception reports are + normally multicast to all participants. + + + + + +Schulzrinne, et al. Standards Track [Page 56] + +RFC 3550 RTP July 2003 + + + SDES: Translators typically forward without change the SDES + information they receive from one cloud to the others, but MAY, + for example, decide to filter non-CNAME SDES information if + bandwidth is limited. The CNAMEs MUST be forwarded to allow SSRC + identifier collision detection to work. A translator that + generates its own RR packets MUST send SDES CNAME information + about itself to the same clouds that it sends those RR packets. + + BYE: Translators forward BYE packets unchanged. A translator + that is about to cease forwarding packets SHOULD send a BYE packet + to each connected cloud containing all the SSRC identifiers that + were previously being forwarded to that cloud, including the + translator's own SSRC identifier if it sent reports of its own. + + APP: Translators forward APP packets unchanged. + +7.3 RTCP Processing in Mixers + + Since a mixer generates a new data stream of its own, it does not + pass through SR or RR packets at all and instead generates new + information for both sides. + + SR sender information: A mixer does not pass through sender + information from the sources it mixes because the characteristics + of the source streams are lost in the mix. As a synchronization + source, the mixer SHOULD generate its own SR packets with sender + information about the mixed data stream and send them in the same + direction as the mixed stream. + + SR/RR reception report blocks: A mixer generates its own + reception reports for sources in each cloud and sends them out + only to the same cloud. It MUST NOT send these reception reports + to the other clouds and MUST NOT forward reception reports from + one cloud to the others because the sources would not be SSRCs + there (only CSRCs). + + SDES: Mixers typically forward without change the SDES + information they receive from one cloud to the others, but MAY, + for example, decide to filter non-CNAME SDES information if + bandwidth is limited. The CNAMEs MUST be forwarded to allow SSRC + identifier collision detection to work. (An identifier in a CSRC + list generated by a mixer might collide with an SSRC identifier + generated by an end system.) A mixer MUST send SDES CNAME + information about itself to the same clouds that it sends SR or RR + packets. + + + + + + +Schulzrinne, et al. Standards Track [Page 57] + +RFC 3550 RTP July 2003 + + + Since mixers do not forward SR or RR packets, they will typically + be extracting SDES packets from a compound RTCP packet. To + minimize overhead, chunks from the SDES packets MAY be aggregated + into a single SDES packet which is then stacked on an SR or RR + packet originating from the mixer. A mixer which aggregates SDES + packets will use more RTCP bandwidth than an individual source + because the compound packets will be longer, but that is + appropriate since the mixer represents multiple sources. + Similarly, a mixer which passes through SDES packets as they are + received will be transmitting RTCP packets at higher than the + single source rate, but again that is correct since the packets + come from multiple sources. The RTCP packet rate may be different + on each side of the mixer. + + A mixer that does not insert CSRC identifiers MAY also refrain + from forwarding SDES CNAMEs. In this case, the SSRC identifier + spaces in the two clouds are independent. As mentioned earlier, + this mode of operation creates a danger that loops can't be + detected. + + BYE: Mixers MUST forward BYE packets. A mixer that is about to + cease forwarding packets SHOULD send a BYE packet to each + connected cloud containing all the SSRC identifiers that were + previously being forwarded to that cloud, including the mixer's + own SSRC identifier if it sent reports of its own. + + APP: The treatment of APP packets by mixers is application-specific. + +7.4 Cascaded Mixers + + An RTP session may involve a collection of mixers and translators as + shown in Fig. 3. If two mixers are cascaded, such as M2 and M3 in + the figure, packets received by a mixer may already have been mixed + and may include a CSRC list with multiple identifiers. The second + mixer SHOULD build the CSRC list for the outgoing packet using the + CSRC identifiers from already-mixed input packets and the SSRC + identifiers from unmixed input packets. This is shown in the output + arc from mixer M3 labeled M3:89(64,45) in the figure. As in the case + of mixers that are not cascaded, if the resulting CSRC list has more + than 15 identifiers, the remainder cannot be included. + + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 58] + +RFC 3550 RTP July 2003 + + +8. SSRC Identifier Allocation and Use + + The SSRC identifier carried in the RTP header and in various fields + of RTCP packets is a random 32-bit number that is required to be + globally unique within an RTP session. It is crucial that the number + be chosen with care in order that participants on the same network or + starting at the same time are not likely to choose the same number. + + It is not sufficient to use the local network address (such as an + IPv4 address) for the identifier because the address may not be + unique. Since RTP translators and mixers enable interoperation among + multiple networks with different address spaces, the allocation + patterns for addresses within two spaces might result in a much + higher rate of collision than would occur with random allocation. + + Multiple sources running on one host would also conflict. + + It is also not sufficient to obtain an SSRC identifier simply by + calling random() without carefully initializing the state. An + example of how to generate a random identifier is presented in + Appendix A.6. + +8.1 Probability of Collision + + Since the identifiers are chosen randomly, it is possible that two or + more sources will choose the same number. Collision occurs with the + highest probability when all sources are started simultaneously, for + example when triggered automatically by some session management + event. If N is the number of sources and L the length of the + identifier (here, 32 bits), the probability that two sources + independently pick the same value can be approximated for large N + [26] as 1 - exp(-N**2 / 2**(L+1)). For N=1000, the probability is + roughly 10**-4. + + The typical collision probability is much lower than the worst-case + above. When one new source joins an RTP session in which all the + other sources already have unique identifiers, the probability of + collision is just the fraction of numbers used out of the space. + Again, if N is the number of sources and L the length of the + identifier, the probability of collision is N / 2**L. For N=1000, + the probability is roughly 2*10**-7. + + The probability of collision is further reduced by the opportunity + for a new source to receive packets from other participants before + sending its first packet (either data or control). If the new source + keeps track of the other participants (by SSRC identifier), then + + + + + +Schulzrinne, et al. Standards Track [Page 59] + +RFC 3550 RTP July 2003 + + + before transmitting its first packet the new source can verify that + its identifier does not conflict with any that have been received, or + else choose again. + +8.2 Collision Resolution and Loop Detection + + Although the probability of SSRC identifier collision is low, all RTP + implementations MUST be prepared to detect collisions and take the + appropriate actions to resolve them. If a source discovers at any + time that another source is using the same SSRC identifier as its + own, it MUST send an RTCP BYE packet for the old identifier and + choose another random one. (As explained below, this step is taken + only once in case of a loop.) If a receiver discovers that two other + sources are colliding, it MAY keep the packets from one and discard + the packets from the other when this can be detected by different + source transport addresses or CNAMEs. The two sources are expected + to resolve the collision so that the situation doesn't last. + + Because the random SSRC identifiers are kept globally unique for each + RTP session, they can also be used to detect loops that may be + introduced by mixers or translators. A loop causes duplication of + data and control information, either unmodified or possibly mixed, as + in the following examples: + + o A translator may incorrectly forward a packet to the same + multicast group from which it has received the packet, either + directly or through a chain of translators. In that case, the + same packet appears several times, originating from different + network sources. + + o Two translators incorrectly set up in parallel, i.e., with the + same multicast groups on both sides, would both forward packets + from one multicast group to the other. Unidirectional translators + would produce two copies; bidirectional translators would form a + loop. + + o A mixer can close a loop by sending to the same transport + destination upon which it receives packets, either directly or + through another mixer or translator. In this case a source might + show up both as an SSRC on a data packet and a CSRC in a mixed + data packet. + + A source may discover that its own packets are being looped, or that + packets from another source are being looped (a third-party loop). + Both loops and collisions in the random selection of a source + identifier result in packets arriving with the same SSRC identifier + but a different source transport address, which may be that of the + end system originating the packet or an intermediate system. + + + +Schulzrinne, et al. Standards Track [Page 60] + +RFC 3550 RTP July 2003 + + + Therefore, if a source changes its source transport address, it MAY + also choose a new SSRC identifier to avoid being interpreted as a + looped source. (This is not MUST because in some applications of RTP + sources may be expected to change addresses during a session.) Note + that if a translator restarts and consequently changes the source + transport address (e.g., changes the UDP source port number) on which + it forwards packets, then all those packets will appear to receivers + to be looped because the SSRC identifiers are applied by the original + source and will not change. This problem can be avoided by keeping + the source transport address fixed across restarts, but in any case + will be resolved after a timeout at the receivers. + + Loops or collisions occurring on the far side of a translator or + mixer cannot be detected using the source transport address if all + copies of the packets go through the translator or mixer, however, + collisions may still be detected when chunks from two RTCP SDES + packets contain the same SSRC identifier but different CNAMEs. + + To detect and resolve these conflicts, an RTP implementation MUST + include an algorithm similar to the one described below, though the + implementation MAY choose a different policy for which packets from + colliding third-party sources are kept. The algorithm described + below ignores packets from a new source or loop that collide with an + established source. It resolves collisions with the participant's + own SSRC identifier by sending an RTCP BYE for the old identifier and + choosing a new one. However, when the collision was induced by a + loop of the participant's own packets, the algorithm will choose a + new identifier only once and thereafter ignore packets from the + looping source transport address. This is required to avoid a flood + of BYE packets. + + This algorithm requires keeping a table indexed by the source + identifier and containing the source transport addresses from the + first RTP packet and first RTCP packet received with that identifier, + along with other state for that source. Two source transport + addresses are required since, for example, the UDP source port + numbers may be different on RTP and RTCP packets. However, it may be + assumed that the network address is the same in both source transport + addresses. + + Each SSRC or CSRC identifier received in an RTP or RTCP packet is + looked up in the source identifier table in order to process that + data or control information. The source transport address from the + packet is compared to the corresponding source transport address in + the table to detect a loop or collision if they don't match. For + control packets, each element with its own SSRC identifier, for + example an SDES chunk, requires a separate lookup. (The SSRC + identifier in a reception report block is an exception because it + + + +Schulzrinne, et al. Standards Track [Page 61] + +RFC 3550 RTP July 2003 + + + identifies a source heard by the reporter, and that SSRC identifier + is unrelated to the source transport address of the RTCP packet sent + by the reporter.) If the SSRC or CSRC is not found, a new entry is + created. These table entries are removed when an RTCP BYE packet is + received with the corresponding SSRC identifier and validated by a + matching source transport address, or after no packets have arrived + for a relatively long time (see Section 6.2.1). + + Note that if two sources on the same host are transmitting with the + same source identifier at the time a receiver begins operation, it + would be possible that the first RTP packet received came from one of + the sources while the first RTCP packet received came from the other. + This would cause the wrong RTCP information to be associated with the + RTP data, but this situation should be sufficiently rare and harmless + that it may be disregarded. + + In order to track loops of the participant's own data packets, the + implementation MUST also keep a separate list of source transport + addresses (not identifiers) that have been found to be conflicting. + As in the source identifier table, two source transport addresses + MUST be kept to separately track conflicting RTP and RTCP packets. + Note that the conflicting address list should be short, usually + empty. Each element in this list stores the source addresses plus + the time when the most recent conflicting packet was received. An + element MAY be removed from the list when no conflicting packet has + arrived from that source for a time on the order of 10 RTCP report + intervals (see Section 6.2). + + For the algorithm as shown, it is assumed that the participant's own + source identifier and state are included in the source identifier + table. The algorithm could be restructured to first make a separate + comparison against the participant's own source identifier. + + if (SSRC or CSRC identifier is not found in the source + identifier table) { + create a new entry storing the data or control source + transport address, the SSRC or CSRC and other state; + } + + /* Identifier is found in the table */ + + else if (table entry was created on receipt of a control packet + and this is the first data packet or vice versa) { + store the source transport address from this packet; + } + else if (source transport address from the packet does not match + the one saved in the table entry for this identifier) { + + + + +Schulzrinne, et al. Standards Track [Page 62] + +RFC 3550 RTP July 2003 + + + /* An identifier collision or a loop is indicated */ + + if (source identifier is not the participant's own) { + /* OPTIONAL error counter step */ + if (source identifier is from an RTCP SDES chunk + containing a CNAME item that differs from the CNAME + in the table entry) { + count a third-party collision; + } else { + count a third-party loop; + } + abort processing of data packet or control element; + /* MAY choose a different policy to keep new source */ + } + + /* A collision or loop of the participant's own packets */ + + else if (source transport address is found in the list of + conflicting data or control source transport + addresses) { + /* OPTIONAL error counter step */ + if (source identifier is not from an RTCP SDES chunk + containing a CNAME item or CNAME is the + participant's own) { + count occurrence of own traffic looped; + } + mark current time in conflicting address list entry; + abort processing of data packet or control element; + } + + /* New collision, change SSRC identifier */ + + else { + log occurrence of a collision; + create a new entry in the conflicting data or control + source transport address list and mark current time; + send an RTCP BYE packet with the old SSRC identifier; + choose a new SSRC identifier; + create a new entry in the source identifier table with + the old SSRC plus the source transport address from + the data or control packet being processed; + } + } + + In this algorithm, packets from a newly conflicting source address + will be ignored and packets from the original source address will be + kept. If no packets arrive from the original source for an extended + period, the table entry will be timed out and the new source will be + + + +Schulzrinne, et al. Standards Track [Page 63] + +RFC 3550 RTP July 2003 + + + able to take over. This might occur if the original source detects + the collision and moves to a new source identifier, but in the usual + case an RTCP BYE packet will be received from the original source to + delete the state without having to wait for a timeout. + + If the original source address was received through a mixer (i.e., + learned as a CSRC) and later the same source is received directly, + the receiver may be well advised to switch to the new source address + unless other sources in the mix would be lost. Furthermore, for + applications such as telephony in which some sources such as mobile + entities may change addresses during the course of an RTP session, + the RTP implementation SHOULD modify the collision detection + algorithm to accept packets from the new source transport address. + To guard against flip-flopping between addresses if a genuine + collision does occur, the algorithm SHOULD include some means to + detect this case and avoid switching. + + When a new SSRC identifier is chosen due to a collision, the + candidate identifier SHOULD first be looked up in the source + identifier table to see if it was already in use by some other + source. If so, another candidate MUST be generated and the process + repeated. + + A loop of data packets to a multicast destination can cause severe + network flooding. All mixers and translators MUST implement a loop + detection algorithm like the one here so that they can break loops. + This should limit the excess traffic to no more than one duplicate + copy of the original traffic, which may allow the session to continue + so that the cause of the loop can be found and fixed. However, in + extreme cases where a mixer or translator does not properly break the + loop and high traffic levels result, it may be necessary for end + systems to cease transmitting data or control packets entirely. This + decision may depend upon the application. An error condition SHOULD + be indicated as appropriate. Transmission MAY be attempted again + periodically after a long, random time (on the order of minutes). + +8.3 Use with Layered Encodings + + For layered encodings transmitted on separate RTP sessions (see + Section 2.4), a single SSRC identifier space SHOULD be used across + the sessions of all layers and the core (base) layer SHOULD be used + for SSRC identifier allocation and collision resolution. When a + source discovers that it has collided, it transmits an RTCP BYE + packet on only the base layer but changes the SSRC identifier to the + new value in all layers. + + + + + + +Schulzrinne, et al. Standards Track [Page 64] + +RFC 3550 RTP July 2003 + + +9. Security + + Lower layer protocols may eventually provide all the security + services that may be desired for applications of RTP, including + authentication, integrity, and confidentiality. These services have + been specified for IP in [27]. Since the initial audio and video + applications using RTP needed a confidentiality service before such + services were available for the IP layer, the confidentiality service + described in the next section was defined for use with RTP and RTCP. + That description is included here to codify existing practice. New + applications of RTP MAY implement this RTP-specific confidentiality + service for backward compatibility, and/or they MAY implement + alternative security services. The overhead on the RTP protocol for + this confidentiality service is low, so the penalty will be minimal + if this service is obsoleted by other services in the future. + + Alternatively, other services, other implementations of services and + other algorithms may be defined for RTP in the future. In + particular, an RTP profile called Secure Real-time Transport Protocol + (SRTP) [28] is being developed to provide confidentiality of the RTP + payload while leaving the RTP header in the clear so that link-level + header compression algorithms can still operate. It is expected that + SRTP will be the correct choice for many applications. SRTP is based + on the Advanced Encryption Standard (AES) and provides stronger + security than the service described here. No claim is made that the + methods presented here are appropriate for a particular security + need. A profile may specify which services and algorithms should be + offered by applications, and may provide guidance as to their + appropriate use. + + Key distribution and certificates are outside the scope of this + document. + +9.1 Confidentiality + + Confidentiality means that only the intended receiver(s) can decode + the received packets; for others, the packet contains no useful + information. Confidentiality of the content is achieved by + encryption. + + When it is desired to encrypt RTP or RTCP according to the method + specified in this section, all the octets that will be encapsulated + for transmission in a single lower-layer packet are encrypted as a + unit. For RTCP, a 32-bit random number redrawn for each unit MUST be + prepended to the unit before encryption. For RTP, no prefix is + prepended; instead, the sequence number and timestamp fields are + initialized with random offsets. This is considered to be a weak + + + + +Schulzrinne, et al. Standards Track [Page 65] + +RFC 3550 RTP July 2003 + + + initialization vector (IV) because of poor randomness properties. In + addition, if the subsequent field, the SSRC, can be manipulated by an + enemy, there is further weakness of the encryption method. + + For RTCP, an implementation MAY segregate the individual RTCP packets + in a compound RTCP packet into two separate compound RTCP packets, + one to be encrypted and one to be sent in the clear. For example, + SDES information might be encrypted while reception reports were sent + in the clear to accommodate third-party monitors that are not privy + to the encryption key. In this example, depicted in Fig. 4, the SDES + information MUST be appended to an RR packet with no reports (and the + random number) to satisfy the requirement that all compound RTCP + packets begin with an SR or RR packet. The SDES CNAME item is + required in either the encrypted or unencrypted packet, but not both. + The same SDES information SHOULD NOT be carried in both packets as + this may compromise the encryption. + + UDP packet UDP packet + ----------------------------- ------------------------------ + [random][RR][SDES #CNAME ...] [SR #senderinfo #site1 #site2] + ----------------------------- ------------------------------ + encrypted not encrypted + + #: SSRC identifier + + Figure 4: Encrypted and non-encrypted RTCP packets + + The presence of encryption and the use of the correct key are + confirmed by the receiver through header or payload validity checks. + Examples of such validity checks for RTP and RTCP headers are given + in Appendices A.1 and A.2. + + To be consistent with existing implementations of the initial + specification of RTP in RFC 1889, the default encryption algorithm is + the Data Encryption Standard (DES) algorithm in cipher block chaining + (CBC) mode, as described in Section 1.1 of RFC 1423 [29], except that + padding to a multiple of 8 octets is indicated as described for the P + bit in Section 5.1. The initialization vector is zero because random + values are supplied in the RTP header or by the random prefix for + compound RTCP packets. For details on the use of CBC initialization + vectors, see [30]. + + Implementations that support the encryption method specified here + SHOULD always support the DES algorithm in CBC mode as the default + cipher for this method to maximize interoperability. This method was + chosen because it has been demonstrated to be easy and practical to + use in experimental audio and video tools in operation on the + Internet. However, DES has since been found to be too easily broken. + + + +Schulzrinne, et al. Standards Track [Page 66] + +RFC 3550 RTP July 2003 + + + It is RECOMMENDED that stronger encryption algorithms such as + Triple-DES be used in place of the default algorithm. Furthermore, + secure CBC mode requires that the first block of each packet be XORed + with a random, independent IV of the same size as the cipher's block + size. For RTCP, this is (partially) achieved by prepending each + packet with a 32-bit random number, independently chosen for each + packet. For RTP, the timestamp and sequence number start from random + values, but consecutive packets will not be independently randomized. + It should be noted that the randomness in both cases (RTP and RTCP) + is limited. High-security applications SHOULD consider other, more + conventional, protection means. Other encryption algorithms MAY be + specified dynamically for a session by non-RTP means. In particular, + the SRTP profile [28] based on AES is being developed to take into + account known plaintext and CBC plaintext manipulation concerns, and + will be the correct choice in the future. + + As an alternative to encryption at the IP level or at the RTP level + as described above, profiles MAY define additional payload types for + encrypted encodings. Those encodings MUST specify how padding and + other aspects of the encryption are to be handled. This method + allows encrypting only the data while leaving the headers in the + clear for applications where that is desired. It may be particularly + useful for hardware devices that will handle both decryption and + decoding. It is also valuable for applications where link-level + compression of RTP and lower-layer headers is desired and + confidentiality of the payload (but not addresses) is sufficient + since encryption of the headers precludes compression. + +9.2 Authentication and Message Integrity + + Authentication and message integrity services are not defined at the + RTP level since these services would not be directly feasible without + a key management infrastructure. It is expected that authentication + and integrity services will be provided by lower layer protocols. + +10. Congestion Control + + All transport protocols used on the Internet need to address + congestion control in some way [31]. RTP is not an exception, but + because the data transported over RTP is often inelastic (generated + at a fixed or controlled rate), the means to control congestion in + RTP may be quite different from those for other transport protocols + such as TCP. In one sense, inelasticity reduces the risk of + congestion because the RTP stream will not expand to consume all + available bandwidth as a TCP stream can. However, inelasticity also + means that the RTP stream cannot arbitrarily reduce its load on the + network to eliminate congestion when it occurs. + + + + +Schulzrinne, et al. Standards Track [Page 67] + +RFC 3550 RTP July 2003 + + + Since RTP may be used for a wide variety of applications in many + different contexts, there is no single congestion control mechanism + that will work for all. Therefore, congestion control SHOULD be + defined in each RTP profile as appropriate. For some profiles, it + may be sufficient to include an applicability statement restricting + the use of that profile to environments where congestion is avoided + by engineering. For other profiles, specific methods such as data + rate adaptation based on RTCP feedback may be required. + +11. RTP over Network and Transport Protocols + + This section describes issues specific to carrying RTP packets within + particular network and transport protocols. The following rules + apply unless superseded by protocol-specific definitions outside this + specification. + + RTP relies on the underlying protocol(s) to provide demultiplexing of + RTP data and RTCP control streams. For UDP and similar protocols, + RTP SHOULD use an even destination port number and the corresponding + RTCP stream SHOULD use the next higher (odd) destination port number. + For applications that take a single port number as a parameter and + derive the RTP and RTCP port pair from that number, if an odd number + is supplied then the application SHOULD replace that number with the + next lower (even) number to use as the base of the port pair. For + applications in which the RTP and RTCP destination port numbers are + specified via explicit, separate parameters (using a signaling + protocol or other means), the application MAY disregard the + restrictions that the port numbers be even/odd and consecutive + although the use of an even/odd port pair is still encouraged. The + RTP and RTCP port numbers MUST NOT be the same since RTP relies on + the port numbers to demultiplex the RTP data and RTCP control + streams. + + In a unicast session, both participants need to identify a port pair + for receiving RTP and RTCP packets. Both participants MAY use the + same port pair. A participant MUST NOT assume that the source port + of the incoming RTP or RTCP packet can be used as the destination + port for outgoing RTP or RTCP packets. When RTP data packets are + being sent in both directions, each participant's RTCP SR packets + MUST be sent to the port that the other participant has specified for + reception of RTCP. The RTCP SR packets combine sender information + for the outgoing data plus reception report information for the + incoming data. If a side is not actively sending data (see Section + 6.4), an RTCP RR packet is sent instead. + + It is RECOMMENDED that layered encoding applications (see Section + 2.4) use a set of contiguous port numbers. The port numbers MUST be + distinct because of a widespread deficiency in existing operating + + + +Schulzrinne, et al. Standards Track [Page 68] + +RFC 3550 RTP July 2003 + + + systems that prevents use of the same port with multiple multicast + addresses, and for unicast, there is only one permissible address. + Thus for layer n, the data port is P + 2n, and the control port is P + + 2n + 1. When IP multicast is used, the addresses MUST also be + distinct because multicast routing and group membership are managed + on an address granularity. However, allocation of contiguous IP + multicast addresses cannot be assumed because some groups may require + different scopes and may therefore be allocated from different + address ranges. + + The previous paragraph conflicts with the SDP specification, RFC 2327 + [15], which says that it is illegal for both multiple addresses and + multiple ports to be specified in the same session description + because the association of addresses with ports could be ambiguous. + It is intended that this restriction will be relaxed in a revision of + RFC 2327 to allow an equal number of addresses and ports to be + specified with a one-to-one mapping implied. + + RTP data packets contain no length field or other delineation, + therefore RTP relies on the underlying protocol(s) to provide a + length indication. The maximum length of RTP packets is limited only + by the underlying protocols. + + If RTP packets are to be carried in an underlying protocol that + provides the abstraction of a continuous octet stream rather than + messages (packets), an encapsulation of the RTP packets MUST be + defined to provide a framing mechanism. Framing is also needed if + the underlying protocol may contain padding so that the extent of the + RTP payload cannot be determined. The framing mechanism is not + defined here. + + A profile MAY specify a framing method to be used even when RTP is + carried in protocols that do provide framing in order to allow + carrying several RTP packets in one lower-layer protocol data unit, + such as a UDP packet. Carrying several RTP packets in one network or + transport packet reduces header overhead and may simplify + synchronization between different streams. + +12. Summary of Protocol Constants + + This section contains a summary listing of the constants defined in + this specification. + + The RTP payload type (PT) constants are defined in profiles rather + than this document. However, the octet of the RTP header which + contains the marker bit(s) and payload type MUST avoid the reserved + values 200 and 201 (decimal) to distinguish RTP packets from the RTCP + SR and RR packet types for the header validation procedure described + + + +Schulzrinne, et al. Standards Track [Page 69] + +RFC 3550 RTP July 2003 + + + in Appendix A.1. For the standard definition of one marker bit and a + 7-bit payload type field as shown in this specification, this + restriction means that payload types 72 and 73 are reserved. + +12.1 RTCP Packet Types + + abbrev. name value + SR sender report 200 + RR receiver report 201 + SDES source description 202 + BYE goodbye 203 + APP application-defined 204 + + These type values were chosen in the range 200-204 for improved + header validity checking of RTCP packets compared to RTP packets or + other unrelated packets. When the RTCP packet type field is compared + to the corresponding octet of the RTP header, this range corresponds + to the marker bit being 1 (which it usually is not in data packets) + and to the high bit of the standard payload type field being 1 (since + the static payload types are typically defined in the low half). + This range was also chosen to be some distance numerically from 0 and + 255 since all-zeros and all-ones are common data patterns. + + Since all compound RTCP packets MUST begin with SR or RR, these codes + were chosen as an even/odd pair to allow the RTCP validity check to + test the maximum number of bits with mask and value. + + Additional RTCP packet types may be registered through IANA (see + Section 15). + +12.2 SDES Types + + abbrev. name value + END end of SDES list 0 + CNAME canonical name 1 + NAME user name 2 + EMAIL user's electronic mail address 3 + PHONE user's phone number 4 + LOC geographic user location 5 + TOOL name of application or tool 6 + NOTE notice about the source 7 + PRIV private extensions 8 + + Additional SDES types may be registered through IANA (see Section + 15). + + + + + + +Schulzrinne, et al. Standards Track [Page 70] + +RFC 3550 RTP July 2003 + + +13. RTP Profiles and Payload Format Specifications + + A complete specification of RTP for a particular application will + require one or more companion documents of two types described here: + profiles, and payload format specifications. + + RTP may be used for a variety of applications with somewhat differing + requirements. The flexibility to adapt to those requirements is + provided by allowing multiple choices in the main protocol + specification, then selecting the appropriate choices or defining + extensions for a particular environment and class of applications in + a separate profile document. Typically an application will operate + under only one profile in a particular RTP session, so there is no + explicit indication within the RTP protocol itself as to which + profile is in use. A profile for audio and video applications may be + found in the companion RFC 3551. Profiles are typically titled "RTP + Profile for ...". + + The second type of companion document is a payload format + specification, which defines how a particular kind of payload data, + such as H.261 encoded video, should be carried in RTP. These + documents are typically titled "RTP Payload Format for XYZ + Audio/Video Encoding". Payload formats may be useful under multiple + profiles and may therefore be defined independently of any particular + profile. The profile documents are then responsible for assigning a + default mapping of that format to a payload type value if needed. + + Within this specification, the following items have been identified + for possible definition within a profile, but this list is not meant + to be exhaustive: + + RTP data header: The octet in the RTP data header that contains + the marker bit and payload type field MAY be redefined by a + profile to suit different requirements, for example with more or + fewer marker bits (Section 5.3, p. 18). + + Payload types: Assuming that a payload type field is included, + the profile will usually define a set of payload formats (e.g., + media encodings) and a default static mapping of those formats to + payload type values. Some of the payload formats may be defined + by reference to separate payload format specifications. For each + payload type defined, the profile MUST specify the RTP timestamp + clock rate to be used (Section 5.1, p. 14). + + RTP data header additions: Additional fields MAY be appended to + the fixed RTP data header if some additional functionality is + required across the profile's class of applications independent of + payload type (Section 5.3, p. 18). + + + +Schulzrinne, et al. Standards Track [Page 71] + +RFC 3550 RTP July 2003 + + + RTP data header extensions: The contents of the first 16 bits of + the RTP data header extension structure MUST be defined if use of + that mechanism is to be allowed under the profile for + implementation-specific extensions (Section 5.3.1, p. 18). + + RTCP packet types: New application-class-specific RTCP packet + types MAY be defined and registered with IANA. + + RTCP report interval: A profile SHOULD specify that the values + suggested in Section 6.2 for the constants employed in the + calculation of the RTCP report interval will be used. Those are + the RTCP fraction of session bandwidth, the minimum report + interval, and the bandwidth split between senders and receivers. + A profile MAY specify alternate values if they have been + demonstrated to work in a scalable manner. + + SR/RR extension: An extension section MAY be defined for the + RTCP SR and RR packets if there is additional information that + should be reported regularly about the sender or receivers + (Section 6.4.3, p. 42 and 43). + + SDES use: The profile MAY specify the relative priorities for + RTCP SDES items to be transmitted or excluded entirely (Section + 6.3.9); an alternate syntax or semantics for the CNAME item + (Section 6.5.1); the format of the LOC item (Section 6.5.5); the + semantics and use of the NOTE item (Section 6.5.7); or new SDES + item types to be registered with IANA. + + Security: A profile MAY specify which security services and + algorithms should be offered by applications, and MAY provide + guidance as to their appropriate use (Section 9, p. 65). + + String-to-key mapping: A profile MAY specify how a user-provided + password or pass phrase is mapped into an encryption key. + + Congestion: A profile SHOULD specify the congestion control + behavior appropriate for that profile. + + Underlying protocol: Use of a particular underlying network or + transport layer protocol to carry RTP packets MAY be required. + + Transport mapping: A mapping of RTP and RTCP to transport-level + addresses, e.g., UDP ports, other than the standard mapping + defined in Section 11, p. 68 may be specified. + + + + + + + +Schulzrinne, et al. Standards Track [Page 72] + +RFC 3550 RTP July 2003 + + + Encapsulation: An encapsulation of RTP packets may be defined to + allow multiple RTP data packets to be carried in one lower-layer + packet or to provide framing over underlying protocols that do not + already do so (Section 11, p. 69). + + It is not expected that a new profile will be required for every + application. Within one application class, it would be better to + extend an existing profile rather than make a new one in order to + facilitate interoperation among the applications since each will + typically run under only one profile. Simple extensions such as the + definition of additional payload type values or RTCP packet types may + be accomplished by registering them through IANA and publishing their + descriptions in an addendum to the profile or in a payload format + specification. + +14. Security Considerations + + RTP suffers from the same security liabilities as the underlying + protocols. For example, an impostor can fake source or destination + network addresses, or change the header or payload. Within RTCP, the + CNAME and NAME information may be used to impersonate another + participant. In addition, RTP may be sent via IP multicast, which + provides no direct means for a sender to know all the receivers of + the data sent and therefore no measure of privacy. Rightly or not, + users may be more sensitive to privacy concerns with audio and video + communication than they have been with more traditional forms of + network communication [33]. Therefore, the use of security + mechanisms with RTP is important. These mechanisms are discussed in + Section 9. + + RTP-level translators or mixers may be used to allow RTP traffic to + reach hosts behind firewalls. Appropriate firewall security + principles and practices, which are beyond the scope of this + document, should be followed in the design and installation of these + devices and in the admission of RTP applications for use behind the + firewall. + +15. IANA Considerations + + Additional RTCP packet types and SDES item types may be registered + through the Internet Assigned Numbers Authority (IANA). Since these + number spaces are small, allowing unconstrained registration of new + values would not be prudent. To facilitate review of requests and to + promote shared use of new types among multiple applications, requests + for registration of new values must be documented in an RFC or other + permanent and readily available reference such as the product of + another cooperative standards body (e.g., ITU-T). Other requests may + also be accepted, under the advice of a "designated expert." + + + +Schulzrinne, et al. Standards Track [Page 73] + +RFC 3550 RTP July 2003 + + + (Contact the IANA for the contact information of the current expert.) + + RTP profile specifications SHOULD register with IANA a name for the + profile in the form "RTP/xxx", where xxx is a short abbreviation of + the profile title. These names are for use by higher-level control + protocols, such as the Session Description Protocol (SDP), RFC 2327 + [15], to refer to transport methods. + +16. Intellectual Property Rights Statement + + The IETF takes no position regarding the validity or scope of any + intellectual property or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; neither does it represent that it + has made any effort to identify any such rights. Information on the + IETF's procedures with respect to rights in standards-track and + standards-related documentation can be found in BCP-11. Copies of + claims of rights made available for publication and any assurances of + licenses to be made available, or the result of an attempt made to + obtain a general license or permission for the use of such + proprietary rights by implementors or users of this specification can + be obtained from the IETF Secretariat. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights which may cover technology that may be required to practice + this standard. Please address the information to the IETF Executive + Director. + +17. Acknowledgments + + This memorandum is based on discussions within the IETF Audio/Video + Transport working group chaired by Stephen Casner and Colin Perkins. + The current protocol has its origins in the Network Voice Protocol + and the Packet Video Protocol (Danny Cohen and Randy Cole) and the + protocol implemented by the vat application (Van Jacobson and Steve + McCanne). Christian Huitema provided ideas for the random identifier + generator. Extensive analysis and simulation of the timer + reconsideration algorithm was done by Jonathan Rosenberg. The + additions for layered encodings were specified by Michael Speer and + Steve McCanne. + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 74] + +RFC 3550 RTP July 2003 + + +Appendix A - Algorithms + + We provide examples of C code for aspects of RTP sender and receiver + algorithms. There may be other implementation methods that are + faster in particular operating environments or have other advantages. + These implementation notes are for informational purposes only and + are meant to clarify the RTP specification. + + The following definitions are used for all examples; for clarity and + brevity, the structure definitions are only valid for 32-bit big- + endian (most significant octet first) architectures. Bit fields are + assumed to be packed tightly in big-endian bit order, with no + additional padding. Modifications would be required to construct a + portable implementation. + + /* + * rtp.h -- RTP header file + */ + #include <sys/types.h> + + /* + * The type definitions below are valid for 32-bit architectures and + * may have to be adjusted for 16- or 64-bit architectures. + */ + typedef unsigned char u_int8; + typedef unsigned short u_int16; + typedef unsigned int u_int32; + typedef short int16; + + /* + * Current protocol version. + */ + #define RTP_VERSION 2 + + #define RTP_SEQ_MOD (1<<16) + #define RTP_MAX_SDES 255 /* maximum text length for SDES */ + + typedef enum { + RTCP_SR = 200, + RTCP_RR = 201, + RTCP_SDES = 202, + RTCP_BYE = 203, + RTCP_APP = 204 + } rtcp_type_t; + + typedef enum { + RTCP_SDES_END = 0, + RTCP_SDES_CNAME = 1, + + + +Schulzrinne, et al. Standards Track [Page 75] + +RFC 3550 RTP July 2003 + + + RTCP_SDES_NAME = 2, + RTCP_SDES_EMAIL = 3, + RTCP_SDES_PHONE = 4, + RTCP_SDES_LOC = 5, + RTCP_SDES_TOOL = 6, + RTCP_SDES_NOTE = 7, + RTCP_SDES_PRIV = 8 + } rtcp_sdes_type_t; + + /* + * RTP data header + */ + typedef struct { + unsigned int version:2; /* protocol version */ + unsigned int p:1; /* padding flag */ + unsigned int x:1; /* header extension flag */ + unsigned int cc:4; /* CSRC count */ + unsigned int m:1; /* marker bit */ + unsigned int pt:7; /* payload type */ + unsigned int seq:16; /* sequence number */ + u_int32 ts; /* timestamp */ + u_int32 ssrc; /* synchronization source */ + u_int32 csrc[1]; /* optional CSRC list */ + } rtp_hdr_t; + + /* + * RTCP common header word + */ + typedef struct { + unsigned int version:2; /* protocol version */ + unsigned int p:1; /* padding flag */ + unsigned int count:5; /* varies by packet type */ + unsigned int pt:8; /* RTCP packet type */ + u_int16 length; /* pkt len in words, w/o this word */ + } rtcp_common_t; + + /* + * Big-endian mask for version, padding bit and packet type pair + */ + #define RTCP_VALID_MASK (0xc000 | 0x2000 | 0xfe) + #define RTCP_VALID_VALUE ((RTP_VERSION << 14) | RTCP_SR) + + /* + * Reception report block + */ + typedef struct { + u_int32 ssrc; /* data source being reported */ + unsigned int fraction:8; /* fraction lost since last SR/RR */ + + + +Schulzrinne, et al. Standards Track [Page 76] + +RFC 3550 RTP July 2003 + + + int lost:24; /* cumul. no. pkts lost (signed!) */ + u_int32 last_seq; /* extended last seq. no. received */ + u_int32 jitter; /* interarrival jitter */ + u_int32 lsr; /* last SR packet from this source */ + u_int32 dlsr; /* delay since last SR packet */ + } rtcp_rr_t; + + /* + * SDES item + */ + typedef struct { + u_int8 type; /* type of item (rtcp_sdes_type_t) */ + u_int8 length; /* length of item (in octets) */ + char data[1]; /* text, not null-terminated */ + } rtcp_sdes_item_t; + + /* + * One RTCP packet + */ + typedef struct { + rtcp_common_t common; /* common header */ + union { + /* sender report (SR) */ + struct { + u_int32 ssrc; /* sender generating this report */ + u_int32 ntp_sec; /* NTP timestamp */ + u_int32 ntp_frac; + u_int32 rtp_ts; /* RTP timestamp */ + u_int32 psent; /* packets sent */ + u_int32 osent; /* octets sent */ + rtcp_rr_t rr[1]; /* variable-length list */ + } sr; + + /* reception report (RR) */ + struct { + u_int32 ssrc; /* receiver generating this report */ + rtcp_rr_t rr[1]; /* variable-length list */ + } rr; + + /* source description (SDES) */ + struct rtcp_sdes { + u_int32 src; /* first SSRC/CSRC */ + rtcp_sdes_item_t item[1]; /* list of SDES items */ + } sdes; + + /* BYE */ + struct { + u_int32 src[1]; /* list of sources */ + + + +Schulzrinne, et al. Standards Track [Page 77] + +RFC 3550 RTP July 2003 + + + /* can't express trailing text for reason */ + } bye; + } r; + } rtcp_t; + + typedef struct rtcp_sdes rtcp_sdes_t; + + /* + * Per-source state information + */ + typedef struct { + u_int16 max_seq; /* highest seq. number seen */ + u_int32 cycles; /* shifted count of seq. number cycles */ + u_int32 base_seq; /* base seq number */ + u_int32 bad_seq; /* last 'bad' seq number + 1 */ + u_int32 probation; /* sequ. packets till source is valid */ + u_int32 received; /* packets received */ + u_int32 expected_prior; /* packet expected at last interval */ + u_int32 received_prior; /* packet received at last interval */ + u_int32 transit; /* relative trans time for prev pkt */ + u_int32 jitter; /* estimated jitter */ + /* ... */ + } source; + +A.1 RTP Data Header Validity Checks + + An RTP receiver should check the validity of the RTP header on + incoming packets since they might be encrypted or might be from a + different application that happens to be misaddressed. Similarly, if + encryption according to the method described in Section 9 is enabled, + the header validity check is needed to verify that incoming packets + have been correctly decrypted, although a failure of the header + validity check (e.g., unknown payload type) may not necessarily + indicate decryption failure. + + Only weak validity checks are possible on an RTP data packet from a + source that has not been heard before: + + o RTP version field must equal 2. + + o The payload type must be known, and in particular it must not be + equal to SR or RR. + + o If the P bit is set, then the last octet of the packet must + contain a valid octet count, in particular, less than the total + packet length minus the header size. + + + + + +Schulzrinne, et al. Standards Track [Page 78] + +RFC 3550 RTP July 2003 + + + o The X bit must be zero if the profile does not specify that the + header extension mechanism may be used. Otherwise, the extension + length field must be less than the total packet size minus the + fixed header length and padding. + + o The length of the packet must be consistent with CC and payload + type (if payloads have a known length). + + The last three checks are somewhat complex and not always possible, + leaving only the first two which total just a few bits. If the SSRC + identifier in the packet is one that has been received before, then + the packet is probably valid and checking if the sequence number is + in the expected range provides further validation. If the SSRC + identifier has not been seen before, then data packets carrying that + identifier may be considered invalid until a small number of them + arrive with consecutive sequence numbers. Those invalid packets MAY + be discarded or they MAY be stored and delivered once validation has + been achieved if the resulting delay is acceptable. + + The routine update_seq shown below ensures that a source is declared + valid only after MIN_SEQUENTIAL packets have been received in + sequence. It also validates the sequence number seq of a newly + received packet and updates the sequence state for the packet's + source in the structure to which s points. + + When a new source is heard for the first time, that is, its SSRC + identifier is not in the table (see Section 8.2), and the per-source + state is allocated for it, s->probation is set to the number of + sequential packets required before declaring a source valid + (parameter MIN_SEQUENTIAL) and other variables are initialized: + + init_seq(s, seq); + s->max_seq = seq - 1; + s->probation = MIN_SEQUENTIAL; + + A non-zero s->probation marks the source as not yet valid so the + state may be discarded after a short timeout rather than a long one, + as discussed in Section 6.2.1. + + After a source is considered valid, the sequence number is considered + valid if it is no more than MAX_DROPOUT ahead of s->max_seq nor more + than MAX_MISORDER behind. If the new sequence number is ahead of + max_seq modulo the RTP sequence number range (16 bits), but is + smaller than max_seq, it has wrapped around and the (shifted) count + of sequence number cycles is incremented. A value of one is returned + to indicate a valid sequence number. + + + + + +Schulzrinne, et al. Standards Track [Page 79] + +RFC 3550 RTP July 2003 + + + Otherwise, the value zero is returned to indicate that the validation + failed, and the bad sequence number plus 1 is stored. If the next + packet received carries the next higher sequence number, it is + considered the valid start of a new packet sequence presumably caused + by an extended dropout or a source restart. Since multiple complete + sequence number cycles may have been missed, the packet loss + statistics are reset. + + Typical values for the parameters are shown, based on a maximum + misordering time of 2 seconds at 50 packets/second and a maximum + dropout of 1 minute. The dropout parameter MAX_DROPOUT should be a + small fraction of the 16-bit sequence number space to give a + reasonable probability that new sequence numbers after a restart will + not fall in the acceptable range for sequence numbers from before the + restart. + + void init_seq(source *s, u_int16 seq) + { + s->base_seq = seq; + s->max_seq = seq; + s->bad_seq = RTP_SEQ_MOD + 1; /* so seq == bad_seq is false */ + s->cycles = 0; + s->received = 0; + s->received_prior = 0; + s->expected_prior = 0; + /* other initialization */ + } + + int update_seq(source *s, u_int16 seq) + { + u_int16 udelta = seq - s->max_seq; + const int MAX_DROPOUT = 3000; + const int MAX_MISORDER = 100; + const int MIN_SEQUENTIAL = 2; + + /* + * Source is not valid until MIN_SEQUENTIAL packets with + * sequential sequence numbers have been received. + */ + if (s->probation) { + /* packet is in sequence */ + if (seq == s->max_seq + 1) { + s->probation--; + s->max_seq = seq; + if (s->probation == 0) { + init_seq(s, seq); + s->received++; + return 1; + + + +Schulzrinne, et al. Standards Track [Page 80] + +RFC 3550 RTP July 2003 + + + } + } else { + s->probation = MIN_SEQUENTIAL - 1; + s->max_seq = seq; + } + return 0; + } else if (udelta < MAX_DROPOUT) { + /* in order, with permissible gap */ + if (seq < s->max_seq) { + /* + * Sequence number wrapped - count another 64K cycle. + */ + s->cycles += RTP_SEQ_MOD; + } + s->max_seq = seq; + } else if (udelta <= RTP_SEQ_MOD - MAX_MISORDER) { + /* the sequence number made a very large jump */ + if (seq == s->bad_seq) { + /* + * Two sequential packets -- assume that the other side + * restarted without telling us so just re-sync + * (i.e., pretend this was the first packet). + */ + init_seq(s, seq); + } + else { + s->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1); + return 0; + } + } else { + /* duplicate or reordered packet */ + } + s->received++; + return 1; + } + + The validity check can be made stronger requiring more than two + packets in sequence. The disadvantages are that a larger number of + initial packets will be discarded (or delayed in a queue) and that + high packet loss rates could prevent validation. However, because + the RTCP header validation is relatively strong, if an RTCP packet is + received from a source before the data packets, the count could be + adjusted so that only two packets are required in sequence. If + initial data loss for a few seconds can be tolerated, an application + MAY choose to discard all data packets from a source until a valid + RTCP packet has been received from that source. + + + + + +Schulzrinne, et al. Standards Track [Page 81] + +RFC 3550 RTP July 2003 + + + Depending on the application and encoding, algorithms may exploit + additional knowledge about the payload format for further validation. + For payload types where the timestamp increment is the same for all + packets, the timestamp values can be predicted from the previous + packet received from the same source using the sequence number + difference (assuming no change in payload type). + + A strong "fast-path" check is possible since with high probability + the first four octets in the header of a newly received RTP data + packet will be just the same as that of the previous packet from the + same SSRC except that the sequence number will have increased by one. + Similarly, a single-entry cache may be used for faster SSRC lookups + in applications where data is typically received from one source at a + time. + +A.2 RTCP Header Validity Checks + + The following checks should be applied to RTCP packets. + + o RTP version field must equal 2. + + o The payload type field of the first RTCP packet in a compound + packet must be equal to SR or RR. + + o The padding bit (P) should be zero for the first packet of a + compound RTCP packet because padding should only be applied, if it + is needed, to the last packet. + + o The length fields of the individual RTCP packets must add up to + the overall length of the compound RTCP packet as received. This + is a fairly strong check. + + The code fragment below performs all of these checks. The packet + type is not checked for subsequent packets since unknown packet types + may be present and should be ignored. + + u_int32 len; /* length of compound RTCP packet in words */ + rtcp_t *r; /* RTCP header */ + rtcp_t *end; /* end of compound RTCP packet */ + + if ((*(u_int16 *)r & RTCP_VALID_MASK) != RTCP_VALID_VALUE) { + /* something wrong with packet format */ + } + end = (rtcp_t *)((u_int32 *)r + len); + + do r = (rtcp_t *)((u_int32 *)r + r->common.length + 1); + while (r < end && r->common.version == 2); + + + + +Schulzrinne, et al. Standards Track [Page 82] + +RFC 3550 RTP July 2003 + + + if (r != end) { + /* something wrong with packet format */ + } + +A.3 Determining Number of Packets Expected and Lost + + In order to compute packet loss rates, the number of RTP packets + expected and actually received from each source needs to be known, + using per-source state information defined in struct source + referenced via pointer s in the code below. The number of packets + received is simply the count of packets as they arrive, including any + late or duplicate packets. The number of packets expected can be + computed by the receiver as the difference between the highest + sequence number received (s->max_seq) and the first sequence number + received (s->base_seq). Since the sequence number is only 16 bits + and will wrap around, it is necessary to extend the highest sequence + number with the (shifted) count of sequence number wraparounds + (s->cycles). Both the received packet count and the count of cycles + are maintained the RTP header validity check routine in Appendix A.1. + + extended_max = s->cycles + s->max_seq; + expected = extended_max - s->base_seq + 1; + + The number of packets lost is defined to be the number of packets + expected less the number of packets actually received: + + lost = expected - s->received; + + Since this signed number is carried in 24 bits, it should be clamped + at 0x7fffff for positive loss or 0x800000 for negative loss rather + than wrapping around. + + The fraction of packets lost during the last reporting interval + (since the previous SR or RR packet was sent) is calculated from + differences in the expected and received packet counts across the + interval, where expected_prior and received_prior are the values + saved when the previous reception report was generated: + + expected_interval = expected - s->expected_prior; + s->expected_prior = expected; + received_interval = s->received - s->received_prior; + s->received_prior = s->received; + lost_interval = expected_interval - received_interval; + if (expected_interval == 0 || lost_interval <= 0) fraction = 0; + else fraction = (lost_interval << 8) / expected_interval; + + The resulting fraction is an 8-bit fixed point number with the binary + point at the left edge. + + + +Schulzrinne, et al. Standards Track [Page 83] + +RFC 3550 RTP July 2003 + + +A.4 Generating RTCP SDES Packets + + This function builds one SDES chunk into buffer b composed of argc + items supplied in arrays type, value and length. It returns a + pointer to the next available location within b. + + char *rtp_write_sdes(char *b, u_int32 src, int argc, + rtcp_sdes_type_t type[], char *value[], + int length[]) + { + rtcp_sdes_t *s = (rtcp_sdes_t *)b; + rtcp_sdes_item_t *rsp; + int i; + int len; + int pad; + + /* SSRC header */ + s->src = src; + rsp = &s->item[0]; + + /* SDES items */ + for (i = 0; i < argc; i++) { + rsp->type = type[i]; + len = length[i]; + if (len > RTP_MAX_SDES) { + /* invalid length, may want to take other action */ + len = RTP_MAX_SDES; + } + rsp->length = len; + memcpy(rsp->data, value[i], len); + rsp = (rtcp_sdes_item_t *)&rsp->data[len]; + } + + /* terminate with end marker and pad to next 4-octet boundary */ + len = ((char *) rsp) - b; + pad = 4 - (len & 0x3); + b = (char *) rsp; + while (pad--) *b++ = RTCP_SDES_END; + + return b; + } + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 84] + +RFC 3550 RTP July 2003 + + +A.5 Parsing RTCP SDES Packets + + This function parses an SDES packet, calling functions find_member() + to find a pointer to the information for a session member given the + SSRC identifier and member_sdes() to store the new SDES information + for that member. This function expects a pointer to the header of + the RTCP packet. + + void rtp_read_sdes(rtcp_t *r) + { + int count = r->common.count; + rtcp_sdes_t *sd = &r->r.sdes; + rtcp_sdes_item_t *rsp, *rspn; + rtcp_sdes_item_t *end = (rtcp_sdes_item_t *) + ((u_int32 *)r + r->common.length + 1); + source *s; + + while (--count >= 0) { + rsp = &sd->item[0]; + if (rsp >= end) break; + s = find_member(sd->src); + + for (; rsp->type; rsp = rspn ) { + rspn = (rtcp_sdes_item_t *)((char*)rsp+rsp->length+2); + if (rspn >= end) { + rsp = rspn; + break; + } + member_sdes(s, rsp->type, rsp->data, rsp->length); + } + sd = (rtcp_sdes_t *) + ((u_int32 *)sd + (((char *)rsp - (char *)sd) >> 2)+1); + } + if (count >= 0) { + /* invalid packet format */ + } + } + +A.6 Generating a Random 32-bit Identifier + + The following subroutine generates a random 32-bit identifier using + the MD5 routines published in RFC 1321 [32]. The system routines may + not be present on all operating systems, but they should serve as + hints as to what kinds of information may be used. Other system + calls that may be appropriate include + + + + + + +Schulzrinne, et al. Standards Track [Page 85] + +RFC 3550 RTP July 2003 + + + o getdomainname(), + + o getwd(), or + + o getrusage(). + + "Live" video or audio samples are also a good source of random + numbers, but care must be taken to avoid using a turned-off + microphone or blinded camera as a source [17]. + + Use of this or a similar routine is recommended to generate the + initial seed for the random number generator producing the RTCP + period (as shown in Appendix A.7), to generate the initial values for + the sequence number and timestamp, and to generate SSRC values. + Since this routine is likely to be CPU-intensive, its direct use to + generate RTCP periods is inappropriate because predictability is not + an issue. Note that this routine produces the same result on + repeated calls until the value of the system clock changes unless + different values are supplied for the type argument. + + /* + * Generate a random 32-bit quantity. + */ + #include <sys/types.h> /* u_long */ + #include <sys/time.h> /* gettimeofday() */ + #include <unistd.h> /* get..() */ + #include <stdio.h> /* printf() */ + #include <time.h> /* clock() */ + #include <sys/utsname.h> /* uname() */ + #include "global.h" /* from RFC 1321 */ + #include "md5.h" /* from RFC 1321 */ + + #define MD_CTX MD5_CTX + #define MDInit MD5Init + #define MDUpdate MD5Update + #define MDFinal MD5Final + + static u_long md_32(char *string, int length) + { + MD_CTX context; + union { + char c[16]; + u_long x[4]; + } digest; + u_long r; + int i; + + MDInit (&context); + + + +Schulzrinne, et al. Standards Track [Page 86] + +RFC 3550 RTP July 2003 + + + MDUpdate (&context, string, length); + MDFinal ((unsigned char *)&digest, &context); + r = 0; + for (i = 0; i < 3; i++) { + r ^= digest.x[i]; + } + return r; + } /* md_32 */ + + /* + * Return random unsigned 32-bit quantity. Use 'type' argument if + * you need to generate several different values in close succession. + */ + u_int32 random32(int type) + { + struct { + int type; + struct timeval tv; + clock_t cpu; + pid_t pid; + u_long hid; + uid_t uid; + gid_t gid; + struct utsname name; + } s; + + gettimeofday(&s.tv, 0); + uname(&s.name); + s.type = type; + s.cpu = clock(); + s.pid = getpid(); + s.hid = gethostid(); + s.uid = getuid(); + s.gid = getgid(); + /* also: system uptime */ + + return md_32((char *)&s, sizeof(s)); + } /* random32 */ + +A.7 Computing the RTCP Transmission Interval + + The following functions implement the RTCP transmission and reception + rules described in Section 6.2. These rules are coded in several + functions: + + o rtcp_interval() computes the deterministic calculated interval, + measured in seconds. The parameters are defined in Section 6.3. + + + + +Schulzrinne, et al. Standards Track [Page 87] + +RFC 3550 RTP July 2003 + + + o OnExpire() is called when the RTCP transmission timer expires. + + o OnReceive() is called whenever an RTCP packet is received. + + Both OnExpire() and OnReceive() have event e as an argument. This is + the next scheduled event for that participant, either an RTCP report + or a BYE packet. It is assumed that the following functions are + available: + + o Schedule(time t, event e) schedules an event e to occur at time t. + When time t arrives, the function OnExpire is called with e as an + argument. + + o Reschedule(time t, event e) reschedules a previously scheduled + event e for time t. + + o SendRTCPReport(event e) sends an RTCP report. + + o SendBYEPacket(event e) sends a BYE packet. + + o TypeOfEvent(event e) returns EVENT_BYE if the event being + processed is for a BYE packet to be sent, else it returns + EVENT_REPORT. + + o PacketType(p) returns PACKET_RTCP_REPORT if packet p is an RTCP + report (not BYE), PACKET_BYE if its a BYE RTCP packet, and + PACKET_RTP if its a regular RTP data packet. + + o ReceivedPacketSize() and SentPacketSize() return the size of the + referenced packet in octets. + + o NewMember(p) returns a 1 if the participant who sent packet p is + not currently in the member list, 0 otherwise. Note this function + is not sufficient for a complete implementation because each CSRC + identifier in an RTP packet and each SSRC in a BYE packet should + be processed. + + o NewSender(p) returns a 1 if the participant who sent packet p is + not currently in the sender sublist of the member list, 0 + otherwise. + + o AddMember() and RemoveMember() to add and remove participants from + the member list. + + o AddSender() and RemoveSender() to add and remove participants from + the sender sublist of the member list. + + + + + +Schulzrinne, et al. Standards Track [Page 88] + +RFC 3550 RTP July 2003 + + + These functions would have to be extended for an implementation that + allows the RTCP bandwidth fractions for senders and non-senders to be + specified as explicit parameters rather than fixed values of 25% and + 75%. The extended implementation of rtcp_interval() would need to + avoid division by zero if one of the parameters was zero. + + double rtcp_interval(int members, + int senders, + double rtcp_bw, + int we_sent, + double avg_rtcp_size, + int initial) + { + /* + * Minimum average time between RTCP packets from this site (in + * seconds). This time prevents the reports from `clumping' when + * sessions are small and the law of large numbers isn't helping + * to smooth out the traffic. It also keeps the report interval + * from becoming ridiculously small during transient outages like + * a network partition. + */ + double const RTCP_MIN_TIME = 5.; + /* + * Fraction of the RTCP bandwidth to be shared among active + * senders. (This fraction was chosen so that in a typical + * session with one or two active senders, the computed report + * time would be roughly equal to the minimum report time so that + * we don't unnecessarily slow down receiver reports.) The + * receiver fraction must be 1 - the sender fraction. + */ + double const RTCP_SENDER_BW_FRACTION = 0.25; + double const RTCP_RCVR_BW_FRACTION = (1-RTCP_SENDER_BW_FRACTION); + /* + /* To compensate for "timer reconsideration" converging to a + * value below the intended average. + */ + double const COMPENSATION = 2.71828 - 1.5; + + double t; /* interval */ + double rtcp_min_time = RTCP_MIN_TIME; + int n; /* no. of members for computation */ + + /* + * Very first call at application start-up uses half the min + * delay for quicker notification while still allowing some time + * before reporting for randomization and to learn about other + * sources so the report interval will converge to the correct + * interval more quickly. + + + +Schulzrinne, et al. Standards Track [Page 89] + +RFC 3550 RTP July 2003 + + + */ + if (initial) { + rtcp_min_time /= 2; + } + /* + * Dedicate a fraction of the RTCP bandwidth to senders unless + * the number of senders is large enough that their share is + * more than that fraction. + */ + n = members; + if (senders <= members * RTCP_SENDER_BW_FRACTION) { + if (we_sent) { + rtcp_bw *= RTCP_SENDER_BW_FRACTION; + n = senders; + } else { + rtcp_bw *= RTCP_RCVR_BW_FRACTION; + n -= senders; + } + } + + /* + * The effective number of sites times the average packet size is + * the total number of octets sent when each site sends a report. + * Dividing this by the effective bandwidth gives the time + * interval over which those packets must be sent in order to + * meet the bandwidth target, with a minimum enforced. In that + * time interval we send one report so this time is also our + * average time between reports. + */ + t = avg_rtcp_size * n / rtcp_bw; + if (t < rtcp_min_time) t = rtcp_min_time; + + /* + * To avoid traffic bursts from unintended synchronization with + * other sites, we then pick our actual next report interval as a + * random number uniformly distributed between 0.5*t and 1.5*t. + */ + t = t * (drand48() + 0.5); + t = t / COMPENSATION; + return t; + } + + void OnExpire(event e, + int members, + int senders, + double rtcp_bw, + int we_sent, + double *avg_rtcp_size, + + + +Schulzrinne, et al. Standards Track [Page 90] + +RFC 3550 RTP July 2003 + + + int *initial, + time_tp tc, + time_tp *tp, + int *pmembers) + { + /* This function is responsible for deciding whether to send an + * RTCP report or BYE packet now, or to reschedule transmission. + * It is also responsible for updating the pmembers, initial, tp, + * and avg_rtcp_size state variables. This function should be + * called upon expiration of the event timer used by Schedule(). + */ + + double t; /* Interval */ + double tn; /* Next transmit time */ + + /* In the case of a BYE, we use "timer reconsideration" to + * reschedule the transmission of the BYE if necessary */ + + if (TypeOfEvent(e) == EVENT_BYE) { + t = rtcp_interval(members, + senders, + rtcp_bw, + we_sent, + *avg_rtcp_size, + *initial); + tn = *tp + t; + if (tn <= tc) { + SendBYEPacket(e); + exit(1); + } else { + Schedule(tn, e); + } + + } else if (TypeOfEvent(e) == EVENT_REPORT) { + t = rtcp_interval(members, + senders, + rtcp_bw, + we_sent, + *avg_rtcp_size, + *initial); + tn = *tp + t; + if (tn <= tc) { + SendRTCPReport(e); + *avg_rtcp_size = (1./16.)*SentPacketSize(e) + + (15./16.)*(*avg_rtcp_size); + *tp = tc; + + /* We must redraw the interval. Don't reuse the + + + +Schulzrinne, et al. Standards Track [Page 91] + +RFC 3550 RTP July 2003 + + + one computed above, since its not actually + distributed the same, as we are conditioned + on it being small enough to cause a packet to + be sent */ + + t = rtcp_interval(members, + senders, + rtcp_bw, + we_sent, + *avg_rtcp_size, + *initial); + + Schedule(t+tc,e); + *initial = 0; + } else { + Schedule(tn, e); + } + *pmembers = members; + } + } + + void OnReceive(packet p, + event e, + int *members, + int *pmembers, + int *senders, + double *avg_rtcp_size, + double *tp, + double tc, + double tn) + { + /* What we do depends on whether we have left the group, and are + * waiting to send a BYE (TypeOfEvent(e) == EVENT_BYE) or an RTCP + * report. p represents the packet that was just received. */ + + if (PacketType(p) == PACKET_RTCP_REPORT) { + if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) { + AddMember(p); + *members += 1; + } + *avg_rtcp_size = (1./16.)*ReceivedPacketSize(p) + + (15./16.)*(*avg_rtcp_size); + } else if (PacketType(p) == PACKET_RTP) { + if (NewMember(p) && (TypeOfEvent(e) == EVENT_REPORT)) { + AddMember(p); + *members += 1; + } + if (NewSender(p) && (TypeOfEvent(e) == EVENT_REPORT)) { + + + +Schulzrinne, et al. Standards Track [Page 92] + +RFC 3550 RTP July 2003 + + + AddSender(p); + *senders += 1; + } + } else if (PacketType(p) == PACKET_BYE) { + *avg_rtcp_size = (1./16.)*ReceivedPacketSize(p) + + (15./16.)*(*avg_rtcp_size); + + if (TypeOfEvent(e) == EVENT_REPORT) { + if (NewSender(p) == FALSE) { + RemoveSender(p); + *senders -= 1; + } + + if (NewMember(p) == FALSE) { + RemoveMember(p); + *members -= 1; + } + + if (*members < *pmembers) { + tn = tc + + (((double) *members)/(*pmembers))*(tn - tc); + *tp = tc - + (((double) *members)/(*pmembers))*(tc - *tp); + + /* Reschedule the next report for time tn */ + + Reschedule(tn, e); + *pmembers = *members; + } + + } else if (TypeOfEvent(e) == EVENT_BYE) { + *members += 1; + } + } + } + + + + + + + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 93] + +RFC 3550 RTP July 2003 + + +A.8 Estimating the Interarrival Jitter + + The code fragments below implement the algorithm given in Section + 6.4.1 for calculating an estimate of the statistical variance of the + RTP data interarrival time to be inserted in the interarrival jitter + field of reception reports. The inputs are r->ts, the timestamp from + the incoming packet, and arrival, the current time in the same units. + Here s points to state for the source; s->transit holds the relative + transit time for the previous packet, and s->jitter holds the + estimated jitter. The jitter field of the reception report is + measured in timestamp units and expressed as an unsigned integer, but + the jitter estimate is kept in a floating point. As each data packet + arrives, the jitter estimate is updated: + + int transit = arrival - r->ts; + int d = transit - s->transit; + s->transit = transit; + if (d < 0) d = -d; + s->jitter += (1./16.) * ((double)d - s->jitter); + + When a reception report block (to which rr points) is generated for + this member, the current jitter estimate is returned: + + rr->jitter = (u_int32) s->jitter; + + Alternatively, the jitter estimate can be kept as an integer, but + scaled to reduce round-off error. The calculation is the same except + for the last line: + + s->jitter += d - ((s->jitter + 8) >> 4); + + In this case, the estimate is sampled for the reception report as: + + rr->jitter = s->jitter >> 4; + + + + + + + + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 94] + +RFC 3550 RTP July 2003 + + +Appendix B - Changes from RFC 1889 + + Most of this RFC is identical to RFC 1889. There are no changes in + the packet formats on the wire, only changes to the rules and + algorithms governing how the protocol is used. The biggest change is + an enhancement to the scalable timer algorithm for calculating when + to send RTCP packets: + + o The algorithm for calculating the RTCP transmission interval + specified in Sections 6.2 and 6.3 and illustrated in Appendix A.7 + is augmented to include "reconsideration" to minimize transmission + in excess of the intended rate when many participants join a + session simultaneously, and "reverse reconsideration" to reduce + the incidence and duration of false participant timeouts when the + number of participants drops rapidly. Reverse reconsideration is + also used to possibly shorten the delay before sending RTCP SR + when transitioning from passive receiver to active sender mode. + + o Section 6.3.7 specifies new rules controlling when an RTCP BYE + packet should be sent in order to avoid a flood of packets when + many participants leave a session simultaneously. + + o The requirement to retain state for inactive participants for a + period long enough to span typical network partitions was removed + from Section 6.2.1. In a session where many participants join for + a brief time and fail to send BYE, this requirement would cause a + significant overestimate of the number of participants. The + reconsideration algorithm added in this revision compensates for + the large number of new participants joining simultaneously when a + partition heals. + + It should be noted that these enhancements only have a significant + effect when the number of session participants is large (thousands) + and most of the participants join or leave at the same time. This + makes testing in a live network difficult. However, the algorithm + was subjected to a thorough analysis and simulation to verify its + performance. Furthermore, the enhanced algorithm was designed to + interoperate with the algorithm in RFC 1889 such that the degree of + reduction in excess RTCP bandwidth during a step join is proportional + to the fraction of participants that implement the enhanced + algorithm. Interoperation of the two algorithms has been verified + experimentally on live networks. + + Other functional changes were: + + o Section 6.2.1 specifies that implementations may store only a + sampling of the participants' SSRC identifiers to allow scaling to + very large sessions. Algorithms are specified in RFC 2762 [21]. + + + +Schulzrinne, et al. Standards Track [Page 95] + +RFC 3550 RTP July 2003 + + + o In Section 6.2 it is specified that RTCP sender and non-sender + bandwidths may be set as separate parameters of the session rather + than a strict percentage of the session bandwidth, and may be set + to zero. The requirement that RTCP was mandatory for RTP sessions + using IP multicast was relaxed. However, a clarification was also + added that turning off RTCP is NOT RECOMMENDED. + + o In Sections 6.2, 6.3.1 and Appendix A.7, it is specified that the + fraction of participants below which senders get dedicated RTCP + bandwidth changes from the fixed 1/4 to a ratio based on the RTCP + sender and non-sender bandwidth parameters when those are given. + The condition that no bandwidth is dedicated to senders when there + are no senders was removed since that is expected to be a + transitory state. It also keeps non-senders from using sender + RTCP bandwidth when that is not intended. + + o Also in Section 6.2 it is specified that the minimum RTCP interval + may be scaled to smaller values for high bandwidth sessions, and + that the initial RTCP delay may be set to zero for unicast + sessions. + + o Timing out a participant is to be based on inactivity for a number + of RTCP report intervals calculated using the receiver RTCP + bandwidth fraction even for active senders. + + o Sections 7.2 and 7.3 specify that translators and mixers should + send BYE packets for the sources they are no longer forwarding. + + o Rule changes for layered encodings are defined in Sections 2.4, + 6.3.9, 8.3 and 11. In the last of these, it is noted that the + address and port assignment rule conflicts with the SDP + specification, RFC 2327 [15], but it is intended that this + restriction will be relaxed in a revision of RFC 2327. + + o The convention for using even/odd port pairs for RTP and RTCP in + Section 11 was clarified to refer to destination ports. The + requirement to use an even/odd port pair was removed if the two + ports are specified explicitly. For unicast RTP sessions, + distinct port pairs may be used for the two ends (Sections 3, 7.1 + and 11). + + o A new Section 10 was added to explain the requirement for + congestion control in applications using RTP. + + o In Section 8.2, the requirement that a new SSRC identifier MUST be + chosen whenever the source transport address is changed has been + relaxed to say that a new SSRC identifier MAY be chosen. + Correspondingly, it was clarified that an implementation MAY + + + +Schulzrinne, et al. Standards Track [Page 96] + +RFC 3550 RTP July 2003 + + + choose to keep packets from the new source address rather than the + existing source address when an SSRC collision occurs between two + other participants, and SHOULD do so for applications such as + telephony in which some sources such as mobile entities may change + addresses during the course of an RTP session. + + o An indentation bug in the RFC 1889 printing of the pseudo-code for + the collision detection and resolution algorithm in Section 8.2 + has been corrected by translating the syntax to pseudo C language, + and the algorithm has been modified to remove the restriction that + both RTP and RTCP must be sent from the same source port number. + + o The description of the padding mechanism for RTCP packets was + clarified and it is specified that padding MUST only be applied to + the last packet of a compound RTCP packet. + + o In Section A.1, initialization of base_seq was corrected to be seq + rather than seq - 1, and the text was corrected to say the bad + sequence number plus 1 is stored. The initialization of max_seq + and other variables for the algorithm was separated from the text + to make clear that this initialization must be done in addition to + calling the init_seq() function (and a few words lost in RFC 1889 + when processing the document from source to output form were + restored). + + o Clamping of number of packets lost in Section A.3 was corrected to + use both positive and negative limits. + + o The specification of "relative" NTP timestamp in the RTCP SR + section now defines these timestamps to be based on the most + common system-specific clock, such as system uptime, rather than + on session elapsed time which would not be the same for multiple + applications started on the same machine at different times. + + Non-functional changes: + + o It is specified that a receiver MUST ignore packets with payload + types it does not understand. + + o In Fig. 2, the floating point NTP timestamp value was corrected, + some missing leading zeros were added in a hex number, and the UTC + timezone was specified. + + o The inconsequence of NTP timestamps wrapping around in the year + 2036 is explained. + + + + + + +Schulzrinne, et al. Standards Track [Page 97] + +RFC 3550 RTP July 2003 + + + o The policy for registration of RTCP packet types and SDES types + was clarified in a new Section 15, IANA Considerations. The + suggestion that experimenters register the numbers they need and + then unregister those which prove to be unneeded has been removed + in favor of using APP and PRIV. Registration of profile names was + also specified. + + o The reference for the UTF-8 character set was changed from an + X/Open Preliminary Specification to be RFC 2279. + + o The reference for RFC 1597 was updated to RFC 1918 and the + reference for RFC 2543 was updated to RFC 3261. + + o The last paragraph of the introduction in RFC 1889, which + cautioned implementors to limit deployment in the Internet, was + removed because it was deemed no longer relevant. + + o A non-normative note regarding the use of RTP with Source-Specific + Multicast (SSM) was added in Section 6. + + o The definition of "RTP session" in Section 3 was expanded to + acknowledge that a single session may use multiple destination + transport addresses (as was always the case for a translator or + mixer) and to explain that the distinguishing feature of an RTP + session is that each corresponds to a separate SSRC identifier + space. A new definition of "multimedia session" was added to + reduce confusion about the word "session". + + o The meaning of "sampling instant" was explained in more detail as + part of the definition of the timestamp field of the RTP header in + Section 5.1. + + o Small clarifications of the text have been made in several places, + some in response to questions from readers. In particular: + + - In RFC 1889, the first five words of the second sentence of + Section 2.2 were lost in processing the document from source to + output form, but are now restored. + + - A definition for "RTP media type" was added in Section 3 to + allow the explanation of multiplexing RTP sessions in Section + 5.2 to be more clear regarding the multiplexing of multiple + media. That section also now explains that multiplexing + multiple sources of the same medium based on SSRC identifiers + may be appropriate and is the norm for multicast sessions. + + - The definition for "non-RTP means" was expanded to include + examples of other protocols constituting non-RTP means. + + + +Schulzrinne, et al. Standards Track [Page 98] + +RFC 3550 RTP July 2003 + + + - The description of the session bandwidth parameter is expanded + in Section 6.2, including a clarification that the control + traffic bandwidth is in addition to the session bandwidth for + the data traffic. + + - The effect of varying packet duration on the jitter calculation + was explained in Section 6.4.4. + + - The method for terminating and padding a sequence of SDES items + was clarified in Section 6.5. + + - IPv6 address examples were added in the description of SDES + CNAME in Section 6.5.1, and "example.com" was used in place of + other example domain names. + + - The Security section added a formal reference to IPSEC now that + it is available, and says that the confidentiality method + defined in this specification is primarily to codify existing + practice. It is RECOMMENDED that stronger encryption + algorithms such as Triple-DES be used in place of the default + algorithm, and noted that the SRTP profile based on AES will be + the correct choice in the future. A caution about the weakness + of the RTP header as an initialization vector was added. It + was also noted that payload-only encryption is necessary to + allow for header compression. + + - The method for partial encryption of RTCP was clarified; in + particular, SDES CNAME is carried in only one part when the + compound RTCP packet is split. + + - It is clarified that only one compound RTCP packet should be + sent per reporting interval and that if there are too many + active sources for the reports to fit in the MTU, then a subset + of the sources should be selected round-robin over multiple + intervals. + + - A note was added in Appendix A.1 that packets may be saved + during RTP header validation and delivered upon success. + + - Section 7.3 now explains that a mixer aggregating SDES packets + uses more RTCP bandwidth due to longer packets, and a mixer + passing through RTCP naturally sends packets at higher than the + single source rate, but both behaviors are valid. + + - Section 13 clarifies that an RTP application may use multiple + profiles but typically only one in a given session. + + + + + +Schulzrinne, et al. Standards Track [Page 99] + +RFC 3550 RTP July 2003 + + + - The terms MUST, SHOULD, MAY, etc. are used as defined in RFC + 2119. + + - The bibliography was divided into normative and informative + references. + +References + +Normative References + + [1] Schulzrinne, H. and S. Casner, "RTP Profile for Audio and Video + Conferences with Minimal Control", RFC 3551, July 2003. + + [2] Bradner, S., "Key Words for Use in RFCs to Indicate Requirement + Levels", BCP 14, RFC 2119, March 1997. + + [3] Postel, J., "Internet Protocol", STD 5, RFC 791, September 1981. + + [4] Mills, D., "Network Time Protocol (Version 3) Specification, + Implementation and Analysis", RFC 1305, March 1992. + + [5] Yergeau, F., "UTF-8, a Transformation Format of ISO 10646", RFC + 2279, January 1998. + + [6] Mockapetris, P., "Domain Names - Concepts and Facilities", STD + 13, RFC 1034, November 1987. + + [7] Mockapetris, P., "Domain Names - Implementation and + Specification", STD 13, RFC 1035, November 1987. + + [8] Braden, R., "Requirements for Internet Hosts - Application and + Support", STD 3, RFC 1123, October 1989. + + [9] Resnick, P., "Internet Message Format", RFC 2822, April 2001. + +Informative References + + [10] Clark, D. and D. Tennenhouse, "Architectural Considerations for + a New Generation of Protocols," in SIGCOMM Symposium on + Communications Architectures and Protocols , (Philadelphia, + Pennsylvania), pp. 200--208, IEEE Computer Communications + Review, Vol. 20(4), September 1990. + + [11] Schulzrinne, H., "Issues in designing a transport protocol for + audio and video conferences and other multiparticipant real-time + applications." expired Internet Draft, October 1993. + + + + + +Schulzrinne, et al. Standards Track [Page 100] + +RFC 3550 RTP July 2003 + + + [12] Comer, D., Internetworking with TCP/IP , vol. 1. Englewood + Cliffs, New Jersey: Prentice Hall, 1991. + + [13] Rosenberg, J., Schulzrinne, H., Camarillo, G., Johnston, A., + Peterson, J., Sparks, R., Handley, M. and E. Schooler, "SIP: + Session Initiation Protocol", RFC 3261, June 2002. + + [14] International Telecommunication Union, "Visual telephone systems + and equipment for local area networks which provide a non- + guaranteed quality of service", Recommendation H.323, + Telecommunication Standardization Sector of ITU, Geneva, + Switzerland, July 2003. + + [15] Handley, M. and V. Jacobson, "SDP: Session Description + Protocol", RFC 2327, April 1998. + + [16] Schulzrinne, H., Rao, A. and R. Lanphier, "Real Time Streaming + Protocol (RTSP)", RFC 2326, April 1998. + + [17] Eastlake 3rd, D., Crocker, S. and J. Schiller, "Randomness + Recommendations for Security", RFC 1750, December 1994. + + [18] Bolot, J.-C., Turletti, T. and I. Wakeman, "Scalable Feedback + Control for Multicast Video Distribution in the Internet", in + SIGCOMM Symposium on Communications Architectures and Protocols, + (London, England), pp. 58--67, ACM, August 1994. + + [19] Busse, I., Deffner, B. and H. Schulzrinne, "Dynamic QoS Control + of Multimedia Applications Based on RTP", Computer + Communications , vol. 19, pp. 49--58, January 1996. + + [20] Floyd, S. and V. Jacobson, "The Synchronization of Periodic + Routing Messages", in SIGCOMM Symposium on Communications + Architectures and Protocols (D. P. Sidhu, ed.), (San Francisco, + California), pp. 33--44, ACM, September 1993. Also in [34]. + + [21] Rosenberg, J. and H. Schulzrinne, "Sampling of the Group + Membership in RTP", RFC 2762, February 2000. + + [22] Cadzow, J., Foundations of Digital Signal Processing and Data + Analysis New York, New York: Macmillan, 1987. + + [23] Hinden, R. and S. Deering, "Internet Protocol Version 6 (IPv6) + Addressing Architecture", RFC 3513, April 2003. + + [24] Rekhter, Y., Moskowitz, B., Karrenberg, D., de Groot, G. and E. + Lear, "Address Allocation for Private Internets", RFC 1918, + February 1996. + + + +Schulzrinne, et al. Standards Track [Page 101] + +RFC 3550 RTP July 2003 + + + [25] Lear, E., Fair, E., Crocker, D. and T. Kessler, "Network 10 + Considered Harmful (Some Practices Shouldn't be Codified)", RFC + 1627, July 1994. + + [26] Feller, W., An Introduction to Probability Theory and its + Applications, vol. 1. New York, New York: John Wiley and Sons, + third ed., 1968. + + [27] Kent, S. and R. Atkinson, "Security Architecture for the + Internet Protocol", RFC 2401, November 1998. + + [28] Baugher, M., Blom, R., Carrara, E., McGrew, D., Naslund, M., + Norrman, K. and D. Oran, "Secure Real-time Transport Protocol", + Work in Progress, April 2003. + + [29] Balenson, D., "Privacy Enhancement for Internet Electronic Mail: + Part III", RFC 1423, February 1993. + + [30] Voydock, V. and S. Kent, "Security Mechanisms in High-Level + Network Protocols", ACM Computing Surveys, vol. 15, pp. 135-171, + June 1983. + + [31] Floyd, S., "Congestion Control Principles", BCP 41, RFC 2914, + September 2000. + + [32] Rivest, R., "The MD5 Message-Digest Algorithm", RFC 1321, April + 1992. + + [33] Stubblebine, S., "Security Services for Multimedia + Conferencing", in 16th National Computer Security Conference, + (Baltimore, Maryland), pp. 391--395, September 1993. + + [34] Floyd, S. and V. Jacobson, "The Synchronization of Periodic + Routing Messages", IEEE/ACM Transactions on Networking, vol. 2, + pp. 122--136, April 1994. + + + + + + + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 102] + +RFC 3550 RTP July 2003 + + +Authors' Addresses + + Henning Schulzrinne + Department of Computer Science + Columbia University + 1214 Amsterdam Avenue + New York, NY 10027 + United States + + EMail: schulzrinne@cs.columbia.edu + + + Stephen L. Casner + Packet Design + 3400 Hillview Avenue, Building 3 + Palo Alto, CA 94304 + United States + + EMail: casner@acm.org + + + Ron Frederick + Blue Coat Systems Inc. + 650 Almanor Avenue + Sunnyvale, CA 94085 + United States + + EMail: ronf@bluecoat.com + + + Van Jacobson + Packet Design + 3400 Hillview Avenue, Building 3 + Palo Alto, CA 94304 + United States + + EMail: van@packetdesign.com + + + + + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 103] + +RFC 3550 RTP July 2003 + + +Full Copyright Statement + + Copyright (C) The Internet Society (2003). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Schulzrinne, et al. Standards Track [Page 104] + diff --git a/src/modules/rtp/rfc3551.txt b/src/modules/rtp/rfc3551.txt new file mode 100644 index 00000000..c43ff34d --- /dev/null +++ b/src/modules/rtp/rfc3551.txt @@ -0,0 +1,2467 @@ + + + + + + +Network Working Group H. Schulzrinne +Request for Comments: 3551 Columbia University +Obsoletes: 1890 S. Casner +Category: Standards Track Packet Design + July 2003 + + + RTP Profile for Audio and Video Conferences + with Minimal Control + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (2003). All Rights Reserved. + +Abstract + + This document describes a profile called "RTP/AVP" for the use of the + real-time transport protocol (RTP), version 2, and the associated + control protocol, RTCP, within audio and video multiparticipant + conferences with minimal control. It provides interpretations of + generic fields within the RTP specification suitable for audio and + video conferences. In particular, this document defines a set of + default mappings from payload type numbers to encodings. + + This document also describes how audio and video data may be carried + within RTP. It defines a set of standard encodings and their names + when used within RTP. The descriptions provide pointers to reference + implementations and the detailed standards. This document is meant + as an aid for implementors of audio, video and other real-time + multimedia applications. + + This memorandum obsoletes RFC 1890. It is mostly backwards- + compatible except for functions removed because two interoperable + implementations were not found. The additions to RFC 1890 codify + existing practice in the use of payload formats under this profile + and include new payload formats defined since RFC 1890 was published. + + + + + + + +Schulzrinne & Casner Standards Track [Page 1] + +RFC 3551 RTP A/V Profile July 2003 + + +Table of Contents + + 1. Introduction ................................................. 3 + 1.1 Terminology ............................................. 3 + 2. RTP and RTCP Packet Forms and Protocol Behavior .............. 4 + 3. Registering Additional Encodings ............................. 6 + 4. Audio ........................................................ 8 + 4.1 Encoding-Independent Rules .............................. 8 + 4.2 Operating Recommendations ............................... 9 + 4.3 Guidelines for Sample-Based Audio Encodings ............. 10 + 4.4 Guidelines for Frame-Based Audio Encodings .............. 11 + 4.5 Audio Encodings ......................................... 12 + 4.5.1 DVI4 ............................................ 13 + 4.5.2 G722 ............................................ 14 + 4.5.3 G723 ............................................ 14 + 4.5.4 G726-40, G726-32, G726-24, and G726-16 .......... 18 + 4.5.5 G728 ............................................ 19 + 4.5.6 G729 ............................................ 20 + 4.5.7 G729D and G729E ................................. 22 + 4.5.8 GSM ............................................. 24 + 4.5.9 GSM-EFR ......................................... 27 + 4.5.10 L8 .............................................. 27 + 4.5.11 L16 ............................................. 27 + 4.5.12 LPC ............................................. 27 + 4.5.13 MPA ............................................. 28 + 4.5.14 PCMA and PCMU ................................... 28 + 4.5.15 QCELP ........................................... 28 + 4.5.16 RED ............................................. 29 + 4.5.17 VDVI ............................................ 29 + 5. Video ........................................................ 30 + 5.1 CelB .................................................... 30 + 5.2 JPEG .................................................... 30 + 5.3 H261 .................................................... 30 + 5.4 H263 .................................................... 31 + 5.5 H263-1998 ............................................... 31 + 5.6 MPV ..................................................... 31 + 5.7 MP2T .................................................... 31 + 5.8 nv ...................................................... 32 + 6. Payload Type Definitions ..................................... 32 + 7. RTP over TCP and Similar Byte Stream Protocols ............... 34 + 8. Port Assignment .............................................. 34 + 9. Changes from RFC 1890 ........................................ 35 + 10. Security Considerations ...................................... 38 + 11. IANA Considerations .......................................... 39 + 12. References ................................................... 39 + 12.1 Normative References .................................... 39 + 12.2 Informative References .................................. 39 + 13. Current Locations of Related Resources ....................... 41 + + + +Schulzrinne & Casner Standards Track [Page 2] + +RFC 3551 RTP A/V Profile July 2003 + + + 14. Acknowledgments .............................................. 42 + 15. Intellectual Property Rights Statement ....................... 43 + 16. Authors' Addresses ........................................... 43 + 17. Full Copyright Statement ..................................... 44 + +1. Introduction + + This profile defines aspects of RTP left unspecified in the RTP + Version 2 protocol definition (RFC 3550) [1]. This profile is + intended for the use within audio and video conferences with minimal + session control. In particular, no support for the negotiation of + parameters or membership control is provided. The profile is + expected to be useful in sessions where no negotiation or membership + control are used (e.g., using the static payload types and the + membership indications provided by RTCP), but this profile may also + be useful in conjunction with a higher-level control protocol. + + Use of this profile may be implicit in the use of the appropriate + applications; there may be no explicit indication by port number, + protocol identifier or the like. Applications such as session + directories may use the name for this profile specified in Section + 11. + + Other profiles may make different choices for the items specified + here. + + This document also defines a set of encodings and payload formats for + audio and video. These payload format descriptions are included here + only as a matter of convenience since they are too small to warrant + separate documents. Use of these payload formats is NOT REQUIRED to + use this profile. Only the binding of some of the payload formats to + static payload type numbers in Tables 4 and 5 is normative. + +1.1 Terminology + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this + document are to be interpreted as described in RFC 2119 [2] and + indicate requirement levels for implementations compliant with this + RTP profile. + + This document defines the term media type as dividing encodings of + audio and video content into three classes: audio, video and + audio/video (interleaved). + + + + + + + +Schulzrinne & Casner Standards Track [Page 3] + +RFC 3551 RTP A/V Profile July 2003 + + +2. RTP and RTCP Packet Forms and Protocol Behavior + + The section "RTP Profiles and Payload Format Specifications" of RFC + 3550 enumerates a number of items that can be specified or modified + in a profile. This section addresses these items. Generally, this + profile follows the default and/or recommended aspects of the RTP + specification. + + RTP data header: The standard format of the fixed RTP data + header is used (one marker bit). + + Payload types: Static payload types are defined in Section 6. + + RTP data header additions: No additional fixed fields are + appended to the RTP data header. + + RTP data header extensions: No RTP header extensions are + defined, but applications operating under this profile MAY use + such extensions. Thus, applications SHOULD NOT assume that the + RTP header X bit is always zero and SHOULD be prepared to ignore + the header extension. If a header extension is defined in the + future, that definition MUST specify the contents of the first 16 + bits in such a way that multiple different extensions can be + identified. + + RTCP packet types: No additional RTCP packet types are defined + by this profile specification. + + RTCP report interval: The suggested constants are to be used for + the RTCP report interval calculation. Sessions operating under + this profile MAY specify a separate parameter for the RTCP traffic + bandwidth rather than using the default fraction of the session + bandwidth. The RTCP traffic bandwidth MAY be divided into two + separate session parameters for those participants which are + active data senders and those which are not. Following the + recommendation in the RTP specification [1] that 1/4 of the RTCP + bandwidth be dedicated to data senders, the RECOMMENDED default + values for these two parameters would be 1.25% and 3.75%, + respectively. For a particular session, the RTCP bandwidth for + non-data-senders MAY be set to zero when operating on + unidirectional links or for sessions that don't require feedback + on the quality of reception. The RTCP bandwidth for data senders + SHOULD be kept non-zero so that sender reports can still be sent + for inter-media synchronization and to identify the source by + CNAME. The means by which the one or two session parameters for + RTCP bandwidth are specified is beyond the scope of this memo. + + + + + +Schulzrinne & Casner Standards Track [Page 4] + +RFC 3551 RTP A/V Profile July 2003 + + + SR/RR extension: No extension section is defined for the RTCP SR + or RR packet. + + SDES use: Applications MAY use any of the SDES items described + in the RTP specification. While CNAME information MUST be sent + every reporting interval, other items SHOULD only be sent every + third reporting interval, with NAME sent seven out of eight times + within that slot and the remaining SDES items cyclically taking up + the eighth slot, as defined in Section 6.2.2 of the RTP + specification. In other words, NAME is sent in RTCP packets 1, 4, + 7, 10, 13, 16, 19, while, say, EMAIL is used in RTCP packet 22. + + Security: The RTP default security services are also the default + under this profile. + + String-to-key mapping: No mapping is specified by this profile. + + Congestion: RTP and this profile may be used in the context of + enhanced network service, for example, through Integrated Services + (RFC 1633) [4] or Differentiated Services (RFC 2475) [5], or they + may be used with best effort service. + + If enhanced service is being used, RTP receivers SHOULD monitor + packet loss to ensure that the service that was requested is + actually being delivered. If it is not, then they SHOULD assume + that they are receiving best-effort service and behave + accordingly. + + If best-effort service is being used, RTP receivers SHOULD monitor + packet loss to ensure that the packet loss rate is within + acceptable parameters. Packet loss is considered acceptable if a + TCP flow across the same network path and experiencing the same + network conditions would achieve an average throughput, measured + on a reasonable timescale, that is not less than the RTP flow is + achieving. This condition can be satisfied by implementing + congestion control mechanisms to adapt the transmission rate (or + the number of layers subscribed for a layered multicast session), + or by arranging for a receiver to leave the session if the loss + rate is unacceptably high. + + The comparison to TCP cannot be specified exactly, but is intended + as an "order-of-magnitude" comparison in timescale and throughput. + The timescale on which TCP throughput is measured is the round- + trip time of the connection. In essence, this requirement states + that it is not acceptable to deploy an application (using RTP or + any other transport protocol) on the best-effort Internet which + consumes bandwidth arbitrarily and does not compete fairly with + TCP within an order of magnitude. + + + +Schulzrinne & Casner Standards Track [Page 5] + +RFC 3551 RTP A/V Profile July 2003 + + + Underlying protocol: The profile specifies the use of RTP over + unicast and multicast UDP as well as TCP. (This does not preclude + the use of these definitions when RTP is carried by other lower- + layer protocols.) + + Transport mapping: The standard mapping of RTP and RTCP to + transport-level addresses is used. + + Encapsulation: This profile leaves to applications the + specification of RTP encapsulation in protocols other than UDP. + +3. Registering Additional Encodings + + This profile lists a set of encodings, each of which is comprised of + a particular media data compression or representation plus a payload + format for encapsulation within RTP. Some of those payload formats + are specified here, while others are specified in separate RFCs. It + is expected that additional encodings beyond the set listed here will + be created in the future and specified in additional payload format + RFCs. + + This profile also assigns to each encoding a short name which MAY be + used by higher-level control protocols, such as the Session + Description Protocol (SDP), RFC 2327 [6], to identify encodings + selected for a particular RTP session. + + In some contexts it may be useful to refer to these encodings in the + form of a MIME content-type. To facilitate this, RFC 3555 [7] + provides registrations for all of the encodings names listed here as + MIME subtype names under the "audio" and "video" MIME types through + the MIME registration procedure as specified in RFC 2048 [8]. + + Any additional encodings specified for use under this profile (or + others) may also be assigned names registered as MIME subtypes with + the Internet Assigned Numbers Authority (IANA). This registry + provides a means to insure that the names assigned to the additional + encodings are kept unique. RFC 3555 specifies the information that + is required for the registration of RTP encodings. + + In addition to assigning names to encodings, this profile also + assigns static RTP payload type numbers to some of them. However, + the payload type number space is relatively small and cannot + accommodate assignments for all existing and future encodings. + During the early stages of RTP development, it was necessary to use + statically assigned payload types because no other mechanism had been + specified to bind encodings to payload types. It was anticipated + that non-RTP means beyond the scope of this memo (such as directory + services or invitation protocols) would be specified to establish a + + + +Schulzrinne & Casner Standards Track [Page 6] + +RFC 3551 RTP A/V Profile July 2003 + + + dynamic mapping between a payload type and an encoding. Now, + mechanisms for defining dynamic payload type bindings have been + specified in the Session Description Protocol (SDP) and in other + protocols such as ITU-T Recommendation H.323/H.245. These mechanisms + associate the registered name of the encoding/payload format, along + with any additional required parameters, such as the RTP timestamp + clock rate and number of channels, with a payload type number. This + association is effective only for the duration of the RTP session in + which the dynamic payload type binding is made. This association + applies only to the RTP session for which it is made, thus the + numbers can be re-used for different encodings in different sessions + so the number space limitation is avoided. + + This profile reserves payload type numbers in the range 96-127 + exclusively for dynamic assignment. Applications SHOULD first use + values in this range for dynamic payload types. Those applications + which need to define more than 32 dynamic payload types MAY bind + codes below 96, in which case it is RECOMMENDED that unassigned + payload type numbers be used first. However, the statically assigned + payload types are default bindings and MAY be dynamically bound to + new encodings if needed. Redefining payload types below 96 may cause + incorrect operation if an attempt is made to join a session without + obtaining session description information that defines the dynamic + payload types. + + Dynamic payload types SHOULD NOT be used without a well-defined + mechanism to indicate the mapping. Systems that expect to + interoperate with others operating under this profile SHOULD NOT make + their own assignments of proprietary encodings to particular, fixed + payload types. + + This specification establishes the policy that no additional static + payload types will be assigned beyond the ones defined in this + document. Establishing this policy avoids the problem of trying to + create a set of criteria for accepting static assignments and + encourages the implementation and deployment of the dynamic payload + type mechanisms. + + The final set of static payload type assignments is provided in + Tables 4 and 5. + + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 7] + +RFC 3551 RTP A/V Profile July 2003 + + +4. Audio + +4.1 Encoding-Independent Rules + + Since the ability to suppress silence is one of the primary + motivations for using packets to transmit voice, the RTP header + carries both a sequence number and a timestamp to allow a receiver to + distinguish between lost packets and periods of time when no data was + transmitted. Discontiguous transmission (silence suppression) MAY be + used with any audio payload format. Receivers MUST assume that + senders may suppress silence unless this is restricted by signaling + specified elsewhere. (Even if the transmitter does not suppress + silence, the receiver should be prepared to handle periods when no + data is present since packets may be lost.) + + Some payload formats (see Sections 4.5.3 and 4.5.6) define a "silence + insertion descriptor" or "comfort noise" frame to specify parameters + for artificial noise that may be generated during a period of silence + to approximate the background noise at the source. For other payload + formats, a generic Comfort Noise (CN) payload format is specified in + RFC 3389 [9]. When the CN payload format is used with another + payload format, different values in the RTP payload type field + distinguish comfort-noise packets from those of the selected payload + format. + + For applications which send either no packets or occasional comfort- + noise packets during silence, the first packet of a talkspurt, that + is, the first packet after a silence period during which packets have + not been transmitted contiguously, SHOULD be distinguished by setting + the marker bit in the RTP data header to one. The marker bit in all + other packets is zero. The beginning of a talkspurt MAY be used to + adjust the playout delay to reflect changing network delays. + Applications without silence suppression MUST set the marker bit to + zero. + + The RTP clock rate used for generating the RTP timestamp is + independent of the number of channels and the encoding; it usually + equals the number of sampling periods per second. For N-channel + encodings, each sampling period (say, 1/8,000 of a second) generates + N samples. (This terminology is standard, but somewhat confusing, as + the total number of samples generated per second is then the sampling + rate times the channel count.) + + If multiple audio channels are used, channels are numbered left-to- + right, starting at one. In RTP audio packets, information from + lower-numbered channels precedes that from higher-numbered channels. + + + + + +Schulzrinne & Casner Standards Track [Page 8] + +RFC 3551 RTP A/V Profile July 2003 + + + For more than two channels, the convention followed by the AIFF-C + audio interchange format SHOULD be followed [3], using the following + notation, unless some other convention is specified for a particular + encoding or payload format: + + l left + r right + c center + S surround + F front + R rear + + channels description channel + 1 2 3 4 5 6 + _________________________________________________ + 2 stereo l r + 3 l r c + 4 l c r S + 5 Fl Fr Fc Sl Sr + 6 l lc c r rc S + + Note: RFC 1890 defined two conventions for the ordering of four + audio channels. Since the ordering is indicated implicitly by + the number of channels, this was ambiguous. In this revision, + the order described as "quadrophonic" has been eliminated to + remove the ambiguity. This choice was based on the observation + that quadrophonic consumer audio format did not become popular + whereas surround-sound subsequently has. + + Samples for all channels belonging to a single sampling instant MUST + be within the same packet. The interleaving of samples from + different channels depends on the encoding. General guidelines are + given in Section 4.3 and 4.4. + + The sampling frequency SHOULD be drawn from the set: 8,000, 11,025, + 16,000, 22,050, 24,000, 32,000, 44,100 and 48,000 Hz. (Older Apple + Macintosh computers had a native sample rate of 22,254.54 Hz, which + can be converted to 22,050 with acceptable quality by dropping 4 + samples in a 20 ms frame.) However, most audio encodings are defined + for a more restricted set of sampling frequencies. Receivers SHOULD + be prepared to accept multi-channel audio, but MAY choose to only + play a single channel. + +4.2 Operating Recommendations + + The following recommendations are default operating parameters. + Applications SHOULD be prepared to handle other values. The ranges + given are meant to give guidance to application writers, allowing a + + + +Schulzrinne & Casner Standards Track [Page 9] + +RFC 3551 RTP A/V Profile July 2003 + + + set of applications conforming to these guidelines to interoperate + without additional negotiation. These guidelines are not intended to + restrict operating parameters for applications that can negotiate a + set of interoperable parameters, e.g., through a conference control + protocol. + + For packetized audio, the default packetization interval SHOULD have + a duration of 20 ms or one frame, whichever is longer, unless + otherwise noted in Table 1 (column "ms/packet"). The packetization + interval determines the minimum end-to-end delay; longer packets + introduce less header overhead but higher delay and make packet loss + more noticeable. For non-interactive applications such as lectures + or for links with severe bandwidth constraints, a higher + packetization delay MAY be used. A receiver SHOULD accept packets + representing between 0 and 200 ms of audio data. (For framed audio + encodings, a receiver SHOULD accept packets with a number of frames + equal to 200 ms divided by the frame duration, rounded up.) This + restriction allows reasonable buffer sizing for the receiver. + +4.3 Guidelines for Sample-Based Audio Encodings + + In sample-based encodings, each audio sample is represented by a + fixed number of bits. Within the compressed audio data, codes for + individual samples may span octet boundaries. An RTP audio packet + may contain any number of audio samples, subject to the constraint + that the number of bits per sample times the number of samples per + packet yields an integral octet count. Fractional encodings produce + less than one octet per sample. + + The duration of an audio packet is determined by the number of + samples in the packet. + + For sample-based encodings producing one or more octets per sample, + samples from different channels sampled at the same sampling instant + SHOULD be packed in consecutive octets. For example, for a two- + channel encoding, the octet sequence is (left channel, first sample), + (right channel, first sample), (left channel, second sample), (right + channel, second sample), .... For multi-octet encodings, octets + SHOULD be transmitted in network byte order (i.e., most significant + octet first). + + The packing of sample-based encodings producing less than one octet + per sample is encoding-specific. + + The RTP timestamp reflects the instant at which the first sample in + the packet was sampled, that is, the oldest information in the + packet. + + + + +Schulzrinne & Casner Standards Track [Page 10] + +RFC 3551 RTP A/V Profile July 2003 + + +4.4 Guidelines for Frame-Based Audio Encodings + + Frame-based encodings encode a fixed-length block of audio into + another block of compressed data, typically also of fixed length. + For frame-based encodings, the sender MAY choose to combine several + such frames into a single RTP packet. The receiver can tell the + number of frames contained in an RTP packet, if all the frames have + the same length, by dividing the RTP payload length by the audio + frame size which is defined as part of the encoding. This does not + work when carrying frames of different sizes unless the frame sizes + are relatively prime. If not, the frames MUST indicate their size. + + For frame-based codecs, the channel order is defined for the whole + block. That is, for two-channel audio, right and left samples SHOULD + be coded independently, with the encoded frame for the left channel + preceding that for the right channel. + + All frame-oriented audio codecs SHOULD be able to encode and decode + several consecutive frames within a single packet. Since the frame + size for the frame-oriented codecs is given, there is no need to use + a separate designation for the same encoding, but with different + number of frames per packet. + + RTP packets SHALL contain a whole number of frames, with frames + inserted according to age within a packet, so that the oldest frame + (to be played first) occurs immediately after the RTP packet header. + The RTP timestamp reflects the instant at which the first sample in + the first frame was sampled, that is, the oldest information in the + packet. + + + + + + + + + + + + + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 11] + +RFC 3551 RTP A/V Profile July 2003 + + +4.5 Audio Encodings + + name of sampling default + encoding sample/frame bits/sample rate ms/frame ms/packet + __________________________________________________________________ + DVI4 sample 4 var. 20 + G722 sample 8 16,000 20 + G723 frame N/A 8,000 30 30 + G726-40 sample 5 8,000 20 + G726-32 sample 4 8,000 20 + G726-24 sample 3 8,000 20 + G726-16 sample 2 8,000 20 + G728 frame N/A 8,000 2.5 20 + G729 frame N/A 8,000 10 20 + G729D frame N/A 8,000 10 20 + G729E frame N/A 8,000 10 20 + GSM frame N/A 8,000 20 20 + GSM-EFR frame N/A 8,000 20 20 + L8 sample 8 var. 20 + L16 sample 16 var. 20 + LPC frame N/A 8,000 20 20 + MPA frame N/A var. var. + PCMA sample 8 var. 20 + PCMU sample 8 var. 20 + QCELP frame N/A 8,000 20 20 + VDVI sample var. var. 20 + + Table 1: Properties of Audio Encodings (N/A: not applicable; var.: + variable) + + The characteristics of the audio encodings described in this document + are shown in Table 1; they are listed in order of their payload type + in Table 4. While most audio codecs are only specified for a fixed + sampling rate, some sample-based algorithms (indicated by an entry of + "var." in the sampling rate column of Table 1) may be used with + different sampling rates, resulting in different coded bit rates. + When used with a sampling rate other than that for which a static + payload type is defined, non-RTP means beyond the scope of this memo + MUST be used to define a dynamic payload type and MUST indicate the + selected RTP timestamp clock rate, which is usually the same as the + sampling rate for audio. + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 12] + +RFC 3551 RTP A/V Profile July 2003 + + +4.5.1 DVI4 + + DVI4 uses an adaptive delta pulse code modulation (ADPCM) encoding + scheme that was specified by the Interactive Multimedia Association + (IMA) as the "IMA ADPCM wave type". However, the encoding defined + here as DVI4 differs in three respects from the IMA specification: + + o The RTP DVI4 header contains the predicted value rather than the + first sample value contained the IMA ADPCM block header. + + o IMA ADPCM blocks contain an odd number of samples, since the first + sample of a block is contained just in the header (uncompressed), + followed by an even number of compressed samples. DVI4 has an + even number of compressed samples only, using the `predict' word + from the header to decode the first sample. + + o For DVI4, the 4-bit samples are packed with the first sample in + the four most significant bits and the second sample in the four + least significant bits. In the IMA ADPCM codec, the samples are + packed in the opposite order. + + Each packet contains a single DVI block. This profile only defines + the 4-bit-per-sample version, while IMA also specified a 3-bit-per- + sample encoding. + + The "header" word for each channel has the following structure: + + int16 predict; /* predicted value of first sample + from the previous block (L16 format) */ + u_int8 index; /* current index into stepsize table */ + u_int8 reserved; /* set to zero by sender, ignored by receiver */ + + Each octet following the header contains two 4-bit samples, thus the + number of samples per packet MUST be even because there is no means + to indicate a partially filled last octet. + + Packing of samples for multiple channels is for further study. + + The IMA ADPCM algorithm was described in the document IMA Recommended + Practices for Enhancing Digital Audio Compatibility in Multimedia + Systems (version 3.0). However, the Interactive Multimedia + Association ceased operations in 1997. Resources for an archived + copy of that document and a software implementation of the RTP DVI4 + encoding are listed in Section 13. + + + + + + + +Schulzrinne & Casner Standards Track [Page 13] + +RFC 3551 RTP A/V Profile July 2003 + + +4.5.2 G722 + + G722 is specified in ITU-T Recommendation G.722, "7 kHz audio-coding + within 64 kbit/s". The G.722 encoder produces a stream of octets, + each of which SHALL be octet-aligned in an RTP packet. The first bit + transmitted in the G.722 octet, which is the most significant bit of + the higher sub-band sample, SHALL correspond to the most significant + bit of the octet in the RTP packet. + + Even though the actual sampling rate for G.722 audio is 16,000 Hz, + the RTP clock rate for the G722 payload format is 8,000 Hz because + that value was erroneously assigned in RFC 1890 and must remain + unchanged for backward compatibility. The octet rate or sample-pair + rate is 8,000 Hz. + +4.5.3 G723 + + G723 is specified in ITU Recommendation G.723.1, "Dual-rate speech + coder for multimedia communications transmitting at 5.3 and 6.3 + kbit/s". The G.723.1 5.3/6.3 kbit/s codec was defined by the ITU-T + as a mandatory codec for ITU-T H.324 GSTN videophone terminal + applications. The algorithm has a floating point specification in + Annex B to G.723.1, a silence compression algorithm in Annex A to + G.723.1 and a scalable channel coding scheme for wireless + applications in G.723.1 Annex C. + + This Recommendation specifies a coded representation that can be used + for compressing the speech signal component of multi-media services + at a very low bit rate. Audio is encoded in 30 ms frames, with an + additional delay of 7.5 ms due to look-ahead. A G.723.1 frame can be + one of three sizes: 24 octets (6.3 kb/s frame), 20 octets (5.3 kb/s + frame), or 4 octets. These 4-octet frames are called SID frames + (Silence Insertion Descriptor) and are used to specify comfort noise + parameters. There is no restriction on how 4, 20, and 24 octet + frames are intermixed. The least significant two bits of the first + octet in the frame determine the frame size and codec type: + + bits content octets/frame + 00 high-rate speech (6.3 kb/s) 24 + 01 low-rate speech (5.3 kb/s) 20 + 10 SID frame 4 + 11 reserved + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 14] + +RFC 3551 RTP A/V Profile July 2003 + + + It is possible to switch between the two rates at any 30 ms frame + boundary. Both (5.3 kb/s and 6.3 kb/s) rates are a mandatory part of + the encoder and decoder. Receivers MUST accept both data rates and + MUST accept SID frames unless restriction of these capabilities has + been signaled. The MIME registration for G723 in RFC 3555 [7] + specifies parameters that MAY be used with MIME or SDP to restrict to + a single data rate or to restrict the use of SID frames. This coder + was optimized to represent speech with near-toll quality at the above + rates using a limited amount of complexity. + + The packing of the encoded bit stream into octets and the + transmission order of the octets is specified in Rec. G.723.1 and is + the same as that produced by the G.723 C code reference + implementation. For the 6.3 kb/s data rate, this packing is + illustrated as follows, where the header (HDR) bits are always "0 0" + as shown in Fig. 1 to indicate operation at 6.3 kb/s, and the Z bit + is always set to zero. The diagrams show the bit packing in "network + byte order", also known as big-endian order. The bits of each 32-bit + word are numbered 0 to 31, with the most significant bit on the left + and numbered 0. The octets (bytes) of each word are transmitted most + significant octet first. The bits of each data field are numbered in + the order of the bit stream representation of the encoding (least + significant bit first). The vertical bars indicate the boundaries + between field fragments. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 15] + +RFC 3551 RTP A/V Profile July 2003 + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | LPC |HDR| LPC | LPC | ACL0 |LPC| + | | | | | | | + |0 0 0 0 0 0|0 0|1 1 1 1 0 0 0 0|2 2 1 1 1 1 1 1|0 0 0 0 0 0|2 2| + |5 4 3 2 1 0| |3 2 1 0 9 8 7 6|1 0 9 8 7 6 5 4|5 4 3 2 1 0|3 2| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ACL2 |ACL|A| GAIN0 |ACL|ACL| GAIN0 | GAIN1 | + | | 1 |C| | 3 | 2 | | | + |0 0 0 0 0|0 0|0|0 0 0 0|0 0|0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0| + |4 3 2 1 0|1 0|6|3 2 1 0|1 0|6 5|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | GAIN2 | GAIN1 | GAIN2 | GAIN3 | GRID | GAIN3 | + | | | | | | | + |0 0 0 0|1 1 0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0|1 1 0 0| + |3 2 1 0|1 0 9 8|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0|3 2 1 0|1 0 9 8| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | MSBPOS |Z|POS| MSBPOS | POS0 |POS| POS0 | + | | | 0 | | | 1 | | + |0 0 0 0 0 0 0|0|0 0|1 1 1 0 0 0|0 0 0 0 0 0 0 0|0 0|1 1 1 1 1 1| + |6 5 4 3 2 1 0| |1 0|2 1 0 9 8 7|9 8 7 6 5 4 3 2|1 0|5 4 3 2 1 0| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | POS1 | POS2 | POS1 | POS2 | POS3 | POS2 | + | | | | | | | + |0 0 0 0 0 0 0 0|0 0 0 0|1 1 1 1|1 1 0 0 0 0 0 0|0 0 0 0|1 1 1 1| + |9 8 7 6 5 4 3 2|3 2 1 0|3 2 1 0|1 0 9 8 7 6 5 4|3 2 1 0|5 4 3 2| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | POS3 | PSIG0 |POS|PSIG2| PSIG1 | PSIG3 |PSIG2| + | | | 3 | | | | | + |1 1 0 0 0 0 0 0|0 0 0 0 0 0|1 1|0 0 0|0 0 0 0 0|0 0 0 0 0|0 0 0| + |1 0 9 8 7 6 5 4|5 4 3 2 1 0|3 2|2 1 0|4 3 2 1 0|4 3 2 1 0|5 4 3| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 1: G.723 (6.3 kb/s) bit packing + + For the 5.3 kb/s data rate, the header (HDR) bits are always "0 1", + as shown in Fig. 2, to indicate operation at 5.3 kb/s. + + + + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 16] + +RFC 3551 RTP A/V Profile July 2003 + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | LPC |HDR| LPC | LPC | ACL0 |LPC| + | | | | | | | + |0 0 0 0 0 0|0 1|1 1 1 1 0 0 0 0|2 2 1 1 1 1 1 1|0 0 0 0 0 0|2 2| + |5 4 3 2 1 0| |3 2 1 0 9 8 7 6|1 0 9 8 7 6 5 4|5 4 3 2 1 0|3 2| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | ACL2 |ACL|A| GAIN0 |ACL|ACL| GAIN0 | GAIN1 | + | | 1 |C| | 3 | 2 | | | + |0 0 0 0 0|0 0|0|0 0 0 0|0 0|0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0| + |4 3 2 1 0|1 0|6|3 2 1 0|1 0|6 5|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | GAIN2 | GAIN1 | GAIN2 | GAIN3 | GRID | GAIN3 | + | | | | | | | + |0 0 0 0|1 1 0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0|0 0 0 0|1 1 0 0| + |3 2 1 0|1 0 9 8|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0|4 3 2 1|1 0 9 8| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | POS0 | POS1 | POS0 | POS1 | POS2 | + | | | | | | + |0 0 0 0 0 0 0 0|0 0 0 0|1 1 0 0|1 1 0 0 0 0 0 0|0 0 0 0 0 0 0 0| + |7 6 5 4 3 2 1 0|3 2 1 0|1 0 9 8|1 0 9 8 7 6 5 4|7 6 5 4 3 2 1 0| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | POS3 | POS2 | POS3 | PSIG1 | PSIG0 | PSIG3 | PSIG2 | + | | | | | | | | + |0 0 0 0|1 1 0 0|1 1 0 0 0 0 0 0|0 0 0 0|0 0 0 0|0 0 0 0|0 0 0 0| + |3 2 1 0|1 0 9 8|1 0 9 8 7 6 5 4|3 2 1 0|3 2 1 0|3 2 1 0|3 2 1 0| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 2: G.723 (5.3 kb/s) bit packing + + The packing of G.723.1 SID (silence) frames, which are indicated by + the header (HDR) bits having the pattern "1 0", is depicted in Fig. + 3. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | LPC |HDR| LPC | LPC | GAIN |LPC| + | | | | | | | + |0 0 0 0 0 0|1 0|1 1 1 1 0 0 0 0|2 2 1 1 1 1 1 1|0 0 0 0 0 0|2 2| + |5 4 3 2 1 0| |3 2 1 0 9 8 7 6|1 0 9 8 7 6 5 4|5 4 3 2 1 0|3 2| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 3: G.723 SID mode bit packing + + + + + + +Schulzrinne & Casner Standards Track [Page 17] + +RFC 3551 RTP A/V Profile July 2003 + + +4.5.4 G726-40, G726-32, G726-24, and G726-16 + + ITU-T Recommendation G.726 describes, among others, the algorithm + recommended for conversion of a single 64 kbit/s A-law or mu-law PCM + channel encoded at 8,000 samples/sec to and from a 40, 32, 24, or 16 + kbit/s channel. The conversion is applied to the PCM stream using an + Adaptive Differential Pulse Code Modulation (ADPCM) transcoding + technique. The ADPCM representation consists of a series of + codewords with a one-to-one correspondence to the samples in the PCM + stream. The G726 data rates of 40, 32, 24, and 16 kbit/s have + codewords of 5, 4, 3, and 2 bits, respectively. + + The 16 and 24 kbit/s encodings do not provide toll quality speech. + They are designed for used in overloaded Digital Circuit + Multiplication Equipment (DCME). ITU-T G.726 recommends that the 16 + and 24 kbit/s encodings should be alternated with higher data rate + encodings to provide an average sample size of between 3.5 and 3.7 + bits per sample. + + The encodings of G.726 are here denoted as G726-40, G726-32, G726-24, + and G726-16. Prior to 1990, G721 described the 32 kbit/s ADPCM + encoding, and G723 described the 40, 32, and 16 kbit/s encodings. + Thus, G726-32 designates the same algorithm as G721 in RFC 1890. + + A stream of G726 codewords contains no information on the encoding + being used, therefore transitions between G726 encoding types are not + permitted within a sequence of packed codewords. Applications MUST + determine the encoding type of packed codewords from the RTP payload + identifier. + + No payload-specific header information SHALL be included as part of + the audio data. A stream of G726 codewords MUST be packed into + octets as follows: the first codeword is placed into the first octet + such that the least significant bit of the codeword aligns with the + least significant bit in the octet, the second codeword is then + packed so that its least significant bit coincides with the least + significant unoccupied bit in the octet. When a complete codeword + cannot be placed into an octet, the bits overlapping the octet + boundary are placed into the least significant bits of the next + octet. Packing MUST end with a completely packed final octet. The + number of codewords packed will therefore be a multiple of 8, 2, 8, + and 4 for G726-40, G726-32, G726-24, and G726-16, respectively. An + example of the packing scheme for G726-32 codewords is as shown, + where bit 7 is the least significant bit of the first octet, and bit + A3 is the least significant bit of the first codeword: + + + + + + +Schulzrinne & Casner Standards Track [Page 18] + +RFC 3551 RTP A/V Profile July 2003 + + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + |B B B B|A A A A|D D D D|C C C C| ... + |0 1 2 3|0 1 2 3|0 1 2 3|0 1 2 3| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + + An example of the packing scheme for G726-24 codewords follows, where + again bit 7 is the least significant bit of the first octet, and bit + A2 is the least significant bit of the first codeword: + + 0 1 2 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + |C C|B B B|A A A|F|E E E|D D D|C|H H H|G G G|F F| ... + |1 2|0 1 2|0 1 2|2|0 1 2|0 1 2|0|0 1 2|0 1 2|0 1| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- + + Note that the "little-endian" direction in which samples are packed + into octets in the G726-16, -24, -32 and -40 payload formats + specified here is consistent with ITU-T Recommendation X.420, but is + the opposite of what is specified in ITU-T Recommendation I.366.2 + Annex E for ATM AAL2 transport. A second set of RTP payload formats + matching the packetization of I.366.2 Annex E and identified by MIME + subtypes AAL2-G726-16, -24, -32 and -40 will be specified in a + separate document. + +4.5.5 G728 + + G728 is specified in ITU-T Recommendation G.728, "Coding of speech at + 16 kbit/s using low-delay code excited linear prediction". + + A G.278 encoder translates 5 consecutive audio samples into a 10-bit + codebook index, resulting in a bit rate of 16 kb/s for audio sampled + at 8,000 samples per second. The group of five consecutive samples + is called a vector. Four consecutive vectors, labeled V1 to V4 + (where V1 is to be played first by the receiver), build one G.728 + frame. The four vectors of 40 bits are packed into 5 octets, labeled + B1 through B5. B1 SHALL be placed first in the RTP packet. + + Referring to the figure below, the principle for bit order is + "maintenance of bit significance". Bits from an older vector are + more significant than bits from newer vectors. The MSB of the frame + goes to the MSB of B1 and the LSB of the frame goes to LSB of B5. + + + + + + + +Schulzrinne & Casner Standards Track [Page 19] + +RFC 3551 RTP A/V Profile July 2003 + + + 1 2 3 3 + 0 0 0 0 9 + ++++++++++++++++++++++++++++++++++++++++ + <---V1---><---V2---><---V3---><---V4---> vectors + <--B1--><--B2--><--B3--><--B4--><--B5--> octets + <------------- frame 1 ----------------> + + In particular, B1 contains the eight most significant bits of V1, + with the MSB of V1 being the MSB of B1. B2 contains the two least + significant bits of V1, the more significant of the two in its MSB, + and the six most significant bits of V2. B1 SHALL be placed first in + the RTP packet and B5 last. + +4.5.6 G729 + + G729 is specified in ITU-T Recommendation G.729, "Coding of speech at + 8 kbit/s using conjugate structure-algebraic code excited linear + prediction (CS-ACELP)". A reduced-complexity version of the G.729 + algorithm is specified in Annex A to Rec. G.729. The speech coding + algorithms in the main body of G.729 and in G.729 Annex A are fully + interoperable with each other, so there is no need to further + distinguish between them. An implementation that signals or accepts + use of G729 payload format may implement either G.729 or G.729A + unless restricted by additional signaling specified elsewhere related + specifically to the encoding rather than the payload format. The + G.729 and G.729 Annex A codecs were optimized to represent speech + with high quality, where G.729 Annex A trades some speech quality for + an approximate 50% complexity reduction [10]. See the next Section + (4.5.7) for other data rates added in later G.729 Annexes. For all + data rates, the sampling frequency (and RTP timestamp clock rate) is + 8,000 Hz. + + A voice activity detector (VAD) and comfort noise generator (CNG) + algorithm in Annex B of G.729 is RECOMMENDED for digital simultaneous + voice and data applications and can be used in conjunction with G.729 + or G.729 Annex A. A G.729 or G.729 Annex A frame contains 10 octets, + while the G.729 Annex B comfort noise frame occupies 2 octets. + Receivers MUST accept comfort noise frames if restriction of their + use has not been signaled. The MIME registration for G729 in RFC + 3555 [7] specifies a parameter that MAY be used with MIME or SDP to + restrict the use of comfort noise frames. + + A G729 RTP packet may consist of zero or more G.729 or G.729 Annex A + frames, followed by zero or one G.729 Annex B frames. The presence + of a comfort noise frame can be deduced from the length of the RTP + payload. The default packetization interval is 20 ms (two frames), + but in some situations it may be desirable to send 10 ms packets. An + + + + +Schulzrinne & Casner Standards Track [Page 20] + +RFC 3551 RTP A/V Profile July 2003 + + + example would be a transition from speech to comfort noise in the + first 10 ms of the packet. For some applications, a longer + packetization interval may be required to reduce the packet rate. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |L| L1 | L2 | L3 | P1 |P| C1 | + |0| | | | |0| | + | |0 1 2 3 4 5 6|0 1 2 3 4|0 1 2 3 4|0 1 2 3 4 5 6 7| |0 1 2 3 4| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | C1 | S1 | GA1 | GB1 | P2 | C2 | + | 1 1 1| | | | | | + |5 6 7 8 9 0 1 2|0 1 2 3|0 1 2|0 1 2 3|0 1 2 3 4|0 1 2 3 4 5 6 7| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | C2 | S2 | GA2 | GB2 | + | 1 1 1| | | | + |8 9 0 1 2|0 1 2 3|0 1 2|0 1 2 3| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 4: G.729 and G.729A bit packing + + The transmitted parameters of a G.729/G.729A 10-ms frame, consisting + of 80 bits, are defined in Recommendation G.729, Table 8/G.729. The + mapping of the these parameters is given below in Fig. 4. The + diagrams show the bit packing in "network byte order", also known as + big-endian order. The bits of each 32-bit word are numbered 0 to 31, + with the most significant bit on the left and numbered 0. The octets + (bytes) of each word are transmitted most significant octet first. + The bits of each data field are numbered in the order as produced by + the G.729 C code reference implementation. + + The packing of the G.729 Annex B comfort noise frame is shown in Fig. + 5. + + 0 1 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |L| LSF1 | LSF2 | GAIN |R| + |S| | | |E| + |F| | | |S| + |0|0 1 2 3 4|0 1 2 3|0 1 2 3 4|V| RESV = Reserved (zero) + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 5: G.729 Annex B bit packing + + + + + + +Schulzrinne & Casner Standards Track [Page 21] + +RFC 3551 RTP A/V Profile July 2003 + + +4.5.7 G729D and G729E + + Annexes D and E to ITU-T Recommendation G.729 provide additional data + rates. Because the data rate is not signaled in the bitstream, the + different data rates are given distinct RTP encoding names which are + mapped to distinct payload type numbers. G729D indicates a 6.4 + kbit/s coding mode (G.729 Annex D, for momentary reduction in channel + capacity), while G729E indicates an 11.8 kbit/s mode (G.729 Annex E, + for improved performance with a wide range of narrow-band input + signals, e.g., music and background noise). Annex E has two + operating modes, backward adaptive and forward adaptive, which are + signaled by the first two bits in each frame (the most significant + two bits of the first octet). + + The voice activity detector (VAD) and comfort noise generator (CNG) + algorithm specified in Annex B of G.729 may be used with Annex D and + Annex E frames in addition to G.729 and G.729 Annex A frames. The + algorithm details for the operation of Annexes D and E with the Annex + B CNG are specified in G.729 Annexes F and G. Note that Annexes F + and G do not introduce any new encodings. Receivers MUST accept + comfort noise frames if restriction of their use has not been + signaled. The MIME registrations for G729D and G729E in RFC 3555 [7] + specify a parameter that MAY be used with MIME or SDP to restrict the + use of comfort noise frames. + + For G729D, an RTP packet may consist of zero or more G.729 Annex D + frames, followed by zero or one G.729 Annex B frame. Similarly, for + G729E, an RTP packet may consist of zero or more G.729 Annex E + frames, followed by zero or one G.729 Annex B frame. The presence of + a comfort noise frame can be deduced from the length of the RTP + payload. + + A single RTP packet must contain frames of only one data rate, + optionally followed by one comfort noise frame. The data rate may be + changed from packet to packet by changing the payload type number. + G.729 Annexes D, E and H describe what the encoding and decoding + algorithms must do to accommodate a change in data rate. + + For G729D, the bits of a G.729 Annex D frame are formatted as shown + below in Fig. 6 (cf. Table D.1/G.729). The frame length is 64 bits. + + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 22] + +RFC 3551 RTP A/V Profile July 2003 + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |L| L1 | L2 | L3 | P1 | C1 | + |0| | | | | | + | |0 1 2 3 4 5 6|0 1 2 3 4|0 1 2 3 4|0 1 2 3 4 5 6 7|0 1 2 3 4 5| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | C1 |S1 | GA1 | GB1 | P2 | C2 |S2 | GA2 | GB2 | + | | | | | | | | | | + |6 7 8|0 1|0 1 2|0 1 2|0 1 2 3|0 1 2 3 4 5 6 7 8|0 1|0 1 2|0 1 2| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 6: G.729 Annex D bit packing + + The net bit rate for the G.729 Annex E algorithm is 11.8 kbit/s and a + total of 118 bits are used. Two bits are appended as "don't care" + bits to complete an integer number of octets for the frame. For + G729E, the bits of a data frame are formatted as shown in the next + two diagrams (cf. Table E.1/G.729). The fields for the G729E forward + adaptive mode are packed as shown in Fig. 7. + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |0 0|L| L1 | L2 | L3 | P1 |P| C0_1| + | |0| | | | |0| | + | | |0 1 2 3 4 5 6|0 1 2 3 4|0 1 2 3 4|0 1 2 3 4 5 6 7| |0 1 2| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | C1_1 | C2_1 | C3_1 | C4_1 | + | | | | | | + |3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5 6| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | GA1 | GB1 | P2 | C0_2 | C1_2 | C2_2 | + | | | | | | | + |0 1 2|0 1 2 3|0 1 2 3 4|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | C3_2 | C4_2 | GA2 | GB2 |DC | + | | | | | | | + |6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2|0 1 2 3|0 1| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 7: G.729 Annex E (forward adaptive mode) bit packing + + The fields for the G729E backward adaptive mode are packed as shown + in Fig. 8. + + + + + + +Schulzrinne & Casner Standards Track [Page 23] + +RFC 3551 RTP A/V Profile July 2003 + + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + |1 1| P1 |P| C0_1 | C1_1 | + | | |0| 1 1 1| | + | |0 1 2 3 4 5 6 7|0|0 1 2 3 4 5 6 7 8 9 0 1 2|0 1 2 3 4 5 6 7| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | C2_1 | C3_1 | C4_1 |GA1 | GB1 |P2 | + | | | | | | | | + |8 9|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2|0 1 2 3|0 1| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | C0_2 | C1_2 | C2_2 | + | | 1 1 1| | | + |2 3 4|0 1 2 3 4 5 6 7 8 9 0 1 2|0 1 2 3 4 5 6 7 8 9|0 1 2 3 4 5| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | | C3_2 | C4_2 | GA2 | GB2 |DC | + | | | | | | | + |6|0 1 2 3 4 5 6|0 1 2 3 4 5 6|0 1 2|0 1 2 3|0 1| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + Figure 8: G.729 Annex E (backward adaptive mode) bit packing + +4.5.8 GSM + + GSM (Group Speciale Mobile) denotes the European GSM 06.10 standard + for full-rate speech transcoding, ETS 300 961, which is based on + RPE/LTP (residual pulse excitation/long term prediction) coding at a + rate of 13 kb/s [11,12,13]. The text of the standard can be obtained + from: + + ETSI (European Telecommunications Standards Institute) + ETSI Secretariat: B.P.152 + F-06561 Valbonne Cedex + France + Phone: +33 92 94 42 00 + Fax: +33 93 65 47 16 + + Blocks of 160 audio samples are compressed into 33 octets, for an + effective data rate of 13,200 b/s. + +4.5.8.1 General Packaging Issues + + The GSM standard (ETS 300 961) specifies the bit stream produced by + the codec, but does not specify how these bits should be packed for + transmission. The packetization specified here has subsequently been + adopted in ETSI Technical Specification TS 101 318. Some software + implementations of the GSM codec use a different packing than that + specified here. + + + +Schulzrinne & Casner Standards Track [Page 24] + +RFC 3551 RTP A/V Profile July 2003 + + + field field name bits field field name bits + ________________________________________________ + 1 LARc[0] 6 39 xmc[22] 3 + 2 LARc[1] 6 40 xmc[23] 3 + 3 LARc[2] 5 41 xmc[24] 3 + 4 LARc[3] 5 42 xmc[25] 3 + 5 LARc[4] 4 43 Nc[2] 7 + 6 LARc[5] 4 44 bc[2] 2 + 7 LARc[6] 3 45 Mc[2] 2 + 8 LARc[7] 3 46 xmaxc[2] 6 + 9 Nc[0] 7 47 xmc[26] 3 + 10 bc[0] 2 48 xmc[27] 3 + 11 Mc[0] 2 49 xmc[28] 3 + 12 xmaxc[0] 6 50 xmc[29] 3 + 13 xmc[0] 3 51 xmc[30] 3 + 14 xmc[1] 3 52 xmc[31] 3 + 15 xmc[2] 3 53 xmc[32] 3 + 16 xmc[3] 3 54 xmc[33] 3 + 17 xmc[4] 3 55 xmc[34] 3 + 18 xmc[5] 3 56 xmc[35] 3 + 19 xmc[6] 3 57 xmc[36] 3 + 20 xmc[7] 3 58 xmc[37] 3 + 21 xmc[8] 3 59 xmc[38] 3 + 22 xmc[9] 3 60 Nc[3] 7 + 23 xmc[10] 3 61 bc[3] 2 + 24 xmc[11] 3 62 Mc[3] 2 + 25 xmc[12] 3 63 xmaxc[3] 6 + 26 Nc[1] 7 64 xmc[39] 3 + 27 bc[1] 2 65 xmc[40] 3 + 28 Mc[1] 2 66 xmc[41] 3 + 29 xmaxc[1] 6 67 xmc[42] 3 + 30 xmc[13] 3 68 xmc[43] 3 + 31 xmc[14] 3 69 xmc[44] 3 + 32 xmc[15] 3 70 xmc[45] 3 + 33 xmc[16] 3 71 xmc[46] 3 + 34 xmc[17] 3 72 xmc[47] 3 + 35 xmc[18] 3 73 xmc[48] 3 + 36 xmc[19] 3 74 xmc[49] 3 + 37 xmc[20] 3 75 xmc[50] 3 + 38 xmc[21] 3 76 xmc[51] 3 + + Table 2: Ordering of GSM variables + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 25] + +RFC 3551 RTP A/V Profile July 2003 + + + Octet Bit 0 Bit 1 Bit 2 Bit 3 Bit 4 Bit 5 Bit 6 Bit 7 + _____________________________________________________________________ + 0 1 1 0 1 LARc0.0 LARc0.1 LARc0.2 LARc0.3 + 1 LARc0.4 LARc0.5 LARc1.0 LARc1.1 LARc1.2 LARc1.3 LARc1.4 LARc1.5 + 2 LARc2.0 LARc2.1 LARc2.2 LARc2.3 LARc2.4 LARc3.0 LARc3.1 LARc3.2 + 3 LARc3.3 LARc3.4 LARc4.0 LARc4.1 LARc4.2 LARc4.3 LARc5.0 LARc5.1 + 4 LARc5.2 LARc5.3 LARc6.0 LARc6.1 LARc6.2 LARc7.0 LARc7.1 LARc7.2 + 5 Nc0.0 Nc0.1 Nc0.2 Nc0.3 Nc0.4 Nc0.5 Nc0.6 bc0.0 + 6 bc0.1 Mc0.0 Mc0.1 xmaxc00 xmaxc01 xmaxc02 xmaxc03 xmaxc04 + 7 xmaxc05 xmc0.0 xmc0.1 xmc0.2 xmc1.0 xmc1.1 xmc1.2 xmc2.0 + 8 xmc2.1 xmc2.2 xmc3.0 xmc3.1 xmc3.2 xmc4.0 xmc4.1 xmc4.2 + 9 xmc5.0 xmc5.1 xmc5.2 xmc6.0 xmc6.1 xmc6.2 xmc7.0 xmc7.1 + 10 xmc7.2 xmc8.0 xmc8.1 xmc8.2 xmc9.0 xmc9.1 xmc9.2 xmc10.0 + 11 xmc10.1 xmc10.2 xmc11.0 xmc11.1 xmc11.2 xmc12.0 xmc12.1 xcm12.2 + 12 Nc1.0 Nc1.1 Nc1.2 Nc1.3 Nc1.4 Nc1.5 Nc1.6 bc1.0 + 13 bc1.1 Mc1.0 Mc1.1 xmaxc10 xmaxc11 xmaxc12 xmaxc13 xmaxc14 + 14 xmax15 xmc13.0 xmc13.1 xmc13.2 xmc14.0 xmc14.1 xmc14.2 xmc15.0 + 15 xmc15.1 xmc15.2 xmc16.0 xmc16.1 xmc16.2 xmc17.0 xmc17.1 xmc17.2 + 16 xmc18.0 xmc18.1 xmc18.2 xmc19.0 xmc19.1 xmc19.2 xmc20.0 xmc20.1 + 17 xmc20.2 xmc21.0 xmc21.1 xmc21.2 xmc22.0 xmc22.1 xmc22.2 xmc23.0 + 18 xmc23.1 xmc23.2 xmc24.0 xmc24.1 xmc24.2 xmc25.0 xmc25.1 xmc25.2 + 19 Nc2.0 Nc2.1 Nc2.2 Nc2.3 Nc2.4 Nc2.5 Nc2.6 bc2.0 + 20 bc2.1 Mc2.0 Mc2.1 xmaxc20 xmaxc21 xmaxc22 xmaxc23 xmaxc24 + 21 xmaxc25 xmc26.0 xmc26.1 xmc26.2 xmc27.0 xmc27.1 xmc27.2 xmc28.0 + 22 xmc28.1 xmc28.2 xmc29.0 xmc29.1 xmc29.2 xmc30.0 xmc30.1 xmc30.2 + 23 xmc31.0 xmc31.1 xmc31.2 xmc32.0 xmc32.1 xmc32.2 xmc33.0 xmc33.1 + 24 xmc33.2 xmc34.0 xmc34.1 xmc34.2 xmc35.0 xmc35.1 xmc35.2 xmc36.0 + 25 Xmc36.1 xmc36.2 xmc37.0 xmc37.1 xmc37.2 xmc38.0 xmc38.1 xmc38.2 + 26 Nc3.0 Nc3.1 Nc3.2 Nc3.3 Nc3.4 Nc3.5 Nc3.6 bc3.0 + 27 bc3.1 Mc3.0 Mc3.1 xmaxc30 xmaxc31 xmaxc32 xmaxc33 xmaxc34 + 28 xmaxc35 xmc39.0 xmc39.1 xmc39.2 xmc40.0 xmc40.1 xmc40.2 xmc41.0 + 29 xmc41.1 xmc41.2 xmc42.0 xmc42.1 xmc42.2 xmc43.0 xmc43.1 xmc43.2 + 30 xmc44.0 xmc44.1 xmc44.2 xmc45.0 xmc45.1 xmc45.2 xmc46.0 xmc46.1 + 31 xmc46.2 xmc47.0 xmc47.1 xmc47.2 xmc48.0 xmc48.1 xmc48.2 xmc49.0 + 32 xmc49.1 xmc49.2 xmc50.0 xmc50.1 xmc50.2 xmc51.0 xmc51.1 xmc51.2 + + Table 3: GSM payload format + + In the GSM packing used by RTP, the bits SHALL be packed beginning + from the most significant bit. Every 160 sample GSM frame is coded + into one 33 octet (264 bit) buffer. Every such buffer begins with a + 4 bit signature (0xD), followed by the MSB encoding of the fields of + the frame. The first octet thus contains 1101 in the 4 most + significant bits (0-3) and the 4 most significant bits of F1 (0-3) in + the 4 least significant bits (4-7). The second octet contains the 2 + least significant bits of F1 in bits 0-1, and F2 in bits 2-7, and so + on. The order of the fields in the frame is described in Table 2. + + + + +Schulzrinne & Casner Standards Track [Page 26] + +RFC 3551 RTP A/V Profile July 2003 + + +4.5.8.2 GSM Variable Names and Numbers + + In the RTP encoding we have the bit pattern described in Table 3, + where F.i signifies the ith bit of the field F, bit 0 is the most + significant bit, and the bits of every octet are numbered from 0 to 7 + from most to least significant. + +4.5.9 GSM-EFR + + GSM-EFR denotes GSM 06.60 enhanced full rate speech transcoding, + specified in ETS 300 726 which is available from ETSI at the address + given in Section 4.5.8. This codec has a frame length of 244 bits. + For transmission in RTP, each codec frame is packed into a 31 octet + (248 bit) buffer beginning with a 4-bit signature 0xC in a manner + similar to that specified here for the original GSM 06.10 codec. The + packing is specified in ETSI Technical Specification TS 101 318. + +4.5.10 L8 + + L8 denotes linear audio data samples, using 8-bits of precision with + an offset of 128, that is, the most negative signal is encoded as + zero. + +4.5.11 L16 + + L16 denotes uncompressed audio data samples, using 16-bit signed + representation with 65,535 equally divided steps between minimum and + maximum signal level, ranging from -32,768 to 32,767. The value is + represented in two's complement notation and transmitted in network + byte order (most significant byte first). + + The MIME registration for L16 in RFC 3555 [7] specifies parameters + that MAY be used with MIME or SDP to indicate that analog pre- + emphasis was applied to the signal before quantization or to indicate + that a multiple-channel audio stream follows a different channel + ordering convention than is specified in Section 4.1. + +4.5.12 LPC + + LPC designates an experimental linear predictive encoding contributed + by Ron Frederick, which is based on an implementation written by Ron + Zuckerman posted to the Usenet group comp.dsp on June 26, 1992. The + codec generates 14 octets for every frame. The framesize is set to + 20 ms, resulting in a bit rate of 5,600 b/s. + + + + + + + +Schulzrinne & Casner Standards Track [Page 27] + +RFC 3551 RTP A/V Profile July 2003 + + +4.5.13 MPA + + MPA denotes MPEG-1 or MPEG-2 audio encapsulated as elementary + streams. The encoding is defined in ISO standards ISO/IEC 11172-3 + and 13818-3. The encapsulation is specified in RFC 2250 [14]. + + The encoding may be at any of three levels of complexity, called + Layer I, II and III. The selected layer as well as the sampling rate + and channel count are indicated in the payload. The RTP timestamp + clock rate is always 90,000, independent of the sampling rate. + MPEG-1 audio supports sampling rates of 32, 44.1, and 48 kHz (ISO/IEC + 11172-3, section 1.1; "Scope"). MPEG-2 supports sampling rates of + 16, 22.05 and 24 kHz. The number of samples per frame is fixed, but + the frame size will vary with the sampling rate and bit rate. + + The MIME registration for MPA in RFC 3555 [7] specifies parameters + that MAY be used with MIME or SDP to restrict the selection of layer, + channel count, sampling rate, and bit rate. + +4.5.14 PCMA and PCMU + + PCMA and PCMU are specified in ITU-T Recommendation G.711. Audio + data is encoded as eight bits per sample, after logarithmic scaling. + PCMU denotes mu-law scaling, PCMA A-law scaling. A detailed + description is given by Jayant and Noll [15]. Each G.711 octet SHALL + be octet-aligned in an RTP packet. The sign bit of each G.711 octet + SHALL correspond to the most significant bit of the octet in the RTP + packet (i.e., assuming the G.711 samples are handled as octets on the + host machine, the sign bit SHALL be the most significant bit of the + octet as defined by the host machine format). The 56 kb/s and 48 + kb/s modes of G.711 are not applicable to RTP, since PCMA and PCMU + MUST always be transmitted as 8-bit samples. + + See Section 4.1 regarding silence suppression. + +4.5.15 QCELP + + The Electronic Industries Association (EIA) & Telecommunications + Industry Association (TIA) standard IS-733, "TR45: High Rate Speech + Service Option for Wideband Spread Spectrum Communications Systems", + defines the QCELP audio compression algorithm for use in wireless + CDMA applications. The QCELP CODEC compresses each 20 milliseconds + of 8,000 Hz, 16-bit sampled input speech into one of four different + size output frames: Rate 1 (266 bits), Rate 1/2 (124 bits), Rate 1/4 + (54 bits) or Rate 1/8 (20 bits). For typical speech patterns, this + results in an average output of 6.8 kb/s for normal mode and 4.7 kb/s + for reduced rate mode. The packetization of the QCELP audio codec is + described in [16]. + + + +Schulzrinne & Casner Standards Track [Page 28] + +RFC 3551 RTP A/V Profile July 2003 + + +4.5.16 RED + + The redundant audio payload format "RED" is specified by RFC 2198 + [17]. It defines a means by which multiple redundant copies of an + audio packet may be transmitted in a single RTP stream. Each packet + in such a stream contains, in addition to the audio data for that + packetization interval, a (more heavily compressed) copy of the data + from a previous packetization interval. This allows an approximation + of the data from lost packets to be recovered upon decoding of a + subsequent packet, giving much improved sound quality when compared + with silence substitution for lost packets. + +4.5.17 VDVI + + VDVI is a variable-rate version of DVI4, yielding speech bit rates of + between 10 and 25 kb/s. It is specified for single-channel operation + only. Samples are packed into octets starting at the most- + significant bit. The last octet is padded with 1 bits if the last + sample does not fill the last octet. This padding is distinct from + the valid codewords. The receiver needs to detect the padding + because there is no explicit count of samples in the packet. + + It uses the following encoding: + + DVI4 codeword VDVI bit pattern + _______________________________ + 0 00 + 1 010 + 2 1100 + 3 11100 + 4 111100 + 5 1111100 + 6 11111100 + 7 11111110 + 8 10 + 9 011 + 10 1101 + 11 11101 + 12 111101 + 13 1111101 + 14 11111101 + 15 11111111 + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 29] + +RFC 3551 RTP A/V Profile July 2003 + + +5. Video + + The following sections describe the video encodings that are defined + in this memo and give their abbreviated names used for + identification. These video encodings and their payload types are + listed in Table 5. + + All of these video encodings use an RTP timestamp frequency of 90,000 + Hz, the same as the MPEG presentation time stamp frequency. This + frequency yields exact integer timestamp increments for the typical + 24 (HDTV), 25 (PAL), and 29.97 (NTSC) and 30 Hz (HDTV) frame rates + and 50, 59.94 and 60 Hz field rates. While 90 kHz is the RECOMMENDED + rate for future video encodings used within this profile, other rates + MAY be used. However, it is not sufficient to use the video frame + rate (typically between 15 and 30 Hz) because that does not provide + adequate resolution for typical synchronization requirements when + calculating the RTP timestamp corresponding to the NTP timestamp in + an RTCP SR packet. The timestamp resolution MUST also be sufficient + for the jitter estimate contained in the receiver reports. + + For most of these video encodings, the RTP timestamp encodes the + sampling instant of the video image contained in the RTP data packet. + If a video image occupies more than one packet, the timestamp is the + same on all of those packets. Packets from different video images + are distinguished by their different timestamps. + + Most of these video encodings also specify that the marker bit of the + RTP header SHOULD be set to one in the last packet of a video frame + and otherwise set to zero. Thus, it is not necessary to wait for a + following packet with a different timestamp to detect that a new + frame should be displayed. + +5.1 CelB + + The CELL-B encoding is a proprietary encoding proposed by Sun + Microsystems. The byte stream format is described in RFC 2029 [18]. + +5.2 JPEG + + The encoding is specified in ISO Standards 10918-1 and 10918-2. The + RTP payload format is as specified in RFC 2435 [19]. + +5.3 H261 + + The encoding is specified in ITU-T Recommendation H.261, "Video codec + for audiovisual services at p x 64 kbit/s". The packetization and + RTP-specific properties are described in RFC 2032 [20]. + + + + +Schulzrinne & Casner Standards Track [Page 30] + +RFC 3551 RTP A/V Profile July 2003 + + +5.4 H263 + + The encoding is specified in the 1996 version of ITU-T Recommendation + H.263, "Video coding for low bit rate communication". The + packetization and RTP-specific properties are described in RFC 2190 + [21]. The H263-1998 payload format is RECOMMENDED over this one for + use by new implementations. + +5.5 H263-1998 + + The encoding is specified in the 1998 version of ITU-T Recommendation + H.263, "Video coding for low bit rate communication". The + packetization and RTP-specific properties are described in RFC 2429 + [22]. Because the 1998 version of H.263 is a superset of the 1996 + syntax, this payload format can also be used with the 1996 version of + H.263, and is RECOMMENDED for this use by new implementations. This + payload format does not replace RFC 2190, which continues to be used + by existing implementations, and may be required for backward + compatibility in new implementations. Implementations using the new + features of the 1998 version of H.263 MUST use the payload format + described in RFC 2429. + +5.6 MPV + + MPV designates the use of MPEG-1 and MPEG-2 video encoding elementary + streams as specified in ISO Standards ISO/IEC 11172 and 13818-2, + respectively. The RTP payload format is as specified in RFC 2250 + [14], Section 3. + + The MIME registration for MPV in RFC 3555 [7] specifies a parameter + that MAY be used with MIME or SDP to restrict the selection of the + type of MPEG video. + +5.7 MP2T + + MP2T designates the use of MPEG-2 transport streams, for either audio + or video. The RTP payload format is described in RFC 2250 [14], + Section 2. + + + + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 31] + +RFC 3551 RTP A/V Profile July 2003 + + +5.8 nv + + The encoding is implemented in the program `nv', version 4, developed + at Xerox PARC by Ron Frederick. Further information is available + from the author: + + Ron Frederick + Blue Coat Systems Inc. + 650 Almanor Avenue + Sunnyvale, CA 94085 + United States + EMail: ronf@bluecoat.com + +6. Payload Type Definitions + + Tables 4 and 5 define this profile's static payload type values for + the PT field of the RTP data header. In addition, payload type + values in the range 96-127 MAY be defined dynamically through a + conference control protocol, which is beyond the scope of this + document. For example, a session directory could specify that for a + given session, payload type 96 indicates PCMU encoding, 8,000 Hz + sampling rate, 2 channels. Entries in Tables 4 and 5 with payload + type "dyn" have no static payload type assigned and are only used + with a dynamic payload type. Payload type 2 was assigned to G721 in + RFC 1890 and to its equivalent successor G726-32 in draft versions of + this specification, but its use is now deprecated and that static + payload type is marked reserved due to conflicting use for the + payload formats G726-32 and AAL2-G726-32 (see Section 4.5.4). + Payload type 13 indicates the Comfort Noise (CN) payload format + specified in RFC 3389 [9]. Payload type 19 is marked "reserved" + because some draft versions of this specification assigned that + number to an earlier version of the comfort noise payload format. + The payload type range 72-76 is marked "reserved" so that RTCP and + RTP packets can be reliably distinguished (see Section "Summary of + Protocol Constants" of the RTP protocol specification). + + The payload types currently defined in this profile are assigned to + exactly one of three categories or media types: audio only, video + only and those combining audio and video. The media types are marked + in Tables 4 and 5 as "A", "V" and "AV", respectively. Payload types + of different media types SHALL NOT be interleaved or multiplexed + within a single RTP session, but multiple RTP sessions MAY be used in + parallel to send multiple media types. An RTP source MAY change + payload types within the same media type during a session. See the + section "Multiplexing RTP Sessions" of RFC 3550 for additional + explanation. + + + + + +Schulzrinne & Casner Standards Track [Page 32] + +RFC 3551 RTP A/V Profile July 2003 + + + PT encoding media type clock rate channels + name (Hz) + ___________________________________________________ + 0 PCMU A 8,000 1 + 1 reserved A + 2 reserved A + 3 GSM A 8,000 1 + 4 G723 A 8,000 1 + 5 DVI4 A 8,000 1 + 6 DVI4 A 16,000 1 + 7 LPC A 8,000 1 + 8 PCMA A 8,000 1 + 9 G722 A 8,000 1 + 10 L16 A 44,100 2 + 11 L16 A 44,100 1 + 12 QCELP A 8,000 1 + 13 CN A 8,000 1 + 14 MPA A 90,000 (see text) + 15 G728 A 8,000 1 + 16 DVI4 A 11,025 1 + 17 DVI4 A 22,050 1 + 18 G729 A 8,000 1 + 19 reserved A + 20 unassigned A + 21 unassigned A + 22 unassigned A + 23 unassigned A + dyn G726-40 A 8,000 1 + dyn G726-32 A 8,000 1 + dyn G726-24 A 8,000 1 + dyn G726-16 A 8,000 1 + dyn G729D A 8,000 1 + dyn G729E A 8,000 1 + dyn GSM-EFR A 8,000 1 + dyn L8 A var. var. + dyn RED A (see text) + dyn VDVI A var. 1 + + Table 4: Payload types (PT) for audio encodings + + + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 33] + +RFC 3551 RTP A/V Profile July 2003 + + + PT encoding media type clock rate + name (Hz) + _____________________________________________ + 24 unassigned V + 25 CelB V 90,000 + 26 JPEG V 90,000 + 27 unassigned V + 28 nv V 90,000 + 29 unassigned V + 30 unassigned V + 31 H261 V 90,000 + 32 MPV V 90,000 + 33 MP2T AV 90,000 + 34 H263 V 90,000 + 35-71 unassigned ? + 72-76 reserved N/A N/A + 77-95 unassigned ? + 96-127 dynamic ? + dyn H263-1998 V 90,000 + + Table 5: Payload types (PT) for video and combined + encodings + + Session participants agree through mechanisms beyond the scope of + this specification on the set of payload types allowed in a given + session. This set MAY, for example, be defined by the capabilities + of the applications used, negotiated by a conference control protocol + or established by agreement between the human participants. + + Audio applications operating under this profile SHOULD, at a minimum, + be able to send and/or receive payload types 0 (PCMU) and 5 (DVI4). + This allows interoperability without format negotiation and ensures + successful negotiation with a conference control protocol. + +7. RTP over TCP and Similar Byte Stream Protocols + + Under special circumstances, it may be necessary to carry RTP in + protocols offering a byte stream abstraction, such as TCP, possibly + multiplexed with other data. The application MUST define its own + method of delineating RTP and RTCP packets (RTSP [23] provides an + example of such an encapsulation specification). + +8. Port Assignment + + As specified in the RTP protocol definition, RTP data SHOULD be + carried on an even UDP port number and the corresponding RTCP packets + SHOULD be carried on the next higher (odd) port number. + + + + +Schulzrinne & Casner Standards Track [Page 34] + +RFC 3551 RTP A/V Profile July 2003 + + + Applications operating under this profile MAY use any such UDP port + pair. For example, the port pair MAY be allocated randomly by a + session management program. A single fixed port number pair cannot + be required because multiple applications using this profile are + likely to run on the same host, and there are some operating systems + that do not allow multiple processes to use the same UDP port with + different multicast addresses. + + However, port numbers 5004 and 5005 have been registered for use with + this profile for those applications that choose to use them as the + default pair. Applications that operate under multiple profiles MAY + use this port pair as an indication to select this profile if they + are not subject to the constraint of the previous paragraph. + Applications need not have a default and MAY require that the port + pair be explicitly specified. The particular port numbers were + chosen to lie in the range above 5000 to accommodate port number + allocation practice within some versions of the Unix operating + system, where port numbers below 1024 can only be used by privileged + processes and port numbers between 1024 and 5000 are automatically + assigned by the operating system. + +9. Changes from RFC 1890 + + This RFC revises RFC 1890. It is mostly backwards-compatible with + RFC 1890 except for functions removed because two interoperable + implementations were not found. The additions to RFC 1890 codify + existing practice in the use of payload formats under this profile. + Since this profile may be used without using any of the payload + formats listed here, the addition of new payload formats in this + revision does not affect backwards compatibility. The changes are + listed below, categorized into functional and non-functional changes. + + Functional changes: + + o Section 11, "IANA Considerations" was added to specify the + registration of the name for this profile. That appendix also + references a new Section 3 "Registering Additional Encodings" + which establishes a policy that no additional registration of + static payload types for this profile will be made beyond those + added in this revision and included in Tables 4 and 5. Instead, + additional encoding names may be registered as MIME subtypes for + binding to dynamic payload types. Non-normative references were + added to RFC 3555 [7] where MIME subtypes for all the listed + payload formats are registered, some with optional parameters for + use of the payload formats. + + + + + + +Schulzrinne & Casner Standards Track [Page 35] + +RFC 3551 RTP A/V Profile July 2003 + + + o Static payload types 4, 16, 17 and 34 were added to incorporate + IANA registrations made since the publication of RFC 1890, along + with the corresponding payload format descriptions for G723 and + H263. + + o Following working group discussion, static payload types 12 and 18 + were added along with the corresponding payload format + descriptions for QCELP and G729. Static payload type 13 was + assigned to the Comfort Noise (CN) payload format defined in RFC + 3389. Payload type 19 was marked reserved because it had been + temporarily allocated to an earlier version of Comfort Noise + present in some draft revisions of this document. + + o The payload format for G721 was renamed to G726-32 following the + ITU-T renumbering, and the payload format description for G726 was + expanded to include the -16, -24 and -40 data rates. Because of + confusion regarding draft revisions of this document, some + implementations of these G726 payload formats packed samples into + octets starting with the most significant bit rather than the + least significant bit as specified here. To partially resolve + this incompatibility, new payload formats named AAL2-G726-16, -24, + -32 and -40 will be specified in a separate document (see note in + Section 4.5.4), and use of static payload type 2 is deprecated as + explained in Section 6. + + o Payload formats G729D and G729E were added following the ITU-T + addition of Annexes D and E to Recommendation G.729. Listings + were added for payload formats GSM-EFR, RED, and H263-1998 + published in other documents subsequent to RFC 1890. These + additional payload formats are referenced only by dynamic payload + type numbers. + + o The descriptions of the payload formats for G722, G728, GSM, VDVI + were expanded. + + o The payload format for 1016 audio was removed and its static + payload type assignment 1 was marked "reserved" because two + interoperable implementations were not found. + + o Requirements for congestion control were added in Section 2. + + o This profile follows the suggestion in the revised RTP spec that + RTCP bandwidth may be specified separately from the session + bandwidth and separately for active senders and passive receivers. + + o The mapping of a user pass-phrase string into an encryption key + was deleted from Section 2 because two interoperable + implementations were not found. + + + +Schulzrinne & Casner Standards Track [Page 36] + +RFC 3551 RTP A/V Profile July 2003 + + + o The "quadrophonic" sample ordering convention for four-channel + audio was removed to eliminate an ambiguity as noted in Section + 4.1. + + Non-functional changes: + + o In Section 4.1, it is now explicitly stated that silence + suppression is allowed for all audio payload formats. (This has + always been the case and derives from a fundamental aspect of + RTP's design and the motivations for packet audio, but was not + explicit stated before.) The use of comfort noise is also + explained. + + o In Section 4.1, the requirement level for setting of the marker + bit on the first packet after silence for audio was changed from + "is" to "SHOULD be", and clarified that the marker bit is set only + when packets are intentionally not sent. + + o Similarly, text was added to specify that the marker bit SHOULD be + set to one on the last packet of a video frame, and that video + frames are distinguished by their timestamps. + + o RFC references are added for payload formats published after RFC + 1890. + + o The security considerations and full copyright sections were + added. + + o According to Peter Hoddie of Apple, only pre-1994 Macintosh used + the 22254.54 rate and none the 11127.27 rate, so the latter was + dropped from the discussion of suggested sampling frequencies. + + o Table 1 was corrected to move some values from the "ms/packet" + column to the "default ms/packet" column where they belonged. + + o Since the Interactive Multimedia Association ceased operations, an + alternate resource was provided for a referenced IMA document. + + o A note has been added for G722 to clarify a discrepancy between + the actual sampling rate and the RTP timestamp clock rate. + + o Small clarifications of the text have been made in several places, + some in response to questions from readers. In particular: + + - A definition for "media type" is given in Section 1.1 to allow + the explanation of multiplexing RTP sessions in Section 6 to be + more clear regarding the multiplexing of multiple media. + + + + +Schulzrinne & Casner Standards Track [Page 37] + +RFC 3551 RTP A/V Profile July 2003 + + + - The explanation of how to determine the number of audio frames + in a packet from the length was expanded. + + - More description of the allocation of bandwidth to SDES items + is given. + + - A note was added that the convention for the order of channels + specified in Section 4.1 may be overridden by a particular + encoding or payload format specification. + + - The terms MUST, SHOULD, MAY, etc. are used as defined in RFC + 2119. + + o A second author for this document was added. + +10. Security Considerations + + Implementations using the profile defined in this specification are + subject to the security considerations discussed in the RTP + specification [1]. This profile does not specify any different + security services. The primary function of this profile is to list a + set of data compression encodings for audio and video media. + + Confidentiality of the media streams is achieved by encryption. + Because the data compression used with the payload formats described + in this profile is applied end-to-end, encryption may be performed + after compression so there is no conflict between the two operations. + + A potential denial-of-service threat exists for data encodings using + compression techniques that have non-uniform receiver-end + computational load. The attacker can inject pathological datagrams + into the stream which are complex to decode and cause the receiver to + be overloaded. + + As with any IP-based protocol, in some circumstances a receiver may + be overloaded simply by the receipt of too many packets, either + desired or undesired. Network-layer authentication MAY be used to + discard packets from undesired sources, but the processing cost of + the authentication itself may be too high. In a multicast + environment, source pruning is implemented in IGMPv3 (RFC 3376) [24] + and in multicast routing protocols to allow a receiver to select + which sources are allowed to reach it. + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 38] + +RFC 3551 RTP A/V Profile July 2003 + + +11. IANA Considerations + + The RTP specification establishes a registry of profile names for use + by higher-level control protocols, such as the Session Description + Protocol (SDP), RFC 2327 [6], to refer to transport methods. This + profile registers the name "RTP/AVP". + + Section 3 establishes the policy that no additional registration of + static RTP payload types for this profile will be made beyond those + added in this document revision and included in Tables 4 and 5. IANA + may reference that section in declining to accept any additional + registration requests. In Tables 4 and 5, note that types 1 and 2 + have been marked reserved and the set of "dyn" payload types included + has been updated. These changes are explained in Sections 6 and 9. + +12. References + +12.1 Normative References + + [1] Schulzrinne, H., Casner, S., Frederick, R. and V. Jacobson, + "RTP: A Transport Protocol for Real-Time Applications", RFC + 3550, July 2003. + + [2] Bradner, S., "Key Words for Use in RFCs to Indicate Requirement + Levels", BCP 14, RFC 2119, March 1997. + + [3] Apple Computer, "Audio Interchange File Format AIFF-C", August + 1991. (also ftp://ftp.sgi.com/sgi/aiff-c.9.26.91.ps.Z). + +12.2 Informative References + + [4] Braden, R., Clark, D. and S. Shenker, "Integrated Services in + the Internet Architecture: an Overview", RFC 1633, June 1994. + + [5] Blake, S., Black, D., Carlson, M., Davies, E., Wang, Z. and W. + Weiss, "An Architecture for Differentiated Service", RFC 2475, + December 1998. + + [6] Handley, M. and V. Jacobson, "SDP: Session Description + Protocol", RFC 2327, April 1998. + + [7] Casner, S. and P. Hoschka, "MIME Type Registration of RTP + Payload Types", RFC 3555, July 2003. + + [8] Freed, N., Klensin, J. and J. Postel, "Multipurpose Internet + Mail Extensions (MIME) Part Four: Registration Procedures", BCP + 13, RFC 2048, November 1996. + + + + +Schulzrinne & Casner Standards Track [Page 39] + +RFC 3551 RTP A/V Profile July 2003 + + + [9] Zopf, R., "Real-time Transport Protocol (RTP) Payload for + Comfort Noise (CN)", RFC 3389, September 2002. + + [10] Deleam, D. and J.-P. Petit, "Real-time implementations of the + recent ITU-T low bit rate speech coders on the TI TMS320C54X + DSP: results, methodology, and applications", in Proc. of + International Conference on Signal Processing, Technology, and + Applications (ICSPAT) , (Boston, Massachusetts), pp. 1656--1660, + October 1996. + + [11] Mouly, M. and M.-B. Pautet, The GSM system for mobile + communications Lassay-les-Chateaux, France: Europe Media + Duplication, 1993. + + [12] Degener, J., "Digital Speech Compression", Dr. Dobb's Journal, + December 1994. + + [13] Redl, S., Weber, M. and M. Oliphant, An Introduction to GSM + Boston: Artech House, 1995. + + [14] Hoffman, D., Fernando, G., Goyal, V. and M. Civanlar, "RTP + Payload Format for MPEG1/MPEG2 Video", RFC 2250, January 1998. + + [15] Jayant, N. and P. Noll, Digital Coding of Waveforms--Principles + and Applications to Speech and Video Englewood Cliffs, New + Jersey: Prentice-Hall, 1984. + + [16] McKay, K., "RTP Payload Format for PureVoice(tm) Audio", RFC + 2658, August 1999. + + [17] Perkins, C., Kouvelas, I., Hodson, O., Hardman, V., Handley, M., + Bolot, J.-C., Vega-Garcia, A. and S. Fosse-Parisis, "RTP Payload + for Redundant Audio Data", RFC 2198, September 1997. + + [18] Speer, M. and D. Hoffman, "RTP Payload Format of Sun's CellB + Video Encoding", RFC 2029, October 1996. + + [19] Berc, L., Fenner, W., Frederick, R., McCanne, S. and P. Stewart, + "RTP Payload Format for JPEG-Compressed Video", RFC 2435, + October 1998. + + [20] Turletti, T. and C. Huitema, "RTP Payload Format for H.261 Video + Streams", RFC 2032, October 1996. + + [21] Zhu, C., "RTP Payload Format for H.263 Video Streams", RFC 2190, + September 1997. + + + + + +Schulzrinne & Casner Standards Track [Page 40] + +RFC 3551 RTP A/V Profile July 2003 + + + [22] Bormann, C., Cline, L., Deisher, G., Gardos, T., Maciocco, C., + Newell, D., Ott, J., Sullivan, G., Wenger, S. and C. Zhu, "RTP + Payload Format for the 1998 Version of ITU-T Rec. H.263 Video + (H.263+)", RFC 2429, October 1998. + + [23] Schulzrinne, H., Rao, A. and R. Lanphier, "Real Time Streaming + Protocol (RTSP)", RFC 2326, April 1998. + + [24] Cain, B., Deering, S., Kouvelas, I., Fenner, B. and A. + Thyagarajan, "Internet Group Management Protocol, Version 3", + RFC 3376, October 2002. + +13. Current Locations of Related Resources + + Note: Several sections below refer to the ITU-T Software Tool + Library (STL). It is available from the ITU Sales Service, Place des + Nations, CH-1211 Geneve 20, Switzerland (also check + http://www.itu.int). The ITU-T STL is covered by a license defined + in ITU-T Recommendation G.191, "Software tools for speech and audio + coding standardization". + + DVI4 + + An archived copy of the document IMA Recommended Practices for + Enhancing Digital Audio Compatibility in Multimedia Systems (version + 3.0), which describes the IMA ADPCM algorithm, is available at: + + http://www.cs.columbia.edu/~hgs/audio/dvi/ + + An implementation is available from Jack Jansen at + + ftp://ftp.cwi.nl/local/pub/audio/adpcm.shar + + G722 + + An implementation of the G.722 algorithm is available as part of the + ITU-T STL, described above. + + G723 + + The reference C code implementation defining the G.723.1 algorithm + and its Annexes A, B, and C are available as an integral part of + Recommendation G.723.1 from the ITU Sales Service, address listed + above. Both the algorithm and C code are covered by a specific + license. The ITU-T Secretariat should be contacted to obtain such + licensing information. + + + + + +Schulzrinne & Casner Standards Track [Page 41] + +RFC 3551 RTP A/V Profile July 2003 + + + G726 + + G726 is specified in the ITU-T Recommendation G.726, "40, 32, 24, and + 16 kb/s Adaptive Differential Pulse Code Modulation (ADPCM)". An + implementation of the G.726 algorithm is available as part of the + ITU-T STL, described above. + + G729 + + The reference C code implementation defining the G.729 algorithm and + its Annexes A through I are available as an integral part of + Recommendation G.729 from the ITU Sales Service, listed above. Annex + I contains the integrated C source code for all G.729 operating + modes. The G.729 algorithm and associated C code are covered by a + specific license. The contact information for obtaining the license + is available from the ITU-T Secretariat. + + GSM + + A reference implementation was written by Carsten Bormann and Jutta + Degener (then at TU Berlin, Germany). It is available at + + http://www.dmn.tzi.org/software/gsm/ + + Although the RPE-LTP algorithm is not an ITU-T standard, there is a C + code implementation of the RPE-LTP algorithm available as part of the + ITU-T STL. The STL implementation is an adaptation of the TU Berlin + version. + + LPC + + An implementation is available at + + ftp://parcftp.xerox.com/pub/net-research/lpc.tar.Z + + PCMU, PCMA + + An implementation of these algorithms is available as part of the + ITU-T STL, described above. + +14. Acknowledgments + + The comments and careful review of Simao Campos, Richard Cox and AVT + Working Group participants are gratefully acknowledged. The GSM + description was adopted from the IMTC Voice over IP Forum Service + Interoperability Implementation Agreement (January 1997). Fred Burg + and Terry Lyons helped with the G.729 description. + + + + +Schulzrinne & Casner Standards Track [Page 42] + +RFC 3551 RTP A/V Profile July 2003 + + +15. Intellectual Property Rights Statement + + The IETF takes no position regarding the validity or scope of any + intellectual property or other rights that might be claimed to + pertain to the implementation or use of the technology described in + this document or the extent to which any license under such rights + might or might not be available; neither does it represent that it + has made any effort to identify any such rights. Information on the + IETF's procedures with respect to rights in standards-track and + standards-related documentation can be found in BCP-11. Copies of + claims of rights made available for publication and any assurances of + licenses to be made available, or the result of an attempt made to + obtain a general license or permission for the use of such + proprietary rights by implementors or users of this specification can + be obtained from the IETF Secretariat. + + The IETF invites any interested party to bring to its attention any + copyrights, patents or patent applications, or other proprietary + rights which may cover technology that may be required to practice + this standard. Please address the information to the IETF Executive + Director. + +16. Authors' Addresses + + Henning Schulzrinne + Department of Computer Science + Columbia University + 1214 Amsterdam Avenue + New York, NY 10027 + United States + + EMail: schulzrinne@cs.columbia.edu + + + Stephen L. Casner + Packet Design + 3400 Hillview Avenue, Building 3 + Palo Alto, CA 94304 + United States + + EMail: casner@acm.org + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 43] + +RFC 3551 RTP A/V Profile July 2003 + + +17. Full Copyright Statement + + Copyright (C) The Internet Society (2003). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + +Acknowledgement + + Funding for the RFC Editor function is currently provided by the + Internet Society. + + + + + + + + + + + + + + + + + + + +Schulzrinne & Casner Standards Track [Page 44] + diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c new file mode 100644 index 00000000..997fcc34 --- /dev/null +++ b/src/modules/rtp/rtp.c @@ -0,0 +1,364 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "rtp.h" + +pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssrc, uint8_t payload, size_t frame_size) { + pa_assert(c); + pa_assert(fd >= 0); + + c->fd = fd; + c->sequence = (uint16_t) (rand()*rand()); + c->timestamp = 0; + c->ssrc = ssrc ? ssrc : (uint32_t) (rand()*rand()); + c->payload = payload & 127; + c->frame_size = frame_size; + + return c; +} + +#define MAX_IOVECS 16 + +int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q) { + struct iovec iov[MAX_IOVECS]; + pa_memblock* mb[MAX_IOVECS]; + int iov_idx = 1; + size_t n = 0; + + pa_assert(c); + pa_assert(size > 0); + pa_assert(q); + + if (pa_memblockq_get_length(q) < size) + return 0; + + for (;;) { + int r; + pa_memchunk chunk; + + pa_memchunk_reset(&chunk); + + if ((r = pa_memblockq_peek(q, &chunk)) >= 0) { + + size_t k = n + chunk.length > size ? size - n : chunk.length; + + pa_assert(chunk.memblock); + + iov[iov_idx].iov_base = ((uint8_t*) pa_memblock_acquire(chunk.memblock) + chunk.index); + iov[iov_idx].iov_len = k; + mb[iov_idx] = chunk.memblock; + iov_idx ++; + + n += k; + pa_memblockq_drop(q, k); + } + + pa_assert(n % c->frame_size == 0); + + if (r < 0 || n >= size || iov_idx >= MAX_IOVECS) { + uint32_t header[3]; + struct msghdr m; + int k, i; + + if (n > 0) { + header[0] = htonl(((uint32_t) 2 << 30) | ((uint32_t) c->payload << 16) | ((uint32_t) c->sequence)); + header[1] = htonl(c->timestamp); + header[2] = htonl(c->ssrc); + + iov[0].iov_base = (void*)header; + iov[0].iov_len = sizeof(header); + + m.msg_name = NULL; + m.msg_namelen = 0; + m.msg_iov = iov; + m.msg_iovlen = iov_idx; + m.msg_control = NULL; + m.msg_controllen = 0; + m.msg_flags = 0; + + k = sendmsg(c->fd, &m, MSG_DONTWAIT); + + for (i = 1; i < iov_idx; i++) { + pa_memblock_release(mb[i]); + pa_memblock_unref(mb[i]); + } + + c->sequence++; + } else + k = 0; + + c->timestamp += n/c->frame_size; + + if (k < 0) { + if (errno != EAGAIN && errno != EINTR) /* If the queue is full, just ignore it */ + pa_log("sendmsg() failed: %s", pa_cstrerror(errno)); + return -1; + } + + if (r < 0 || pa_memblockq_get_length(q) < size) + break; + + n = 0; + iov_idx = 1; + } + } + + return 0; +} + +pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame_size) { + pa_assert(c); + + c->fd = fd; + c->frame_size = frame_size; + return c; +} + +int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { + int size; + struct msghdr m; + struct iovec iov; + uint32_t header; + int cc; + ssize_t r; + + pa_assert(c); + pa_assert(chunk); + + pa_memchunk_reset(chunk); + + if (ioctl(c->fd, FIONREAD, &size) < 0) { + pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (!size) + return 0; + + chunk->memblock = pa_memblock_new(pool, size); + + iov.iov_base = pa_memblock_acquire(chunk->memblock); + iov.iov_len = size; + + m.msg_name = NULL; + m.msg_namelen = 0; + m.msg_iov = &iov; + m.msg_iovlen = 1; + m.msg_control = NULL; + m.msg_controllen = 0; + m.msg_flags = 0; + + r = recvmsg(c->fd, &m, 0); + pa_memblock_release(chunk->memblock); + + if (r != size) { + if (r < 0 && errno != EAGAIN && errno != EINTR) + pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); + + goto fail; + } + + if (size < 12) { + pa_log_warn("RTP packet too short."); + goto fail; + } + + memcpy(&header, iov.iov_base, sizeof(uint32_t)); + memcpy(&c->timestamp, (uint8_t*) iov.iov_base + 4, sizeof(uint32_t)); + memcpy(&c->ssrc, (uint8_t*) iov.iov_base + 8, sizeof(uint32_t)); + + header = ntohl(header); + c->timestamp = ntohl(c->timestamp); + c->ssrc = ntohl(c->ssrc); + + if ((header >> 30) != 2) { + pa_log_warn("Unsupported RTP version."); + goto fail; + } + + if ((header >> 29) & 1) { + pa_log_warn("RTP padding not supported."); + goto fail; + } + + if ((header >> 28) & 1) { + pa_log_warn("RTP header extensions not supported."); + goto fail; + } + + cc = (header >> 24) & 0xF; + c->payload = (header >> 16) & 127; + c->sequence = header & 0xFFFF; + + if (12 + cc*4 > size) { + pa_log_warn("RTP packet too short. (CSRC)"); + goto fail; + } + + chunk->index = 12 + cc*4; + chunk->length = size - chunk->index; + + if (chunk->length % c->frame_size != 0) { + pa_log_warn("Bad RTP packet size."); + goto fail; + } + + return 0; + +fail: + if (chunk->memblock) + pa_memblock_unref(chunk->memblock); + + return -1; +} + +uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss) { + pa_assert(ss); + + if (ss->format == PA_SAMPLE_ULAW && ss->rate == 8000 && ss->channels == 1) + return 0; + if (ss->format == PA_SAMPLE_ALAW && ss->rate == 8000 && ss->channels == 1) + return 8; + if (ss->format == PA_SAMPLE_S16BE && ss->rate == 44100 && ss->channels == 2) + return 10; + if (ss->format == PA_SAMPLE_S16BE && ss->rate == 44100 && ss->channels == 1) + return 11; + + return 127; +} + +pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec *ss) { + pa_assert(ss); + + switch (payload) { + case 0: + ss->channels = 1; + ss->format = PA_SAMPLE_ULAW; + ss->rate = 8000; + break; + + case 8: + ss->channels = 1; + ss->format = PA_SAMPLE_ALAW; + ss->rate = 8000; + break; + + case 10: + ss->channels = 2; + ss->format = PA_SAMPLE_S16BE; + ss->rate = 44100; + break; + + case 11: + ss->channels = 1; + ss->format = PA_SAMPLE_S16BE; + ss->rate = 44100; + break; + + default: + return NULL; + } + + return ss; +} + +pa_sample_spec *pa_rtp_sample_spec_fixup(pa_sample_spec * ss) { + pa_assert(ss); + + if (!pa_rtp_sample_spec_valid(ss)) + ss->format = PA_SAMPLE_S16BE; + + pa_assert(pa_rtp_sample_spec_valid(ss)); + return ss; +} + +int pa_rtp_sample_spec_valid(const pa_sample_spec *ss) { + pa_assert(ss); + + if (!pa_sample_spec_valid(ss)) + return 0; + + return + ss->format == PA_SAMPLE_U8 || + ss->format == PA_SAMPLE_ALAW || + ss->format == PA_SAMPLE_ULAW || + ss->format == PA_SAMPLE_S16BE; +} + +void pa_rtp_context_destroy(pa_rtp_context *c) { + pa_assert(c); + + pa_close(c->fd); +} + +const char* pa_rtp_format_to_string(pa_sample_format_t f) { + switch (f) { + case PA_SAMPLE_S16BE: + return "L16"; + case PA_SAMPLE_U8: + return "L8"; + case PA_SAMPLE_ALAW: + return "PCMA"; + case PA_SAMPLE_ULAW: + return "PCMU"; + default: + return NULL; + } +} + +pa_sample_format_t pa_rtp_string_to_format(const char *s) { + pa_assert(s); + + if (!(strcmp(s, "L16"))) + return PA_SAMPLE_S16BE; + else if (!strcmp(s, "L8")) + return PA_SAMPLE_U8; + else if (!strcmp(s, "PCMA")) + return PA_SAMPLE_ALAW; + else if (!strcmp(s, "PCMU")) + return PA_SAMPLE_ULAW; + else + return PA_SAMPLE_INVALID; +} + diff --git a/src/modules/rtp/rtp.h b/src/modules/rtp/rtp.h new file mode 100644 index 00000000..ad7175ca --- /dev/null +++ b/src/modules/rtp/rtp.h @@ -0,0 +1,59 @@ +#ifndef foortphfoo +#define foortphfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/memblockq.h> +#include <pulsecore/memchunk.h> + +typedef struct pa_rtp_context { + int fd; + uint16_t sequence; + uint32_t timestamp; + uint32_t ssrc; + uint8_t payload; + size_t frame_size; +} pa_rtp_context; + +pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssrc, uint8_t payload, size_t frame_size); +int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q); + +pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame_size); +int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool); + +void pa_rtp_context_destroy(pa_rtp_context *c); + +pa_sample_spec* pa_rtp_sample_spec_fixup(pa_sample_spec *ss); +int pa_rtp_sample_spec_valid(const pa_sample_spec *ss); + +uint8_t pa_rtp_payload_from_sample_spec(const pa_sample_spec *ss); +pa_sample_spec *pa_rtp_sample_spec_from_payload(uint8_t payload, pa_sample_spec *ss); + +const char* pa_rtp_format_to_string(pa_sample_format_t f); +pa_sample_format_t pa_rtp_string_to_format(const char *s); + +#endif diff --git a/src/modules/rtp/sap.c b/src/modules/rtp/sap.c new file mode 100644 index 00000000..ed7eb0be --- /dev/null +++ b/src/modules/rtp/sap.c @@ -0,0 +1,223 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <time.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <errno.h> +#include <string.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/log.h> +#include <pulsecore/macro.h> + +#include "sap.h" +#include "sdp.h" + +#define MIME_TYPE "application/sdp" + +pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_data) { + pa_assert(c); + pa_assert(fd >= 0); + pa_assert(sdp_data); + + c->fd = fd; + c->sdp_data = sdp_data; + c->msg_id_hash = (uint16_t) (rand()*rand()); + + return c; +} + +void pa_sap_context_destroy(pa_sap_context *c) { + pa_assert(c); + + pa_close(c->fd); + pa_xfree(c->sdp_data); +} + +int pa_sap_send(pa_sap_context *c, int goodbye) { + uint32_t header; + struct sockaddr_storage sa_buf; + struct sockaddr *sa = (struct sockaddr*) &sa_buf; + socklen_t salen = sizeof(sa_buf); + struct iovec iov[4]; + struct msghdr m; + int k; + + if (getsockname(c->fd, sa, &salen) < 0) { + pa_log("getsockname() failed: %s\n", pa_cstrerror(errno)); + return -1; + } + + pa_assert(sa->sa_family == AF_INET || sa->sa_family == AF_INET6); + + header = htonl(((uint32_t) 1 << 29) | + (sa->sa_family == AF_INET6 ? (uint32_t) 1 << 28 : 0) | + (goodbye ? (uint32_t) 1 << 26 : 0) | + (c->msg_id_hash)); + + iov[0].iov_base = &header; + iov[0].iov_len = sizeof(header); + + iov[1].iov_base = sa->sa_family == AF_INET ? (void*) &((struct sockaddr_in*) sa)->sin_addr : (void*) &((struct sockaddr_in6*) sa)->sin6_addr; + iov[1].iov_len = sa->sa_family == AF_INET ? 4 : 16; + + iov[2].iov_base = (char*) MIME_TYPE; + iov[2].iov_len = sizeof(MIME_TYPE); + + iov[3].iov_base = c->sdp_data; + iov[3].iov_len = strlen(c->sdp_data); + + m.msg_name = NULL; + m.msg_namelen = 0; + m.msg_iov = iov; + m.msg_iovlen = 4; + m.msg_control = NULL; + m.msg_controllen = 0; + m.msg_flags = 0; + + if ((k = sendmsg(c->fd, &m, MSG_DONTWAIT)) < 0) + pa_log_warn("sendmsg() failed: %s\n", pa_cstrerror(errno)); + + return k; +} + +pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd) { + pa_assert(c); + pa_assert(fd >= 0); + + c->fd = fd; + c->sdp_data = NULL; + return c; +} + +int pa_sap_recv(pa_sap_context *c, int *goodbye) { + struct msghdr m; + struct iovec iov; + int size, k; + char *buf = NULL, *e; + uint32_t header; + int six, ac; + ssize_t r; + + pa_assert(c); + pa_assert(goodbye); + + if (ioctl(c->fd, FIONREAD, &size) < 0) { + pa_log_warn("FIONREAD failed: %s", pa_cstrerror(errno)); + goto fail; + } + + buf = pa_xnew(char, size+1); + buf[size] = 0; + + iov.iov_base = buf; + iov.iov_len = size; + + m.msg_name = NULL; + m.msg_namelen = 0; + m.msg_iov = &iov; + m.msg_iovlen = 1; + m.msg_control = NULL; + m.msg_controllen = 0; + m.msg_flags = 0; + + if ((r = recvmsg(c->fd, &m, 0)) != size) { + pa_log_warn("recvmsg() failed: %s", r < 0 ? pa_cstrerror(errno) : "size mismatch"); + goto fail; + } + + if (size < 4) { + pa_log_warn("SAP packet too short."); + goto fail; + } + + memcpy(&header, buf, sizeof(uint32_t)); + header = ntohl(header); + + if (header >> 29 != 1) { + pa_log_warn("Unsupported SAP version."); + goto fail; + } + + if ((header >> 25) & 1) { + pa_log_warn("Encrypted SAP not supported."); + goto fail; + } + + if ((header >> 24) & 1) { + pa_log_warn("Compressed SAP not supported."); + goto fail; + } + + six = (header >> 28) & 1; + ac = (header >> 16) & 0xFF; + + k = 4 + (six ? 16 : 4) + ac*4; + if (size < k) { + pa_log_warn("SAP packet too short (AD)."); + goto fail; + } + + e = buf + k; + size -= k; + + if ((unsigned) size >= sizeof(MIME_TYPE) && !strcmp(e, MIME_TYPE)) { + e += sizeof(MIME_TYPE); + size -= sizeof(MIME_TYPE); + } else if ((unsigned) size < sizeof(PA_SDP_HEADER)-1 || strncmp(e, PA_SDP_HEADER, sizeof(PA_SDP_HEADER)-1)) { + pa_log_warn("Invalid SDP header."); + goto fail; + } + + if (c->sdp_data) + pa_xfree(c->sdp_data); + + c->sdp_data = pa_xstrndup(e, size); + pa_xfree(buf); + + *goodbye = !!((header >> 26) & 1); + + return 0; + +fail: + pa_xfree(buf); + + return -1; +} diff --git a/src/modules/rtp/sap.h b/src/modules/rtp/sap.h new file mode 100644 index 00000000..f906a32b --- /dev/null +++ b/src/modules/rtp/sap.h @@ -0,0 +1,48 @@ +#ifndef foosaphfoo +#define foosaphfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/memblockq.h> +#include <pulsecore/memchunk.h> + +typedef struct pa_sap_context { + int fd; + char *sdp_data; + + uint16_t msg_id_hash; +} pa_sap_context; + +pa_sap_context* pa_sap_context_init_send(pa_sap_context *c, int fd, char *sdp_data); +void pa_sap_context_destroy(pa_sap_context *c); + +int pa_sap_send(pa_sap_context *c, int goodbye); + +pa_sap_context* pa_sap_context_init_recv(pa_sap_context *c, int fd); +int pa_sap_recv(pa_sap_context *c, int *goodbye); + +#endif diff --git a/src/modules/rtp/sdp.c b/src/modules/rtp/sdp.c new file mode 100644 index 00000000..50ac157a --- /dev/null +++ b/src/modules/rtp/sdp.c @@ -0,0 +1,261 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <time.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "sdp.h" +#include "rtp.h" + +char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, uint16_t port, uint8_t payload, const pa_sample_spec *ss) { + uint32_t ntp; + char buf_src[64], buf_dst[64], un[64]; + const char *u, *f, *a; + + pa_assert(src); + pa_assert(dst); + pa_assert(af == AF_INET || af == AF_INET6); + + pa_assert_se(f = pa_rtp_format_to_string(ss->format)); + + if (!(u = pa_get_user_name(un, sizeof(un)))) + u = "-"; + + ntp = time(NULL) + 2208988800U; + + pa_assert_se(a = inet_ntop(af, src, buf_src, sizeof(buf_src))); + pa_assert_se(a = inet_ntop(af, dst, buf_dst, sizeof(buf_dst))); + + return pa_sprintf_malloc( + PA_SDP_HEADER + "o=%s %lu 0 IN %s %s\n" + "s=%s\n" + "c=IN %s %s\n" + "t=%lu 0\n" + "a=recvonly\n" + "m=audio %u RTP/AVP %i\n" + "a=rtpmap:%i %s/%u/%u\n" + "a=type:broadcast\n", + u, (unsigned long) ntp, af == AF_INET ? "IP4" : "IP6", buf_src, + name, + af == AF_INET ? "IP4" : "IP6", buf_dst, + (unsigned long) ntp, + port, payload, + payload, f, ss->rate, ss->channels); +} + +static pa_sample_spec *parse_sdp_sample_spec(pa_sample_spec *ss, char *c) { + unsigned rate, channels; + pa_assert(ss); + pa_assert(c); + + if (pa_startswith(c, "L16/")) { + ss->format = PA_SAMPLE_S16BE; + c += 4; + } else if (pa_startswith(c, "L8/")) { + ss->format = PA_SAMPLE_U8; + c += 3; + } else if (pa_startswith(c, "PCMA/")) { + ss->format = PA_SAMPLE_ALAW; + c += 5; + } else if (pa_startswith(c, "PCMU/")) { + ss->format = PA_SAMPLE_ULAW; + c += 5; + } else + return NULL; + + if (sscanf(c, "%u/%u", &rate, &channels) == 2) { + ss->rate = rate; + ss->channels = channels; + } else if (sscanf(c, "%u", &rate) == 2) { + ss->rate = rate; + ss->channels = 1; + } else + return NULL; + + if (!pa_sample_spec_valid(ss)) + return NULL; + + return ss; +} + +pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *i, int is_goodbye) { + uint16_t port = 0; + int ss_valid = 0; + + pa_assert(t); + pa_assert(i); + + i->origin = i->session_name = NULL; + i->salen = 0; + i->payload = 255; + + if (!pa_startswith(t, PA_SDP_HEADER)) { + pa_log("Failed to parse SDP data: invalid header."); + goto fail; + } + + t += sizeof(PA_SDP_HEADER)-1; + + while (*t) { + size_t l; + + l = strcspn(t, "\n"); + + if (l <= 2) { + pa_log("Failed to parse SDP data: line too short: >%s<.", t); + goto fail; + } + + if (pa_startswith(t, "o=")) + i->origin = pa_xstrndup(t+2, l-2); + else if (pa_startswith(t, "s=")) + i->session_name = pa_xstrndup(t+2, l-2); + else if (pa_startswith(t, "c=IN IP4 ")) { + char a[64]; + size_t k; + + k = l-8 > sizeof(a) ? sizeof(a) : l-8; + + pa_strlcpy(a, t+9, k); + a[strcspn(a, "/")] = 0; + + if (inet_pton(AF_INET, a, &((struct sockaddr_in*) &i->sa)->sin_addr) <= 0) { + pa_log("Failed to parse SDP data: bad address: >%s<.", a); + goto fail; + } + + ((struct sockaddr_in*) &i->sa)->sin_family = AF_INET; + ((struct sockaddr_in*) &i->sa)->sin_port = 0; + i->salen = sizeof(struct sockaddr_in); + } else if (pa_startswith(t, "c=IN IP6 ")) { + char a[64]; + size_t k; + + k = l-8 > sizeof(a) ? sizeof(a) : l-8; + + pa_strlcpy(a, t+9, k); + a[strcspn(a, "/")] = 0; + + if (inet_pton(AF_INET6, a, &((struct sockaddr_in6*) &i->sa)->sin6_addr) <= 0) { + pa_log("Failed to parse SDP data: bad address: >%s<.", a); + goto fail; + } + + ((struct sockaddr_in6*) &i->sa)->sin6_family = AF_INET6; + ((struct sockaddr_in6*) &i->sa)->sin6_port = 0; + i->salen = sizeof(struct sockaddr_in6); + } else if (pa_startswith(t, "m=audio ")) { + + if (i->payload > 127) { + int _port, _payload; + + if (sscanf(t+8, "%i RTP/AVP %i", &_port, &_payload) == 2) { + + if (_port <= 0 || _port > 0xFFFF) { + pa_log("Failed to parse SDP data: invalid port %i.", _port); + goto fail; + } + + if (_payload < 0 || _payload > 127) { + pa_log("Failed to parse SDP data: invalid payload %i.", _payload); + goto fail; + } + + port = (uint16_t) _port; + i->payload = (uint8_t) _payload; + + if (pa_rtp_sample_spec_from_payload(i->payload, &i->sample_spec)) + ss_valid = 1; + } + } + } else if (pa_startswith(t, "a=rtpmap:")) { + + if (i->payload <= 127) { + char c[64]; + int _payload; + + if (sscanf(t+9, "%i %64c", &_payload, c) == 2) { + + if (_payload < 0 || _payload > 127) { + pa_log("Failed to parse SDP data: invalid payload %i.", _payload); + goto fail; + } + if (_payload == i->payload) { + + c[strcspn(c, "\n")] = 0; + + if (parse_sdp_sample_spec(&i->sample_spec, c)) + ss_valid = 1; + } + } + } + } + + t += l; + + if (*t == '\n') + t++; + } + + if (!i->origin || (!is_goodbye && (!i->salen || i->payload > 127 || !ss_valid || port == 0))) { + pa_log("Failed to parse SDP data: missing data."); + goto fail; + } + + if (((struct sockaddr*) &i->sa)->sa_family == AF_INET) + ((struct sockaddr_in*) &i->sa)->sin_port = htons(port); + else + ((struct sockaddr_in6*) &i->sa)->sin6_port = htons(port); + + return i; + +fail: + pa_xfree(i->origin); + pa_xfree(i->session_name); + + return NULL; +} + +void pa_sdp_info_destroy(pa_sdp_info *i) { + pa_assert(i); + + pa_xfree(i->origin); + pa_xfree(i->session_name); +} diff --git a/src/modules/rtp/sdp.h b/src/modules/rtp/sdp.h new file mode 100644 index 00000000..7c91fca6 --- /dev/null +++ b/src/modules/rtp/sdp.h @@ -0,0 +1,52 @@ +#ifndef foosdphfoo +#define foosdphfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulse/sample.h> + +#define PA_SDP_HEADER "v=0\n" + +typedef struct pa_sdp_info { + char *origin; + char *session_name; + + struct sockaddr_storage sa; + socklen_t salen; + + pa_sample_spec sample_spec; + uint8_t payload; +} pa_sdp_info; + +char *pa_sdp_build(int af, const void *src, const void *dst, const char *name, uint16_t port, uint8_t payload, const pa_sample_spec *ss); + +pa_sdp_info *pa_sdp_parse(const char *t, pa_sdp_info *info, int is_goodbye); + +void pa_sdp_info_destroy(pa_sdp_info *i); + +#endif diff --git a/src/pulse/Makefile b/src/pulse/Makefile new file mode 100644 index 00000000..7c8875f3 --- /dev/null +++ b/src/pulse/Makefile @@ -0,0 +1,13 @@ +# This is a dirty trick just to ease compilation with emacs +# +# This file is not intended to be distributed or anything +# +# So: don't touch it, even better ignore it! + +all: + $(MAKE) -C .. + +clean: + $(MAKE) -C .. clean + +.PHONY: all clean diff --git a/src/pulse/browser.c b/src/pulse/browser.c new file mode 100644 index 00000000..55e0b2cd --- /dev/null +++ b/src/pulse/browser.c @@ -0,0 +1,453 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> + +#include <avahi-client/lookup.h> +#include <avahi-common/domain.h> +#include <avahi-common/error.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/avahi-wrap.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/macro.h> + +#include "browser.h" + +#define SERVICE_TYPE_SINK "_pulse-sink._tcp." +#define SERVICE_TYPE_SOURCE "_pulse-source._tcp." +#define SERVICE_TYPE_SERVER "_pulse-server._tcp." + +struct pa_browser { + PA_REFCNT_DECLARE; + + pa_mainloop_api *mainloop; + AvahiPoll* avahi_poll; + + pa_browse_cb_t callback; + void *userdata; + + pa_browser_error_cb_t error_callback; + void *error_userdata; + + AvahiClient *client; + AvahiServiceBrowser *server_browser, *sink_browser, *source_browser; + +}; + +static int map_to_opcode(const char *type, int new) { + + if (avahi_domain_equal(type, SERVICE_TYPE_SINK)) + return new ? PA_BROWSE_NEW_SINK : PA_BROWSE_REMOVE_SINK; + else if (avahi_domain_equal(type, SERVICE_TYPE_SOURCE)) + return new ? PA_BROWSE_NEW_SOURCE : PA_BROWSE_REMOVE_SOURCE; + else if (avahi_domain_equal(type, SERVICE_TYPE_SERVER)) + return new ? PA_BROWSE_NEW_SERVER : PA_BROWSE_REMOVE_SERVER; + + return -1; +} + +static void resolve_callback( + AvahiServiceResolver *r, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiResolverEvent event, + const char *name, + const char *type, + const char *domain, + const char *host_name, + const AvahiAddress *aa, + uint16_t port, + AvahiStringList *txt, + AvahiLookupResultFlags flags, + void *userdata) { + + pa_browser *b = userdata; + pa_browse_info i; + char ip[256], a[256]; + int opcode; + int device_found = 0; + uint32_t cookie; + pa_sample_spec ss; + int ss_valid = 0; + char *key = NULL, *value = NULL; + + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); + + memset(&i, 0, sizeof(i)); + i.name = name; + + if (event != AVAHI_RESOLVER_FOUND) + goto fail; + + if (!b->callback) + goto fail; + + opcode = map_to_opcode(type, 1); + pa_assert(opcode >= 0); + + if (aa->proto == AVAHI_PROTO_INET) + pa_snprintf(a, sizeof(a), "tcp:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port); + else { + pa_assert(aa->proto == AVAHI_PROTO_INET6); + pa_snprintf(a, sizeof(a), "tcp6:%s:%u", avahi_address_snprint(ip, sizeof(ip), aa), port); + } + i.server = a; + + + while (txt) { + + if (avahi_string_list_get_pair(txt, &key, &value, NULL) < 0) + break; + + if (!strcmp(key, "device")) { + device_found = 1; + pa_xfree((char*) i.device); + i.device = value; + value = NULL; + } else if (!strcmp(key, "server-version")) { + pa_xfree((char*) i.server_version); + i.server_version = value; + value = NULL; + } else if (!strcmp(key, "user-name")) { + pa_xfree((char*) i.user_name); + i.user_name = value; + value = NULL; + } else if (!strcmp(key, "fqdn")) { + size_t l; + + pa_xfree((char*) i.fqdn); + i.fqdn = value; + value = NULL; + + l = strlen(a); + pa_assert(l+1 <= sizeof(a)); + strncat(a, " ", sizeof(a)-l-1); + strncat(a, i.fqdn, sizeof(a)-l-2); + } else if (!strcmp(key, "cookie")) { + + if (pa_atou(value, &cookie) < 0) + goto fail; + + i.cookie = &cookie; + } else if (!strcmp(key, "description")) { + pa_xfree((char*) i.description); + i.description = value; + value = NULL; + } else if (!strcmp(key, "channels")) { + uint32_t ch; + + if (pa_atou(value, &ch) < 0 || ch <= 0 || ch > 255) + goto fail; + + ss.channels = (uint8_t) ch; + ss_valid |= 1; + + } else if (!strcmp(key, "rate")) { + if (pa_atou(value, &ss.rate) < 0) + goto fail; + ss_valid |= 2; + } else if (!strcmp(key, "format")) { + + if ((ss.format = pa_parse_sample_format(value)) == PA_SAMPLE_INVALID) + goto fail; + + ss_valid |= 4; + } + + pa_xfree(key); + pa_xfree(value); + key = value = NULL; + + txt = avahi_string_list_get_next(txt); + } + + /* No device txt record was sent for a sink or source service */ + if (opcode != PA_BROWSE_NEW_SERVER && !device_found) + goto fail; + + if (ss_valid == 7) + i.sample_spec = &ss; + + b->callback(b, opcode, &i, b->userdata); + +fail: + pa_xfree((void*) i.device); + pa_xfree((void*) i.fqdn); + pa_xfree((void*) i.server_version); + pa_xfree((void*) i.user_name); + pa_xfree((void*) i.description); + + pa_xfree(key); + pa_xfree(value); + + avahi_service_resolver_free(r); +} + +static void handle_failure(pa_browser *b) { + const char *e = NULL; + + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); + + if (b->sink_browser) + avahi_service_browser_free(b->sink_browser); + if (b->source_browser) + avahi_service_browser_free(b->source_browser); + if (b->server_browser) + avahi_service_browser_free(b->server_browser); + + b->sink_browser = b->source_browser = b->server_browser = NULL; + + if (b->client) { + e = avahi_strerror(avahi_client_errno(b->client)); + avahi_client_free(b->client); + } + + b->client = NULL; + + if (b->error_callback) + b->error_callback(b, e, b->error_userdata); +} + +static void browse_callback( + AvahiServiceBrowser *sb, + AvahiIfIndex interface, + AvahiProtocol protocol, + AvahiBrowserEvent event, + const char *name, + const char *type, + const char *domain, + AvahiLookupResultFlags flags, + void *userdata) { + + pa_browser *b = userdata; + + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); + + switch (event) { + case AVAHI_BROWSER_NEW: { + + if (!avahi_service_resolver_new( + b->client, + interface, + protocol, + name, + type, + domain, + AVAHI_PROTO_UNSPEC, + 0, + resolve_callback, + b)) + handle_failure(b); + + break; + } + + case AVAHI_BROWSER_REMOVE: { + + if (b->callback) { + pa_browse_info i; + int opcode; + + memset(&i, 0, sizeof(i)); + i.name = name; + + opcode = map_to_opcode(type, 0); + pa_assert(opcode >= 0); + + b->callback(b, opcode, &i, b->userdata); + } + break; + } + + case AVAHI_BROWSER_FAILURE: { + handle_failure(b); + break; + } + + default: + ; + } +} + +static void client_callback(AvahiClient *s, AvahiClientState state, void *userdata) { + pa_browser *b = userdata; + + pa_assert(s); + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); + + if (state == AVAHI_CLIENT_FAILURE) + handle_failure(b); +} + +static void browser_free(pa_browser *b); + +pa_browser *pa_browser_new(pa_mainloop_api *mainloop) { + return pa_browser_new_full(mainloop, PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, NULL); +} + +pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string) { + pa_browser *b; + int error; + + pa_assert(mainloop); + + if (flags & ~(PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES) || flags == 0) + return NULL; + + b = pa_xnew(pa_browser, 1); + b->mainloop = mainloop; + PA_REFCNT_INIT(b); + b->callback = NULL; + b->userdata = NULL; + b->error_callback = NULL; + b->error_userdata = NULL; + b->sink_browser = b->source_browser = b->server_browser = NULL; + + b->avahi_poll = pa_avahi_poll_new(mainloop); + + if (!(b->client = avahi_client_new(b->avahi_poll, 0, client_callback, b, &error))) { + if (error_string) + *error_string = avahi_strerror(error); + goto fail; + } + + if ((flags & PA_BROWSE_FOR_SERVERS) && + !(b->server_browser = avahi_service_browser_new( + b->client, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_INET, + SERVICE_TYPE_SERVER, + NULL, + 0, + browse_callback, + b))) { + + if (error_string) + *error_string = avahi_strerror(avahi_client_errno(b->client)); + goto fail; + } + + if ((flags & PA_BROWSE_FOR_SINKS) && + !(b->sink_browser = avahi_service_browser_new( + b->client, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + SERVICE_TYPE_SINK, + NULL, + 0, + browse_callback, + b))) { + + if (error_string) + *error_string = avahi_strerror(avahi_client_errno(b->client)); + goto fail; + } + + if ((flags & PA_BROWSE_FOR_SOURCES) && + !(b->source_browser = avahi_service_browser_new( + b->client, + AVAHI_IF_UNSPEC, + AVAHI_PROTO_UNSPEC, + SERVICE_TYPE_SOURCE, + NULL, + 0, + browse_callback, + b))) { + + if (error_string) + *error_string = avahi_strerror(avahi_client_errno(b->client)); + goto fail; + } + + return b; + +fail: + if (b) + browser_free(b); + + return NULL; +} + +static void browser_free(pa_browser *b) { + pa_assert(b); + pa_assert(b->mainloop); + + if (b->sink_browser) + avahi_service_browser_free(b->sink_browser); + if (b->source_browser) + avahi_service_browser_free(b->source_browser); + if (b->server_browser) + avahi_service_browser_free(b->server_browser); + + if (b->client) + avahi_client_free(b->client); + + if (b->avahi_poll) + pa_avahi_poll_free(b->avahi_poll); + + pa_xfree(b); +} + +pa_browser *pa_browser_ref(pa_browser *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); + + PA_REFCNT_INC(b); + return b; +} + +void pa_browser_unref(pa_browser *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); + + if (PA_REFCNT_DEC(b) <= 0) + browser_free(b); +} + +void pa_browser_set_callback(pa_browser *b, pa_browse_cb_t cb, void *userdata) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); + + b->callback = cb; + b->userdata = userdata; +} + +void pa_browser_set_error_callback(pa_browser *b, pa_browser_error_cb_t cb, void *userdata) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) >= 1); + + b->error_callback = cb; + b->error_userdata = userdata; +} diff --git a/src/pulse/browser.h b/src/pulse/browser.h new file mode 100644 index 00000000..b039ca33 --- /dev/null +++ b/src/pulse/browser.h @@ -0,0 +1,100 @@ +#ifndef foobrowserhfoo +#define foobrowserhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/cdecl.h> + +/** \file + * An abstract interface for Zeroconf browsing of PulseAudio servers */ + +PA_C_DECL_BEGIN + +/** An opaque Zeroconf service browser object */ +typedef struct pa_browser pa_browser; + +/** Opcodes for pa_browser_cb_t callbacks */ +typedef enum pa_browse_opcode { + PA_BROWSE_NEW_SERVER = 0, /**< New server found */ + PA_BROWSE_NEW_SINK, /**< New sink found */ + PA_BROWSE_NEW_SOURCE, /**< New source found */ + PA_BROWSE_REMOVE_SERVER, /**< Server disappeared */ + PA_BROWSE_REMOVE_SINK, /**< Sink disappeared */ + PA_BROWSE_REMOVE_SOURCE /**< Source disappeared */ +} pa_browse_opcode_t; + +typedef enum pa_browse_flags { + PA_BROWSE_FOR_SERVERS = 1, /**< Browse for servers */ + PA_BROWSE_FOR_SINKS = 2, /**< Browse for sinks */ + PA_BROWSE_FOR_SOURCES = 4 /** Browse for sources */ +} pa_browse_flags_t; + +/** Create a new browser object on the specified main loop */ +pa_browser *pa_browser_new(pa_mainloop_api *mainloop); + +/** Same pa_browser_new, but pass additional flags parameter. */ +pa_browser *pa_browser_new_full(pa_mainloop_api *mainloop, pa_browse_flags_t flags, const char **error_string); + +/** Increase reference counter of the specified browser object */ +pa_browser *pa_browser_ref(pa_browser *z); + +/** Decrease reference counter of the specified browser object */ +void pa_browser_unref(pa_browser *z); + +/** Information about a sink/source/server found with Zeroconf */ +typedef struct pa_browse_info { + const char *name; /**< Unique service name; always available */ + + const char *server; /**< Server name; always available */ + const char *server_version; /**< Server version string; optional */ + const char *user_name; /**< User name of the server process; optional */ + const char *fqdn; /* Server version; optional */ + const uint32_t *cookie; /* Server cookie; optional */ + + const char *device; /* Device name; always available when this information is of a sink/source */ + const char *description; /* Device description; optional */ + const pa_sample_spec *sample_spec; /* Sample specification of the device; optional */ +} pa_browse_info; + +/** Callback prototype */ +typedef void (*pa_browse_cb_t)(pa_browser *z, pa_browse_opcode_t c, const pa_browse_info *i, void *userdata); + +/** Set the callback pointer for the browser object */ +void pa_browser_set_callback(pa_browser *z, pa_browse_cb_t cb, void *userdata); + +/** Callback prototype for errors */ +typedef void (*pa_browser_error_cb_t)(pa_browser *z, const char *error_string, void *userdata); + +/** Set a callback function that is called whenever the browser object + * becomes invalid due to an error. After this function has been + * called the browser object has become invalid and should be + * freed. */ +void pa_browser_set_error_callback(pa_browser *z, pa_browser_error_cb_t, void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/cdecl.h b/src/pulse/cdecl.h new file mode 100644 index 00000000..e1f23d25 --- /dev/null +++ b/src/pulse/cdecl.h @@ -0,0 +1,62 @@ +#ifndef foopulsecdeclhfoo +#define foopulsecdeclhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + +/** \file + * C++ compatibility support */ + +#ifdef __cplusplus +/** If using C++ this macro enables C mode, otherwise does nothing */ +#define PA_C_DECL_BEGIN extern "C" { +/** If using C++ this macros switches back to C++ mode, otherwise does nothing */ +#define PA_C_DECL_END } + +#else +/** If using C++ this macro enables C mode, otherwise does nothing */ +#define PA_C_DECL_BEGIN +/** If using C++ this macros switches back to C++ mode, otherwise does nothing */ +#define PA_C_DECL_END + +#endif + +#ifndef PA_GCC_PURE +#ifdef __GNUCC__ +#define PA_GCC_PURE __attribute__ ((pure)) +#else +/** This function's return value depends only the arguments list and global state **/ +#define PA_GCC_PURE +#endif +#endif + +#ifndef PA_GCC_CONST +#ifdef __GNUCC__ +#define PA_GCC_CONST __attribute__ ((pure)) +#else +/** This function's return value depends only the arguments list (stricter version of PA_GCC_CONST) **/ +#define PA_GCC_CONST +#endif +#endif + +#endif diff --git a/src/pulse/channelmap.c b/src/pulse/channelmap.c new file mode 100644 index 00000000..2b35ee75 --- /dev/null +++ b/src/pulse/channelmap.c @@ -0,0 +1,533 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdio.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "channelmap.h" + +const char *const table[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = "mono", + + [PA_CHANNEL_POSITION_FRONT_CENTER] = "front-center", + [PA_CHANNEL_POSITION_FRONT_LEFT] = "front-left", + [PA_CHANNEL_POSITION_FRONT_RIGHT] = "front-right", + + [PA_CHANNEL_POSITION_REAR_CENTER] = "rear-center", + [PA_CHANNEL_POSITION_REAR_LEFT] = "rear-left", + [PA_CHANNEL_POSITION_REAR_RIGHT] = "rear-right", + + [PA_CHANNEL_POSITION_LFE] = "lfe", + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = "front-left-of-center", + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = "front-right-of-center", + + [PA_CHANNEL_POSITION_SIDE_LEFT] = "side-left", + [PA_CHANNEL_POSITION_SIDE_RIGHT] = "side-right", + + [PA_CHANNEL_POSITION_AUX0] = "aux0", + [PA_CHANNEL_POSITION_AUX1] = "aux1", + [PA_CHANNEL_POSITION_AUX2] = "aux2", + [PA_CHANNEL_POSITION_AUX3] = "aux3", + [PA_CHANNEL_POSITION_AUX4] = "aux4", + [PA_CHANNEL_POSITION_AUX5] = "aux5", + [PA_CHANNEL_POSITION_AUX6] = "aux6", + [PA_CHANNEL_POSITION_AUX7] = "aux7", + [PA_CHANNEL_POSITION_AUX8] = "aux8", + [PA_CHANNEL_POSITION_AUX9] = "aux9", + [PA_CHANNEL_POSITION_AUX10] = "aux10", + [PA_CHANNEL_POSITION_AUX11] = "aux11", + [PA_CHANNEL_POSITION_AUX12] = "aux12", + [PA_CHANNEL_POSITION_AUX13] = "aux13", + [PA_CHANNEL_POSITION_AUX14] = "aux14", + [PA_CHANNEL_POSITION_AUX15] = "aux15", + [PA_CHANNEL_POSITION_AUX16] = "aux16", + [PA_CHANNEL_POSITION_AUX17] = "aux17", + [PA_CHANNEL_POSITION_AUX18] = "aux18", + [PA_CHANNEL_POSITION_AUX19] = "aux19", + [PA_CHANNEL_POSITION_AUX20] = "aux20", + [PA_CHANNEL_POSITION_AUX21] = "aux21", + [PA_CHANNEL_POSITION_AUX22] = "aux22", + [PA_CHANNEL_POSITION_AUX23] = "aux23", + [PA_CHANNEL_POSITION_AUX24] = "aux24", + [PA_CHANNEL_POSITION_AUX25] = "aux25", + [PA_CHANNEL_POSITION_AUX26] = "aux26", + [PA_CHANNEL_POSITION_AUX27] = "aux27", + [PA_CHANNEL_POSITION_AUX28] = "aux28", + [PA_CHANNEL_POSITION_AUX29] = "aux29", + [PA_CHANNEL_POSITION_AUX30] = "aux30", + [PA_CHANNEL_POSITION_AUX31] = "aux31", + + [PA_CHANNEL_POSITION_TOP_CENTER] = "top-center", + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = "top-front-center", + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = "top-front-left", + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = "top-front-right", + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = "top-rear-center", + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = "top-rear-left", + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = "top-rear-right" +}; + +const char *const pretty_table[PA_CHANNEL_POSITION_MAX] = { + [PA_CHANNEL_POSITION_MONO] = "Mono", + + [PA_CHANNEL_POSITION_FRONT_CENTER] = "Front Center", + [PA_CHANNEL_POSITION_FRONT_LEFT] = "Front Left", + [PA_CHANNEL_POSITION_FRONT_RIGHT] = "Front Right", + + [PA_CHANNEL_POSITION_REAR_CENTER] = "Rear Center", + [PA_CHANNEL_POSITION_REAR_LEFT] = "Rear Left", + [PA_CHANNEL_POSITION_REAR_RIGHT] = "Rear Right", + + [PA_CHANNEL_POSITION_LFE] = "Low Frequency Emmiter", + + [PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER] = "Front Left-of-center", + [PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER] = "Front Right-of-center", + + [PA_CHANNEL_POSITION_SIDE_LEFT] = "Side Left", + [PA_CHANNEL_POSITION_SIDE_RIGHT] = "Side Right", + + [PA_CHANNEL_POSITION_AUX0] = "Auxiliary 0", + [PA_CHANNEL_POSITION_AUX1] = "Auxiliary 1", + [PA_CHANNEL_POSITION_AUX2] = "Auxiliary 2", + [PA_CHANNEL_POSITION_AUX3] = "Auxiliary 3", + [PA_CHANNEL_POSITION_AUX4] = "Auxiliary 4", + [PA_CHANNEL_POSITION_AUX5] = "Auxiliary 5", + [PA_CHANNEL_POSITION_AUX6] = "Auxiliary 6", + [PA_CHANNEL_POSITION_AUX7] = "Auxiliary 7", + [PA_CHANNEL_POSITION_AUX8] = "Auxiliary 8", + [PA_CHANNEL_POSITION_AUX9] = "Auxiliary 9", + [PA_CHANNEL_POSITION_AUX10] = "Auxiliary 10", + [PA_CHANNEL_POSITION_AUX11] = "Auxiliary 11", + [PA_CHANNEL_POSITION_AUX12] = "Auxiliary 12", + [PA_CHANNEL_POSITION_AUX13] = "Auxiliary 13", + [PA_CHANNEL_POSITION_AUX14] = "Auxiliary 14", + [PA_CHANNEL_POSITION_AUX15] = "Auxiliary 15", + [PA_CHANNEL_POSITION_AUX16] = "Auxiliary 16", + [PA_CHANNEL_POSITION_AUX17] = "Auxiliary 17", + [PA_CHANNEL_POSITION_AUX18] = "Auxiliary 18", + [PA_CHANNEL_POSITION_AUX19] = "Auxiliary 19", + [PA_CHANNEL_POSITION_AUX20] = "Auxiliary 20", + [PA_CHANNEL_POSITION_AUX21] = "Auxiliary 21", + [PA_CHANNEL_POSITION_AUX22] = "Auxiliary 22", + [PA_CHANNEL_POSITION_AUX23] = "Auxiliary 23", + [PA_CHANNEL_POSITION_AUX24] = "Auxiliary 24", + [PA_CHANNEL_POSITION_AUX25] = "Auxiliary 25", + [PA_CHANNEL_POSITION_AUX26] = "Auxiliary 26", + [PA_CHANNEL_POSITION_AUX27] = "Auxiliary 27", + [PA_CHANNEL_POSITION_AUX28] = "Auxiliary 28", + [PA_CHANNEL_POSITION_AUX29] = "Auxiliary 29", + [PA_CHANNEL_POSITION_AUX30] = "Auxiliary 30", + [PA_CHANNEL_POSITION_AUX31] = "Auxiliary 31", + + [PA_CHANNEL_POSITION_TOP_CENTER] = "Top Center", + + [PA_CHANNEL_POSITION_TOP_FRONT_CENTER] = "Top Front Center", + [PA_CHANNEL_POSITION_TOP_FRONT_LEFT] = "Top Front Left", + [PA_CHANNEL_POSITION_TOP_FRONT_RIGHT] = "Top Front Right", + + [PA_CHANNEL_POSITION_TOP_REAR_CENTER] = "Top Rear Center", + [PA_CHANNEL_POSITION_TOP_REAR_LEFT] = "Top Rear left", + [PA_CHANNEL_POSITION_TOP_REAR_RIGHT] = "Top Rear Right" +}; + +pa_channel_map* pa_channel_map_init(pa_channel_map *m) { + unsigned c; + pa_assert(m); + + m->channels = 0; + + for (c = 0; c < PA_CHANNELS_MAX; c++) + m->map[c] = PA_CHANNEL_POSITION_INVALID; + + return m; +} + +pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m) { + pa_assert(m); + + pa_channel_map_init(m); + + m->channels = 1; + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; +} + +pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m) { + pa_assert(m); + + pa_channel_map_init(m); + + m->channels = 2; + m->map[0] = PA_CHANNEL_POSITION_LEFT; + m->map[1] = PA_CHANNEL_POSITION_RIGHT; + return m; +} + +pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def) { + pa_assert(m); + pa_assert(channels > 0); + pa_assert(channels <= PA_CHANNELS_MAX); + + pa_channel_map_init(m); + + m->channels = channels; + + switch (def) { + case PA_CHANNEL_MAP_AIFF: + + /* This is somewhat compatible with RFC3551 */ + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 6: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + m->map[3] = PA_CHANNEL_POSITION_FRONT_RIGHT; + m->map[4] = PA_CHANNEL_POSITION_SIDE_RIGHT; + m->map[5] = PA_CHANNEL_POSITION_LFE; + return m; + + case 5: + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + m->map[3] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[4] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + case 3: + m->map[0] = PA_CHANNEL_POSITION_LEFT; + m->map[1] = PA_CHANNEL_POSITION_RIGHT; + m->map[2] = PA_CHANNEL_POSITION_CENTER; + return m; + + case 4: + m->map[0] = PA_CHANNEL_POSITION_LEFT; + m->map[1] = PA_CHANNEL_POSITION_CENTER; + m->map[2] = PA_CHANNEL_POSITION_RIGHT; + m->map[3] = PA_CHANNEL_POSITION_LFE; + return m; + + default: + return NULL; + } + + case PA_CHANNEL_MAP_ALSA: + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 8: + m->map[6] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT; + /* Fall through */ + + case 6: + m->map[5] = PA_CHANNEL_POSITION_LFE; + /* Fall through */ + + case 5: + m->map[4] = PA_CHANNEL_POSITION_FRONT_CENTER; + /* Fall through */ + + case 4: + m->map[2] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[3] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + default: + return NULL; + } + + case PA_CHANNEL_MAP_AUX: { + unsigned i; + + if (channels >= PA_CHANNELS_MAX) + return NULL; + + for (i = 0; i < channels; i++) + m->map[i] = PA_CHANNEL_POSITION_AUX0 + i; + + return m; + } + + case PA_CHANNEL_MAP_WAVEEX: + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 18: + m->map[15] = PA_CHANNEL_POSITION_TOP_REAR_LEFT; + m->map[16] = PA_CHANNEL_POSITION_TOP_REAR_CENTER; + m->map[17] = PA_CHANNEL_POSITION_TOP_REAR_RIGHT; + /* Fall through */ + + case 15: + m->map[12] = PA_CHANNEL_POSITION_TOP_FRONT_LEFT; + m->map[13] = PA_CHANNEL_POSITION_TOP_FRONT_CENTER; + m->map[14] = PA_CHANNEL_POSITION_TOP_FRONT_RIGHT; + /* Fall through */ + + case 12: + m->map[11] = PA_CHANNEL_POSITION_TOP_CENTER; + /* Fall through */ + + case 11: + m->map[9] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[10] = PA_CHANNEL_POSITION_SIDE_RIGHT; + /* Fall through */ + + case 9: + m->map[8] = PA_CHANNEL_POSITION_REAR_CENTER; + /* Fall through */ + + case 8: + m->map[6] = PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER; + m->map[7] = PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER; + /* Fall through */ + + case 6: + m->map[4] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[5] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 4: + m->map[3] = PA_CHANNEL_POSITION_LFE; + /* Fall through */ + + case 3: + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + default: + return NULL; + } + + case PA_CHANNEL_MAP_OSS: + + switch (channels) { + case 1: + m->map[0] = PA_CHANNEL_POSITION_MONO; + return m; + + case 8: + m->map[6] = PA_CHANNEL_POSITION_REAR_LEFT; + m->map[7] = PA_CHANNEL_POSITION_REAR_RIGHT; + /* Fall through */ + + case 6: + m->map[4] = PA_CHANNEL_POSITION_SIDE_LEFT; + m->map[5] = PA_CHANNEL_POSITION_SIDE_RIGHT; + /* Fall through */ + + case 4: + m->map[3] = PA_CHANNEL_POSITION_LFE; + /* Fall through */ + + case 3: + m->map[2] = PA_CHANNEL_POSITION_FRONT_CENTER; + /* Fall through */ + + case 2: + m->map[0] = PA_CHANNEL_POSITION_FRONT_LEFT; + m->map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT; + return m; + + default: + return NULL; + } + + + default: + return NULL; + } +} + + +const char* pa_channel_position_to_string(pa_channel_position_t pos) { + + if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX) + return NULL; + + return table[pos]; +} + +const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos) { + if (pos < 0 || pos >= PA_CHANNEL_POSITION_MAX) + return NULL; + + return pretty_table[pos]; +} + +int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) { + unsigned c; + + pa_assert(a); + pa_assert(b); + + if (a->channels != b->channels) + return 0; + + for (c = 0; c < a->channels; c++) + if (a->map[c] != b->map[c]) + return 0; + + return 1; +} + +char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map) { + unsigned channel; + int first = 1; + char *e; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(map); + + *(e = s) = 0; + + for (channel = 0; channel < map->channels && l > 1; channel++) { + l -= pa_snprintf(e, l, "%s%s", + first ? "" : ",", + pa_channel_position_to_string(map->map[channel])); + + e = strchr(e, 0); + first = 0; + } + + return s; +} + +pa_channel_map *pa_channel_map_parse(pa_channel_map *rmap, const char *s) { + const char *state; + pa_channel_map map; + char *p; + + pa_assert(rmap); + pa_assert(s); + + memset(&map, 0, sizeof(map)); + + if (strcmp(s, "stereo") == 0) { + map.channels = 2; + map.map[0] = PA_CHANNEL_POSITION_LEFT; + map.map[1] = PA_CHANNEL_POSITION_RIGHT; + goto finish; + } + + state = NULL; + map.channels = 0; + + while ((p = pa_split(s, ",", &state))) { + + if (map.channels >= PA_CHANNELS_MAX) { + pa_xfree(p); + return NULL; + } + + /* Some special aliases */ + if (strcmp(p, "left") == 0) + map.map[map.channels++] = PA_CHANNEL_POSITION_LEFT; + else if (strcmp(p, "right") == 0) + map.map[map.channels++] = PA_CHANNEL_POSITION_RIGHT; + else if (strcmp(p, "center") == 0) + map.map[map.channels++] = PA_CHANNEL_POSITION_CENTER; + else if (strcmp(p, "subwoofer") == 0) + map.map[map.channels++] = PA_CHANNEL_POSITION_SUBWOOFER; + else { + pa_channel_position_t i; + + for (i = 0; i < PA_CHANNEL_POSITION_MAX; i++) + if (strcmp(p, table[i]) == 0) { + map.map[map.channels++] = i; + break; + } + + if (i >= PA_CHANNEL_POSITION_MAX) { + pa_xfree(p); + return NULL; + } + } + + pa_xfree(p); + } + +finish: + + if (!pa_channel_map_valid(&map)) + return NULL; + + *rmap = map; + return rmap; +} + +int pa_channel_map_valid(const pa_channel_map *map) { + unsigned c; + + pa_assert(map); + + if (map->channels <= 0 || map->channels > PA_CHANNELS_MAX) + return 0; + + for (c = 0; c < map->channels; c++) { + + if (map->map[c] < 0 ||map->map[c] >= PA_CHANNEL_POSITION_MAX) + return 0; + + } + + return 1; +} diff --git a/src/pulse/channelmap.h b/src/pulse/channelmap.h new file mode 100644 index 00000000..a05e1911 --- /dev/null +++ b/src/pulse/channelmap.h @@ -0,0 +1,197 @@ +#ifndef foochannelmaphfoo +#define foochannelmaphfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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/sample.h> +#include <pulse/cdecl.h> + +/** \page channelmap Channel Maps + * + * \section overv_sec Overview + * + * Channel maps provide a way to associate channels in a stream with a + * specific speaker position. This relieves applications of having to + * make sure their channel order is identical to the final output. + * + * \section init_sec Initialisation + * + * A channel map consists of an array of \ref pa_channel_position values, + * one for each channel. This array is stored together with a channel count + * in a pa_channel_map structure. + * + * Before filling the structure, the application must initialise it using + * pa_channel_map_init(). There are also a number of convenience functions + * for standard channel mappings: + * + * \li pa_channel_map_init_mono() - Create a channel map with only mono audio. + * \li pa_channel_map_init_stereo() - Create a standard stereo mapping. + * \li pa_channel_map_init_auto() - Create a standard channel map for up to + * six channels. + * + * \section conv_sec Convenience Functions + * + * The library contains a number of convenience functions for dealing with + * channel maps: + * + * \li pa_channel_map_valid() - Tests if a channel map is valid. + * \li pa_channel_map_equal() - Tests if two channel maps are identical. + * \li pa_channel_map_snprint() - Creates a textual description of a channel + * map. + */ + +/** \file + * Constants and routines for channel mapping handling */ + +PA_C_DECL_BEGIN + +/** A list of channel labels */ +typedef enum pa_channel_position { + PA_CHANNEL_POSITION_INVALID = -1, + PA_CHANNEL_POSITION_MONO = 0, + + PA_CHANNEL_POSITION_LEFT, + PA_CHANNEL_POSITION_RIGHT, + PA_CHANNEL_POSITION_CENTER, + + PA_CHANNEL_POSITION_FRONT_LEFT = PA_CHANNEL_POSITION_LEFT, + PA_CHANNEL_POSITION_FRONT_RIGHT = PA_CHANNEL_POSITION_RIGHT, + PA_CHANNEL_POSITION_FRONT_CENTER = PA_CHANNEL_POSITION_CENTER, + + PA_CHANNEL_POSITION_REAR_CENTER, + PA_CHANNEL_POSITION_REAR_LEFT, + PA_CHANNEL_POSITION_REAR_RIGHT, + + PA_CHANNEL_POSITION_LFE, + PA_CHANNEL_POSITION_SUBWOOFER = PA_CHANNEL_POSITION_LFE, + + PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, + PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, + + PA_CHANNEL_POSITION_SIDE_LEFT, + PA_CHANNEL_POSITION_SIDE_RIGHT, + + PA_CHANNEL_POSITION_AUX0, + PA_CHANNEL_POSITION_AUX1, + PA_CHANNEL_POSITION_AUX2, + PA_CHANNEL_POSITION_AUX3, + PA_CHANNEL_POSITION_AUX4, + PA_CHANNEL_POSITION_AUX5, + PA_CHANNEL_POSITION_AUX6, + PA_CHANNEL_POSITION_AUX7, + PA_CHANNEL_POSITION_AUX8, + PA_CHANNEL_POSITION_AUX9, + PA_CHANNEL_POSITION_AUX10, + PA_CHANNEL_POSITION_AUX11, + PA_CHANNEL_POSITION_AUX12, + PA_CHANNEL_POSITION_AUX13, + PA_CHANNEL_POSITION_AUX14, + PA_CHANNEL_POSITION_AUX15, + PA_CHANNEL_POSITION_AUX16, + PA_CHANNEL_POSITION_AUX17, + PA_CHANNEL_POSITION_AUX18, + PA_CHANNEL_POSITION_AUX19, + PA_CHANNEL_POSITION_AUX20, + PA_CHANNEL_POSITION_AUX21, + PA_CHANNEL_POSITION_AUX22, + PA_CHANNEL_POSITION_AUX23, + PA_CHANNEL_POSITION_AUX24, + PA_CHANNEL_POSITION_AUX25, + PA_CHANNEL_POSITION_AUX26, + PA_CHANNEL_POSITION_AUX27, + PA_CHANNEL_POSITION_AUX28, + PA_CHANNEL_POSITION_AUX29, + PA_CHANNEL_POSITION_AUX30, + PA_CHANNEL_POSITION_AUX31, + + PA_CHANNEL_POSITION_TOP_CENTER, + + PA_CHANNEL_POSITION_TOP_FRONT_LEFT, + PA_CHANNEL_POSITION_TOP_FRONT_RIGHT, + PA_CHANNEL_POSITION_TOP_FRONT_CENTER, + + PA_CHANNEL_POSITION_TOP_REAR_LEFT, + PA_CHANNEL_POSITION_TOP_REAR_RIGHT, + PA_CHANNEL_POSITION_TOP_REAR_CENTER, + + PA_CHANNEL_POSITION_MAX +} pa_channel_position_t; + +/** A list of channel mapping definitions for pa_channel_map_init_auto() */ +typedef enum pa_channel_map_def { + PA_CHANNEL_MAP_AIFF, /**< The mapping from RFC3551, which is based on AIFF-C */ + PA_CHANNEL_MAP_ALSA, /**< The default mapping used by ALSA */ + PA_CHANNEL_MAP_AUX, /**< Only aux channels */ + PA_CHANNEL_MAP_WAVEEX, /**< Microsoft's WAVEFORMATEXTENSIBLE mapping */ + PA_CHANNEL_MAP_OSS, /**< The default channel mapping used by OSS as defined in the OSS 4.0 API specs */ + + PA_CHANNEL_MAP_DEFAULT = PA_CHANNEL_MAP_AIFF /**< The default channel map */ +} pa_channel_map_def_t; + +/** A channel map which can be used to attach labels to specific + * channels of a stream. These values are relevant for conversion and + * mixing of streams */ +typedef struct pa_channel_map { + uint8_t channels; /**< Number of channels */ + pa_channel_position_t map[PA_CHANNELS_MAX]; /**< Channel labels */ +} pa_channel_map; + +/** Initialize the specified channel map and return a pointer to it */ +pa_channel_map* pa_channel_map_init(pa_channel_map *m); + +/** Initialize the specified channel map for monoaural audio and return a pointer to it */ +pa_channel_map* pa_channel_map_init_mono(pa_channel_map *m); + +/** Initialize the specified channel map for stereophonic audio and return a pointer to it */ +pa_channel_map* pa_channel_map_init_stereo(pa_channel_map *m); + +/** Initialize the specified channel map for the specified number + * of channels using default labels and return a pointer to it. */ +pa_channel_map* pa_channel_map_init_auto(pa_channel_map *m, unsigned channels, pa_channel_map_def_t def); + +/** Return a text label for the specified channel position */ +const char* pa_channel_position_to_string(pa_channel_position_t pos) PA_GCC_PURE; + +/** Return a human readable text label for the specified channel position. \since 0.9.7 */ +const char* pa_channel_position_to_pretty_string(pa_channel_position_t pos); + +/** The maximum length of strings returned by pa_channel_map_snprint() */ +#define PA_CHANNEL_MAP_SNPRINT_MAX 336 + +/** Make a humand readable string from the specified channel map */ +char* pa_channel_map_snprint(char *s, size_t l, const pa_channel_map *map); + +/** Parse a channel position list into a channel map structure. \since 0.8.1 */ +pa_channel_map *pa_channel_map_parse(pa_channel_map *map, const char *s); + +/** Compare two channel maps. Return 1 if both match. */ +int pa_channel_map_equal(const pa_channel_map *a, const pa_channel_map *b) PA_GCC_PURE; + +/** Return non-zero of the specified channel map is considered valid */ +int pa_channel_map_valid(const pa_channel_map *map) PA_GCC_PURE; + +PA_C_DECL_END + +#endif diff --git a/src/pulse/client-conf-x11.c b/src/pulse/client-conf-x11.c new file mode 100644 index 00000000..e240ba88 --- /dev/null +++ b/src/pulse/client-conf-x11.c @@ -0,0 +1,97 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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-13071 + USA. +***/ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/x11prop.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "client-conf-x11.h" + +int pa_client_conf_from_x11(pa_client_conf *c, const char *dname) { + Display *d = NULL; + int ret = -1; + char t[1024]; + + pa_assert(c); + + if (!dname && (!(dname = getenv("DISPLAY")) || *dname == '\0')) + goto finish; + + if (!(d = XOpenDisplay(dname))) { + pa_log("XOpenDisplay() failed"); + goto finish; + } + + if (pa_x11_get_prop(d, "PULSE_SERVER", t, sizeof(t))) { + pa_xfree(c->default_server); + c->default_server = pa_xstrdup(t); + } + + if (pa_x11_get_prop(d, "PULSE_SINK", t, sizeof(t))) { + pa_xfree(c->default_sink); + c->default_sink = pa_xstrdup(t); + } + + if (pa_x11_get_prop(d, "PULSE_SOURCE", t, sizeof(t))) { + pa_xfree(c->default_source); + c->default_source = pa_xstrdup(t); + } + + if (pa_x11_get_prop(d, "PULSE_COOKIE", t, sizeof(t))) { + uint8_t cookie[PA_NATIVE_COOKIE_LENGTH]; + + if (pa_parsehex(t, cookie, sizeof(cookie)) != sizeof(cookie)) { + pa_log("failed to parse cookie data"); + goto finish; + } + + pa_assert(sizeof(cookie) == sizeof(c->cookie)); + memcpy(c->cookie, cookie, sizeof(cookie)); + + c->cookie_valid = 1; + + pa_xfree(c->cookie_file); + c->cookie_file = NULL; + } + + ret = 0; + +finish: + if (d) + XCloseDisplay(d); + + return ret; + +} diff --git a/src/pulse/client-conf-x11.h b/src/pulse/client-conf-x11.h new file mode 100644 index 00000000..56cd406d --- /dev/null +++ b/src/pulse/client-conf-x11.h @@ -0,0 +1,33 @@ +#ifndef fooclientconfx11hfoo +#define fooclientconfx11hfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 "client-conf.h" + +/* Load client configuration data from the specified X11 display, + * overwriting the current settings in *c */ +int pa_client_conf_from_x11(pa_client_conf *c, const char *display); + +#endif diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c new file mode 100644 index 00000000..c054f663 --- /dev/null +++ b/src/pulse/client-conf.c @@ -0,0 +1,185 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <unistd.h> +#include <errno.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/conf-parser.h> +#include <pulsecore/core-util.h> +#include <pulsecore/authkey.h> + +#include "client-conf.h" + +#define DEFAULT_CLIENT_CONFIG_FILE PA_DEFAULT_CONFIG_DIR PA_PATH_SEP "client.conf" +#define DEFAULT_CLIENT_CONFIG_FILE_USER "client.conf" + +#define ENV_CLIENT_CONFIG_FILE "PULSE_CLIENTCONFIG" +#define ENV_DEFAULT_SINK "PULSE_SINK" +#define ENV_DEFAULT_SOURCE "PULSE_SOURCE" +#define ENV_DEFAULT_SERVER "PULSE_SERVER" +#define ENV_DAEMON_BINARY "PULSE_BINARY" +#define ENV_COOKIE_FILE "PULSE_COOKIE" + +static const pa_client_conf default_conf = { + .daemon_binary = NULL, + .extra_arguments = NULL, + .default_sink = NULL, + .default_source = NULL, + .default_server = NULL, + .autospawn = FALSE, + .disable_shm = FALSE, + .cookie_file = NULL, + .cookie_valid = FALSE, +}; + +pa_client_conf *pa_client_conf_new(void) { + pa_client_conf *c = pa_xmemdup(&default_conf, sizeof(default_conf)); + + c->daemon_binary = pa_xstrdup(PA_BINARY); + c->extra_arguments = pa_xstrdup("--log-target=syslog --exit-idle-time=5"); + c->cookie_file = pa_xstrdup(PA_NATIVE_COOKIE_FILE); + + return c; +} + +void pa_client_conf_free(pa_client_conf *c) { + pa_assert(c); + pa_xfree(c->daemon_binary); + pa_xfree(c->extra_arguments); + pa_xfree(c->default_sink); + pa_xfree(c->default_source); + pa_xfree(c->default_server); + pa_xfree(c->cookie_file); + pa_xfree(c); +} + +int pa_client_conf_load(pa_client_conf *c, const char *filename) { + FILE *f = NULL; + char *fn = NULL; + int r = -1; + + /* Prepare the configuration parse table */ + pa_config_item table[] = { + { "daemon-binary", pa_config_parse_string, NULL }, + { "extra-arguments", pa_config_parse_string, NULL }, + { "default-sink", pa_config_parse_string, NULL }, + { "default-source", pa_config_parse_string, NULL }, + { "default-server", pa_config_parse_string, NULL }, + { "autospawn", pa_config_parse_bool, NULL }, + { "cookie-file", pa_config_parse_string, NULL }, + { "disable-shm", pa_config_parse_bool, NULL }, + { NULL, NULL, NULL }, + }; + + table[0].data = &c->daemon_binary; + table[1].data = &c->extra_arguments; + table[2].data = &c->default_sink; + table[3].data = &c->default_source; + table[4].data = &c->default_server; + table[5].data = &c->autospawn; + table[6].data = &c->cookie_file; + table[7].data = &c->disable_shm; + + f = filename ? + fopen((fn = pa_xstrdup(filename)), "r") : + pa_open_config_file(DEFAULT_CLIENT_CONFIG_FILE, DEFAULT_CLIENT_CONFIG_FILE_USER, ENV_CLIENT_CONFIG_FILE, &fn, "r"); + + if (!f && errno != EINTR) { + pa_log_warn("Failed to open configuration file '%s': %s", fn, pa_cstrerror(errno)); + goto finish; + } + + r = f ? pa_config_parse(fn, f, table, NULL) : 0; + + if (!r) + r = pa_client_conf_load_cookie(c); + + +finish: + pa_xfree(fn); + + if (f) + fclose(f); + + return r; +} + +int pa_client_conf_env(pa_client_conf *c) { + char *e; + + if ((e = getenv(ENV_DEFAULT_SINK))) { + pa_xfree(c->default_sink); + c->default_sink = pa_xstrdup(e); + } + + if ((e = getenv(ENV_DEFAULT_SOURCE))) { + pa_xfree(c->default_source); + c->default_source = pa_xstrdup(e); + } + + if ((e = getenv(ENV_DEFAULT_SERVER))) { + pa_xfree(c->default_server); + c->default_server = pa_xstrdup(e); + } + + if ((e = getenv(ENV_DAEMON_BINARY))) { + pa_xfree(c->daemon_binary); + c->daemon_binary = pa_xstrdup(e); + } + + if ((e = getenv(ENV_COOKIE_FILE))) { + pa_xfree(c->cookie_file); + c->cookie_file = pa_xstrdup(e); + + return pa_client_conf_load_cookie(c); + } + + return 0; +} + +int pa_client_conf_load_cookie(pa_client_conf* c) { + pa_assert(c); + + c->cookie_valid = FALSE; + + if (!c->cookie_file) + return -1; + + if (pa_authkey_load_auto(c->cookie_file, c->cookie, sizeof(c->cookie)) < 0) + return -1; + + c->cookie_valid = TRUE; + return 0; +} diff --git a/src/pulse/client-conf.h b/src/pulse/client-conf.h new file mode 100644 index 00000000..7cc975e6 --- /dev/null +++ b/src/pulse/client-conf.h @@ -0,0 +1,54 @@ +#ifndef fooclientconfhfoo +#define fooclientconfhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/native-common.h> + +/* A structure containing configuration data for PulseAudio clients. */ + +typedef struct pa_client_conf { + char *daemon_binary, *extra_arguments, *default_sink, *default_source, *default_server, *cookie_file; + pa_bool_t autospawn, disable_shm; + uint8_t cookie[PA_NATIVE_COOKIE_LENGTH]; + pa_bool_t cookie_valid; /* non-zero, when cookie is valid */ +} pa_client_conf; + +/* Create a new configuration data object and reset it to defaults */ +pa_client_conf *pa_client_conf_new(void); +void pa_client_conf_free(pa_client_conf *c); + +/* Load the configuration data from the speicified file, overwriting + * the current settings in *c. When the filename is NULL, the + * default client configuration file name is used. */ +int pa_client_conf_load(pa_client_conf *c, const char *filename); + +/* Load the configuration data from the environment of the current + process, overwriting the current settings in *c. */ +int pa_client_conf_env(pa_client_conf *c); + +/* Load cookie data from c->cookie_file into c->cookie */ +int pa_client_conf_load_cookie(pa_client_conf* c); + +#endif diff --git a/src/pulse/client.conf.in b/src/pulse/client.conf.in new file mode 100644 index 00000000..2bc8a7c8 --- /dev/null +++ b/src/pulse/client.conf.in @@ -0,0 +1,34 @@ +# $Id$ +# +# This file is part of PulseAudio. +# +# 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. + +## Configuration file for PulseAudio clients. See pulse-client.conf(5) for +## more information. Default values a commented out. Use either ; or # for +## commenting. + +; default-sink = +; default-source = +; default-server = + +; autospawn = no +; daemon-binary = @PA_BINARY@ +; extra-arguments = --log-target=syslog --exit-idle-time=5 + +; cookie-file = + +; disable-shm = no diff --git a/src/pulse/context.c b/src/pulse/context.c new file mode 100644 index 00000000..7243a29d --- /dev/null +++ b/src/pulse/context.c @@ -0,0 +1,1041 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> +#include <unistd.h> +#include <sys/stat.h> +#include <errno.h> +#include <signal.h> +#include <limits.h> + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#include <pulse/version.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/winsock.h> +#include <pulsecore/core-error.h> + +#include <pulsecore/native-common.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/pstream.h> +#include <pulsecore/dynarray.h> +#include <pulsecore/socket-client.h> +#include <pulsecore/pstream-util.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/creds.h> +#include <pulsecore/macro.h> + +#include "internal.h" + +#include "client-conf.h" + +#ifdef HAVE_X11 +#include "client-conf-x11.h" +#endif + +#include "context.h" + +#define AUTOSPAWN_LOCK "autospawn.lock" + +static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { + [PA_COMMAND_REQUEST] = pa_command_request, + [PA_COMMAND_OVERFLOW] = pa_command_overflow_or_underflow, + [PA_COMMAND_UNDERFLOW] = pa_command_overflow_or_underflow, + [PA_COMMAND_PLAYBACK_STREAM_KILLED] = pa_command_stream_killed, + [PA_COMMAND_RECORD_STREAM_KILLED] = pa_command_stream_killed, + [PA_COMMAND_PLAYBACK_STREAM_MOVED] = pa_command_stream_moved, + [PA_COMMAND_RECORD_STREAM_MOVED] = pa_command_stream_moved, + [PA_COMMAND_PLAYBACK_STREAM_SUSPENDED] = pa_command_stream_suspended, + [PA_COMMAND_RECORD_STREAM_SUSPENDED] = pa_command_stream_suspended, + [PA_COMMAND_SUBSCRIBE_EVENT] = pa_command_subscribe_event +}; + +static void unlock_autospawn_lock_file(pa_context *c) { + pa_assert(c); + + if (c->autospawn_lock_fd >= 0) { + char lf[PATH_MAX]; + pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf)); + + pa_unlock_lockfile(lf, c->autospawn_lock_fd); + c->autospawn_lock_fd = -1; + } +} + +static void context_free(pa_context *c); + +pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name) { + pa_context *c; + + pa_assert(mainloop); + pa_assert(name); + + c = pa_xnew(pa_context, 1); + PA_REFCNT_INIT(c); + c->name = pa_xstrdup(name); + c->mainloop = mainloop; + c->client = NULL; + c->pstream = NULL; + c->pdispatch = NULL; + c->playback_streams = pa_dynarray_new(); + c->record_streams = pa_dynarray_new(); + + PA_LLIST_HEAD_INIT(pa_stream, c->streams); + PA_LLIST_HEAD_INIT(pa_operation, c->operations); + + c->error = PA_OK; + c->state = PA_CONTEXT_UNCONNECTED; + c->ctag = 0; + c->csyncid = 0; + + c->state_callback = NULL; + c->state_userdata = NULL; + + c->subscribe_callback = NULL; + c->subscribe_userdata = NULL; + + c->is_local = -1; + c->server_list = NULL; + c->server = NULL; + c->autospawn_lock_fd = -1; + memset(&c->spawn_api, 0, sizeof(c->spawn_api)); + c->do_autospawn = 0; + +#ifndef MSG_NOSIGNAL +#ifdef SIGPIPE + pa_check_signal_is_blocked(SIGPIPE); +#endif +#endif + + c->conf = pa_client_conf_new(); + pa_client_conf_load(c->conf, NULL); +#ifdef HAVE_X11 + pa_client_conf_from_x11(c->conf, NULL); +#endif + pa_client_conf_env(c->conf); + + if (!(c->mempool = pa_mempool_new(!c->conf->disable_shm))) { + + if (!c->conf->disable_shm) + c->mempool = pa_mempool_new(0); + + if (!c->mempool) { + context_free(c); + return NULL; + } + } + + return c; +} + +static void context_free(pa_context *c) { + pa_assert(c); + + unlock_autospawn_lock_file(c); + + while (c->operations) + pa_operation_cancel(c->operations); + + while (c->streams) + pa_stream_set_state(c->streams, PA_STREAM_TERMINATED); + + if (c->client) + pa_socket_client_unref(c->client); + if (c->pdispatch) + pa_pdispatch_unref(c->pdispatch); + if (c->pstream) { + pa_pstream_unlink(c->pstream); + pa_pstream_unref(c->pstream); + } + + if (c->record_streams) + pa_dynarray_free(c->record_streams, NULL, NULL); + if (c->playback_streams) + pa_dynarray_free(c->playback_streams, NULL, NULL); + + if (c->mempool) + pa_mempool_free(c->mempool); + + if (c->conf) + pa_client_conf_free(c->conf); + + pa_strlist_free(c->server_list); + + pa_xfree(c->name); + pa_xfree(c->server); + pa_xfree(c); +} + +pa_context* pa_context_ref(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_REFCNT_INC(c); + return c; +} + +void pa_context_unref(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (PA_REFCNT_DEC(c) <= 0) + context_free(c); +} + +void pa_context_set_state(pa_context *c, pa_context_state_t st) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (c->state == st) + return; + + pa_context_ref(c); + + c->state = st; + if (c->state_callback) + c->state_callback(c, c->state_userdata); + + if (st == PA_CONTEXT_FAILED || st == PA_CONTEXT_TERMINATED) { + pa_stream *s; + + s = c->streams ? pa_stream_ref(c->streams) : NULL; + while (s) { + pa_stream *n = s->next ? pa_stream_ref(s->next) : NULL; + pa_stream_set_state(s, st == PA_CONTEXT_FAILED ? PA_STREAM_FAILED : PA_STREAM_TERMINATED); + pa_stream_unref(s); + s = n; + } + + if (c->pdispatch) + pa_pdispatch_unref(c->pdispatch); + c->pdispatch = NULL; + + if (c->pstream) { + pa_pstream_unlink(c->pstream); + pa_pstream_unref(c->pstream); + } + c->pstream = NULL; + + if (c->client) + pa_socket_client_unref(c->client); + c->client = NULL; + } + + pa_context_unref(c); +} + +void pa_context_fail(pa_context *c, int error) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_set_error(c, error); + pa_context_set_state(c, PA_CONTEXT_FAILED); +} + +int pa_context_set_error(pa_context *c, int error) { + pa_assert(error >= 0); + pa_assert(error < PA_ERR_MAX); + + if (c) + c->error = error; + + return error; +} + +static void pstream_die_callback(pa_pstream *p, void *userdata) { + pa_context *c = userdata; + + pa_assert(p); + pa_assert(c); + + pa_context_fail(c, PA_ERR_CONNECTIONTERMINATED); +} + +static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_creds *creds, void *userdata) { + pa_context *c = userdata; + + pa_assert(p); + pa_assert(packet); + pa_assert(c); + + pa_context_ref(c); + + if (pa_pdispatch_run(c->pdispatch, packet, creds, c) < 0) + pa_context_fail(c, PA_ERR_PROTOCOL); + + pa_context_unref(c); +} + +static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + + pa_assert(p); + pa_assert(chunk); + pa_assert(chunk->memblock); + pa_assert(chunk->length); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if ((s = pa_dynarray_get(c->record_streams, channel))) { + + pa_assert(seek == PA_SEEK_RELATIVE); + pa_assert(offset == 0); + + pa_memblockq_seek(s->record_memblockq, offset, seek); + pa_memblockq_push_align(s->record_memblockq, chunk); + + if (s->read_callback) { + size_t l; + + if ((l = pa_memblockq_get_length(s->record_memblockq)) > 0) + s->read_callback(s, l, s->read_userdata); + } + } + + pa_context_unref(c); +} + +int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (command == PA_COMMAND_ERROR) { + pa_assert(t); + + if (pa_tagstruct_getu32(t, &c->error) < 0) { + pa_context_fail(c, PA_ERR_PROTOCOL); + return -1; + + } + } else if (command == PA_COMMAND_TIMEOUT) + c->error = PA_ERR_TIMEOUT; + else { + pa_context_fail(c, PA_ERR_PROTOCOL); + return -1; + } + + return 0; +} + +static void setup_complete_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + + pa_assert(pd); + pa_assert(c); + pa_assert(c->state == PA_CONTEXT_AUTHORIZING || c->state == PA_CONTEXT_SETTING_NAME); + + pa_context_ref(c); + + if (command != PA_COMMAND_REPLY) { + + if (pa_context_handle_error(c, command, t) < 0) + pa_context_fail(c, PA_ERR_PROTOCOL); + + pa_context_fail(c, c->error); + goto finish; + } + + switch(c->state) { + case PA_CONTEXT_AUTHORIZING: { + pa_tagstruct *reply; + + if (pa_tagstruct_getu32(t, &c->version) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + /* Minimum supported version */ + if (c->version < 8) { + pa_context_fail(c, PA_ERR_VERSION); + goto finish; + } + + /* Enable shared memory support if possible */ + if (c->version >= 10 && + pa_mempool_is_shared(c->mempool) && + c->is_local > 0) { + + /* Only enable SHM if both sides are owned by the same + * user. This is a security measure because otherwise + * data private to the user might leak. */ + +#ifdef HAVE_CREDS + const pa_creds *creds; + if ((creds = pa_pdispatch_creds(pd))) + if (getuid() == creds->uid) + pa_pstream_use_shm(c->pstream, 1); +#endif + } + + reply = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag); + pa_tagstruct_puts(reply, c->name); + pa_pstream_send_tagstruct(c->pstream, reply); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c, NULL); + + pa_context_set_state(c, PA_CONTEXT_SETTING_NAME); + break; + } + + case PA_CONTEXT_SETTING_NAME : + pa_context_set_state(c, PA_CONTEXT_READY); + break; + + default: + pa_assert(0); + } + +finish: + pa_context_unref(c); +} + +static void setup_context(pa_context *c, pa_iochannel *io) { + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(io); + + pa_context_ref(c); + + pa_assert(!c->pstream); + c->pstream = pa_pstream_new(c->mainloop, io, c->mempool); + + pa_pstream_set_die_callback(c->pstream, pstream_die_callback, c); + pa_pstream_set_recieve_packet_callback(c->pstream, pstream_packet_callback, c); + pa_pstream_set_recieve_memblock_callback(c->pstream, pstream_memblock_callback, c); + + pa_assert(!c->pdispatch); + c->pdispatch = pa_pdispatch_new(c->mainloop, command_table, PA_COMMAND_MAX); + + if (!c->conf->cookie_valid) + pa_log_warn("No cookie loaded. Attempting to connect without."); + + t = pa_tagstruct_command(c, PA_COMMAND_AUTH, &tag); + pa_tagstruct_putu32(t, PA_PROTOCOL_VERSION); + pa_tagstruct_put_arbitrary(t, c->conf->cookie, sizeof(c->conf->cookie)); + +#ifdef HAVE_CREDS +{ + pa_creds ucred; + + if (pa_iochannel_creds_supported(io)) + pa_iochannel_creds_enable(io); + + ucred.uid = getuid(); + ucred.gid = getgid(); + + pa_pstream_send_tagstruct_with_creds(c->pstream, t, &ucred); +} +#else + pa_pstream_send_tagstruct(c->pstream, t); +#endif + + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, setup_complete_callback, c, NULL); + + pa_context_set_state(c, PA_CONTEXT_AUTHORIZING); + + pa_context_unref(c); +} + +static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata); + +#ifndef OS_IS_WIN32 + +static int context_connect_spawn(pa_context *c) { + pid_t pid; + int status, r; + int fds[2] = { -1, -1} ; + pa_iochannel *io; + + pa_context_ref(c); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + pa_log("socketpair(): %s", pa_cstrerror(errno)); + pa_context_fail(c, PA_ERR_INTERNAL); + goto fail; + } + + pa_make_fd_cloexec(fds[0]); + + pa_make_socket_low_delay(fds[0]); + pa_make_socket_low_delay(fds[1]); + + if (c->spawn_api.prefork) + c->spawn_api.prefork(); + + if ((pid = fork()) < 0) { + pa_log("fork(): %s", pa_cstrerror(errno)); + pa_context_fail(c, PA_ERR_INTERNAL); + + if (c->spawn_api.postfork) + c->spawn_api.postfork(); + + goto fail; + } else if (!pid) { + /* Child */ + + char t[128]; + const char *state = NULL; +#define MAX_ARGS 64 + const char * argv[MAX_ARGS+1]; + int n; + + /* Not required, since fds[0] has CLOEXEC enabled anyway */ + pa_assert_se(pa_close(fds[0]) == 0); + + if (c->spawn_api.atfork) + c->spawn_api.atfork(); + + /* Setup argv */ + + n = 0; + + argv[n++] = c->conf->daemon_binary; + argv[n++] = "--daemonize=yes"; + + pa_snprintf(t, sizeof(t), "-Lmodule-native-protocol-fd fd=%i", fds[1]); + argv[n++] = strdup(t); + + while (n < MAX_ARGS) { + char *a; + + if (!(a = pa_split_spaces(c->conf->extra_arguments, &state))) + break; + + argv[n++] = a; + } + + argv[n++] = NULL; + + execv(argv[0], (char * const *) argv); + _exit(1); +#undef MAX_ARGS + } + + /* Parent */ + + r = waitpid(pid, &status, 0); + + if (c->spawn_api.postfork) + c->spawn_api.postfork(); + + if (r < 0) { + pa_log("waitpid(): %s", pa_cstrerror(errno)); + pa_context_fail(c, PA_ERR_INTERNAL); + goto fail; + } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { + pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); + goto fail; + } + + pa_assert_se(pa_close(fds[1]) == 0); + + c->is_local = 1; + + io = pa_iochannel_new(c->mainloop, fds[0], fds[0]); + + setup_context(c, io); + unlock_autospawn_lock_file(c); + + pa_context_unref(c); + + return 0; + +fail: + pa_close_pipe(fds); + + unlock_autospawn_lock_file(c); + + pa_context_unref(c); + + return -1; +} + +#endif /* OS_IS_WIN32 */ + +static int try_next_connection(pa_context *c) { + char *u = NULL; + int r = -1; + + pa_assert(c); + pa_assert(!c->client); + + for (;;) { + pa_xfree(u); + u = NULL; + + c->server_list = pa_strlist_pop(c->server_list, &u); + + if (!u) { + +#ifndef OS_IS_WIN32 + if (c->do_autospawn) { + r = context_connect_spawn(c); + goto finish; + } +#endif + + pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); + goto finish; + } + + pa_log_debug("Trying to connect to %s...", u); + + pa_xfree(c->server); + c->server = pa_xstrdup(u); + + if (!(c->client = pa_socket_client_new_string(c->mainloop, u, PA_NATIVE_DEFAULT_PORT))) + continue; + + c->is_local = pa_socket_client_is_local(c->client); + pa_socket_client_set_callback(c->client, on_connection, c); + break; + } + + r = 0; + +finish: + pa_xfree(u); + + return r; +} + +static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userdata) { + pa_context *c = userdata; + + pa_assert(client); + pa_assert(c); + pa_assert(c->state == PA_CONTEXT_CONNECTING); + + pa_context_ref(c); + + pa_socket_client_unref(client); + c->client = NULL; + + if (!io) { + /* Try the item in the list */ + if (errno == ECONNREFUSED || errno == ETIMEDOUT || errno == EHOSTUNREACH) { + try_next_connection(c); + goto finish; + } + + pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); + goto finish; + } + + unlock_autospawn_lock_file(c); + setup_context(c, io); + +finish: + pa_context_unref(c); +} + +int pa_context_connect( + pa_context *c, + const char *server, + pa_context_flags_t flags, + const pa_spawn_api *api) { + + int r = -1; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY(c, c->state == PA_CONTEXT_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(c, !(flags & ~PA_CONTEXT_NOAUTOSPAWN), PA_ERR_INVALID); + PA_CHECK_VALIDITY(c, !server || *server, PA_ERR_INVALID); + + if (!server) + server = c->conf->default_server; + + pa_context_ref(c); + + pa_assert(!c->server_list); + + if (server) { + if (!(c->server_list = pa_strlist_parse(server))) { + pa_context_fail(c, PA_ERR_INVALIDSERVER); + goto finish; + } + } else { + char *d; + char ufn[PATH_MAX]; + + /* Prepend in reverse order */ + + if ((d = getenv("DISPLAY"))) { + char *e; + d = pa_xstrdup(d); + if ((e = strchr(d, ':'))) + *e = 0; + + if (*d) + c->server_list = pa_strlist_prepend(c->server_list, d); + + pa_xfree(d); + } + + c->server_list = pa_strlist_prepend(c->server_list, "tcp6:localhost"); + c->server_list = pa_strlist_prepend(c->server_list, "tcp4:localhost"); + + /* The system wide instance */ + c->server_list = pa_strlist_prepend(c->server_list, PA_SYSTEM_RUNTIME_PATH "/" PA_NATIVE_DEFAULT_UNIX_SOCKET); + + /* The per-user instance */ + c->server_list = pa_strlist_prepend(c->server_list, pa_runtime_path(PA_NATIVE_DEFAULT_UNIX_SOCKET, ufn, sizeof(ufn))); + + /* Wrap the connection attempts in a single transaction for sane autospawn locking */ + if (!(flags & PA_CONTEXT_NOAUTOSPAWN) && c->conf->autospawn) { + char lf[PATH_MAX]; + + pa_runtime_path(AUTOSPAWN_LOCK, lf, sizeof(lf)); + pa_make_secure_parent_dir(lf, 0700, (uid_t)-1, (gid_t)-1); + pa_assert(c->autospawn_lock_fd <= 0); + c->autospawn_lock_fd = pa_lock_lockfile(lf); + + if (api) + c->spawn_api = *api; + c->do_autospawn = 1; + } + + } + + pa_context_set_state(c, PA_CONTEXT_CONNECTING); + r = try_next_connection(c); + +finish: + pa_context_unref(c); + + return r; +} + +void pa_context_disconnect(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_set_state(c, PA_CONTEXT_TERMINATED); +} + +pa_context_state_t pa_context_get_state(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return c->state; +} + +int pa_context_errno(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return c->error; +} + +void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + c->state_callback = cb; + c->state_userdata = userdata; +} + +int pa_context_is_pending(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY(c, + c->state == PA_CONTEXT_CONNECTING || + c->state == PA_CONTEXT_AUTHORIZING || + c->state == PA_CONTEXT_SETTING_NAME || + c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + return (c->pstream && pa_pstream_is_pending(c->pstream)) || + (c->pdispatch && pa_pdispatch_is_pending(c->pdispatch)) || + c->client; +} + +static void set_dispatch_callbacks(pa_operation *o); + +static void pdispatch_drain_callback(PA_GCC_UNUSED pa_pdispatch*pd, void *userdata) { + set_dispatch_callbacks(userdata); +} + +static void pstream_drain_callback(PA_GCC_UNUSED pa_pstream *s, void *userdata) { + set_dispatch_callbacks(userdata); +} + +static void set_dispatch_callbacks(pa_operation *o) { + int done = 1; + + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + pa_assert(o->context); + pa_assert(PA_REFCNT_VALUE(o->context) >= 1); + pa_assert(o->context->state == PA_CONTEXT_READY); + + pa_pstream_set_drain_callback(o->context->pstream, NULL, NULL); + pa_pdispatch_set_drain_callback(o->context->pdispatch, NULL, NULL); + + if (pa_pdispatch_is_pending(o->context->pdispatch)) { + pa_pdispatch_set_drain_callback(o->context->pdispatch, pdispatch_drain_callback, o); + done = 0; + } + + if (pa_pstream_is_pending(o->context->pstream)) { + pa_pstream_set_drain_callback(o->context->pstream, pstream_drain_callback, o); + done = 0; + } + + if (done) { + if (o->callback) { + pa_context_notify_cb_t cb = (pa_context_notify_cb_t) o->callback; + cb(o->context, o->userdata); + } + + pa_operation_done(o); + pa_operation_unref(o); + } +} + +pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_context_is_pending(c), PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + set_dispatch_callbacks(pa_operation_ref(o)); + + return o; +} + +void pa_context_simple_ack_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + success = 0; + } else if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_context_success_cb_t cb = (pa_context_success_cb_t) o->callback; + cb(o->context, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_EXIT, &tag); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, pa_pdispatch_cb_t internal_cb, pa_operation_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, cb, userdata); + + t = pa_tagstruct_command(c, command, &tag); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, internal_cb, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_DEFAULT_SINK, &tag); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_DEFAULT_SOURCE, &tag); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +int pa_context_is_local(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY(c, c->is_local >= 0, PA_ERR_BADSTATE); + + return c->is_local; +} + +pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_CLIENT_NAME, &tag); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +const char* pa_get_library_version(void) { + return PACKAGE_VERSION; +} + +const char* pa_context_get_server(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (!c->server) + return NULL; + + if (*c->server == '{') { + char *e = strchr(c->server+1, '}'); + return e ? e+1 : c->server; + } + + return c->server; +} + +uint32_t pa_context_get_protocol_version(PA_GCC_UNUSED pa_context *c) { + return PA_PROTOCOL_VERSION; +} + +uint32_t pa_context_get_server_protocol_version(pa_context *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return c->version; +} + +pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *tag) { + pa_tagstruct *t; + + pa_assert(c); + pa_assert(tag); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, command); + pa_tagstruct_putu32(t, *tag = c->ctag++); + + return t; +} diff --git a/src/pulse/context.h b/src/pulse/context.h new file mode 100644 index 00000000..1de3abad --- /dev/null +++ b/src/pulse/context.h @@ -0,0 +1,233 @@ +#ifndef foocontexthfoo +#define foocontexthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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/sample.h> +#include <pulse/def.h> +#include <pulse/mainloop-api.h> +#include <pulse/cdecl.h> +#include <pulse/operation.h> + +/** \page async Asynchronous API + * + * \section overv_sec Overview + * + * The asynchronous API is the native interface to the PulseAudio library. + * It allows full access to all available functions. This also means that + * it is rather complex and can take some time to fully master. + * + * \section mainloop_sec Main Loop Abstraction + * + * The API is based around an asynchronous event loop, or main loop, + * abstraction. This abstraction contains three basic elements: + * + * \li Deferred events - Events that will trigger as soon as possible. Note + * that some implementations may block all other events + * when a deferred event is active. + * \li I/O events - Events that trigger on file descriptor activities. + * \li Times events - Events that trigger after a fixed ammount of time. + * + * The abstraction is represented as a number of function pointers in the + * pa_mainloop_api structure. + * + * To actually be able to use these functions, an implementation needs to + * be coupled to the abstraction. There are three of these shipped with + * PulseAudio, but any other can be used with a minimal ammount of work, + * provided it supports the three basic events listed above. + * + * The implementations shipped with PulseAudio are: + * + * \li \subpage mainloop - A minimal but fast implementation based on poll(). + * \li \subpage threaded_mainloop - A special version of the previous + * implementation where all of PulseAudio's + * internal handling runs in a separate + * thread. + * \li \subpage glib-mainloop - A wrapper around GLIB's main loop. Available + * for both GLIB 1.2 and GLIB 2.x. + * + * UNIX signals may be hooked to a main loop using the functions from + * \ref mainloop-signal.h. These rely only on the main loop abstraction + * and can therefore be used with any of the implementations. + * + * \section refcnt_sec Reference Counting + * + * Almost all objects in PulseAudio are reference counted. What that means + * is that you rarely malloc() or free() any objects. Instead you increase + * and decrease their reference counts. Whenever an object's reference + * count reaches zero, that object gets destroy and any resources it uses + * get freed. + * + * The benefit of this design is that an application need not worry about + * whether or not it needs to keep an object around in case the library is + * using it internally. If it is, then it has made sure it has its own + * reference to it. + * + * Whenever the library creates an object, it will have an initial + * reference count of one. Most of the time, this single reference will be + * sufficient for the application, so all required reference count + * interaction will be a single call to the objects unref function. + * + * \section context_sec Context + * + * A context is the basic object for a connection to a PulseAudio server. + * It multiplexes commands, data streams and events through a single + * channel. + * + * There is no need for more than one context per application, unless + * connections to multiple servers are needed. + * + * \subsection ops_subsec Operations + * + * All operations on the context are performed asynchronously. I.e. the + * client will not wait for the server to complete the request. To keep + * track of all these in-flight operations, the application is given a + * pa_operation object for each asynchronous operation. + * + * There are only two actions (besides reference counting) that can be + * performed on a pa_operation: querying its state with + * pa_operation_get_state() and aborting it with pa_operation_cancel(). + * + * A pa_operation object is reference counted, so an application must + * make sure to unreference it, even if it has no intention of using it. + * + * \subsection conn_subsec Connecting + * + * A context must be connected to a server before any operation can be + * issued. Calling pa_context_connect() will initiate the connection + * procedure. Unlike most asynchronous operations, connecting does not + * result in a pa_operation object. Instead, the application should + * register a callback using pa_context_set_state_callback(). + * + * \subsection disc_subsec Disconnecting + * + * When the sound support is no longer needed, the connection needs to be + * closed using pa_context_disconnect(). This is an immediate function that + * works synchronously. + * + * Since the context object has references to other objects it must be + * disconnected after use or there is a high risk of memory leaks. If the + * connection has terminated by itself, then there is no need to explicitly + * disconnect the context using pa_context_disconnect(). + * + * \section Functions + * + * The sound server's functionality can be divided into a number of + * subsections: + * + * \li \subpage streams + * \li \subpage scache + * \li \subpage introspect + * \li \subpage subscribe + */ + +/** \file + * Connection contexts for asynchrononous communication with a + * server. A pa_context object wraps a connection to a PulseAudio + * server using its native protocol. */ + +/** \example pacat.c + * A playback and recording tool using the asynchronous API */ + +/** \example paplay.c + * A sound file playback tool using the asynchronous API, based on libsndfile */ + +PA_C_DECL_BEGIN + +/** An opaque connection context to a daemon */ +typedef struct pa_context pa_context; + +/** Generic notification callback prototype */ +typedef void (*pa_context_notify_cb_t)(pa_context *c, void *userdata); + +/** A generic callback for operation completion */ +typedef void (*pa_context_success_cb_t) (pa_context *c, int success, void *userdata); + +/** Instantiate a new connection context with an abstract mainloop API + * and an application name */ +pa_context *pa_context_new(pa_mainloop_api *mainloop, const char *name); + +/** Decrease the reference counter of the context by one */ +void pa_context_unref(pa_context *c); + +/** Increase the reference counter of the context by one */ +pa_context* pa_context_ref(pa_context *c); + +/** Set a callback function that is called whenever the context status changes */ +void pa_context_set_state_callback(pa_context *c, pa_context_notify_cb_t cb, void *userdata); + +/** Return the error number of the last failed operation */ +int pa_context_errno(pa_context *c); + +/** Return non-zero if some data is pending to be written to the connection */ +int pa_context_is_pending(pa_context *c); + +/** Return the current context status */ +pa_context_state_t pa_context_get_state(pa_context *c); + +/** Connect the context to the specified server. If server is NULL, +connect to the default server. This routine may but will not always +return synchronously on error. Use pa_context_set_state_callback() to +be notified when the connection is established. If flags doesn't have +PA_NOAUTOSPAWN set and no specific server is specified or accessible a +new daemon is spawned. If api is non-NULL, the functions specified in +the structure are used when forking a new child process. */ +int pa_context_connect(pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api); + +/** Terminate the context connection immediately */ +void pa_context_disconnect(pa_context *c); + +/** Drain the context. If there is nothing to drain, the function returns NULL */ +pa_operation* pa_context_drain(pa_context *c, pa_context_notify_cb_t cb, void *userdata); + +/** Tell the daemon to exit. The returned operation is unlikely to + * complete succesfully, since the daemon probably died before + * returning a success notification */ +pa_operation* pa_context_exit_daemon(pa_context *c, pa_context_success_cb_t cb, void *userdata); + +/** Set the name of the default sink. \since 0.4 */ +pa_operation* pa_context_set_default_sink(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); + +/** Set the name of the default source. \since 0.4 */ +pa_operation* pa_context_set_default_source(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); + +/** Returns 1 when the connection is to a local daemon. Returns negative when no connection has been made yet. \since 0.5 */ +int pa_context_is_local(pa_context *c); + +/** Set a different application name for context on the server. \since 0.5 */ +pa_operation* pa_context_set_name(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata); + +/** Return the server name this context is connected to. \since 0.7 */ +const char* pa_context_get_server(pa_context *c); + +/** Return the protocol version of the library. \since 0.8 */ +uint32_t pa_context_get_protocol_version(pa_context *c); + +/** Return the protocol version of the connected server. \since 0.8 */ +uint32_t pa_context_get_server_protocol_version(pa_context *c); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/def.h b/src/pulse/def.h new file mode 100644 index 00000000..dabbc5eb --- /dev/null +++ b/src/pulse/def.h @@ -0,0 +1,397 @@ +#ifndef foodefhfoo +#define foodefhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <inttypes.h> +#include <sys/time.h> +#include <time.h> + +#include <pulse/cdecl.h> +#include <pulse/sample.h> + +/** \file + * Global definitions */ + +PA_C_DECL_BEGIN + +/** The state of a connection context */ +typedef enum pa_context_state { + PA_CONTEXT_UNCONNECTED, /**< The context hasn't been connected yet */ + PA_CONTEXT_CONNECTING, /**< A connection is being established */ + PA_CONTEXT_AUTHORIZING, /**< The client is authorizing itself to the daemon */ + PA_CONTEXT_SETTING_NAME, /**< The client is passing its application name to the daemon */ + PA_CONTEXT_READY, /**< The connection is established, the context is ready to execute operations */ + PA_CONTEXT_FAILED, /**< The connection failed or was disconnected */ + PA_CONTEXT_TERMINATED /**< The connection was terminated cleanly */ +} pa_context_state_t; + +/** The state of a stream */ +typedef enum pa_stream_state { + PA_STREAM_UNCONNECTED, /**< The stream is not yet connected to any sink or source */ + PA_STREAM_CREATING, /**< The stream is being created */ + PA_STREAM_READY, /**< The stream is established, you may pass audio data to it now */ + PA_STREAM_FAILED, /**< An error occured that made the stream invalid */ + PA_STREAM_TERMINATED /**< The stream has been terminated cleanly */ +} pa_stream_state_t; + +/** The state of an operation */ +typedef enum pa_operation_state { + PA_OPERATION_RUNNING, /**< The operation is still running */ + PA_OPERATION_DONE, /**< The operation has been completed */ + PA_OPERATION_CANCELED /**< The operation has been canceled */ +} pa_operation_state_t; + +/** An invalid index */ +#define PA_INVALID_INDEX ((uint32_t) -1) + +/** Some special flags for contexts. \since 0.8 */ +typedef enum pa_context_flags { + PA_CONTEXT_NOAUTOSPAWN = 1 /**< Disabled autospawning of the PulseAudio daemon if required */ +} pa_context_flags_t; + +/** The direction of a pa_stream object */ +typedef enum pa_stream_direction { + PA_STREAM_NODIRECTION, /**< Invalid direction */ + PA_STREAM_PLAYBACK, /**< Playback stream */ + PA_STREAM_RECORD, /**< Record stream */ + PA_STREAM_UPLOAD /**< Sample upload stream */ +} pa_stream_direction_t; + +/** Some special flags for stream connections. \since 0.6 */ +typedef enum pa_stream_flags { + PA_STREAM_START_CORKED = 1, /**< Create the stream corked, requiring an explicit pa_stream_cork() call to uncork it. */ + PA_STREAM_INTERPOLATE_TIMING = 2, /**< Interpolate the latency for + * this stream. When enabled, + * pa_stream_get_latency() and + * pa_stream_get_time() will try + * to estimate the current + * record/playback time based on + * the local time that passed + * since the last timing info + * update. Using this option + * has the advantage of not + * requiring a whole roundtrip + * when the current + * playback/recording time is + * needed. Consider using this + * option when requesting + * latency information + * frequently. This is + * especially useful on long + * latency network + * connections. It makes a lot + * of sense to combine this + * option with + * PA_STREAM_AUTO_TIMING_UPDATE. */ + PA_STREAM_NOT_MONOTONOUS = 4, /**< Don't force the time to + * increase monotonically. If + * this option is enabled, + * pa_stream_get_time() will not + * necessarily return always + * monotonically increasing time + * values on each call. This may + * confuse applications which + * cannot deal with time going + * 'backwards', but has the + * advantage that bad transport + * latency estimations that + * caused the time to to jump + * ahead can be corrected + * quickly, without the need to + * wait. */ + PA_STREAM_AUTO_TIMING_UPDATE = 8, /**< If set timing update requests + * are issued periodically + * automatically. Combined with + * PA_STREAM_INTERPOLATE_TIMING + * you will be able to query the + * current time and latency with + * pa_stream_get_time() and + * pa_stream_get_latency() at + * all times without a packet + * round trip.*/ + PA_STREAM_NO_REMAP_CHANNELS = 16, /**< Don't remap channels by + * their name, instead map them + * simply by their + * index. Implies + * PA_STREAM_NO_REMIX_CHANNELS. Only + * supported when the server is + * at least PA 0.9.8. It is + * ignored on older + * servers.\since 0.9.8 */ + PA_STREAM_NO_REMIX_CHANNELS = 32, /**< When remapping channels by + * name, don't upmix or downmix + * them to related + * channels. Copy them into + * matching channels of the + * device 1:1. Only supported + * when the server is at least + * PA 0.9.8. It is ignored on + * older servers. \since + * 0.9.8 */ + PA_STREAM_FIX_FORMAT = 64, /**< Use the sample format of the + * sink/device this stream is being + * connected to, and possibly ignore + * the format the sample spec contains + * -- but you still have to pass a + * valid value in it as a hint to + * PulseAudio what would suit your + * stream best. If this is used you + * should query the used sample format + * after creating the stream by using + * pa_stream_get_sample_spec(). Also, + * if you specified manual buffer + * metrics it is recommended to update + * them with + * pa_stream_set_buffer_attr() to + * compensate for the changed frame + * sizes. Only supported when the + * server is at least PA 0.9.8. It is + * ignored on older servers. \since + * 0.9.8 */ + + PA_STREAM_FIX_RATE = 128, /**< Use the sample rate of the sink, + * and possibly ignore the rate the + * sample spec contains. Usage similar + * to PA_STREAM_FIX_FORMAT.Only + * supported when the server is at least + * PA 0.9.8. It is ignored on older + * servers. \since 0.9.8 */ + + PA_STREAM_FIX_CHANNELS = 256, /**< Use the number of channels and + * the channel map of the sink, and + * possibly ignore the number of + * channels and the map the sample spec + * and the passed channel map + * contains. Usage similar to + * PA_STREAM_FIX_FORMAT. Only supported + * when the server is at least PA + * 0.9.8. It is ignored on older + * servers. \since 0.9.8 */ + PA_STREAM_DONT_MOVE = 512, /**< Don't allow moving of this stream to + * another sink/device. Useful if you use + * any of the PA_STREAM_FIX_ flags and + * want to make sure that resampling + * never takes place -- which might + * happen if the stream is moved to + * another sink/source whith a different + * sample spec/channel map. Only + * supported when the server is at least + * PA 0.9.8. It is ignored on older + * servers. \since 0.9.8 */ + PA_STREAM_VARIABLE_RATE = 1024, /**< Allow dynamic changing of the + * sampling rate during playback + * with + * pa_stream_update_sample_rate(). Only + * supported when the server is at + * least PA 0.9.8. It is ignored + * on older servers. \since + * 0.9.8 */ +} pa_stream_flags_t; + +/** Playback and record buffer metrics */ +typedef struct pa_buffer_attr { + uint32_t maxlength; /**< Maximum length of the buffer */ + uint32_t tlength; /**< Playback only: target length of the buffer. The server tries to assure that at least tlength bytes are always available in the buffer */ + uint32_t prebuf; /**< Playback only: pre-buffering. The server does not start with playback before at least prebug bytes are available in the buffer */ + uint32_t minreq; /**< Playback only: minimum request. The server does not request less than minreq bytes from the client, instead waints until the buffer is free enough to request more bytes at once */ + uint32_t fragsize; /**< Recording only: fragment size. The server sends data in blocks of fragsize bytes size. Large values deminish interactivity with other operations on the connection context but decrease control overhead. */ +} pa_buffer_attr; + +/** Error values as used by pa_context_errno(). Use pa_strerror() to convert these values to human readable strings */ +enum { + PA_OK = 0, /**< No error */ + PA_ERR_ACCESS, /**< Access failure */ + PA_ERR_COMMAND, /**< Unknown command */ + PA_ERR_INVALID, /**< Invalid argument */ + PA_ERR_EXIST, /**< Entity exists */ + PA_ERR_NOENTITY, /**< No such entity */ + PA_ERR_CONNECTIONREFUSED, /**< Connection refused */ + PA_ERR_PROTOCOL, /**< Protocol error */ + PA_ERR_TIMEOUT, /**< Timeout */ + PA_ERR_AUTHKEY, /**< No authorization key */ + PA_ERR_INTERNAL, /**< Internal error */ + PA_ERR_CONNECTIONTERMINATED, /**< Connection terminated */ + PA_ERR_KILLED, /**< Entity killed */ + PA_ERR_INVALIDSERVER, /**< Invalid server */ + PA_ERR_MODINITFAILED, /**< Module initialization failed */ + PA_ERR_BADSTATE, /**< Bad state */ + PA_ERR_NODATA, /**< No data */ + PA_ERR_VERSION, /**< Incompatible protocol version \since 0.8 */ + PA_ERR_TOOLARGE, /**< Data too large \since 0.8.1 */ + PA_ERR_NOTSUPPORTED, /**< Operation not supported \since 0.9.5 */ + PA_ERR_MAX /**< Not really an error but the first invalid error code */ +}; + +/** Subscription event mask, as used by pa_context_subscribe() */ +typedef enum pa_subscription_mask { + PA_SUBSCRIPTION_MASK_NULL = 0, /**< No events */ + PA_SUBSCRIPTION_MASK_SINK = 1, /**< Sink events */ + PA_SUBSCRIPTION_MASK_SOURCE = 2, /**< Source events */ + PA_SUBSCRIPTION_MASK_SINK_INPUT = 4, /**< Sink input events */ + PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT = 8, /**< Source output events */ + PA_SUBSCRIPTION_MASK_MODULE = 16, /**< Module events */ + PA_SUBSCRIPTION_MASK_CLIENT = 32, /**< Client events */ + PA_SUBSCRIPTION_MASK_SAMPLE_CACHE = 64, /**< Sample cache events */ + PA_SUBSCRIPTION_MASK_SERVER = 128, /**< Other global server changes. \since 0.4 */ + PA_SUBSCRIPTION_MASK_AUTOLOAD = 256, /**< Autoload table events. \since 0.5 */ + PA_SUBSCRIPTION_MASK_ALL = 511 /**< Catch all events \since 0.8 */ +} pa_subscription_mask_t; + +/** Subscription event types, as used by pa_context_subscribe() */ +typedef enum pa_subscription_event_type { + PA_SUBSCRIPTION_EVENT_SINK = 0, /**< Event type: Sink */ + PA_SUBSCRIPTION_EVENT_SOURCE = 1, /**< Event type: Source */ + PA_SUBSCRIPTION_EVENT_SINK_INPUT = 2, /**< Event type: Sink input */ + PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT = 3, /**< Event type: Source output */ + PA_SUBSCRIPTION_EVENT_MODULE = 4, /**< Event type: Module */ + PA_SUBSCRIPTION_EVENT_CLIENT = 5, /**< Event type: Client */ + PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE = 6, /**< Event type: Sample cache item */ + PA_SUBSCRIPTION_EVENT_SERVER = 7, /**< Event type: Global server change, only occuring with PA_SUBSCRIPTION_EVENT_CHANGE. \since 0.4 */ + PA_SUBSCRIPTION_EVENT_AUTOLOAD = 8, /**< Event type: Autoload table changes. \since 0.5 */ + PA_SUBSCRIPTION_EVENT_FACILITY_MASK = 15, /**< A mask to extract the event type from an event value */ + + PA_SUBSCRIPTION_EVENT_NEW = 0, /**< A new object was created */ + PA_SUBSCRIPTION_EVENT_CHANGE = 16, /**< A property of the object was modified */ + PA_SUBSCRIPTION_EVENT_REMOVE = 32, /**< An object was removed */ + PA_SUBSCRIPTION_EVENT_TYPE_MASK = 16+32 /**< A mask to extract the event operation from an event value */ +} pa_subscription_event_type_t; + +/** Return one if an event type t matches an event mask bitfield */ +#define pa_subscription_match_flags(m, t) (!!((m) & (1 << ((t) & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)))) + +/** A structure for all kinds of timing information of a stream. See + * pa_stream_update_timing_info() and pa_stream_get_timing_info(). The + * total output latency a sample that is written with + * pa_stream_write() takes to be played may be estimated by + * sink_usec+buffer_usec+transport_usec. (where buffer_usec is defined + * as pa_bytes_to_usec(write_index-read_index)) The output buffer + * which buffer_usec relates to may be manipulated freely (with + * pa_stream_write()'s seek argument, pa_stream_flush() and friends), + * the buffers sink_usec and source_usec relate to are first-in + * first-out (FIFO) buffers which cannot be flushed or manipulated in + * any way. The total input latency a sample that is recorded takes to + * be delivered to the application is: + * source_usec+buffer_usec+transport_usec-sink_usec. (Take care of + * sign issues!) When connected to a monitor source sink_usec contains + * the latency of the owning sink. The two latency estimations + * described here are implemented in pa_stream_get_latency().*/ +typedef struct pa_timing_info { + struct timeval timestamp; /**< The time when this timing info structure was current */ + int synchronized_clocks; /**< Non-zero if the local and the + * remote machine have synchronized + * clocks. If synchronized clocks are + * detected transport_usec becomes much + * more reliable. However, the code that + * detects synchronized clocks is very + * limited und unreliable itself. \since + * 0.5 */ + + pa_usec_t sink_usec; /**< Time in usecs a sample takes to be played on the sink. For playback streams and record streams connected to a monitor source. */ + pa_usec_t source_usec; /**< Time in usecs a sample takes from being recorded to being delivered to the application. Only for record streams. \since 0.5*/ + pa_usec_t transport_usec; /**< Estimated time in usecs a sample takes to be transferred to/from the daemon. For both playback and record streams. \since 0.5 */ + + int playing; /**< Non-zero when the stream is currently playing. Only for playback streams. */ + + int write_index_corrupt; /**< Non-zero if write_index is not + * up-to-date because a local write + * command that corrupted it has been + * issued in the time since this latency + * info was current . Only write + * commands with SEEK_RELATIVE_ON_READ + * and SEEK_RELATIVE_END can corrupt + * write_index. \since 0.8 */ + int64_t write_index; /**< Current write index into the + * playback buffer in bytes. Think twice before + * using this for seeking purposes: it + * might be out of date a the time you + * want to use it. Consider using + * PA_SEEK_RELATIVE instead. \since + * 0.8 */ + + int read_index_corrupt; /**< Non-zero if read_index is not + * up-to-date because a local pause or + * flush request that corrupted it has + * been issued in the time since this + * latency info was current. \since 0.8 */ + + int64_t read_index; /**< Current read index into the + * playback buffer in bytes. Think twice before + * using this for seeking purposes: it + * might be out of date a the time you + * want to use it. Consider using + * PA_SEEK_RELATIVE_ON_READ + * instead. \since 0.8 */ +} pa_timing_info; + +/** A structure for the spawn api. This may be used to integrate auto + * spawned daemons into your application. For more information see + * pa_context_connect(). When spawning a new child process the + * waitpid() is used on the child's PID. The spawn routine will not + * block or ignore SIGCHLD signals, since this cannot be done in a + * thread compatible way. You might have to do this in + * prefork/postfork. \since 0.4 */ +typedef struct pa_spawn_api { + void (*prefork)(void); /**< Is called just before the fork in the parent process. May be NULL. */ + void (*postfork)(void); /**< Is called immediately after the fork in the parent process. May be NULL.*/ + void (*atfork)(void); /**< Is called immediately after the + * fork in the child process. May be + * NULL. It is not safe to close all + * file descriptors in this function + * unconditionally, since a UNIX socket + * (created using socketpair()) is + * passed to the new process. */ +} pa_spawn_api; + +/** Seek type for pa_stream_write(). \since 0.8*/ +typedef enum pa_seek_mode { + PA_SEEK_RELATIVE = 0, /**< Seek relatively to the write index */ + PA_SEEK_ABSOLUTE = 1, /**< Seek relatively to the start of the buffer queue */ + PA_SEEK_RELATIVE_ON_READ = 2, /**< Seek relatively to the read index. */ + PA_SEEK_RELATIVE_END = 3 /**< Seek relatively to the current end of the buffer queue. */ +} pa_seek_mode_t; + +/** Special sink flags. \since 0.8 */ +typedef enum pa_sink_flags { + PA_SINK_HW_VOLUME_CTRL = 1, /**< Supports hardware volume control */ + PA_SINK_LATENCY = 2, /**< Supports latency querying */ + PA_SINK_HARDWARE = 4, /**< Is a hardware sink of some kind, in contrast to "virtual"/software sinks \since 0.9.3 */ + PA_SINK_NETWORK = 8 /**< Is a networked sink of some kind. \since 0.9.7 */ +} pa_sink_flags_t; + +/** Special source flags. \since 0.8 */ +typedef enum pa_source_flags { + PA_SOURCE_HW_VOLUME_CTRL = 1, /**< Supports hardware volume control */ + PA_SOURCE_LATENCY = 2, /**< Supports latency querying */ + PA_SOURCE_HARDWARE = 4, /**< Is a hardware source of some kind, in contrast to "virtual"/software source \since 0.9.3 */ + PA_SOURCE_NETWORK = 8 /**< Is a networked sink of some kind. \since 0.9.7 */ +} pa_source_flags_t; + +/** A generic free() like callback prototype */ +typedef void (*pa_free_cb_t)(void *p); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/error.c b/src/pulse/error.c new file mode 100644 index 00000000..78f0da95 --- /dev/null +++ b/src/pulse/error.c @@ -0,0 +1,69 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/native-common.h> + +#include "error.h" + +const char*pa_strerror(int error) { + + static const char* const errortab[PA_ERR_MAX] = { + [PA_OK] = "OK", + [PA_ERR_ACCESS] = "Access denied", + [PA_ERR_COMMAND] = "Unknown command", + [PA_ERR_INVALID] = "Invalid argument", + [PA_ERR_EXIST] = "Entity exists", + [PA_ERR_NOENTITY] = "No such entity", + [PA_ERR_CONNECTIONREFUSED] = "Connection refused", + [PA_ERR_PROTOCOL] = "Protocol error", + [PA_ERR_TIMEOUT] = "Timeout", + [PA_ERR_AUTHKEY] = "No authorization key", + [PA_ERR_INTERNAL] = "Internal error", + [PA_ERR_CONNECTIONTERMINATED] = "Connection terminated", + [PA_ERR_KILLED] = "Entity killed", + [PA_ERR_INVALIDSERVER] = "Invalid server", + [PA_ERR_MODINITFAILED] = "Module initalization failed", + [PA_ERR_BADSTATE] = "Bad state", + [PA_ERR_NODATA] = "No data", + [PA_ERR_VERSION] = "Incompatible protocol version", + [PA_ERR_TOOLARGE] = "Too large" + }; + + if (error < 0 || error >= PA_ERR_MAX) + return NULL; + + return errortab[error]; +} diff --git a/src/pulse/error.h b/src/pulse/error.h new file mode 100644 index 00000000..44a2e5ec --- /dev/null +++ b/src/pulse/error.h @@ -0,0 +1,41 @@ +#ifndef fooerrorhfoo +#define fooerrorhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/cdecl.h> + +/** \file + * Error management */ + +PA_C_DECL_BEGIN + +/** Return a human readable error message for the specified numeric error code */ +const char* pa_strerror(int error); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/glib-mainloop.c b/src/pulse/glib-mainloop.c new file mode 100644 index 00000000..b7a7537a --- /dev/null +++ b/src/pulse/glib-mainloop.c @@ -0,0 +1,665 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/idxset.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/llist.h> + +#include <glib.h> +#include "glib-mainloop.h" + +struct pa_io_event { + pa_glib_mainloop *mainloop; + int dead; + + GPollFD poll_fd; + int poll_fd_added; + + pa_io_event_cb_t callback; + void *userdata; + pa_io_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_io_event); +}; + +struct pa_time_event { + pa_glib_mainloop *mainloop; + int dead; + + int enabled; + struct timeval timeval; + + pa_time_event_cb_t callback; + void *userdata; + pa_time_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_time_event); +}; + +struct pa_defer_event { + pa_glib_mainloop *mainloop; + int dead; + + int enabled; + + pa_defer_event_cb_t callback; + void *userdata; + pa_defer_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_defer_event); +}; + +struct pa_glib_mainloop { + GSource source; + + pa_mainloop_api api; + GMainContext *context; + + PA_LLIST_HEAD(pa_io_event, io_events); + PA_LLIST_HEAD(pa_time_event, time_events); + PA_LLIST_HEAD(pa_defer_event, defer_events); + + int n_enabled_defer_events, n_enabled_time_events; + int io_events_please_scan, time_events_please_scan, defer_events_please_scan; + + pa_time_event *cached_next_time_event; +}; + +static void cleanup_io_events(pa_glib_mainloop *g, int force) { + pa_io_event *e; + + e = g->io_events; + while (e) { + pa_io_event *n = e->next; + + if (!force && g->io_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_io_event, g->io_events, e); + + if (e->dead) { + g_assert(g->io_events_please_scan > 0); + g->io_events_please_scan--; + } + + if (e->poll_fd_added) + g_source_remove_poll(&g->source, &e->poll_fd); + + if (e->destroy_callback) + e->destroy_callback(&g->api, e, e->userdata); + + pa_xfree(e); + } + + e = n; + } + + g_assert(g->io_events_please_scan == 0); +} + +static void cleanup_time_events(pa_glib_mainloop *g, int force) { + pa_time_event *e; + + e = g->time_events; + while (e) { + pa_time_event *n = e->next; + + if (!force && g->time_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_time_event, g->time_events, e); + + if (e->dead) { + g_assert(g->time_events_please_scan > 0); + g->time_events_please_scan--; + } + + if (!e->dead && e->enabled) { + g_assert(g->n_enabled_time_events > 0); + g->n_enabled_time_events--; + } + + if (e->destroy_callback) + e->destroy_callback(&g->api, e, e->userdata); + + pa_xfree(e); + } + + e = n; + } + + g_assert(g->time_events_please_scan == 0); +} + +static void cleanup_defer_events(pa_glib_mainloop *g, int force) { + pa_defer_event *e; + + e = g->defer_events; + while (e) { + pa_defer_event *n = e->next; + + if (!force && g->defer_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_defer_event, g->defer_events, e); + + if (e->dead) { + g_assert(g->defer_events_please_scan > 0); + g->defer_events_please_scan--; + } + + if (!e->dead && e->enabled) { + g_assert(g->n_enabled_defer_events > 0); + g->n_enabled_defer_events--; + } + + if (e->destroy_callback) + e->destroy_callback(&g->api, e, e->userdata); + + pa_xfree(e); + } + + e = n; + } + + g_assert(g->defer_events_please_scan == 0); +} + +static gushort map_flags_to_glib(pa_io_event_flags_t flags) { + return + (flags & PA_IO_EVENT_INPUT ? G_IO_IN : 0) | + (flags & PA_IO_EVENT_OUTPUT ? G_IO_OUT : 0) | + (flags & PA_IO_EVENT_ERROR ? G_IO_ERR : 0) | + (flags & PA_IO_EVENT_HANGUP ? G_IO_HUP : 0); +} + +static pa_io_event_flags_t map_flags_from_glib(gushort flags) { + return + (flags & G_IO_IN ? PA_IO_EVENT_INPUT : 0) | + (flags & G_IO_OUT ? PA_IO_EVENT_OUTPUT : 0) | + (flags & G_IO_ERR ? PA_IO_EVENT_ERROR : 0) | + (flags & G_IO_HUP ? PA_IO_EVENT_HANGUP : 0); +} + +static pa_io_event* glib_io_new( + pa_mainloop_api*m, + int fd, + pa_io_event_flags_t f, + pa_io_event_cb_t cb, + void *userdata) { + + pa_io_event *e; + pa_glib_mainloop *g; + + g_assert(m); + g_assert(m->userdata); + g_assert(fd >= 0); + g_assert(cb); + + g = m->userdata; + + e = pa_xnew(pa_io_event, 1); + e->mainloop = g; + e->dead = 0; + + e->poll_fd.fd = fd; + e->poll_fd.events = map_flags_to_glib(f); + e->poll_fd.revents = 0; + + e->callback = cb; + e->userdata = userdata; + e->destroy_callback = NULL; + + PA_LLIST_PREPEND(pa_io_event, g->io_events, e); + + g_source_add_poll(&g->source, &e->poll_fd); + e->poll_fd_added = 1; + + return e; +} + +static void glib_io_enable(pa_io_event*e, pa_io_event_flags_t f) { + g_assert(e); + g_assert(!e->dead); + + e->poll_fd.events = map_flags_to_glib(f); +} + +static void glib_io_free(pa_io_event*e) { + g_assert(e); + g_assert(!e->dead); + + e->dead = 1; + e->mainloop->io_events_please_scan++; + + if (e->poll_fd_added) { + g_source_remove_poll(&e->mainloop->source, &e->poll_fd); + e->poll_fd_added = 0; + } +} + +static void glib_io_set_destroy(pa_io_event*e, pa_io_event_destroy_cb_t cb) { + g_assert(e); + g_assert(!e->dead); + + e->destroy_callback = cb; +} + +/* Time sources */ + +static pa_time_event* glib_time_new( + pa_mainloop_api*m, + const struct timeval *tv, + pa_time_event_cb_t cb, + void *userdata) { + + pa_glib_mainloop *g; + pa_time_event *e; + + g_assert(m); + g_assert(m->userdata); + g_assert(cb); + + g = m->userdata; + + e = pa_xnew(pa_time_event, 1); + e->mainloop = g; + e->dead = 0; + + if ((e->enabled = !!tv)) { + e->timeval = *tv; + g->n_enabled_time_events++; + + if (g->cached_next_time_event) { + g_assert(g->cached_next_time_event->enabled); + + if (pa_timeval_cmp(tv, &g->cached_next_time_event->timeval) < 0) + g->cached_next_time_event = e; + } + } + + e->callback = cb; + e->userdata = userdata; + e->destroy_callback = NULL; + + PA_LLIST_PREPEND(pa_time_event, g->time_events, e); + + return e; +} + +static void glib_time_restart(pa_time_event*e, const struct timeval *tv) { + g_assert(e); + g_assert(!e->dead); + + if (e->enabled && !tv) { + g_assert(e->mainloop->n_enabled_time_events > 0); + e->mainloop->n_enabled_time_events--; + } else if (!e->enabled && tv) + e->mainloop->n_enabled_time_events++; + + if ((e->enabled = !!tv)) + e->timeval = *tv; + + if (e->mainloop->cached_next_time_event && e->enabled) { + g_assert(e->mainloop->cached_next_time_event->enabled); + + if (pa_timeval_cmp(tv, &e->mainloop->cached_next_time_event->timeval) < 0) + e->mainloop->cached_next_time_event = e; + } else if (e->mainloop->cached_next_time_event == e) + e->mainloop->cached_next_time_event = NULL; + } + +static void glib_time_free(pa_time_event *e) { + g_assert(e); + g_assert(!e->dead); + + e->dead = 1; + e->mainloop->time_events_please_scan++; + + if (e->enabled) + e->mainloop->n_enabled_time_events--; + + if (e->mainloop->cached_next_time_event == e) + e->mainloop->cached_next_time_event = NULL; +} + +static void glib_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t cb) { + g_assert(e); + g_assert(!e->dead); + + e->destroy_callback = cb; +} + +/* Deferred sources */ + +static pa_defer_event* glib_defer_new( + pa_mainloop_api*m, + pa_defer_event_cb_t cb, + void *userdata) { + + pa_defer_event *e; + pa_glib_mainloop *g; + + g_assert(m); + g_assert(m->userdata); + g_assert(cb); + + g = m->userdata; + + e = pa_xnew(pa_defer_event, 1); + e->mainloop = g; + e->dead = 0; + + e->enabled = 1; + g->n_enabled_defer_events++; + + e->callback = cb; + e->userdata = userdata; + e->destroy_callback = NULL; + + PA_LLIST_PREPEND(pa_defer_event, g->defer_events, e); + return e; +} + +static void glib_defer_enable(pa_defer_event *e, int b) { + g_assert(e); + g_assert(!e->dead); + + if (e->enabled && !b) { + g_assert(e->mainloop->n_enabled_defer_events > 0); + e->mainloop->n_enabled_defer_events--; + } else if (!e->enabled && b) + e->mainloop->n_enabled_defer_events++; + + e->enabled = b; +} + +static void glib_defer_free(pa_defer_event *e) { + g_assert(e); + g_assert(!e->dead); + + e->dead = 1; + e->mainloop->defer_events_please_scan++; + + if (e->enabled) { + g_assert(e->mainloop->n_enabled_defer_events > 0); + e->mainloop->n_enabled_defer_events--; + } +} + +static void glib_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t cb) { + g_assert(e); + g_assert(!e->dead); + + e->destroy_callback = cb; +} + +/* quit() */ + +static void glib_quit(pa_mainloop_api*a, PA_GCC_UNUSED int retval) { + + g_warning("quit() ignored"); + + /* NOOP */ +} + +static pa_time_event* find_next_time_event(pa_glib_mainloop *g) { + pa_time_event *t, *n = NULL; + g_assert(g); + + if (g->cached_next_time_event) + return g->cached_next_time_event; + + for (t = g->time_events; t; t = t->next) { + + if (t->dead || !t->enabled) + continue; + + if (!n || pa_timeval_cmp(&t->timeval, &n->timeval) < 0) { + n = t; + + /* Shortcut for tv = { 0, 0 } */ + if (n->timeval.tv_sec <= 0) + break; + } + } + + g->cached_next_time_event = n; + return n; +} + +static void scan_dead(pa_glib_mainloop *g) { + g_assert(g); + + if (g->io_events_please_scan) + cleanup_io_events(g, 0); + + if (g->time_events_please_scan) + cleanup_time_events(g, 0); + + if (g->defer_events_please_scan) + cleanup_defer_events(g, 0); +} + +static gboolean prepare_func(GSource *source, gint *timeout) { + pa_glib_mainloop *g = (pa_glib_mainloop*) source; + + g_assert(g); + g_assert(timeout); + + scan_dead(g); + + if (g->n_enabled_defer_events) { + *timeout = 0; + return TRUE; + } else if (g->n_enabled_time_events) { + pa_time_event *t; + GTimeVal now; + struct timeval tvnow; + pa_usec_t usec; + + t = find_next_time_event(g); + g_assert(t); + + g_source_get_current_time(source, &now); + tvnow.tv_sec = now.tv_sec; + tvnow.tv_usec = now.tv_usec; + + if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) { + *timeout = 0; + return TRUE; + } + usec = pa_timeval_diff(&t->timeval, &tvnow); + *timeout = (gint) (usec / 1000); + } else + *timeout = -1; + + return FALSE; +} +static gboolean check_func(GSource *source) { + pa_glib_mainloop *g = (pa_glib_mainloop*) source; + pa_io_event *e; + + g_assert(g); + + if (g->n_enabled_defer_events) + return TRUE; + else if (g->n_enabled_time_events) { + pa_time_event *t; + GTimeVal now; + struct timeval tvnow; + + t = find_next_time_event(g); + g_assert(t); + + g_source_get_current_time(source, &now); + tvnow.tv_sec = now.tv_sec; + tvnow.tv_usec = now.tv_usec; + + if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) + return TRUE; + } + + for (e = g->io_events; e; e = e->next) + if (!e->dead && e->poll_fd.revents != 0) + return TRUE; + + return FALSE; +} + +static gboolean dispatch_func(GSource *source, PA_GCC_UNUSED GSourceFunc callback, PA_GCC_UNUSED gpointer userdata) { + pa_glib_mainloop *g = (pa_glib_mainloop*) source; + pa_io_event *e; + + g_assert(g); + + if (g->n_enabled_defer_events) { + pa_defer_event *d; + + for (d = g->defer_events; d; d = d->next) { + if (d->dead || !d->enabled) + continue; + + break; + } + + g_assert(d); + + d->callback(&g->api, d, d->userdata); + return TRUE; + } + + if (g->n_enabled_time_events) { + GTimeVal now; + struct timeval tvnow; + pa_time_event *t; + + t = find_next_time_event(g); + g_assert(t); + + g_source_get_current_time(source, &now); + tvnow.tv_sec = now.tv_sec; + tvnow.tv_usec = now.tv_usec; + + if (pa_timeval_cmp(&t->timeval, &tvnow) <= 0) { + + /* Disable time event */ + glib_time_restart(t, NULL); + + t->callback(&g->api, t, &t->timeval, t->userdata); + return TRUE; + } + } + + for (e = g->io_events; e; e = e->next) + if (!e->dead && e->poll_fd.revents != 0) { + e->callback(&g->api, e, e->poll_fd.fd, map_flags_from_glib(e->poll_fd.revents), e->userdata); + e->poll_fd.revents = 0; + return TRUE; + } + + return FALSE; +} + +static const pa_mainloop_api vtable = { + .userdata = NULL, + + .io_new = glib_io_new, + .io_enable = glib_io_enable, + .io_free = glib_io_free, + .io_set_destroy= glib_io_set_destroy, + + .time_new = glib_time_new, + .time_restart = glib_time_restart, + .time_free = glib_time_free, + .time_set_destroy = glib_time_set_destroy, + + .defer_new = glib_defer_new, + .defer_enable = glib_defer_enable, + .defer_free = glib_defer_free, + .defer_set_destroy = glib_defer_set_destroy, + + .quit = glib_quit, +}; + +pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c) { + pa_glib_mainloop *g; + + static GSourceFuncs source_funcs = { + prepare_func, + check_func, + dispatch_func, + NULL, + NULL, + NULL + }; + + g = (pa_glib_mainloop*) g_source_new(&source_funcs, sizeof(pa_glib_mainloop)); + g_main_context_ref(g->context = c ? c : g_main_context_default()); + + g->api = vtable; + g->api.userdata = g; + + PA_LLIST_HEAD_INIT(pa_io_event, g->io_events); + PA_LLIST_HEAD_INIT(pa_time_event, g->time_events); + PA_LLIST_HEAD_INIT(pa_defer_event, g->defer_events); + + g->n_enabled_defer_events = g->n_enabled_time_events = 0; + g->io_events_please_scan = g->time_events_please_scan = g->defer_events_please_scan = 0; + + g->cached_next_time_event = NULL; + + g_source_attach(&g->source, g->context); + g_source_set_can_recurse(&g->source, FALSE); + + return g; +} + +void pa_glib_mainloop_free(pa_glib_mainloop* g) { + g_assert(g); + + cleanup_io_events(g, 1); + cleanup_defer_events(g, 1); + cleanup_time_events(g, 1); + + g_main_context_unref(g->context); + g_source_destroy(&g->source); + g_source_unref(&g->source); +} + +pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g) { + g_assert(g); + + return &g->api; +} diff --git a/src/pulse/glib-mainloop.h b/src/pulse/glib-mainloop.h new file mode 100644 index 00000000..a4e06ea0 --- /dev/null +++ b/src/pulse/glib-mainloop.h @@ -0,0 +1,65 @@ +#ifndef fooglibmainloophfoo +#define fooglibmainloophfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <glib.h> + +#include <pulse/mainloop-api.h> +#include <pulse/cdecl.h> + +/** \page glib-mainloop GLIB Main Loop Bindings + * + * \section overv_sec Overview + * + * The GLIB main loop bindings are extremely easy to use. All that is + * required is to create a pa_glib_mainloop object using + * pa_glib_mainloop_new(). When the main loop abstraction is needed, it is + * provided by pa_glib_mainloop_get_api(). + * + */ + +/** \file + * GLIB main loop support */ + +PA_C_DECL_BEGIN + +/** An opaque GLIB main loop object */ +typedef struct pa_glib_mainloop pa_glib_mainloop; + +/** Create a new GLIB main loop object for the specified GLIB main + * loop context. Takes an argument c for the + * GMainContext to use. If c is NULL the default context is used. */ +pa_glib_mainloop *pa_glib_mainloop_new(GMainContext *c); + +/** Free the GLIB main loop object */ +void pa_glib_mainloop_free(pa_glib_mainloop* g); + +/** Return the abstract main loop API vtable for the GLIB main loop object */ +pa_mainloop_api* pa_glib_mainloop_get_api(pa_glib_mainloop *g); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/internal.h b/src/pulse/internal.h new file mode 100644 index 00000000..873f1363 --- /dev/null +++ b/src/pulse/internal.h @@ -0,0 +1,230 @@ +#ifndef foointernalhfoo +#define foointernalhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/operation.h> +#include <pulse/subscribe.h> + +#include <pulsecore/socket-client.h> +#include <pulsecore/pstream.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/dynarray.h> +#include <pulsecore/llist.h> +#include <pulsecore/native-common.h> +#include <pulsecore/strlist.h> +#include <pulsecore/mcalign.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/refcnt.h> + +#include "client-conf.h" + +#define DEFAULT_TIMEOUT (30) + +struct pa_context { + PA_REFCNT_DECLARE; + + char *name; + pa_mainloop_api* mainloop; + + pa_socket_client *client; + pa_pstream *pstream; + pa_pdispatch *pdispatch; + + pa_dynarray *record_streams, *playback_streams; + PA_LLIST_HEAD(pa_stream, streams); + PA_LLIST_HEAD(pa_operation, operations); + + uint32_t version; + uint32_t ctag; + uint32_t csyncid; + uint32_t error; + pa_context_state_t state; + + pa_context_notify_cb_t state_callback; + void *state_userdata; + + pa_context_subscribe_cb_t subscribe_callback; + void *subscribe_userdata; + + pa_mempool *mempool; + + int is_local; + int do_autospawn; + int autospawn_lock_fd; + pa_spawn_api spawn_api; + + pa_strlist *server_list; + + char *server; + + pa_client_conf *conf; +}; + +#define PA_MAX_WRITE_INDEX_CORRECTIONS 10 + +typedef struct pa_index_correction { + uint32_t tag; + int valid; + int64_t value; + int absolute, corrupt; +} pa_index_correction; + +struct pa_stream { + PA_REFCNT_DECLARE; + pa_context *context; + pa_mainloop_api *mainloop; + PA_LLIST_FIELDS(pa_stream); + + char *name; + pa_bool_t manual_buffer_attr; + pa_buffer_attr buffer_attr; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_stream_flags_t flags; + uint32_t channel; + uint32_t syncid; + int channel_valid; + uint32_t stream_index; + pa_stream_direction_t direction; + pa_stream_state_t state; + pa_bool_t buffer_attr_not_ready, timing_info_not_ready; + + uint32_t requested_bytes; + + uint32_t device_index; + char *device_name; + pa_bool_t suspended; + + pa_memchunk peek_memchunk; + void *peek_data; + pa_memblockq *record_memblockq; + + int corked; + + /* Store latest latency info */ + pa_timing_info timing_info; + int timing_info_valid; + + /* Use to make sure that time advances monotonically */ + pa_usec_t previous_time; + + /* time updates with tags older than these are invalid */ + uint32_t write_index_not_before; + uint32_t read_index_not_before; + + /* Data about individual timing update correctoins */ + pa_index_correction write_index_corrections[PA_MAX_WRITE_INDEX_CORRECTIONS]; + int current_write_index_correction; + + /* Latency interpolation stuff */ + pa_time_event *auto_timing_update_event; + int auto_timing_update_requested; + + pa_usec_t cached_time; + int cached_time_valid; + + /* Callbacks */ + pa_stream_notify_cb_t state_callback; + void *state_userdata; + pa_stream_request_cb_t read_callback; + void *read_userdata; + pa_stream_request_cb_t write_callback; + void *write_userdata; + pa_stream_notify_cb_t overflow_callback; + void *overflow_userdata; + pa_stream_notify_cb_t underflow_callback; + void *underflow_userdata; + pa_stream_notify_cb_t latency_update_callback; + void *latency_update_userdata; + pa_stream_notify_cb_t moved_callback; + void *moved_userdata; + pa_stream_notify_cb_t suspended_callback; + void *suspended_userdata; +}; + +typedef void (*pa_operation_cb_t)(void); + +struct pa_operation { + PA_REFCNT_DECLARE; + + pa_context *context; + pa_stream *stream; + + PA_LLIST_FIELDS(pa_operation); + + pa_operation_state_t state; + void *userdata; + pa_operation_cb_t callback; + + void *private; /* some operations might need this */ +}; + +void pa_command_request(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); + +pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t callback, void *userdata); +void pa_operation_done(pa_operation *o); + +void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_stream_disconnect_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_context_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); + +void pa_context_fail(pa_context *c, int error); +int pa_context_set_error(pa_context *c, int error); +void pa_context_set_state(pa_context *c, pa_context_state_t st); +int pa_context_handle_error(pa_context *c, uint32_t command, pa_tagstruct *t); +pa_operation* pa_context_send_simple_command(pa_context *c, uint32_t command, void (*internal_callback)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata), void (*cb)(void), void *userdata); + +void pa_stream_set_state(pa_stream *s, pa_stream_state_t st); + +pa_tagstruct *pa_tagstruct_command(pa_context *c, uint32_t command, uint32_t *tag); + +#define PA_CHECK_VALIDITY(context, expression, error) do { \ + if (!(expression)) \ + return -pa_context_set_error((context), (error)); \ +} while(0) + + +#define PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, value) do { \ + if (!(expression)) { \ + pa_context_set_error((context), (error)); \ + return value; \ + } \ +} while(0) + +#define PA_CHECK_VALIDITY_RETURN_NULL(context, expression, error) PA_CHECK_VALIDITY_RETURN_ANY(context, expression, error, NULL) + + +#endif diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c new file mode 100644 index 00000000..6610a724 --- /dev/null +++ b/src/pulse/introspect.c @@ -0,0 +1,1473 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/context.h> + +#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" + +#include "introspect.h" + +/*** Statistics ***/ + +static void context_stat_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + pa_stat_info i, *p = &i; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + memset(&i, 0, sizeof(i)); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + p = NULL; + } else if (pa_tagstruct_getu32(t, &i.memblock_total) < 0 || + pa_tagstruct_getu32(t, &i.memblock_total_size) < 0 || + pa_tagstruct_getu32(t, &i.memblock_allocated) < 0 || + pa_tagstruct_getu32(t, &i.memblock_allocated_size) < 0 || + pa_tagstruct_getu32(t, &i.scache_size) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_stat_info_cb_t cb = (pa_stat_info_cb_t) o->callback; + cb(o->context, p, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_stat(pa_context *c, pa_stat_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_STAT, context_stat_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Server Info ***/ + +static void context_get_server_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + pa_server_info i, *p = &i; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + memset(&i, 0, sizeof(i)); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + p = NULL; + } else if (pa_tagstruct_gets(t, &i.server_name) < 0 || + pa_tagstruct_gets(t, &i.server_version) < 0 || + pa_tagstruct_gets(t, &i.user_name) < 0 || + pa_tagstruct_gets(t, &i.host_name) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_gets(t, &i.default_sink_name) < 0 || + pa_tagstruct_gets(t, &i.default_source_name) < 0 || + pa_tagstruct_getu32(t, &i.cookie) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_server_info_cb_t cb = (pa_server_info_cb_t) o->callback; + cb(o->context, p, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_server_info(pa_context *c, pa_server_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SERVER_INFO, context_get_server_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Sink Info ***/ + +static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + eol = -1; + } else { + uint32_t flags; + + while (!pa_tagstruct_eof(t)) { + pa_sink_info i; + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_gets(t, &i.description) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_get_boolean(t, &i.mute) < 0 || + pa_tagstruct_getu32(t, &i.monitor_source) < 0 || + pa_tagstruct_gets(t, &i.monitor_source_name) < 0 || + pa_tagstruct_get_usec(t, &i.latency) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 || + pa_tagstruct_getu32(t, &flags) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + i.flags = (pa_sink_flags_t) flags; + + if (o->callback) { + pa_sink_info_cb_t cb = (pa_sink_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + } + } + + if (o->callback) { + pa_sink_info_cb_t cb = (pa_sink_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_sink_info_list(pa_context *c, pa_sink_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SINK_INFO_LIST, context_get_sink_info_callback, (pa_operation_cb_t) cb, userdata); +} + +pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t idx, pa_sink_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SINK_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SINK_INFO, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +/*** Source info ***/ + +static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_source_info i; + uint32_t flags; + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_gets(t, &i.description) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_get_boolean(t, &i.mute) < 0 || + pa_tagstruct_getu32(t, &i.monitor_of_sink) < 0 || + pa_tagstruct_gets(t, &i.monitor_of_sink_name) < 0 || + pa_tagstruct_get_usec(t, &i.latency) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 || + pa_tagstruct_getu32(t, &flags) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + i.flags = (pa_source_flags_t) flags; + + if (o->callback) { + pa_source_info_cb_t cb = (pa_source_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + } + } + + if (o->callback) { + pa_source_info_cb_t cb = (pa_source_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_source_info_list(pa_context *c, pa_source_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SOURCE_INFO_LIST, context_get_source_info_callback, (pa_operation_cb_t) cb, userdata); +} + +pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t idx, pa_source_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SOURCE_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SOURCE_INFO, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +/*** Client info ***/ + +static void context_get_client_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_client_info i; + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 ) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_client_info_cb_t cb = (pa_client_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + } + } + + if (o->callback) { + pa_client_info_cb_t cb = (pa_client_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_CLIENT_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_client_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_CLIENT_INFO_LIST, context_get_client_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Module info ***/ + +static void context_get_module_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_module_info i; + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_gets(t, &i.argument) < 0 || + pa_tagstruct_getu32(t, &i.n_used) < 0 || + pa_tagstruct_get_boolean(t, &i.auto_unload) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_module_info_cb_t cb = (pa_module_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + } + } + + if (o->callback) { + pa_module_info_cb_t cb = (pa_module_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_MODULE_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_module_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_MODULE_INFO_LIST, context_get_module_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Sink input info ***/ + +static void context_get_sink_input_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_sink_input_info i; + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_getu32(t, &i.client) < 0 || + pa_tagstruct_getu32(t, &i.sink) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 || + pa_tagstruct_get_usec(t, &i.sink_usec) < 0 || + pa_tagstruct_gets(t, &i.resample_method) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0 || + (o->context->version >= 11 && pa_tagstruct_get_boolean(t, &i.mute) < 0)) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_sink_input_info_cb_t cb = (pa_sink_input_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + } + } + + if (o->callback) { + pa_sink_input_info_cb_t cb = (pa_sink_input_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SINK_INPUT_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sink_input_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_sink_input_info_list(pa_context *c, void (*cb)(pa_context *c, const pa_sink_input_info*i, int is_last, void *userdata), void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SINK_INPUT_INFO_LIST, context_get_sink_input_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Source output info ***/ + +static void context_get_source_output_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_source_output_info i; + + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_getu32(t, &i.owner_module) < 0 || + pa_tagstruct_getu32(t, &i.client) < 0 || + pa_tagstruct_getu32(t, &i.source) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_get_usec(t, &i.buffer_usec) < 0 || + pa_tagstruct_get_usec(t, &i.source_usec) < 0 || + pa_tagstruct_gets(t, &i.resample_method) < 0 || + pa_tagstruct_gets(t, &i.driver) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_source_output_info_cb_t cb = (pa_source_output_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + } + } + + if (o->callback) { + pa_source_output_info_cb_t cb = (pa_source_output_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_source_output_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SOURCE_OUTPUT_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_source_output_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_source_output_info_list(pa_context *c, pa_source_output_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST, context_get_source_output_info_callback, (pa_operation_cb_t) cb, userdata); +} + +/*** Volume manipulation ***/ + +pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_VOLUME, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_VOLUME, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_MUTE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_MUTE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_INPUT_VOLUME, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SINK_INPUT_MUTE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_VOLUME, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + pa_assert(volume); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, pa_cvolume_valid(volume), PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_VOLUME, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_put_cvolume(t, volume); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_MUTE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, !name || *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SET_SOURCE_MUTE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_tagstruct_put_boolean(t, mute); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +/** Sample Cache **/ + +static void context_get_sample_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_sample_info i; + + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_get_cvolume(t, &i.volume) < 0 || + pa_tagstruct_get_usec(t, &i.duration) < 0 || + pa_tagstruct_get_sample_spec(t, &i.sample_spec) < 0 || + pa_tagstruct_get_channel_map(t, &i.channel_map) < 0 || + pa_tagstruct_getu32(t, &i.bytes) < 0 || + pa_tagstruct_get_boolean(t, &i.lazy) < 0 || + pa_tagstruct_gets(t, &i.filename) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_sample_info_cb_t cb = (pa_sample_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + } + } + + if (o->callback) { + pa_sample_info_cb_t cb = (pa_sample_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name, pa_sample_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SAMPLE_INFO, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sample_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, pa_sample_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_SAMPLE_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_sample_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_sample_info_list(pa_context *c, pa_sample_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_SAMPLE_INFO_LIST, context_get_sample_info_callback, (pa_operation_cb_t) cb, userdata); +} + +static pa_operation* command_kill(pa_context *c, uint32_t command, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, command, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + return command_kill(c, PA_COMMAND_KILL_CLIENT, idx, cb, userdata); +} + +pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + return command_kill(c, PA_COMMAND_KILL_SINK_INPUT, idx, cb, userdata); +} + +pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + return command_kill(c, PA_COMMAND_KILL_SOURCE_OUTPUT, idx, cb, userdata); +} + +static void context_index_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + uint32_t idx; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + idx = PA_INVALID_INDEX; + } else if (pa_tagstruct_getu32(t, &idx) || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_context_index_cb_t cb = (pa_context_index_cb_t) o->callback; + cb(o->context, idx, o->userdata); + } + + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_LOAD_MODULE, &tag); + pa_tagstruct_puts(t, name); + pa_tagstruct_puts(t, argument); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_index_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata) { + return command_kill(c, PA_COMMAND_UNLOAD_MODULE, idx, cb, userdata); +} + +/*** Autoload stuff ***/ + +static void context_get_autoload_info_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int eol = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + eol = -1; + } else { + + while (!pa_tagstruct_eof(t)) { + pa_autoload_info i; + + memset(&i, 0, sizeof(i)); + + if (pa_tagstruct_getu32(t, &i.index) < 0 || + pa_tagstruct_gets(t, &i.name) < 0 || + pa_tagstruct_getu32(t, &i.type) < 0 || + pa_tagstruct_gets(t, &i.module) < 0 || + pa_tagstruct_gets(t, &i.argument) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_autoload_info_cb_t cb = (pa_autoload_info_cb_t) o->callback; + cb(o->context, &i, 0, o->userdata); + } + } + } + + if (o->callback) { + pa_autoload_info_cb_t cb = (pa_autoload_info_cb_t) o->callback; + cb(o->context, NULL, eol, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, type == PA_AUTOLOAD_SINK || type == PA_AUTOLOAD_SOURCE, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_AUTOLOAD_INFO, &tag); + pa_tagstruct_puts(t, name); + pa_tagstruct_putu32(t, type); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_autoload_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(cb); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_GET_AUTOLOAD_INFO, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_get_autoload_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata) { + return pa_context_send_simple_command(c, PA_COMMAND_GET_AUTOLOAD_INFO_LIST, context_get_autoload_info_callback, (pa_operation_cb_t) cb, userdata); +} + +pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, type == PA_AUTOLOAD_SINK || type == PA_AUTOLOAD_SOURCE, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, module && *module, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_ADD_AUTOLOAD, &tag); + pa_tagstruct_puts(t, name); + pa_tagstruct_putu32(t, type); + pa_tagstruct_puts(t, module); + pa_tagstruct_puts(t, argument); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, context_index_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, type == PA_AUTOLOAD_SINK || type == PA_AUTOLOAD_SOURCE, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_AUTOLOAD, &tag); + pa_tagstruct_puts(t, name); + pa_tagstruct_putu32(t, type); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_AUTOLOAD, &tag); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, char *sink_name, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, sink_name && *sink_name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SINK_INPUT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, sink_name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, sink_idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SINK_INPUT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_putu32(t, sink_idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, char *source_name, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, source_name && *source_name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SOURCE_OUTPUT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, source_name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 10, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, idx != PA_INVALID_INDEX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, source_idx != PA_INVALID_INDEX, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_MOVE_SOURCE_OUTPUT, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_putu32(t, source_idx); + pa_tagstruct_puts(t, NULL); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_suspend_sink_by_name(pa_context *c, char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, !sink_name || *sink_name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SINK, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, sink_name); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SINK, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, idx == PA_INVALID_INDEX ? "" : NULL); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_suspend_source_by_name(pa_context *c, char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, !source_name || *source_name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SOURCE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, source_name); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 11, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUSPEND_SOURCE, &tag); + pa_tagstruct_putu32(t, idx); + pa_tagstruct_puts(t, idx == PA_INVALID_INDEX ? "" : NULL); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h new file mode 100644 index 00000000..c148ee5e --- /dev/null +++ b/src/pulse/introspect.h @@ -0,0 +1,520 @@ +#ifndef foointrospecthfoo +#define foointrospecthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/operation.h> +#include <pulse/context.h> +#include <pulse/cdecl.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> + +/** \page introspect Server Query and Control + * + * \section overv_sec Overview + * + * Sometimes it is necessary to query and modify global settings in the + * server. For this, PulseAudio has the introspection API. It can list sinks, + * sources, samples and other aspects of the server. It can also modify the + * attributes of the server that will affect operations on a global level, + * and not just the application's context. + * + * \section query_sec Querying + * + * All querying is done through callbacks. This design is necessary to + * maintain an asynchronous design. The client will request the information + * and some time later, the server will respond with the desired data. + * + * Some objects can have multiple entries at the server. When requesting all + * of these at once, the callback will be called multiple times, once for + * each object. When the list has been exhausted, the callback will be called + * without an information structure and the eol parameter set to a non-zero + * value. + * + * Note that even if a single object is requested, and not the entire list, + * the terminating call will still be made. + * + * If an error occurs, the callback will be called without and information + * structure and eol set to zero. + * + * Data members in the information structures are only valid during the + * duration of the callback. If they are required after the callback is + * finished, a deep copy must be performed. + * + * \subsection server_subsec Server Information + * + * The server can be queried about its name, the environment it's running on + * and the currently active global defaults. Calling + * pa_context_get_server_info() will get access to a pa_server_info structure + * containing all of these. + * + * \subsection memstat_subsec Memory Usage + * + * Statistics about memory usage can be fetched using pa_context_stat(), + * giving a pa_stat_info structure. + * + * \subsection sinksrc_subsec Sinks and Sources + * + * The server can have an arbitrary number of sinks and sources. Each sink + * and source have both an index and a name associated with it. As such + * there are three ways to get access to them: + * + * \li By index - pa_context_get_sink_info_by_index() / + * pa_context_get_source_info_by_index() + * \li By name - pa_context_get_sink_info_by_name() / + * pa_context_get_source_info_by_name() + * \li All - pa_context_get_sink_info_list() / + * pa_context_get_source_info_list() + * + * All three method use the same callback and will provide a pa_sink_info or + * pa_source_info structure. + * + * \subsection siso_subsec Sink Inputs and Source Outputs + * + * Sink inputs and source outputs are the representations of the client ends + * of streams inside the server. I.e. they connect a client stream to one of + * the global sinks or sources. + * + * Sink inputs and source outputs only have an index to identify them. As + * such, there are only two ways to get information about them: + * + * \li By index - pa_context_get_sink_input_info() / + * pa_context_get_source_output_info() + * \li All - pa_context_get_sink_input_info_list() / + * pa_context_get_source_output_info_list() + * + * The structure returned is the pa_sink_input_info or pa_source_output_info + * structure. + * + * \subsection samples_subsec Samples + * + * The list of cached samples can be retrieved from the server. Three methods + * exist for querying the sample cache list: + * + * \li By index - pa_context_get_sample_info_by_index() + * \li By name - pa_context_get_sample_info_by_name() + * \li All - pa_context_get_sample_info_list() + * + * Note that this only retrieves information about the sample, not the sample + * data itself. + * + * \subsection module_subsec Driver Modules + * + * PulseAudio driver modules are identified by index and are retrieved using either + * pa_context_get_module_info() or pa_context_get_module_info_list(). The + * information structure is called pa_module_info. + * + * \subsection autoload_subsec Autoload Entries + * + * Modules can be autoloaded as a result of a client requesting a certain + * sink or source. This mapping between sink/source names and modules can be + * queried from the server: + * + * \li By index - pa_context_get_autoload_info_by_index() + * \li By sink/source name - pa_context_get_autoload_info_by_name() + * \li All - pa_context_get_autoload_info_list() + * + * \subsection client_subsec Clients + * + * PulseAudio clients are also identified by index and are retrieved using + * either pa_context_get_client_info() or pa_context_get_client_info_list(). + * The information structure is called pa_client_info. + * + * \section ctrl_sec Control + * + * Some parts of the server are only possible to read, but most can also be + * modified in different ways. Note that these changes will affect all + * connected clients and not just the one issuing the request. + * + * \subsection sinksrc_subsec Sinks and Sources + * + * The most common change one would want to do to sinks and sources is to + * modify the volume of the audio. Identical to how sinks and sources can + * be queried, there are two ways of identifying them: + * + * \li By index - pa_context_set_sink_volume_by_index() / + * pa_context_set_source_volume_by_index() + * \li By name - pa_context_set_sink_volume_by_name() / + * pa_context_set_source_volume_by_name() + * + * It is also possible to mute a sink or source: + * + * \li By index - pa_context_set_sink_mute_by_index() / + * pa_context_set_source_mute_by_index() + * \li By name - pa_context_set_sink_mute_by_name() / + * pa_context_set_source_mute_by_name() + * + * \subsection siso_subsec Sink Inputs and Source Outputs + * + * If an application desires to modify the volume of just a single stream + * (commonly one of its own streams), this can be done by setting the volume + * of its associated sink input, using pa_context_set_sink_input_volume(). + * + * There is no support for modifying the volume of source outputs. + * + * It is also possible to remove sink inputs and source outputs, terminating + * the streams associated with them: + * + * \li Sink input - pa_context_kill_sink_input() + * \li Source output - pa_context_kill_source_output() + * + * \subsection module_subsec Modules + * + * Server modules can be remotely loaded and unloaded using + * pa_context_load_module() and pa_context_unload_module(). + * + * \subsection autoload_subsec Autoload Entries + * + * New module autoloading rules can be added, and existing can be removed + * using pa_context_add_autoload() and pa_context_remove_autoload_by_index() + * / pa_context_remove_autoload_by_name(). + * + * \subsection client_subsec Clients + * + * The only operation supported on clients, is the possibility of kicking + * them off the server using pa_context_kill_client(). + */ + +/** \file + * + * Routines for daemon introspection. + */ + +PA_C_DECL_BEGIN + +/** Stores information about sinks */ +typedef struct pa_sink_info { + const char *name; /**< Name of the sink */ + uint32_t index; /**< Index of the sink */ + const char *description; /**< Description of this sink */ + pa_sample_spec sample_spec; /**< Sample spec of this sink */ + pa_channel_map channel_map; /**< Channel map \since 0.8 */ + uint32_t owner_module; /**< Index of the owning module of this sink, or PA_INVALID_INDEX */ + pa_cvolume volume; /**< Volume of the sink */ + int mute; /**< Mute switch of the sink \since 0.8 */ + uint32_t monitor_source; /**< Index of the monitor source connected to this sink */ + const char *monitor_source_name; /**< The name of the monitor source */ + pa_usec_t latency; /**< Length of filled playback buffer of this sink */ + const char *driver; /**< Driver name. \since 0.8 */ + pa_sink_flags_t flags; /**< Flags \since 0.8 */ +} pa_sink_info; + +/** Callback prototype for pa_context_get_sink_info_by_name() and friends */ +typedef void (*pa_sink_info_cb_t)(pa_context *c, const pa_sink_info *i, int eol, void *userdata); + +/** Get information about a sink by its name */ +pa_operation* pa_context_get_sink_info_by_name(pa_context *c, const char *name, pa_sink_info_cb_t cb, void *userdata); + +/** Get information about a sink by its index */ +pa_operation* pa_context_get_sink_info_by_index(pa_context *c, uint32_t id, pa_sink_info_cb_t cb, void *userdata); + +/** Get the complete sink list */ +pa_operation* pa_context_get_sink_info_list(pa_context *c, pa_sink_info_cb_t cb, void *userdata); + +/** Stores information about sources */ +typedef struct pa_source_info { + const char *name ; /**< Name of the source */ + uint32_t index; /**< Index of the source */ + const char *description; /**< Description of this source */ + pa_sample_spec sample_spec; /**< Sample spec of this source */ + pa_channel_map channel_map; /**< Channel map \since 0.8 */ + uint32_t owner_module; /**< Owning module index, or PA_INVALID_INDEX */ + pa_cvolume volume; /**< Volume of the source \since 0.8 */ + int mute; /**< Mute switch of the sink \since 0.8 */ + uint32_t monitor_of_sink; /**< If this is a monitor source the index of the owning sink, otherwise PA_INVALID_INDEX */ + const char *monitor_of_sink_name; /**< Name of the owning sink, or PA_INVALID_INDEX */ + pa_usec_t latency; /**< Length of filled record buffer of this source. \since 0.5 */ + const char *driver; /**< Driver name \since 0.8 */ + pa_source_flags_t flags; /**< Flags \since 0.8 */ +} pa_source_info; + +/** Callback prototype for pa_context_get_source_info_by_name() and friends */ +typedef void (*pa_source_info_cb_t)(pa_context *c, const pa_source_info *i, int eol, void *userdata); + +/** Get information about a source by its name */ +pa_operation* pa_context_get_source_info_by_name(pa_context *c, const char *name, pa_source_info_cb_t cb, void *userdata); + +/** Get information about a source by its index */ +pa_operation* pa_context_get_source_info_by_index(pa_context *c, uint32_t id, pa_source_info_cb_t cb, void *userdata); + +/** Get the complete source list */ +pa_operation* pa_context_get_source_info_list(pa_context *c, pa_source_info_cb_t cb, void *userdata); + +/** Server information */ +typedef struct pa_server_info { + const char *user_name; /**< User name of the daemon process */ + const char *host_name; /**< Host name the daemon is running on */ + const char *server_version; /**< Version string of the daemon */ + const char *server_name; /**< Server package name (usually "pulseaudio") */ + pa_sample_spec sample_spec; /**< Default sample specification */ + const char *default_sink_name; /**< Name of default sink. \since 0.4 */ + const char *default_source_name; /**< Name of default sink. \since 0.4*/ + uint32_t cookie; /**< A random cookie for identifying this instance of PulseAudio. \since 0.8 */ +} pa_server_info; + +/** Callback prototype for pa_context_get_server_info() */ +typedef void (*pa_server_info_cb_t) (pa_context *c, const pa_server_info*i, void *userdata); + +/** Get some information about the server */ +pa_operation* pa_context_get_server_info(pa_context *c, pa_server_info_cb_t cb, void *userdata); + +/** Stores information about modules */ +typedef struct pa_module_info { + uint32_t index; /**< Index of the module */ + const char*name, /**< Name of the module */ + *argument; /**< Argument string of the module */ + uint32_t n_used; /**< Usage counter or PA_INVALID_INDEX */ + int auto_unload; /**< Non-zero if this is an autoloaded module */ +} pa_module_info; + +/** Callback prototype for pa_context_get_module_info() and firends*/ +typedef void (*pa_module_info_cb_t) (pa_context *c, const pa_module_info*i, int eol, void *userdata); + +/** Get some information about a module by its index */ +pa_operation* pa_context_get_module_info(pa_context *c, uint32_t idx, pa_module_info_cb_t cb, void *userdata); + +/** Get the complete list of currently loaded modules */ +pa_operation* pa_context_get_module_info_list(pa_context *c, pa_module_info_cb_t cb, void *userdata); + +/** Stores information about clients */ +typedef struct pa_client_info { + uint32_t index; /**< Index of this client */ + const char *name; /**< Name of this client */ + uint32_t owner_module; /**< Index of the owning module, or PA_INVALID_INDEX */ + const char *driver; /**< Driver name \since 0.8 */ +} pa_client_info; + +/** Callback prototype for pa_context_get_client_info() and firends*/ +typedef void (*pa_client_info_cb_t) (pa_context *c, const pa_client_info*i, int eol, void *userdata); + +/** Get information about a client by its index */ +pa_operation* pa_context_get_client_info(pa_context *c, uint32_t idx, pa_client_info_cb_t cb, void *userdata); + +/** Get the complete client list */ +pa_operation* pa_context_get_client_info_list(pa_context *c, pa_client_info_cb_t cb, void *userdata); + +/** Stores information about sink inputs */ +typedef struct pa_sink_input_info { + uint32_t index; /**< Index of the sink input */ + const char *name; /**< Name of the sink input */ + uint32_t owner_module; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module */ + uint32_t client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client */ + uint32_t sink; /**< Index of the connected sink */ + pa_sample_spec sample_spec; /**< The sample specification of the sink input */ + pa_channel_map channel_map; /**< Channel map */ + pa_cvolume volume; /**< The volume of this sink input */ + pa_usec_t buffer_usec; /**< Latency due to buffering in sink input, see pa_latency_info for details */ + pa_usec_t sink_usec; /**< Latency of the sink device, see pa_latency_info for details */ + const char *resample_method; /**< Thre resampling method used by this sink input. \since 0.7 */ + const char *driver; /**< Driver name \since 0.8 */ + int mute; /**< Stream muted \since 0.9.7 */ +} pa_sink_input_info; + +/** Callback prototype for pa_context_get_sink_input_info() and firends*/ +typedef void (*pa_sink_input_info_cb_t) (pa_context *c, const pa_sink_input_info *i, int eol, void *userdata); + +/** Get some information about a sink input by its index */ +pa_operation* pa_context_get_sink_input_info(pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata); + +/** Get the complete sink input list */ +pa_operation* pa_context_get_sink_input_info_list(pa_context *c, pa_sink_input_info_cb_t cb, void *userdata); + +/** Stores information about source outputs */ +typedef struct pa_source_output_info { + uint32_t index; /**< Index of the sink input */ + const char *name; /**< Name of the sink input */ + uint32_t owner_module; /**< Index of the module this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any module */ + uint32_t client; /**< Index of the client this sink input belongs to, or PA_INVALID_INDEX when it does not belong to any client */ + uint32_t source; /**< Index of the connected source */ + pa_sample_spec sample_spec; /**< The sample specification of the source output */ + pa_channel_map channel_map; /**< Channel map */ + pa_usec_t buffer_usec; /**< Latency due to buffering in the source output, see pa_latency_info for details. \since 0.5 */ + pa_usec_t source_usec; /**< Latency of the source device, see pa_latency_info for details. \since 0.5 */ + const char *resample_method; /**< Thre resampling method used by this source output. \since 0.7 */ + const char *driver; /**< Driver name \since 0.8 */ +} pa_source_output_info; + +/** Callback prototype for pa_context_get_source_output_info() and firends*/ +typedef void (*pa_source_output_info_cb_t) (pa_context *c, const pa_source_output_info *i, int eol, void *userdata); + +/** Get information about a source output by its index */ +pa_operation* pa_context_get_source_output_info(pa_context *c, uint32_t idx, pa_source_output_info_cb_t cb, void *userdata); + +/** Get the complete list of source outputs */ +pa_operation* pa_context_get_source_output_info_list(pa_context *c, pa_source_output_info_cb_t cb, void *userdata); + +/** Set the volume of a sink device specified by its index */ +pa_operation* pa_context_set_sink_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the volume of a sink device specified by its name */ +pa_operation* pa_context_set_sink_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a sink device specified by its index \since 0.8 */ +pa_operation* pa_context_set_sink_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a sink device specified by its name \since 0.8 */ +pa_operation* pa_context_set_sink_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Set the volume of a sink input stream */ +pa_operation* pa_context_set_sink_input_volume(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a sink input stream \since 0.9.7 */ +pa_operation* pa_context_set_sink_input_mute(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Set the volume of a source device specified by its index \since 0.8 */ +pa_operation* pa_context_set_source_volume_by_index(pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the volume of a source device specified by its name \since 0.8 */ +pa_operation* pa_context_set_source_volume_by_name(pa_context *c, const char *name, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a source device specified by its index \since 0.8 */ +pa_operation* pa_context_set_source_mute_by_index(pa_context *c, uint32_t idx, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Set the mute switch of a source device specified by its name \since 0.8 */ +pa_operation* pa_context_set_source_mute_by_name(pa_context *c, const char *name, int mute, pa_context_success_cb_t cb, void *userdata); + +/** Memory block statistics */ +typedef struct pa_stat_info { + uint32_t memblock_total; /**< Currently allocated memory blocks */ + uint32_t memblock_total_size; /**< Currentl total size of allocated memory blocks */ + uint32_t memblock_allocated; /**< Allocated memory blocks during the whole lifetime of the daemon */ + uint32_t memblock_allocated_size; /**< Total size of all memory blocks allocated during the whole lifetime of the daemon */ + uint32_t scache_size; /**< Total size of all sample cache entries. \since 0.4 */ +} pa_stat_info; + +/** Callback prototype for pa_context_stat() */ +typedef void (*pa_stat_info_cb_t) (pa_context *c, const pa_stat_info *i, void *userdata); + +/** Get daemon memory block statistics */ +pa_operation* pa_context_stat(pa_context *c, pa_stat_info_cb_t cb, void *userdata); + +/** Stores information about sample cache entries */ +typedef struct pa_sample_info { + uint32_t index; /**< Index of this entry */ + const char *name; /**< Name of this entry */ + pa_cvolume volume; /**< Default volume of this entry */ + pa_sample_spec sample_spec; /**< Sample specification of the sample */ + pa_channel_map channel_map; /**< The channel map */ + pa_usec_t duration; /**< Duration of this entry */ + uint32_t bytes; /**< Length of this sample in bytes. \since 0.4 */ + int lazy; /**< Non-zero when this is a lazy cache entry. \since 0.5 */ + const char *filename; /**< In case this is a lazy cache entry, the filename for the sound file to be loaded on demand. \since 0.5 */ +} pa_sample_info; + +/** Callback prototype for pa_context_get_sample_info_by_name() and firends */ +typedef void (*pa_sample_info_cb_t)(pa_context *c, const pa_sample_info *i, int eol, void *userdata); + +/** Get information about a sample by its name */ +pa_operation* pa_context_get_sample_info_by_name(pa_context *c, const char *name, pa_sample_info_cb_t cb, void *userdata); + +/** Get information about a sample by its index */ +pa_operation* pa_context_get_sample_info_by_index(pa_context *c, uint32_t idx, pa_sample_info_cb_t cb, void *userdata); + +/** Get the complete list of samples stored in the daemon. */ +pa_operation* pa_context_get_sample_info_list(pa_context *c, pa_sample_info_cb_t cb, void *userdata); + +/** Kill a client. \since 0.5 */ +pa_operation* pa_context_kill_client(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** Kill a sink input. \since 0.5 */ +pa_operation* pa_context_kill_sink_input(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** Kill a source output. \since 0.5 */ +pa_operation* pa_context_kill_source_output(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** Callback prototype for pa_context_load_module() and pa_context_add_autoload() */ +typedef void (*pa_context_index_cb_t)(pa_context *c, uint32_t idx, void *userdata); + +/** Load a module. \since 0.5 */ +pa_operation* pa_context_load_module(pa_context *c, const char*name, const char *argument, pa_context_index_cb_t cb, void *userdata); + +/** Unload a module. \since 0.5 */ +pa_operation* pa_context_unload_module(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void *userdata); + +/** Type of an autoload entry. \since 0.5 */ +typedef enum pa_autoload_type { + PA_AUTOLOAD_SINK = 0, + PA_AUTOLOAD_SOURCE = 1 +} pa_autoload_type_t; + +/** Stores information about autoload entries. \since 0.5 */ +typedef struct pa_autoload_info { + uint32_t index; /**< Index of this autoload entry */ + const char *name; /**< Name of the sink or source */ + pa_autoload_type_t type; /**< Type of the autoload entry */ + const char *module; /**< Module name to load */ + const char *argument; /**< Argument string for module */ +} pa_autoload_info; + +/** Callback prototype for pa_context_get_autoload_info_by_name() and firends */ +typedef void (*pa_autoload_info_cb_t)(pa_context *c, const pa_autoload_info *i, int eol, void *userdata); + +/** Get info about a specific autoload entry. \since 0.6 */ +pa_operation* pa_context_get_autoload_info_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_autoload_info_cb_t cb, void *userdata); + +/** Get info about a specific autoload entry. \since 0.6 */ +pa_operation* pa_context_get_autoload_info_by_index(pa_context *c, uint32_t idx, pa_autoload_info_cb_t cb, void *userdata); + +/** Get the complete list of autoload entries. \since 0.5 */ +pa_operation* pa_context_get_autoload_info_list(pa_context *c, pa_autoload_info_cb_t cb, void *userdata); + +/** Add a new autoload entry. \since 0.5 */ +pa_operation* pa_context_add_autoload(pa_context *c, const char *name, pa_autoload_type_t type, const char *module, const char*argument, pa_context_index_cb_t, void* userdata); + +/** Remove an autoload entry. \since 0.6 */ +pa_operation* pa_context_remove_autoload_by_name(pa_context *c, const char *name, pa_autoload_type_t type, pa_context_success_cb_t cb, void* userdata); + +/** Remove an autoload entry. \since 0.6 */ +pa_operation* pa_context_remove_autoload_by_index(pa_context *c, uint32_t idx, pa_context_success_cb_t cb, void* userdata); + +/** Move the specified sink input to a different sink. \since 0.9.5 */ +pa_operation* pa_context_move_sink_input_by_name(pa_context *c, uint32_t idx, char *sink_name, pa_context_success_cb_t cb, void* userdata); + +/** Move the specified sink input to a different sink. \since 0.9.5 */ +pa_operation* pa_context_move_sink_input_by_index(pa_context *c, uint32_t idx, uint32_t sink_idx, pa_context_success_cb_t cb, void* userdata); + +/** Move the specified source output to a different source. \since 0.9.5 */ +pa_operation* pa_context_move_source_output_by_name(pa_context *c, uint32_t idx, char *source_name, pa_context_success_cb_t cb, void* userdata); + +/** Move the specified source output to a different source. \since 0.9.5 */ +pa_operation* pa_context_move_source_output_by_index(pa_context *c, uint32_t idx, uint32_t source_idx, pa_context_success_cb_t cb, void* userdata); + +/** Suspend/Resume a sink. \since 0.9.7 */ +pa_operation* pa_context_suspend_sink_by_name(pa_context *c, char *sink_name, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** Suspend/Resume a sink. If idx is PA_INVALID_INDEX all sinks will be suspended. \since 0.9.7 */ +pa_operation* pa_context_suspend_sink_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** Suspend/Resume a source. \since 0.9.7 */ +pa_operation* pa_context_suspend_source_by_name(pa_context *c, char *source_name, int suspend, pa_context_success_cb_t cb, void* userdata); + +/** Suspend/Resume a source. If idx is PA_INVALID_INDEX all sources will be suspended. \since 0.9.7 */ +pa_operation* pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, int suspend, pa_context_success_cb_t cb, void* userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/mainloop-api.c b/src/pulse/mainloop-api.c new file mode 100644 index 00000000..b2ed3434 --- /dev/null +++ b/src/pulse/mainloop-api.c @@ -0,0 +1,78 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> + +#include "mainloop-api.h" + +struct once_info { + void (*callback)(pa_mainloop_api*m, void *userdata); + void *userdata; +}; + +static void once_callback(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + struct once_info *i = userdata; + + pa_assert(m); + pa_assert(i); + + pa_assert(i->callback); + i->callback(m, i->userdata); + + pa_assert(m->defer_free); + m->defer_free(e); +} + +static void free_callback(pa_mainloop_api *m, PA_GCC_UNUSED pa_defer_event *e, void *userdata) { + struct once_info *i = userdata; + + pa_assert(m); + pa_assert(i); + pa_xfree(i); +} + +void pa_mainloop_api_once(pa_mainloop_api* m, void (*callback)(pa_mainloop_api *m, void *userdata), void *userdata) { + struct once_info *i; + pa_defer_event *e; + + pa_assert(m); + pa_assert(callback); + + i = pa_xnew(struct once_info, 1); + i->callback = callback; + i->userdata = userdata; + + pa_assert(m->defer_new); + pa_assert_se(e = m->defer_new(m, once_callback, i)); + m->defer_set_destroy(e, free_callback); +} + diff --git a/src/pulse/mainloop-api.h b/src/pulse/mainloop-api.h new file mode 100644 index 00000000..985806e6 --- /dev/null +++ b/src/pulse/mainloop-api.h @@ -0,0 +1,124 @@ +#ifndef foomainloopapihfoo +#define foomainloopapihfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/time.h> +#include <time.h> + +#include <pulse/cdecl.h> + +/** \file + * + * Main loop abstraction layer. Both the PulseAudio core and the + * PulseAudio client library use a main loop abstraction layer. Due to + * this it is possible to embed PulseAudio into other + * applications easily. Two main loop implemenations are + * currently available: + * \li A minimal implementation based on the C library's poll() function (See \ref mainloop.h) + * \li A wrapper around the GLIB main loop. Use this to embed PulseAudio into your GLIB/GTK+/GNOME programs (See \ref glib-mainloop.h) + * + * The structure pa_mainloop_api is used as vtable for the main loop abstraction. + * + * This mainloop abstraction layer has no direct support for UNIX signals. Generic, mainloop implementation agnostic support is available throught \ref mainloop-signal.h. + * */ + +PA_C_DECL_BEGIN + +/** An abstract mainloop API vtable */ +typedef struct pa_mainloop_api pa_mainloop_api; + +/** A bitmask for IO events */ +typedef enum pa_io_event_flags { + PA_IO_EVENT_NULL = 0, /**< No event */ + PA_IO_EVENT_INPUT = 1, /**< Input event */ + PA_IO_EVENT_OUTPUT = 2, /**< Output event */ + PA_IO_EVENT_HANGUP = 4, /**< Hangup event */ + PA_IO_EVENT_ERROR = 8 /**< Error event */ +} pa_io_event_flags_t; + +/** An opaque IO event source object */ +typedef struct pa_io_event pa_io_event; +/** An IO event callback protoype \since 0.9.3 */ +typedef void (*pa_io_event_cb_t)(pa_mainloop_api*ea, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata); +/** A IO event destroy callback prototype \ since 0.9.3 */ +typedef void (*pa_io_event_destroy_cb_t)(pa_mainloop_api*a, pa_io_event *e, void *userdata); + +/** An opaque timer event source object */ +typedef struct pa_time_event pa_time_event; +/** A time event callback prototype \since 0.9.3 */ +typedef void (*pa_time_event_cb_t)(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata); +/** A time event destroy callback prototype \ since 0.9.3 */ +typedef void (*pa_time_event_destroy_cb_t)(pa_mainloop_api*a, pa_time_event *e, void *userdata); + +/** An opaque deferred event source object. Events of this type are triggered once in every main loop iteration */ +typedef struct pa_defer_event pa_defer_event; +/** A defer event callback protoype \since 0.9.3 */ +typedef void (*pa_defer_event_cb_t)(pa_mainloop_api*a, pa_defer_event* e, void *userdata); +/** A defer event destroy callback prototype \ since 0.9.3 */ +typedef void (*pa_defer_event_destroy_cb_t)(pa_mainloop_api*a, pa_defer_event *e, void *userdata); + +/** An abstract mainloop API vtable */ +struct pa_mainloop_api { + /** A pointer to some private, arbitrary data of the main loop implementation */ + void *userdata; + + /** Create a new IO event source object */ + pa_io_event* (*io_new)(pa_mainloop_api*a, int fd, pa_io_event_flags_t events, pa_io_event_cb_t cb, void *userdata); + /** Enable or disable IO events on this object */ + void (*io_enable)(pa_io_event* e, pa_io_event_flags_t events); + /** Free a IO event source object */ + void (*io_free)(pa_io_event* e); + /** Set a function that is called when the IO event source is destroyed. Use this to free the userdata argument if required */ + void (*io_set_destroy)(pa_io_event *e, pa_io_event_destroy_cb_t cb); + + /** Create a new timer event source object for the specified Unix time */ + pa_time_event* (*time_new)(pa_mainloop_api*a, const struct timeval *tv, pa_time_event_cb_t cb, void *userdata); + /** Restart a running or expired timer event source with a new Unix time */ + void (*time_restart)(pa_time_event* e, const struct timeval *tv); + /** Free a deferred timer event source object */ + void (*time_free)(pa_time_event* e); + /** Set a function that is called when the timer event source is destroyed. Use this to free the userdata argument if required */ + void (*time_set_destroy)(pa_time_event *e, pa_time_event_destroy_cb_t cb); + + /** Create a new deferred event source object */ + pa_defer_event* (*defer_new)(pa_mainloop_api*a, pa_defer_event_cb_t cb, void *userdata); + /** Enable or disable a deferred event source temporarily */ + void (*defer_enable)(pa_defer_event* e, int b); + /** Free a deferred event source object */ + void (*defer_free)(pa_defer_event* e); + /** Set a function that is called when the deferred event source is destroyed. Use this to free the userdata argument if required */ + void (*defer_set_destroy)(pa_defer_event *e, pa_defer_event_destroy_cb_t cb); + + /** Exit the main loop and return the specfied retval*/ + void (*quit)(pa_mainloop_api*a, int retval); +}; + +/** Run the specified callback function once from the main loop using an anonymous defer event. */ +void pa_mainloop_api_once(pa_mainloop_api*m, void (*callback)(pa_mainloop_api*m, void *userdata), void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/mainloop-signal.c b/src/pulse/mainloop-signal.c new file mode 100644 index 00000000..e41ed14c --- /dev/null +++ b/src/pulse/mainloop-signal.c @@ -0,0 +1,230 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <signal.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> + +#include "mainloop-signal.h" + +struct pa_signal_event { + int sig; +#ifdef HAVE_SIGACTION + struct sigaction saved_sigaction; +#else + void (*saved_handler)(int sig); +#endif + void (*callback) (pa_mainloop_api*a, pa_signal_event *e, int sig, void *userdata); + void *userdata; + void (*destroy_callback) (pa_mainloop_api*a, pa_signal_event*e, void *userdata); + pa_signal_event *previous, *next; +}; + +static pa_mainloop_api *api = NULL; +static int signal_pipe[2] = { -1, -1 }; +static pa_io_event* io_event = NULL; +static pa_signal_event *signals = NULL; + +static void signal_handler(int sig) { + int saved_errno; + + saved_errno = errno; + +#ifndef HAVE_SIGACTION + signal(sig, signal_handler); +#endif + pa_write(signal_pipe[1], &sig, sizeof(sig), NULL); + + errno = saved_errno; +} + +static void dispatch(pa_mainloop_api*a, int sig) { + pa_signal_event *s; + + for (s = signals; s; s = s->next) + if (s->sig == sig) { + pa_assert(s->callback); + s->callback(a, s, sig, s->userdata); + break; + } +} + +static void callback(pa_mainloop_api*a, pa_io_event*e, int fd, pa_io_event_flags_t f, PA_GCC_UNUSED void *userdata) { + ssize_t r; + int sig; + + pa_assert(a); + pa_assert(e); + pa_assert(f == PA_IO_EVENT_INPUT); + pa_assert(e == io_event); + pa_assert(fd == signal_pipe[0]); + + if ((r = pa_read(signal_pipe[0], &sig, sizeof(sig), NULL)) < 0) { + if (errno == EAGAIN) + return; + + pa_log("read(): %s", pa_cstrerror(errno)); + return; + } + + if (r != sizeof(sig)) { + pa_log("short read()"); + return; + } + + dispatch(a, sig); +} + +int pa_signal_init(pa_mainloop_api *a) { + + pa_assert(a); + pa_assert(!api); + pa_assert(signal_pipe[0] == -1); + pa_assert(signal_pipe[1] == -1); + pa_assert(!io_event); + + if (pipe(signal_pipe) < 0) { + pa_log("pipe(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_make_fd_nonblock(signal_pipe[0]); + pa_make_fd_nonblock(signal_pipe[1]); + pa_make_fd_cloexec(signal_pipe[0]); + pa_make_fd_cloexec(signal_pipe[1]); + + api = a; + + pa_assert_se(io_event = api->io_new(api, signal_pipe[0], PA_IO_EVENT_INPUT, callback, NULL)); + + return 0; +} + +void pa_signal_done(void) { + pa_assert(api); + pa_assert(signal_pipe[0] >= 0); + pa_assert(signal_pipe[1] >= 0); + pa_assert(io_event); + + while (signals) + pa_signal_free(signals); + + api->io_free(io_event); + io_event = NULL; + + pa_close_pipe(signal_pipe); + + api = NULL; +} + +pa_signal_event* pa_signal_new(int sig, void (*_callback) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata), void *userdata) { + pa_signal_event *e = NULL; + +#ifdef HAVE_SIGACTION + struct sigaction sa; +#endif + + pa_assert(sig > 0); + pa_assert(_callback); + + for (e = signals; e; e = e->next) + if (e->sig == sig) + goto fail; + + e = pa_xnew(pa_signal_event, 1); + e->sig = sig; + e->callback = _callback; + e->userdata = userdata; + e->destroy_callback = NULL; + +#ifdef HAVE_SIGACTION + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (sigaction(sig, &sa, &e->saved_sigaction) < 0) +#else + if ((e->saved_handler = signal(sig, signal_handler)) == SIG_ERR) +#endif + goto fail; + + e->previous = NULL; + e->next = signals; + signals = e; + + return e; +fail: + if (e) + pa_xfree(e); + return NULL; +} + +void pa_signal_free(pa_signal_event *e) { + pa_assert(e); + + if (e->next) + e->next->previous = e->previous; + if (e->previous) + e->previous->next = e->next; + else + signals = e->next; + +#ifdef HAVE_SIGACTION + pa_assert_se(sigaction(e->sig, &e->saved_sigaction, NULL) == 0); +#else + pa_assert_se(signal(e->sig, e->saved_handler) == signal_handler); +#endif + + if (e->destroy_callback) + e->destroy_callback(api, e, e->userdata); + + pa_xfree(e); +} + +void pa_signal_set_destroy(pa_signal_event *e, void (*_callback) (pa_mainloop_api *api, pa_signal_event*e, void *userdata)) { + pa_assert(e); + + e->destroy_callback = _callback; +} diff --git a/src/pulse/mainloop-signal.h b/src/pulse/mainloop-signal.h new file mode 100644 index 00000000..50aa99ce --- /dev/null +++ b/src/pulse/mainloop-signal.h @@ -0,0 +1,62 @@ +#ifndef foomainloopsignalhfoo +#define foomainloopsignalhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/cdecl.h> + +PA_C_DECL_BEGIN + +/** \file + * UNIX signal support for main loops. In contrast to other + * main loop event sources such as timer and IO events, UNIX signal + * support requires modification of the global process + * environment. Due to this the generic main loop abstraction layer as + * defined in \ref mainloop-api.h doesn't have direct support for UNIX + * signals. However, you may hook signal support into an abstract main loop via the routines defined herein. + */ + +/** Initialize the UNIX signal subsystem and bind it to the specified main loop */ +int pa_signal_init(pa_mainloop_api *api); + +/** Cleanup the signal subsystem */ +void pa_signal_done(void); + +/** An opaque UNIX signal event source object */ +typedef struct pa_signal_event pa_signal_event; + +/** Create a new UNIX signal event source object */ +pa_signal_event* pa_signal_new(int sig, void (*callback) (pa_mainloop_api *api, pa_signal_event*e, int sig, void *userdata), void *userdata); + +/** Free a UNIX signal event source object */ +void pa_signal_free(pa_signal_event *e); + +/** Set a function that is called when the signal event source is destroyed. Use this to free the userdata argument if required */ +void pa_signal_set_destroy(pa_signal_event *e, void (*callback) (pa_mainloop_api *api, pa_signal_event*e, void *userdata)); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/mainloop.c b/src/pulse/mainloop.c new file mode 100644 index 00000000..ad4e4e97 --- /dev/null +++ b/src/pulse/mainloop.c @@ -0,0 +1,965 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <signal.h> +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <errno.h> + +#ifdef HAVE_POLL_H +#include <poll.h> +#else +#include <pulsecore/poll.h> +#endif + +#ifndef HAVE_PIPE +#include <pulsecore/pipe.h> +#endif + +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/llist.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/winsock.h> +#include <pulsecore/macro.h> + +#include "mainloop.h" + +struct pa_io_event { + pa_mainloop *mainloop; + int dead; + + int fd; + pa_io_event_flags_t events; + struct pollfd *pollfd; + + pa_io_event_cb_t callback; + void *userdata; + pa_io_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_io_event); +}; + +struct pa_time_event { + pa_mainloop *mainloop; + int dead; + + int enabled; + struct timeval timeval; + + pa_time_event_cb_t callback; + void *userdata; + pa_time_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_time_event); +}; + +struct pa_defer_event { + pa_mainloop *mainloop; + int dead; + + int enabled; + + pa_defer_event_cb_t callback; + void *userdata; + pa_defer_event_destroy_cb_t destroy_callback; + + PA_LLIST_FIELDS(pa_defer_event); +}; + +struct pa_mainloop { + PA_LLIST_HEAD(pa_io_event, io_events); + PA_LLIST_HEAD(pa_time_event, time_events); + PA_LLIST_HEAD(pa_defer_event, defer_events); + + int n_enabled_defer_events, n_enabled_time_events, n_io_events; + int io_events_please_scan, time_events_please_scan, defer_events_please_scan; + + struct pollfd *pollfds; + unsigned max_pollfds, n_pollfds; + int rebuild_pollfds; + + int prepared_timeout; + pa_time_event *cached_next_time_event; + + int quit, retval; + pa_mainloop_api api; + + int wakeup_pipe[2]; + int wakeup_pipe_type; + int wakeup_requested; + + enum { + STATE_PASSIVE, + STATE_PREPARED, + STATE_POLLING, + STATE_POLLED, + STATE_QUIT + } state; + + pa_poll_func poll_func; + void *poll_func_userdata; + int poll_func_ret; +}; + +static short map_flags_to_libc(pa_io_event_flags_t flags) { + return + (flags & PA_IO_EVENT_INPUT ? POLLIN : 0) | + (flags & PA_IO_EVENT_OUTPUT ? POLLOUT : 0) | + (flags & PA_IO_EVENT_ERROR ? POLLERR : 0) | + (flags & PA_IO_EVENT_HANGUP ? POLLHUP : 0); +} + +static pa_io_event_flags_t map_flags_from_libc(short flags) { + return + (flags & POLLIN ? PA_IO_EVENT_INPUT : 0) | + (flags & POLLOUT ? PA_IO_EVENT_OUTPUT : 0) | + (flags & POLLERR ? PA_IO_EVENT_ERROR : 0) | + (flags & POLLHUP ? PA_IO_EVENT_HANGUP : 0); +} + +/* IO events */ +static pa_io_event* mainloop_io_new( + pa_mainloop_api*a, + int fd, + pa_io_event_flags_t events, + pa_io_event_cb_t callback, + void *userdata) { + + pa_mainloop *m; + pa_io_event *e; + + pa_assert(a); + pa_assert(a->userdata); + pa_assert(fd >= 0); + pa_assert(callback); + + m = a->userdata; + pa_assert(a == &m->api); + + e = pa_xnew(pa_io_event, 1); + e->mainloop = m; + e->dead = 0; + + e->fd = fd; + e->events = events; + e->pollfd = NULL; + + e->callback = callback; + e->userdata = userdata; + e->destroy_callback = NULL; + +#ifdef OS_IS_WIN32 + { + fd_set xset; + struct timeval tv; + + tv.tv_sec = 0; + tv.tv_usec = 0; + + FD_ZERO (&xset); + FD_SET (fd, &xset); + + if ((select((SELECT_TYPE_ARG1) fd, NULL, NULL, SELECT_TYPE_ARG234 &xset, + SELECT_TYPE_ARG5 &tv) == -1) && + (WSAGetLastError() == WSAENOTSOCK)) { + pa_log_warn("Cannot monitor non-socket file descriptors."); + e->dead = 1; + } + } +#endif + + PA_LLIST_PREPEND(pa_io_event, m->io_events, e); + m->rebuild_pollfds = 1; + m->n_io_events ++; + + pa_mainloop_wakeup(m); + + return e; +} + +static void mainloop_io_enable(pa_io_event *e, pa_io_event_flags_t events) { + pa_assert(e); + pa_assert(!e->dead); + + if (e->events == events) + return; + + e->events = events; + + if (e->pollfd) + e->pollfd->events = map_flags_to_libc(events); + else + e->mainloop->rebuild_pollfds = 1; + + pa_mainloop_wakeup(e->mainloop); +} + +static void mainloop_io_free(pa_io_event *e) { + pa_assert(e); + pa_assert(!e->dead); + + e->dead = 1; + e->mainloop->io_events_please_scan ++; + + e->mainloop->n_io_events --; + e->mainloop->rebuild_pollfds = 1; + + pa_mainloop_wakeup(e->mainloop); +} + +static void mainloop_io_set_destroy(pa_io_event *e, pa_io_event_destroy_cb_t callback) { + pa_assert(e); + + e->destroy_callback = callback; +} + +/* Defer events */ +static pa_defer_event* mainloop_defer_new( + pa_mainloop_api*a, + pa_defer_event_cb_t callback, + void *userdata) { + + pa_mainloop *m; + pa_defer_event *e; + + pa_assert(a); + pa_assert(a->userdata); + pa_assert(callback); + + m = a->userdata; + pa_assert(a == &m->api); + + e = pa_xnew(pa_defer_event, 1); + e->mainloop = m; + e->dead = 0; + + e->enabled = 1; + m->n_enabled_defer_events++; + + e->callback = callback; + e->userdata = userdata; + e->destroy_callback = NULL; + + PA_LLIST_PREPEND(pa_defer_event, m->defer_events, e); + + pa_mainloop_wakeup(e->mainloop); + + return e; +} + +static void mainloop_defer_enable(pa_defer_event *e, int b) { + pa_assert(e); + pa_assert(!e->dead); + + if (e->enabled && !b) { + pa_assert(e->mainloop->n_enabled_defer_events > 0); + e->mainloop->n_enabled_defer_events--; + } else if (!e->enabled && b) { + e->mainloop->n_enabled_defer_events++; + pa_mainloop_wakeup(e->mainloop); + } + + e->enabled = b; +} + +static void mainloop_defer_free(pa_defer_event *e) { + pa_assert(e); + pa_assert(!e->dead); + + e->dead = 1; + e->mainloop->defer_events_please_scan ++; + + if (e->enabled) { + pa_assert(e->mainloop->n_enabled_defer_events > 0); + e->mainloop->n_enabled_defer_events--; + e->enabled = 0; + } +} + +static void mainloop_defer_set_destroy(pa_defer_event *e, pa_defer_event_destroy_cb_t callback) { + pa_assert(e); + pa_assert(!e->dead); + + e->destroy_callback = callback; +} + +/* Time events */ +static pa_time_event* mainloop_time_new( + pa_mainloop_api*a, + const struct timeval *tv, + pa_time_event_cb_t callback, + void *userdata) { + + pa_mainloop *m; + pa_time_event *e; + + pa_assert(a); + pa_assert(a->userdata); + pa_assert(callback); + + m = a->userdata; + pa_assert(a == &m->api); + + e = pa_xnew(pa_time_event, 1); + e->mainloop = m; + e->dead = 0; + + if ((e->enabled = !!tv)) { + e->timeval = *tv; + + m->n_enabled_time_events++; + + if (m->cached_next_time_event) { + pa_assert(m->cached_next_time_event->enabled); + + if (pa_timeval_cmp(tv, &m->cached_next_time_event->timeval) < 0) + m->cached_next_time_event = e; + } + } + + e->callback = callback; + e->userdata = userdata; + e->destroy_callback = NULL; + + PA_LLIST_PREPEND(pa_time_event, m->time_events, e); + + if (e->enabled) + pa_mainloop_wakeup(m); + + return e; +} + +static void mainloop_time_restart(pa_time_event *e, const struct timeval *tv) { + pa_assert(e); + pa_assert(!e->dead); + + if (e->enabled && !tv) { + pa_assert(e->mainloop->n_enabled_time_events > 0); + e->mainloop->n_enabled_time_events--; + } else if (!e->enabled && tv) + e->mainloop->n_enabled_time_events++; + + if ((e->enabled = !!tv)) { + e->timeval = *tv; + pa_mainloop_wakeup(e->mainloop); + } + + if (e->mainloop->cached_next_time_event && e->enabled) { + pa_assert(e->mainloop->cached_next_time_event->enabled); + + if (pa_timeval_cmp(tv, &e->mainloop->cached_next_time_event->timeval) < 0) + e->mainloop->cached_next_time_event = e; + } else if (e->mainloop->cached_next_time_event == e) + e->mainloop->cached_next_time_event = NULL; +} + +static void mainloop_time_free(pa_time_event *e) { + pa_assert(e); + pa_assert(!e->dead); + + e->dead = 1; + e->mainloop->time_events_please_scan ++; + + if (e->enabled) { + pa_assert(e->mainloop->n_enabled_time_events > 0); + e->mainloop->n_enabled_time_events--; + e->enabled = 0; + } + + if (e->mainloop->cached_next_time_event == e) + e->mainloop->cached_next_time_event = NULL; + + /* no wakeup needed here. Think about it! */ +} + +static void mainloop_time_set_destroy(pa_time_event *e, pa_time_event_destroy_cb_t callback) { + pa_assert(e); + pa_assert(!e->dead); + + e->destroy_callback = callback; +} + +/* quit() */ + +static void mainloop_quit(pa_mainloop_api*a, int retval) { + pa_mainloop *m; + + pa_assert(a); + pa_assert(a->userdata); + m = a->userdata; + pa_assert(a == &m->api); + + pa_mainloop_quit(m, retval); +} + +static const pa_mainloop_api vtable = { + .userdata = NULL, + + .io_new= mainloop_io_new, + .io_enable= mainloop_io_enable, + .io_free= mainloop_io_free, + .io_set_destroy= mainloop_io_set_destroy, + + .time_new = mainloop_time_new, + .time_restart = mainloop_time_restart, + .time_free = mainloop_time_free, + .time_set_destroy = mainloop_time_set_destroy, + + .defer_new = mainloop_defer_new, + .defer_enable = mainloop_defer_enable, + .defer_free = mainloop_defer_free, + .defer_set_destroy = mainloop_defer_set_destroy, + + .quit = mainloop_quit, +}; + +pa_mainloop *pa_mainloop_new(void) { + pa_mainloop *m; + + m = pa_xnew(pa_mainloop, 1); + + m->wakeup_pipe_type = 0; + if (pipe(m->wakeup_pipe) < 0) { + pa_log_error("ERROR: cannot create wakeup pipe"); + pa_xfree(m); + return NULL; + } + + pa_make_fd_nonblock(m->wakeup_pipe[0]); + pa_make_fd_nonblock(m->wakeup_pipe[1]); + pa_make_fd_cloexec(m->wakeup_pipe[0]); + pa_make_fd_cloexec(m->wakeup_pipe[1]); + m->wakeup_requested = 0; + + PA_LLIST_HEAD_INIT(pa_io_event, m->io_events); + PA_LLIST_HEAD_INIT(pa_time_event, m->time_events); + PA_LLIST_HEAD_INIT(pa_defer_event, m->defer_events); + + m->n_enabled_defer_events = m->n_enabled_time_events = m->n_io_events = 0; + m->io_events_please_scan = m->time_events_please_scan = m->defer_events_please_scan = 0; + + m->cached_next_time_event = NULL; + m->prepared_timeout = 0; + + m->pollfds = NULL; + m->max_pollfds = m->n_pollfds = 0; + m->rebuild_pollfds = 1; + + m->quit = m->retval = 0; + + m->api = vtable; + m->api.userdata = m; + + m->state = STATE_PASSIVE; + + m->poll_func = NULL; + m->poll_func_userdata = NULL; + m->poll_func_ret = -1; + + return m; +} + +static void cleanup_io_events(pa_mainloop *m, int force) { + pa_io_event *e; + + e = m->io_events; + while (e) { + pa_io_event *n = e->next; + + if (!force && m->io_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_io_event, m->io_events, e); + + if (e->dead) { + pa_assert(m->io_events_please_scan > 0); + m->io_events_please_scan--; + } + + if (e->destroy_callback) + e->destroy_callback(&m->api, e, e->userdata); + + pa_xfree(e); + + m->rebuild_pollfds = 1; + } + + e = n; + } + + pa_assert(m->io_events_please_scan == 0); +} + +static void cleanup_time_events(pa_mainloop *m, int force) { + pa_time_event *e; + + e = m->time_events; + while (e) { + pa_time_event *n = e->next; + + if (!force && m->time_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_time_event, m->time_events, e); + + if (e->dead) { + pa_assert(m->time_events_please_scan > 0); + m->time_events_please_scan--; + } + + if (!e->dead && e->enabled) { + pa_assert(m->n_enabled_time_events > 0); + m->n_enabled_time_events--; + e->enabled = 0; + } + + if (e->destroy_callback) + e->destroy_callback(&m->api, e, e->userdata); + + pa_xfree(e); + } + + e = n; + } + + pa_assert(m->time_events_please_scan == 0); +} + +static void cleanup_defer_events(pa_mainloop *m, int force) { + pa_defer_event *e; + + e = m->defer_events; + while (e) { + pa_defer_event *n = e->next; + + if (!force && m->defer_events_please_scan <= 0) + break; + + if (force || e->dead) { + PA_LLIST_REMOVE(pa_defer_event, m->defer_events, e); + + if (e->dead) { + pa_assert(m->defer_events_please_scan > 0); + m->defer_events_please_scan--; + } + + if (!e->dead && e->enabled) { + pa_assert(m->n_enabled_defer_events > 0); + m->n_enabled_defer_events--; + e->enabled = 0; + } + + if (e->destroy_callback) + e->destroy_callback(&m->api, e, e->userdata); + + pa_xfree(e); + } + + e = n; + } + + pa_assert(m->defer_events_please_scan == 0); +} + + +void pa_mainloop_free(pa_mainloop* m) { + pa_assert(m); + + cleanup_io_events(m, 1); + cleanup_defer_events(m, 1); + cleanup_time_events(m, 1); + + pa_xfree(m->pollfds); + + pa_close_pipe(m->wakeup_pipe); + + pa_xfree(m); +} + +static void scan_dead(pa_mainloop *m) { + pa_assert(m); + + if (m->io_events_please_scan) + cleanup_io_events(m, 0); + + if (m->time_events_please_scan) + cleanup_time_events(m, 0); + + if (m->defer_events_please_scan) + cleanup_defer_events(m, 0); +} + +static void rebuild_pollfds(pa_mainloop *m) { + pa_io_event*e; + struct pollfd *p; + unsigned l; + + l = m->n_io_events + 1; + if (m->max_pollfds < l) { + l *= 2; + m->pollfds = pa_xrealloc(m->pollfds, sizeof(struct pollfd)*l); + m->max_pollfds = l; + } + + m->n_pollfds = 0; + p = m->pollfds; + + if (m->wakeup_pipe[0] >= 0) { + m->pollfds[0].fd = m->wakeup_pipe[0]; + m->pollfds[0].events = POLLIN; + m->pollfds[0].revents = 0; + p++; + m->n_pollfds++; + } + + for (e = m->io_events; e; e = e->next) { + if (e->dead) { + e->pollfd = NULL; + continue; + } + + e->pollfd = p; + p->fd = e->fd; + p->events = map_flags_to_libc(e->events); + p->revents = 0; + + p++; + m->n_pollfds++; + } + + m->rebuild_pollfds = 0; +} + +static int dispatch_pollfds(pa_mainloop *m) { + pa_io_event *e; + int r = 0, k; + + pa_assert(m->poll_func_ret > 0); + + for (e = m->io_events, k = m->poll_func_ret; e && !m->quit && k > 0; e = e->next) { + if (e->dead || !e->pollfd || !e->pollfd->revents) + continue; + + pa_assert(e->pollfd->fd == e->fd); + pa_assert(e->callback); + e->callback(&m->api, e, e->fd, map_flags_from_libc(e->pollfd->revents), e->userdata); + e->pollfd->revents = 0; + r++; + + k--; + } + + return r; +} + +static int dispatch_defer(pa_mainloop *m) { + pa_defer_event *e; + int r = 0; + + if (m->n_enabled_defer_events <= 0) + return 0; + + for (e = m->defer_events; e && !m->quit; e = e->next) { + if (e->dead || !e->enabled) + continue; + + pa_assert(e->callback); + e->callback(&m->api, e, e->userdata); + r++; + } + + return r; +} + +static pa_time_event* find_next_time_event(pa_mainloop *m) { + pa_time_event *t, *n = NULL; + pa_assert(m); + + if (m->cached_next_time_event) + return m->cached_next_time_event; + + for (t = m->time_events; t; t = t->next) { + + if (t->dead || !t->enabled) + continue; + + if (!n || pa_timeval_cmp(&t->timeval, &n->timeval) < 0) { + n = t; + + /* Shortcut for tv = { 0, 0 } */ + if (n->timeval.tv_sec <= 0) + break; + } + } + + m->cached_next_time_event = n; + return n; +} + +static int calc_next_timeout(pa_mainloop *m) { + pa_time_event *t; + struct timeval now; + pa_usec_t usec; + + if (!m->n_enabled_time_events) + return -1; + + t = find_next_time_event(m); + pa_assert(t); + + if (t->timeval.tv_sec <= 0) + return 0; + + pa_gettimeofday(&now); + + if (pa_timeval_cmp(&t->timeval, &now) <= 0) + return 0; + + usec = pa_timeval_diff(&t->timeval, &now); + return (int) (usec / 1000); +} + +static int dispatch_timeout(pa_mainloop *m) { + pa_time_event *e; + struct timeval now; + int r = 0; + pa_assert(m); + + if (m->n_enabled_time_events <= 0) + return 0; + + pa_gettimeofday(&now); + + for (e = m->time_events; e && !m->quit; e = e->next) { + + if (e->dead || !e->enabled) + continue; + + if (pa_timeval_cmp(&e->timeval, &now) <= 0) { + pa_assert(e->callback); + + /* Disable time event */ + mainloop_time_restart(e, NULL); + + e->callback(&m->api, e, &e->timeval, e->userdata); + + r++; + } + } + + return r; +} + +void pa_mainloop_wakeup(pa_mainloop *m) { + char c = 'W'; + pa_assert(m); + + if (m->wakeup_pipe[1] >= 0 && m->state == STATE_POLLING) { + pa_write(m->wakeup_pipe[1], &c, sizeof(c), &m->wakeup_pipe_type); + m->wakeup_requested++; + } +} + +static void clear_wakeup(pa_mainloop *m) { + char c[10]; + + pa_assert(m); + + if (m->wakeup_pipe[0] < 0) + return; + + if (m->wakeup_requested) { + while (pa_read(m->wakeup_pipe[0], &c, sizeof(c), &m->wakeup_pipe_type) == sizeof(c)); + m->wakeup_requested = 0; + } +} + +int pa_mainloop_prepare(pa_mainloop *m, int timeout) { + pa_assert(m); + pa_assert(m->state == STATE_PASSIVE); + + clear_wakeup(m); + scan_dead(m); + + if (m->quit) + goto quit; + + if (m->n_enabled_defer_events <= 0) { + if (m->rebuild_pollfds) + rebuild_pollfds(m); + + m->prepared_timeout = calc_next_timeout(m); + if (timeout >= 0 && (timeout < m->prepared_timeout || m->prepared_timeout < 0)) + m->prepared_timeout = timeout; + } + + m->state = STATE_PREPARED; + return 0; + +quit: + m->state = STATE_QUIT; + return -2; +} + +int pa_mainloop_poll(pa_mainloop *m) { + pa_assert(m); + pa_assert(m->state == STATE_PREPARED); + + if (m->quit) + goto quit; + + m->state = STATE_POLLING; + + if (m->n_enabled_defer_events ) + m->poll_func_ret = 0; + else { + pa_assert(!m->rebuild_pollfds); + + if (m->poll_func) + m->poll_func_ret = m->poll_func(m->pollfds, m->n_pollfds, m->prepared_timeout, m->poll_func_userdata); + else + m->poll_func_ret = poll(m->pollfds, m->n_pollfds, m->prepared_timeout); + + if (m->poll_func_ret < 0) { + if (errno == EINTR) + m->poll_func_ret = 0; + else + pa_log("poll(): %s", pa_cstrerror(errno)); + } + } + + m->state = m->poll_func_ret < 0 ? STATE_PASSIVE : STATE_POLLED; + return m->poll_func_ret; + +quit: + m->state = STATE_QUIT; + return -2; +} + +int pa_mainloop_dispatch(pa_mainloop *m) { + int dispatched = 0; + + pa_assert(m); + pa_assert(m->state == STATE_POLLED); + + if (m->quit) + goto quit; + + if (m->n_enabled_defer_events) + dispatched += dispatch_defer(m); + else { + if (m->n_enabled_time_events) + dispatched += dispatch_timeout(m); + + if (m->quit) + goto quit; + + if (m->poll_func_ret > 0) + dispatched += dispatch_pollfds(m); + } + + if (m->quit) + goto quit; + + m->state = STATE_PASSIVE; + + return dispatched; + +quit: + m->state = STATE_QUIT; + return -2; +} + +int pa_mainloop_get_retval(pa_mainloop *m) { + pa_assert(m); + return m->retval; +} + +int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval) { + int r; + pa_assert(m); + + if ((r = pa_mainloop_prepare(m, block ? -1 : 0)) < 0) + goto quit; + + if ((r = pa_mainloop_poll(m)) < 0) + goto quit; + + if ((r = pa_mainloop_dispatch(m)) < 0) + goto quit; + + return r; + +quit: + + if ((r == -2) && retval) + *retval = pa_mainloop_get_retval(m); + return r; +} + +int pa_mainloop_run(pa_mainloop *m, int *retval) { + int r; + + while ((r = pa_mainloop_iterate(m, 1, retval)) >= 0); + + if (r == -2) + return 1; + else if (r < 0) + return -1; + else + return 0; +} + +void pa_mainloop_quit(pa_mainloop *m, int retval) { + pa_assert(m); + + m->quit = 1; + m->retval = retval; + pa_mainloop_wakeup(m); +} + +pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m) { + pa_assert(m); + return &m->api; +} + +void pa_mainloop_set_poll_func(pa_mainloop *m, pa_poll_func poll_func, void *userdata) { + pa_assert(m); + + m->poll_func = poll_func; + m->poll_func_userdata = userdata; +} diff --git a/src/pulse/mainloop.h b/src/pulse/mainloop.h new file mode 100644 index 00000000..db2797fb --- /dev/null +++ b/src/pulse/mainloop.h @@ -0,0 +1,130 @@ +#ifndef foomainloophfoo +#define foomainloophfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/cdecl.h> + +PA_C_DECL_BEGIN + +struct pollfd; + +/** \page mainloop Main Loop + * + * \section overv_sec Overview + * + * The built-in main loop implementation is based on the poll() system call. + * It supports the functions defined in the main loop abstraction and very + * little else. + * + * The main loop is created using pa_mainloop_new() and destroyed using + * pa_mainloop_free(). To get access to the main loop abstraction, + * pa_mainloop_get_api() is used. + * + * \section iter_sec Iteration + * + * The main loop is designed around the concept of iterations. Each iteration + * consists of three steps that repeat during the application's entire + * lifetime: + * + * -# Prepare - Build a list of file descriptors + * that need to be monitored and calculate the next timeout. + * -# Poll - Execute the actuall poll() system call. + * -# Dispatch - Dispatch any events that have fired. + * + * When using the main loop, the application can either execute each + * iteration, one at a time, using pa_mainloop_iterate(), or let the library + * iterate automatically using pa_mainloop_run(). + * + * \section thread_sec Threads + * + * The main loop functions are designed to be thread safe, but the objects + * are not. What this means is that multiple main loops can be used, but only + * one object per thread. + * + */ + +/** \file + * + * A minimal main loop implementation based on the C library's poll() + * function. Using the routines defined herein you may create a simple + * main loop supporting the generic main loop abstraction layer as + * defined in \ref mainloop-api.h. This implementation is thread safe + * as long as you access the main loop object from a single thread only.*/ + +/** An opaque main loop object */ +typedef struct pa_mainloop pa_mainloop; + +/** Allocate a new main loop object */ +pa_mainloop *pa_mainloop_new(void); + +/** Free a main loop object */ +void pa_mainloop_free(pa_mainloop* m); + +/** Prepare for a single iteration of the main loop. Returns a negative value +on error or exit request. timeout specifies a maximum timeout for the subsequent +poll, or -1 for blocking behaviour. .*/ +int pa_mainloop_prepare(pa_mainloop *m, int timeout); + +/** Execute the previously prepared poll. Returns a negative value on error.*/ +int pa_mainloop_poll(pa_mainloop *m); + +/** Dispatch timeout, io and deferred events from the previously executed poll. Returns +a negative value on error. On success returns the number of source dispatched. */ +int pa_mainloop_dispatch(pa_mainloop *m); + +/** Return the return value as specified with the main loop's quit() routine. */ +int pa_mainloop_get_retval(pa_mainloop *m); + +/** Run a single iteration of the main loop. This is a convenience function +for pa_mainloop_prepare(), pa_mainloop_poll() and pa_mainloop_dispatch(). +Returns a negative value on error or exit request. If block is nonzero, +block for events if none are queued. Optionally return the return value as +specified with the main loop's quit() routine in the integer variable retval points +to. On success returns the number of sources dispatched in this iteration. */ +int pa_mainloop_iterate(pa_mainloop *m, int block, int *retval); + +/** Run unlimited iterations of the main loop object until the main loop's quit() routine is called. */ +int pa_mainloop_run(pa_mainloop *m, int *retval); + +/** Return the abstract main loop abstraction layer vtable for this main loop. */ +pa_mainloop_api* pa_mainloop_get_api(pa_mainloop*m); + +/** Shutdown the main loop */ +void pa_mainloop_quit(pa_mainloop *m, int r); + +/** Interrupt a running poll (for threaded systems) */ +void pa_mainloop_wakeup(pa_mainloop *m); + +/** Generic prototype of a poll() like function */ +typedef int (*pa_poll_func)(struct pollfd *ufds, unsigned long nfds, int timeout, void*userdata); + +/** Change the poll() implementation */ +void pa_mainloop_set_poll_func(pa_mainloop *m, pa_poll_func poll_func, void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/operation.c b/src/pulse/operation.c new file mode 100644 index 00000000..5d2da5b8 --- /dev/null +++ b/src/pulse/operation.c @@ -0,0 +1,128 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> + +#include "internal.h" +#include "operation.h" + +PA_STATIC_FLIST_DECLARE(operations, 0, pa_xfree); + +pa_operation *pa_operation_new(pa_context *c, pa_stream *s, pa_operation_cb_t cb, void *userdata) { + pa_operation *o; + pa_assert(c); + + if (!(o = pa_flist_pop(PA_STATIC_FLIST_GET(operations)))) + o = pa_xnew(pa_operation, 1); + + PA_REFCNT_INIT(o); + o->context = c; + o->stream = s; + o->private = NULL; + + o->state = PA_OPERATION_RUNNING; + o->callback = cb; + o->userdata = userdata; + + /* Refcounting is strictly one-way: from the "bigger" to the "smaller" object. */ + PA_LLIST_PREPEND(pa_operation, c->operations, o); + pa_operation_ref(o); + + return o; +} + +pa_operation *pa_operation_ref(pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + PA_REFCNT_INC(o); + return o; +} +void pa_operation_unref(pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (PA_REFCNT_DEC(o) <= 0) { + pa_assert(!o->context); + pa_assert(!o->stream); + + if (pa_flist_push(PA_STATIC_FLIST_GET(operations), o) < 0) + pa_xfree(o); + } +} + +static void operation_set_state(pa_operation *o, pa_operation_state_t st) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (st == o->state) + return; + + pa_operation_ref(o); + + o->state = st; + + if ((o->state == PA_OPERATION_DONE) || (o->state == PA_OPERATION_CANCELED)) { + + if (o->context) { + pa_assert(PA_REFCNT_VALUE(o) >= 2); + + PA_LLIST_REMOVE(pa_operation, o->context->operations, o); + pa_operation_unref(o); + } + + o->context = NULL; + o->stream = NULL; + o->callback = NULL; + o->userdata = NULL; + } + + pa_operation_unref(o); +} + +void pa_operation_cancel(pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + operation_set_state(o, PA_OPERATION_CANCELED); +} + +void pa_operation_done(pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + operation_set_state(o, PA_OPERATION_DONE); +} + +pa_operation_state_t pa_operation_get_state(pa_operation *o) { + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + return o->state; +} diff --git a/src/pulse/operation.h b/src/pulse/operation.h new file mode 100644 index 00000000..97d1c6b8 --- /dev/null +++ b/src/pulse/operation.h @@ -0,0 +1,52 @@ +#ifndef foooperationhfoo +#define foooperationhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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/cdecl.h> +#include <pulse/def.h> + +/** \file + * Asynchronous operations */ + +PA_C_DECL_BEGIN + +/** An asynchronous operation object */ +typedef struct pa_operation pa_operation; + +/** Increase the reference count by one */ +pa_operation *pa_operation_ref(pa_operation *o); + +/** Decrease the reference count by one */ +void pa_operation_unref(pa_operation *o); + +/** Cancel the operation. Beware! This will not necessarily cancel the execution of the operation on the server side. */ +void pa_operation_cancel(pa_operation *o); + +/** Return the current status of the operation */ +pa_operation_state_t pa_operation_get_state(pa_operation *o); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/proplist.c b/src/pulse/proplist.c new file mode 100644 index 00000000..c27c9d84 --- /dev/null +++ b/src/pulse/proplist.c @@ -0,0 +1,257 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <pulse/utf8.h> + +#include <pulsecore/hashmap.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/core-util.h> + +#include "proplist.h" + +struct property { + char *key; + void *value; + size_t nbytes; +}; + +#define MAKE_HASHMAP(p) ((pa_hashmap*) (p)) +#define MAKE_PROPLIST(p) ((pa_proplist*) (p)) + +static pa_bool_t property_name_valid(const char *key) { + + if (!pa_utf8_valid(key)) + return FALSE; + + if (strlen(key) <= 0) + return FALSE; + + return TRUE; +} + +static void property_free(struct property *prop) { + pa_assert(prop); + + pa_xfree(prop->key); + pa_xfree(prop->value); + pa_xfree(prop); +} + +pa_proplist* pa_proplist_new(void) { + return MAKE_PROPLIST(pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func)); +} + +void pa_proplist_free(pa_proplist* p) { + struct property *prop; + + while ((prop = pa_hashmap_steal_first(MAKE_HASHMAP(p)))) + property_free(prop); + + pa_hashmap_free(MAKE_HASHMAP(p), NULL, NULL); +} + +/** Will accept only valid UTF-8 */ +int pa_proplist_puts(pa_proplist *p, const char *key, const char *value) { + struct property *prop; + pa_bool_t add = FALSE; + + pa_assert(p); + pa_assert(key); + + if (!property_name_valid(key) || !pa_utf8_valid(value)) + return -1; + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP(p), key))) { + prop = pa_xnew(struct property, 1); + prop->key = pa_xstrdup(key); + add = TRUE; + } else + pa_xfree(prop->value); + + prop->value = pa_xstrdup(value); + prop->nbytes = strlen(value)+1; + + if (add) + pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop); + + return 0; +} + +int pa_proplist_put(pa_proplist *p, const char *key, const void *data, size_t nbytes) { + struct property *prop; + pa_bool_t add = FALSE; + + pa_assert(p); + pa_assert(key); + + if (!property_name_valid(key)) + return -1; + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP(p), key))) { + prop = pa_xnew(struct property, 1); + prop->key = pa_xstrdup(key); + add = TRUE; + } else + pa_xfree(prop->value); + + prop->value = pa_xmemdup(data, nbytes); + prop->nbytes = nbytes; + + if (add) + pa_hashmap_put(MAKE_HASHMAP(p), prop->key, prop); + + return 0; +} + +const char *pa_proplist_gets(pa_proplist *p, const char *key) { + struct property *prop; + + pa_assert(p); + pa_assert(key); + + if (!property_name_valid(key)) + return NULL; + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP(p), key))) + return NULL; + + if (prop->nbytes <= 0) + return NULL; + + if (((char*) prop->value)[prop->nbytes-1] != 0) + return NULL; + + if (strlen((char*) prop->value) != prop->nbytes-1) + return NULL; + + if (!pa_utf8_valid((char*) prop->value)) + return NULL; + + return (char*) prop->value; +} + +int pa_proplist_get(pa_proplist *p, const char *key, const void **data, size_t *nbytes) { + struct property *prop; + + pa_assert(p); + pa_assert(key); + + if (!property_name_valid(key)) + return -1; + + if (!(prop = pa_hashmap_get(MAKE_HASHMAP(p), key))) + return -1; + + *data = prop->value; + *nbytes = prop->nbytes; + + return 0; +} + +void pa_proplist_merge(pa_proplist *p, pa_proplist *other) { + struct property *prop; + void *state = NULL; + + pa_assert(p); + pa_assert(other); + + while ((prop = pa_hashmap_iterate(MAKE_HASHMAP(other), &state, NULL))) + pa_assert_se(pa_proplist_put(p, prop->key, prop->value, prop->nbytes) == 0); +} + +int pa_proplist_remove(pa_proplist *p, const char *key) { + struct property *prop; + + pa_assert(p); + pa_assert(key); + + if (!property_name_valid(key)) + return -1; + + if (!(prop = pa_hashmap_remove(MAKE_HASHMAP(p), key))) + return -1; + + property_free(prop); + return 0; +} + +const char *pa_proplist_iterate(pa_proplist *p, void **state) { + struct property *prop; + + if (!(prop = pa_hashmap_iterate(MAKE_HASHMAP(p), state, NULL))) + return NULL; + + return prop->key; +} + +char *pa_proplist_to_string(pa_proplist *p) { + const char *key; + void *state = NULL; + pa_strbuf *buf; + + pa_assert(p); + + buf = pa_strbuf_new(); + + while ((key = pa_proplist_iterate(p, &state))) { + + const char *v; + + if ((v = pa_proplist_gets(p, key))) + pa_strbuf_printf(buf, "%s = \"%s\"\n", key, v); + else { + const void *value; + size_t nbytes; + char *c; + + pa_assert_se(pa_proplist_get(p, key, &value, &nbytes) == 0); + c = pa_xnew(char, nbytes*2+1); + pa_hexstr((const uint8_t*) value, nbytes, c, nbytes*2+1); + + pa_strbuf_printf(buf, "%s = hex:%s\n", key, c); + pa_xfree(c); + } + } + + return pa_strbuf_tostring_free(buf); +} + +int pa_proplist_contains(pa_proplist *p, const char *key) { + pa_assert(p); + pa_assert(key); + + if (!property_name_valid(key)) + return -1; + + if (!(pa_hashmap_get(MAKE_HASHMAP(p), key))) + return 0; + + return 1; +} diff --git a/src/pulse/proplist.h b/src/pulse/proplist.h new file mode 100644 index 00000000..c4cf9ac9 --- /dev/null +++ b/src/pulse/proplist.h @@ -0,0 +1,90 @@ +#ifndef foopulseproplisthfoo +#define foopulseproplisthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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> + +/* Defined properties: + * + * x11.xid + * x11.display + * x11.x_pointer + * x11.y_pointer + * x11.button + * media.name + * media.title + * media.artist + * media.language + * media.filename + * media.icon + * media.icon_name + * media.role video, music, game, event, phone, production + * application.name + * application.version + * application.icon + * application.icon_name + */ + +#define PA_PROP_X11_XID "x11.xid" +#define PA_PROP_X11_DISPLAY "x11.display" +#define PA_PROP_X11_X_POINTER "x11.x_pointer" +#define PA_PROP_X11_Y_POINTER "x11.y_pointer" +#define PA_PROP_X11_BUTTON "x11.button" +#define PA_PROP_MEDIA_NAME "media.name" +#define PA_PROP_MEDIA_TITLE "media.title" +#define PA_PROP_MEDIA_ARTIST "media.artist" +#define PA_PROP_MEDIA_LANGUAGE "media.language" +#define PA_PROP_MEDIA_FILENAME "media.filename" +#define PA_PROP_MEDIA_ICON "media.icon" +#define PA_PROP_MEDIA_ICON_NAME "media.icon_name" +#define PA_PROP_MEDIA_ROLE "media.role" +#define PA_PROP_APPLICATION_NAME "application.name" +#define PA_PROP_APPLICATION_VERSION "application.version" +#define PA_PROP_APPLICATION_ICON "application.icon" +#define PA_PROP_APPLICATION_ICON_NAME "application.icon_name" + +typedef struct pa_proplist pa_proplist; + +pa_proplist* pa_proplist_new(void); +void pa_proplist_free(pa_proplist* p); + +/** Will accept only valid UTF-8 */ +int pa_proplist_puts(pa_proplist *p, const char *key, const char *value); +int pa_proplist_put(pa_proplist *p, const char *key, const void *data, size_t nbytes); + +/* Will return NULL if the data is not valid UTF-8 */ +const char *pa_proplist_gets(pa_proplist *p, const char *key); +int pa_proplist_get(pa_proplist *p, const char *key, const void **data, size_t *nbytes); + +void pa_proplist_merge(pa_proplist *p, pa_proplist *other); +int pa_proplist_remove(pa_proplist *p, const char *key); + +const char *pa_proplist_iterate(pa_proplist *p, void **state); + +char *pa_proplist_to_string(pa_proplist *p); + +int pa_proplist_contains(pa_proplist *p, const char *key); + +#endif diff --git a/src/pulse/pulseaudio.h b/src/pulse/pulseaudio.h new file mode 100644 index 00000000..88d1275b --- /dev/null +++ b/src/pulse/pulseaudio.h @@ -0,0 +1,117 @@ +#ifndef foopulseaudiohfoo +#define foopulseaudiohfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/mainloop-api.h> +#include <pulse/sample.h> +#include <pulse/def.h> +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/introspect.h> +#include <pulse/subscribe.h> +#include <pulse/scache.h> +#include <pulse/version.h> +#include <pulse/error.h> +#include <pulse/operation.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> +#include <pulse/xmalloc.h> +#include <pulse/utf8.h> +#include <pulse/thread-mainloop.h> +#include <pulse/mainloop.h> +#include <pulse/mainloop-signal.h> +#include <pulse/util.h> +#include <pulse/timeval.h> + +/** \file + * Include all libpulse header files at once. The following + * files are included: \ref mainloop-api.h, \ref sample.h, \ref def.h, + * \ref context.h, \ref stream.h, \ref introspect.h, \ref subscribe.h, + * \ref scache.h, \ref version.h, \ref error.h, \ref channelmap.h, + * \ref operation.h,\ref volume.h, \ref xmalloc.h, \ref utf8.h, \ref + * thread-mainloop.h, \ref mainloop.h, \ref util.h, \ref timeval.h and + * \ref mainloop-signal.h at once */ + +/** \mainpage + * + * \section intro_sec Introduction + * + * This document describes the client API for the PulseAudio sound + * server. The API comes in two flavours to accomodate different styles + * of applications and different needs in complexity: + * + * \li The complete but somewhat complicated to use asynchronous API + * \li The simplified, easy to use, but limited synchronous API + * + * All strings in PulseAudio are in the UTF-8 encoding, regardless of current + * locale. Some functions will filter invalid sequences from the string, some + * will simply fail. To ensure reliable behaviour, make sure everything you + * pass to the API is already in UTF-8. + + * \section simple_sec Simple API + * + * Use this if you develop your program in synchronous style and just + * need a way to play or record data on the sound server. See + * \subpage simple for more details. + * + * \section async_sec Asynchronous API + * + * Use this if you develop your programs in asynchronous, event loop + * based style or if you want to use the advanced features of the + * PulseAudio API. A guide can be found in \subpage async. + * + * By using the built-in threaded main loop, it is possible to acheive a + * pseudo-synchronous API, which can be useful in synchronous applications + * where the simple API is insufficient. See the \ref async page for + * details. + * + * \section thread_sec Threads + * + * The PulseAudio client libraries are not designed to be used in a + * heavily threaded environment. They are however designed to be reentrant + * safe. + * + * To use a the libraries in a threaded environment, you must assure that + * all objects are only used in one thread at a time. Normally, this means + * that all objects belonging to a single context must be accessed from the + * same thread. + * + * The included main loop implementation is also not thread safe. Take care + * to make sure event lists are not manipulated when any other code is + * using the main loop. + * + * \section pkgconfig pkg-config + * + * The PulseAudio libraries provide pkg-config snippets for the different + * modules: + * + * \li libpulse - The asynchronous API and the internal main loop implementation. + * \li libpulse-mainloop-glib12 - GLIB 1.2 main loop bindings. + * \li libpulse-mainloop-glib - GLIB 2.x main loop bindings. + * \li libpulse-simple - The simple PulseAudio API. + */ + +#endif diff --git a/src/pulse/sample.c b/src/pulse/sample.c new file mode 100644 index 00000000..27c0df03 --- /dev/null +++ b/src/pulse/sample.c @@ -0,0 +1,185 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <math.h> +#include <string.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "sample.h" + +size_t pa_sample_size(const pa_sample_spec *spec) { + + static const size_t table[] = { + [PA_SAMPLE_U8] = 1, + [PA_SAMPLE_ULAW] = 1, + [PA_SAMPLE_ALAW] = 1, + [PA_SAMPLE_S16LE] = 2, + [PA_SAMPLE_S16BE] = 2, + [PA_SAMPLE_FLOAT32LE] = 4, + [PA_SAMPLE_FLOAT32BE] = 4, + [PA_SAMPLE_S32LE] = 4, + [PA_SAMPLE_S32BE] = 4, + }; + + pa_assert(spec); + pa_assert(spec->format >= 0); + pa_assert(spec->format < PA_SAMPLE_MAX); + + return table[spec->format]; +} + +size_t pa_frame_size(const pa_sample_spec *spec) { + pa_assert(spec); + + return pa_sample_size(spec) * spec->channels; +} + +size_t pa_bytes_per_second(const pa_sample_spec *spec) { + pa_assert(spec); + return spec->rate*pa_frame_size(spec); +} + +pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec) { + pa_assert(spec); + + return (pa_usec_t) (((double) length/pa_frame_size(spec)*1000000)/spec->rate); +} + +size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) { + pa_assert(spec); + + return (size_t) (((double) t * spec->rate / 1000000))*pa_frame_size(spec); +} + +int pa_sample_spec_valid(const pa_sample_spec *spec) { + pa_assert(spec); + + if (spec->rate <= 0 || + spec->rate > PA_RATE_MAX || + spec->channels <= 0 || + spec->channels > PA_CHANNELS_MAX || + spec->format >= PA_SAMPLE_MAX || + spec->format < 0) + return 0; + + return 1; +} + +int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) { + pa_assert(a); + pa_assert(b); + + return (a->format == b->format) && (a->rate == b->rate) && (a->channels == b->channels); +} + +const char *pa_sample_format_to_string(pa_sample_format_t f) { + static const char* const table[]= { + [PA_SAMPLE_U8] = "u8", + [PA_SAMPLE_ALAW] = "aLaw", + [PA_SAMPLE_ULAW] = "uLaw", + [PA_SAMPLE_S16LE] = "s16le", + [PA_SAMPLE_S16BE] = "s16be", + [PA_SAMPLE_FLOAT32LE] = "float32le", + [PA_SAMPLE_FLOAT32BE] = "float32be", + [PA_SAMPLE_S32LE] = "s32le", + [PA_SAMPLE_S32BE] = "s32be", + }; + + if (f < 0 || f >= PA_SAMPLE_MAX) + return NULL; + + return table[f]; +} + +char *pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec) { + pa_assert(s); + pa_assert(l); + pa_assert(spec); + + if (!pa_sample_spec_valid(spec)) + pa_snprintf(s, l, "Invalid"); + else + pa_snprintf(s, l, "%s %uch %uHz", pa_sample_format_to_string(spec->format), spec->channels, spec->rate); + + return s; +} + +char* pa_bytes_snprint(char *s, size_t l, unsigned v) { + pa_assert(s); + + if (v >= ((unsigned) 1024)*1024*1024) + pa_snprintf(s, l, "%0.1f GiB", ((double) v)/1024/1024/1024); + else if (v >= ((unsigned) 1024)*1024) + pa_snprintf(s, l, "%0.1f MiB", ((double) v)/1024/1024); + else if (v >= (unsigned) 1024) + pa_snprintf(s, l, "%0.1f KiB", ((double) v)/1024); + else + pa_snprintf(s, l, "%u B", (unsigned) v); + + return s; +} + +pa_sample_format_t pa_parse_sample_format(const char *format) { + pa_assert(format); + + if (strcasecmp(format, "s16le") == 0) + return PA_SAMPLE_S16LE; + else if (strcasecmp(format, "s16be") == 0) + return PA_SAMPLE_S16BE; + else if (strcasecmp(format, "s16ne") == 0 || strcasecmp(format, "s16") == 0 || strcasecmp(format, "16") == 0) + return PA_SAMPLE_S16NE; + else if (strcasecmp(format, "s16re") == 0) + return PA_SAMPLE_S16RE; + else if (strcasecmp(format, "u8") == 0 || strcasecmp(format, "8") == 0) + return PA_SAMPLE_U8; + else if (strcasecmp(format, "float32") == 0 || strcasecmp(format, "float32ne") == 0 || strcasecmp(format, "float") == 0) + return PA_SAMPLE_FLOAT32NE; + else if (strcasecmp(format, "float32re") == 0) + return PA_SAMPLE_FLOAT32RE; + else if (strcasecmp(format, "float32le") == 0) + return PA_SAMPLE_FLOAT32LE; + else if (strcasecmp(format, "float32be") == 0) + return PA_SAMPLE_FLOAT32BE; + else if (strcasecmp(format, "ulaw") == 0 || strcasecmp(format, "mulaw") == 0) + return PA_SAMPLE_ULAW; + else if (strcasecmp(format, "alaw") == 0) + return PA_SAMPLE_ALAW; + else if (strcasecmp(format, "s32le") == 0) + return PA_SAMPLE_S32LE; + else if (strcasecmp(format, "s32be") == 0) + return PA_SAMPLE_S32BE; + else if (strcasecmp(format, "s32ne") == 0 || strcasecmp(format, "s32") == 0 || strcasecmp(format, "32") == 0) + return PA_SAMPLE_S32NE; + else if (strcasecmp(format, "s32re") == 0) + return PA_SAMPLE_S32RE; + + return -1; +} diff --git a/src/pulse/sample.h b/src/pulse/sample.h new file mode 100644 index 00000000..f0b839fd --- /dev/null +++ b/src/pulse/sample.h @@ -0,0 +1,216 @@ +#ifndef foosamplehfoo +#define foosamplehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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/types.h> +#include <sys/param.h> +#include <math.h> + +#include <pulse/cdecl.h> + +/** \page sample Sample Format Specifications + * + * \section overv_sec Overview + * + * PulseAudio is capable of handling a multitude of sample formats, rates + * and channels, transparently converting and mixing them as needed. + * + * \section format_sec Sample Format + * + * PulseAudio supports the following sample formats: + * + * \li PA_SAMPLE_U8 - Unsigned 8 bit integer PCM. + * \li PA_SAMPLE_S16LE - Signed 16 integer bit PCM, little endian. + * \li PA_SAMPLE_S16BE - Signed 16 integer bit PCM, big endian. + * \li PA_SAMPLE_FLOAT32LE - 32 bit IEEE floating point PCM, little endian. + * \li PA_SAMPLE_FLOAT32BE - 32 bit IEEE floating point PCM, big endian. + * \li PA_SAMPLE_ALAW - 8 bit a-Law. + * \li PA_SAMPLE_ULAW - 8 bit mu-Law. + * \li PA_SAMPLE_S32LE - Signed 32 bit integer PCM, little endian. + * \li PA_SAMPLE_S32BE - Signed 32 bit integer PCM, big endian. + * + * The floating point sample formats have the range from -1 to 1. + * + * The sample formats that are sensitive to endianness have convenience + * macros for native endian (NE), and reverse endian (RE). + * + * \section rate_sec Sample Rates + * + * PulseAudio supports any sample rate between 1 Hz and 4 GHz. There is no + * point trying to exceed the sample rate of the output device though as the + * signal will only get downsampled, consuming CPU on the machine running the + * server. + * + * \section chan_sec Channels + * + * PulseAudio supports up to 16 individiual channels. The order of the + * channels is up to the application, but they must be continous. To map + * channels to speakers, see \ref channelmap. + * + * \section calc_sec Calculations + * + * The PulseAudio library contains a number of convenience functions to do + * calculations on sample formats: + * + * \li pa_bytes_per_second() - The number of bytes one second of audio will + * take given a sample format. + * \li pa_frame_size() - The size, in bytes, of one frame (i.e. one set of + * samples, one for each channel). + * \li pa_sample_size() - The size, in bytes, of one sample. + * \li pa_bytes_to_usec() - Calculate the time it would take to play a buffer + * of a certain size. + * + * \section util_sec Convenience Functions + * + * The library also contains a couple of other convenience functions: + * + * \li pa_sample_spec_valid() - Tests if a sample format specification is + * valid. + * \li pa_sample_spec_equal() - Tests if the sample format specifications are + * identical. + * \li pa_sample_format_to_string() - Return a textual description of a + * sample format. + * \li pa_parse_sample_format() - Parse a text string into a sample format. + * \li pa_sample_spec_snprint() - Create a textual description of a complete + * sample format specification. + * \li pa_bytes_snprint() - Pretty print a byte value (e.g. 2.5 MiB). + */ + +/** \file + * Constants and routines for sample type handling */ + +PA_C_DECL_BEGIN + +#if !defined(WORDS_BIGENDIAN) +#if defined(__BYTE_ORDER) +#if __BYTE_ORDER == __BIG_ENDIAN +#define WORDS_BIGENDIAN +#endif +#endif +#endif + +/** Maximum number of allowed channels */ +#define PA_CHANNELS_MAX 32 + +/** Maximum allowed sample rate */ +#define PA_RATE_MAX (48000*4) + +/** Sample format */ +typedef enum pa_sample_format { + PA_SAMPLE_U8, /**< Unsigned 8 Bit PCM */ + PA_SAMPLE_ALAW, /**< 8 Bit a-Law */ + PA_SAMPLE_ULAW, /**< 8 Bit mu-Law */ + PA_SAMPLE_S16LE, /**< Signed 16 Bit PCM, little endian (PC) */ + PA_SAMPLE_S16BE, /**< Signed 16 Bit PCM, big endian */ + PA_SAMPLE_FLOAT32LE, /**< 32 Bit IEEE floating point, little endian, range -1 to 1 */ + PA_SAMPLE_FLOAT32BE, /**< 32 Bit IEEE floating point, big endian, range -1 to 1 */ + PA_SAMPLE_S32LE, /**< Signed 32 Bit PCM, little endian (PC) */ + PA_SAMPLE_S32BE, /**< Signed 32 Bit PCM, big endian (PC) */ + PA_SAMPLE_MAX, /**< Upper limit of valid sample types */ + PA_SAMPLE_INVALID = -1 /**< An invalid value */ +} pa_sample_format_t; + +#ifdef WORDS_BIGENDIAN +/** Signed 16 Bit PCM, native endian */ +#define PA_SAMPLE_S16NE PA_SAMPLE_S16BE +/** 32 Bit IEEE floating point, native endian */ +#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32BE +/** Signed 32 Bit PCM, native endian */ +#define PA_SAMPLE_S32NE PA_SAMPLE_S32BE +/** Signed 16 Bit PCM reverse endian */ +#define PA_SAMPLE_S16RE PA_SAMPLE_S16LE +/** 32 Bit IEEE floating point, reverse endian */ +#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32LE +/** Signed 32 Bit PCM reverse endian */ +#define PA_SAMPLE_S32RE PA_SAMPLE_S32LE +#else +/** Signed 16 Bit PCM, native endian */ +#define PA_SAMPLE_S16NE PA_SAMPLE_S16LE +/** 32 Bit IEEE floating point, native endian */ +#define PA_SAMPLE_FLOAT32NE PA_SAMPLE_FLOAT32LE +/** Signed 32 Bit PCM, native endian */ +#define PA_SAMPLE_S32NE PA_SAMPLE_S32LE +/** Signed 16 Bit PCM reverse endian */ +#define PA_SAMPLE_S16RE PA_SAMPLE_S16BE +/** 32 Bit IEEE floating point, reverse endian */ +#define PA_SAMPLE_FLOAT32RE PA_SAMPLE_FLOAT32BE +/** Signed 32 Bit PCM reverse endian */ +#define PA_SAMPLE_S32RE PA_SAMPLE_S32BE +#endif + +/** A Shortcut for PA_SAMPLE_FLOAT32NE */ +#define PA_SAMPLE_FLOAT32 PA_SAMPLE_FLOAT32NE + +/** A sample format and attribute specification */ +typedef struct pa_sample_spec { + pa_sample_format_t format; /**< The sample format */ + uint32_t rate; /**< The sample rate. (e.g. 44100) */ + uint8_t channels; /**< Audio channels. (1 for mono, 2 for stereo, ...) */ +} pa_sample_spec; + +/** Type for usec specifications (unsigned). May be either 32 or 64 bit, depending on the architecture */ +typedef uint64_t pa_usec_t; + +/** Return the amount of bytes playback of a second of audio with the specified sample type takes */ +size_t pa_bytes_per_second(const pa_sample_spec *spec) PA_GCC_PURE; + +/** Return the size of a frame with the specific sample type */ +size_t pa_frame_size(const pa_sample_spec *spec) PA_GCC_PURE; + +/** Return the size of a sample with the specific sample type */ +size_t pa_sample_size(const pa_sample_spec *spec) PA_GCC_PURE; + +/** Calculate the time the specified bytes take to play with the specified sample type */ +pa_usec_t pa_bytes_to_usec(uint64_t length, const pa_sample_spec *spec) PA_GCC_PURE; + +/** Calculates the number of bytes that are required for the specified time. \since 0.9 */ +size_t pa_usec_to_bytes(pa_usec_t t, const pa_sample_spec *spec) PA_GCC_PURE; + +/** Return non-zero when the sample type specification is valid */ +int pa_sample_spec_valid(const pa_sample_spec *spec) PA_GCC_PURE; + +/** Return non-zero when the two sample type specifications match */ +int pa_sample_spec_equal(const pa_sample_spec*a, const pa_sample_spec*b) PA_GCC_PURE; + +/** Return a descriptive string for the specified sample format. \since 0.8 */ +const char *pa_sample_format_to_string(pa_sample_format_t f) PA_GCC_PURE; + +/** Parse a sample format text. Inverse of pa_sample_format_to_string() */ +pa_sample_format_t pa_parse_sample_format(const char *format) PA_GCC_PURE; + +/** Maximum required string length for pa_sample_spec_snprint() */ +#define PA_SAMPLE_SPEC_SNPRINT_MAX 32 + +/** Pretty print a sample type specification to a string */ +char* pa_sample_spec_snprint(char *s, size_t l, const pa_sample_spec *spec); + +/** Pretty print a byte size value. (i.e. "2.5 MiB") */ +char* pa_bytes_snprint(char *s, size_t l, unsigned v); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/scache.c b/src/pulse/scache.c new file mode 100644 index 00000000..186b0a3e --- /dev/null +++ b/src/pulse/scache.c @@ -0,0 +1,136 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <stdio.h> +#include <string.h> + +#include <pulsecore/pstream-util.h> +#include <pulsecore/macro.h> + +#include "internal.h" + +#include "scache.h" + +int pa_stream_connect_upload(pa_stream *s, size_t length) { + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, length > 0, PA_ERR_INVALID); + + pa_stream_ref(s); + + s->direction = PA_STREAM_UPLOAD; + + t = pa_tagstruct_command(s->context, PA_COMMAND_CREATE_UPLOAD_STREAM, &tag); + pa_tagstruct_puts(t, s->name); + pa_tagstruct_put_sample_spec(t, &s->sample_spec); + pa_tagstruct_put_channel_map(t, &s->channel_map); + pa_tagstruct_putu32(t, length); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL); + + pa_stream_set_state(s, PA_STREAM_CREATING); + + pa_stream_unref(s); + return 0; +} + +int pa_stream_finish_upload(pa_stream *s) { + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, s->channel_valid, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->context->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + pa_stream_ref(s); + + t = pa_tagstruct_command(s->context, PA_COMMAND_FINISH_UPLOAD_STREAM, &tag); + pa_tagstruct_putu32(t, s->channel); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_disconnect_callback, s, NULL); + + pa_stream_unref(s); + return 0; +} + +pa_operation *pa_context_play_sample(pa_context *c, const char *name, const char *dev, pa_volume_t volume, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, !dev || *dev, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + if (!dev) + dev = c->conf->default_sink; + + t = pa_tagstruct_command(c, PA_COMMAND_PLAY_SAMPLE, &tag); + pa_tagstruct_putu32(t, PA_INVALID_INDEX); + pa_tagstruct_puts(t, dev); + pa_tagstruct_putu32(t, volume); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(c, name && *name, PA_ERR_INVALID); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_REMOVE_SAMPLE, &tag); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + diff --git a/src/pulse/scache.h b/src/pulse/scache.h new file mode 100644 index 00000000..31fd8956 --- /dev/null +++ b/src/pulse/scache.h @@ -0,0 +1,103 @@ +#ifndef fooscachehfoo +#define fooscachehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> + +#include <pulse/context.h> +#include <pulse/stream.h> +#include <pulse/cdecl.h> + +/** \page scache Sample Cache + * + * \section overv_sec Overview + * + * The sample cache provides a simple way of overcoming high network latencies + * and reducing bandwidth. Instead of streaming a sound precisely when it + * should be played, it is stored on the server and only the command to start + * playing it needs to be sent. + * + * \section create_sec Creation + * + * To create a sample, the normal stream API is used (see \ref streams). The + * function pa_stream_connect_upload() will make sure the stream is stored as + * a sample on the server. + * + * To complete the upload, pa_stream_finish_upload() is called and the sample + * will receive the same name as the stream. If the upload should be aborted, + * simply call pa_stream_disconnect(). + * + * \section play_sec Playing samples + * + * To play back a sample, simply call pa_context_play_sample(): + * + * \code + * pa_operation *o; + * + * o = pa_context_play_sample(my_context, + * "sample2", // Name of my sample + * NULL, // Use default sink + * PA_VOLUME_NORM, // Full volume + * NULL, // Don't need a callback + * NULL + * ); + * if (o) + * pa_operation_unref(o); + * \endcode + * + * \section rem_sec Removing samples + * + * When a sample is no longer needed, it should be removed on the server to + * save resources. The sample is deleted using pa_context_remove_sample(). + */ + +/** \file + * All sample cache related routines */ + +PA_C_DECL_BEGIN + +/** Make this stream a sample upload stream */ +int pa_stream_connect_upload(pa_stream *s, size_t length); + +/** Finish the sample upload, the stream name will become the sample name. You cancel a samp + * le upload by issuing pa_stream_disconnect() */ +int pa_stream_finish_upload(pa_stream *s); + +/** Play a sample from the sample cache to the specified device. If the latter is NULL use the default sink. Returns an operation object */ +pa_operation* pa_context_play_sample( + pa_context *c /**< Context */, + const char *name /**< Name of the sample to play */, + const char *dev /**< Sink to play this sample on */, + pa_volume_t volume /**< Volume to play this sample with */ , + pa_context_success_cb_t cb /**< Call this function after successfully starting playback, or NULL */, + void *userdata /**< Userdata to pass to the callback */); + +/** Remove a sample from the sample cache. Returns an operation object which may be used to cancel the operation while it is running */ +pa_operation* pa_context_remove_sample(pa_context *c, const char *name, pa_context_success_cb_t, void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/simple.c b/src/pulse/simple.c new file mode 100644 index 00000000..1072fb4d --- /dev/null +++ b/src/pulse/simple.c @@ -0,0 +1,458 @@ + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> +#include <stdlib.h> + +#include <pulse/pulseaudio.h> +#include <pulse/thread-mainloop.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/native-common.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "simple.h" + +struct pa_simple { + pa_threaded_mainloop *mainloop; + pa_context *context; + pa_stream *stream; + pa_stream_direction_t direction; + + const void *read_data; + size_t read_index, read_length; + + int operation_success; +}; + +#define CHECK_VALIDITY_RETURN_ANY(rerror, expression, error, ret) do { \ +if (!(expression)) { \ + if (rerror) \ + *(rerror) = error; \ + return (ret); \ + } \ +} while(0); + +#define CHECK_SUCCESS_GOTO(p, rerror, expression, label) do { \ +if (!(expression)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + goto label; \ + } \ +} while(0); + +#define CHECK_DEAD_GOTO(p, rerror, label) do { \ +if (!(p)->context || pa_context_get_state((p)->context) != PA_CONTEXT_READY || \ + !(p)->stream || pa_stream_get_state((p)->stream) != PA_STREAM_READY) { \ + if (((p)->context && pa_context_get_state((p)->context) == PA_CONTEXT_FAILED) || \ + ((p)->stream && pa_stream_get_state((p)->stream) == PA_STREAM_FAILED)) { \ + if (rerror) \ + *(rerror) = pa_context_errno((p)->context); \ + } else \ + if (rerror) \ + *(rerror) = PA_ERR_BADSTATE; \ + goto label; \ + } \ +} while(0); + +static void context_state_cb(pa_context *c, void *userdata) { + pa_simple *p = userdata; + pa_assert(c); + pa_assert(p); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void stream_state_cb(pa_stream *s, void * userdata) { + pa_simple *p = userdata; + pa_assert(s); + pa_assert(p); + + switch (pa_stream_get_state(s)) { + + case PA_STREAM_READY: + case PA_STREAM_FAILED: + case PA_STREAM_TERMINATED: + pa_threaded_mainloop_signal(p->mainloop, 0); + break; + + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { + pa_simple *p = userdata; + pa_assert(p); + + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +static void stream_latency_update_cb(pa_stream *s, void *userdata) { + pa_simple *p = userdata; + + pa_assert(p); + + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +pa_simple* pa_simple_new( + const char *server, + const char *name, + pa_stream_direction_t dir, + const char *dev, + const char *stream_name, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_buffer_attr *attr, + int *rerror) { + + pa_simple *p; + int error = PA_ERR_INTERNAL, r; + + CHECK_VALIDITY_RETURN_ANY(rerror, !server || *server, PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, dir == PA_STREAM_PLAYBACK || dir == PA_STREAM_RECORD, PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, !dev || *dev, PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID, NULL); + CHECK_VALIDITY_RETURN_ANY(rerror, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID, NULL) + + p = pa_xnew(pa_simple, 1); + p->context = NULL; + p->stream = NULL; + p->direction = dir; + p->read_data = NULL; + p->read_index = p->read_length = 0; + + if (!(p->mainloop = pa_threaded_mainloop_new())) + goto fail; + + if (!(p->context = pa_context_new(pa_threaded_mainloop_get_api(p->mainloop), name))) + goto fail; + + pa_context_set_state_callback(p->context, context_state_cb, p); + + if (pa_context_connect(p->context, server, 0, NULL) < 0) { + error = pa_context_errno(p->context); + goto fail; + } + + pa_threaded_mainloop_lock(p->mainloop); + + if (pa_threaded_mainloop_start(p->mainloop) < 0) + goto unlock_and_fail; + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait(p->mainloop); + + if (pa_context_get_state(p->context) != PA_CONTEXT_READY) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + if (!(p->stream = pa_stream_new(p->context, stream_name, ss, map))) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + pa_stream_set_state_callback(p->stream, stream_state_cb, p); + pa_stream_set_read_callback(p->stream, stream_request_cb, p); + pa_stream_set_write_callback(p->stream, stream_request_cb, p); + pa_stream_set_latency_update_callback(p->stream, stream_latency_update_cb, p); + + if (dir == PA_STREAM_PLAYBACK) + r = pa_stream_connect_playback(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + else + r = pa_stream_connect_record(p->stream, dev, attr, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE); + + if (r < 0) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait(p->mainloop); + + /* Wait until the stream is ready */ + if (pa_stream_get_state(p->stream) != PA_STREAM_READY) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + pa_threaded_mainloop_unlock(p->mainloop); + + return p; + +unlock_and_fail: + pa_threaded_mainloop_unlock(p->mainloop); + +fail: + if (rerror) + *rerror = error; + pa_simple_free(p); + return NULL; +} + +void pa_simple_free(pa_simple *s) { + pa_assert(s); + + if (s->mainloop) + pa_threaded_mainloop_stop(s->mainloop); + + if (s->stream) + pa_stream_unref(s->stream); + + if (s->context) + pa_context_unref(s->context); + + if (s->mainloop) + pa_threaded_mainloop_free(s->mainloop); + + pa_xfree(s); +} + +int pa_simple_write(pa_simple *p, const void*data, size_t length, int *rerror) { + pa_assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, data && length, PA_ERR_INVALID, -1); + + pa_threaded_mainloop_lock(p->mainloop); + + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + while (length > 0) { + size_t l; + int r; + + while (!(l = pa_stream_writable_size(p->stream))) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + + CHECK_SUCCESS_GOTO(p, rerror, l != (size_t) -1, unlock_and_fail); + + if (l > length) + l = length; + + r = pa_stream_write(p->stream, data, l, NULL, 0, PA_SEEK_RELATIVE); + CHECK_SUCCESS_GOTO(p, rerror, r >= 0, unlock_and_fail); + + data = (const uint8_t*) data + l; + length -= l; + } + + pa_threaded_mainloop_unlock(p->mainloop); + return 0; + +unlock_and_fail: + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +int pa_simple_read(pa_simple *p, void*data, size_t length, int *rerror) { + pa_assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, data && length, PA_ERR_INVALID, -1); + + pa_threaded_mainloop_lock(p->mainloop); + + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + while (length > 0) { + size_t l; + + while (!p->read_data) { + int r; + + r = pa_stream_peek(p->stream, &p->read_data, &p->read_length); + CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail); + + if (!p->read_data) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } else + p->read_index = 0; + } + + l = p->read_length < length ? p->read_length : length; + memcpy(data, (const uint8_t*) p->read_data+p->read_index, l); + + data = (uint8_t*) data + l; + length -= l; + + p->read_index += l; + p->read_length -= l; + + if (!p->read_length) { + int r; + + r = pa_stream_drop(p->stream); + p->read_data = NULL; + p->read_length = 0; + p->read_index = 0; + + CHECK_SUCCESS_GOTO(p, rerror, r == 0, unlock_and_fail); + } + } + + pa_threaded_mainloop_unlock(p->mainloop); + return 0; + +unlock_and_fail: + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +static void success_cb(pa_stream *s, int success, void *userdata) { + pa_simple *p = userdata; + + pa_assert(s); + pa_assert(p); + + p->operation_success = success; + pa_threaded_mainloop_signal(p->mainloop, 0); +} + +int pa_simple_drain(pa_simple *p, int *rerror) { + pa_operation *o = NULL; + + pa_assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); + + pa_threaded_mainloop_lock(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + o = pa_stream_drain(p->stream, success_cb, p); + CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail); + + p->operation_success = 0; + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail); + + pa_operation_unref(o); + pa_threaded_mainloop_unlock(p->mainloop); + + return 0; + +unlock_and_fail: + + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +int pa_simple_flush(pa_simple *p, int *rerror) { + pa_operation *o = NULL; + + pa_assert(p); + + CHECK_VALIDITY_RETURN_ANY(rerror, p->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE, -1); + + pa_threaded_mainloop_lock(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + o = pa_stream_flush(p->stream, success_cb, p); + CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail); + + p->operation_success = 0; + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait(p->mainloop); + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + } + CHECK_SUCCESS_GOTO(p, rerror, p->operation_success, unlock_and_fail); + + pa_operation_unref(o); + pa_threaded_mainloop_unlock(p->mainloop); + + return 0; + +unlock_and_fail: + + if (o) { + pa_operation_cancel(o); + pa_operation_unref(o); + } + + pa_threaded_mainloop_unlock(p->mainloop); + return -1; +} + +pa_usec_t pa_simple_get_latency(pa_simple *p, int *rerror) { + pa_usec_t t; + int negative; + + pa_assert(p); + + pa_threaded_mainloop_lock(p->mainloop); + + for (;;) { + CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); + + if (pa_stream_get_latency(p->stream, &t, &negative) >= 0) + break; + + CHECK_SUCCESS_GOTO(p, rerror, pa_context_errno(p->context) == PA_ERR_NODATA, unlock_and_fail); + + /* Wait until latency data is available again */ + pa_threaded_mainloop_wait(p->mainloop); + } + + pa_threaded_mainloop_unlock(p->mainloop); + + return negative ? 0 : t; + +unlock_and_fail: + + pa_threaded_mainloop_unlock(p->mainloop); + return (pa_usec_t) -1; +} + diff --git a/src/pulse/simple.h b/src/pulse/simple.h new file mode 100644 index 00000000..0ddd57e0 --- /dev/null +++ b/src/pulse/simple.h @@ -0,0 +1,149 @@ +#ifndef foosimplehfoo +#define foosimplehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/def.h> +#include <pulse/cdecl.h> + +/** \page simple Simple API + * + * \section overv_sec Overview + * + * The simple API is designed for applications with very basic sound + * playback or capture needs. It can only support a single stream per + * connection and has no handling of complex features like events, channel + * mappings and volume control. It is, however, very simple to use and + * quite sufficent for many programs. + * + * \section conn_sec Connecting + * + * The first step before using the sound system is to connect to the + * server. This is normally done this way: + * + * \code + * pa_simple *s; + * pa_sample_spec ss; + * + * ss.format = PA_SAMPLE_S16NE; + * ss.channels = 2; + * ss.rate = 44100; + * + * s = pa_simple_new(NULL, // Use the default server. + * "Fooapp", // Our application's name. + * PA_STREAM_PLAYBACK, + * NULL, // Use the default device. + * "Music", // Description of our stream. + * &ss, // Our sample format. + * NULL, // Use default channel map + * NULL, // Use default buffering attributes. + * NULL, // Ignore error code. + * ); + * \endcode + * + * At this point a connected object is returned, or NULL if there was a + * problem connecting. + * + * \section transfer_sec Transferring data + * + * Once the connection is established to the server, data can start flowing. + * Using the connection is very similar to the normal read() and write() + * system calls. The main difference is that they're call pa_simple_read() + * and pa_simple_write(). Note that these operations always block. + * + * \section ctrl_sec Buffer control + * + * If a playback stream is used then a few other operations are available: + * + * \li pa_simple_drain() - Will wait for all sent data to finish playing. + * \li pa_simple_flush() - Will throw away all data currently in buffers. + * \li pa_simple_get_playback_latency() - Will return the total latency of + * the playback pipeline. + * + * \section cleanup_sec Cleanup + * + * Once playback or capture is complete, the connection should be closed + * and resources freed. This is done through: + * + * \code + * pa_simple_free(s); + * \endcode + */ + +/** \file + * A simple but limited synchronous playback and recording + * API. This is a synchronous, simplified wrapper around the standard + * asynchronous API. */ + +/** \example pacat-simple.c + * A simple playback tool using the simple API */ + +/** \example parec-simple.c + * A simple recording tool using the simple API */ + +PA_C_DECL_BEGIN + +/** \struct pa_simple + * An opaque simple connection object */ +typedef struct pa_simple pa_simple; + +/** Create a new connection to the server */ +pa_simple* pa_simple_new( + const char *server, /**< Server name, or NULL for default */ + const char *name, /**< A descriptive name for this client (application name, ...) */ + pa_stream_direction_t dir, /**< Open this stream for recording or playback? */ + const char *dev, /**< Sink (resp. source) name, or NULL for default */ + const char *stream_name, /**< A descriptive name for this client (application name, song title, ...) */ + const pa_sample_spec *ss, /**< The sample type to use */ + const pa_channel_map *map, /**< The channel map to use, or NULL for default */ + const pa_buffer_attr *attr, /**< Buffering attributes, or NULL for default */ + int *error /**< A pointer where the error code is stored when the routine returns NULL. It is OK to pass NULL here. */ + ); + +/** Close and free the connection to the server. The connection objects becomes invalid when this is called. */ +void pa_simple_free(pa_simple *s); + +/** Write some data to the server */ +int pa_simple_write(pa_simple *s, const void*data, size_t bytes, int *error); + +/** Wait until all data already written is played by the daemon */ +int pa_simple_drain(pa_simple *s, int *error); + +/** Read some data from the server */ +int pa_simple_read(pa_simple *s, void*data, size_t bytes, int *error); + +/** Return the playback latency. \since 0.5 */ +pa_usec_t pa_simple_get_latency(pa_simple *s, int *error); + +/** Flush the playback buffer. \since 0.5 */ +int pa_simple_flush(pa_simple *s, int *error); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/stream.c b/src/pulse/stream.c new file mode 100644 index 00000000..c44323fc --- /dev/null +++ b/src/pulse/stream.c @@ -0,0 +1,1839 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> +#include <stdio.h> +#include <string.h> + +#include <pulse/def.h> +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/pstream-util.h> +#include <pulsecore/log.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/macro.h> + +#include "internal.h" + +#define LATENCY_IPOL_INTERVAL_USEC (100000L) + +pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map) { + pa_stream *s; + int i; + pa_channel_map tmap; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, ss && pa_sample_spec_valid(ss), PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(c, c->version >= 12 || (ss->format != PA_SAMPLE_S32LE || ss->format != PA_SAMPLE_S32NE), PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(c, !map || (pa_channel_map_valid(map) && map->channels == ss->channels), PA_ERR_INVALID); + + if (!map) + PA_CHECK_VALIDITY_RETURN_NULL(c, map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT), PA_ERR_INVALID); + + s = pa_xnew(pa_stream, 1); + PA_REFCNT_INIT(s); + s->context = c; + s->mainloop = c->mainloop; + + s->buffer_attr_not_ready = s->timing_info_not_ready = FALSE; + + s->read_callback = NULL; + s->read_userdata = NULL; + s->write_callback = NULL; + s->write_userdata = NULL; + s->state_callback = NULL; + s->state_userdata = NULL; + s->overflow_callback = NULL; + s->overflow_userdata = NULL; + s->underflow_callback = NULL; + s->underflow_userdata = NULL; + s->latency_update_callback = NULL; + s->latency_update_userdata = NULL; + s->moved_callback = NULL; + s->moved_userdata = NULL; + s->suspended_callback = NULL; + s->suspended_userdata = NULL; + + s->direction = PA_STREAM_NODIRECTION; + s->name = pa_xstrdup(name); + s->sample_spec = *ss; + s->channel_map = *map; + s->flags = 0; + + s->channel = 0; + s->channel_valid = 0; + s->syncid = c->csyncid++; + s->stream_index = PA_INVALID_INDEX; + s->requested_bytes = 0; + s->state = PA_STREAM_UNCONNECTED; + + s->manual_buffer_attr = FALSE; + memset(&s->buffer_attr, 0, sizeof(s->buffer_attr)); + + s->device_index = PA_INVALID_INDEX; + s->device_name = NULL; + s->suspended = FALSE; + + s->peek_memchunk.index = 0; + s->peek_memchunk.length = 0; + s->peek_memchunk.memblock = NULL; + s->peek_data = NULL; + + s->record_memblockq = NULL; + + s->previous_time = 0; + s->timing_info_valid = 0; + s->read_index_not_before = 0; + s->write_index_not_before = 0; + + for (i = 0; i < PA_MAX_WRITE_INDEX_CORRECTIONS; i++) + s->write_index_corrections[i].valid = 0; + s->current_write_index_correction = 0; + + s->corked = 0; + + s->cached_time_valid = 0; + + s->auto_timing_update_event = NULL; + s->auto_timing_update_requested = 0; + + /* Refcounting is strictly one-way: from the "bigger" to the "smaller" object. */ + PA_LLIST_PREPEND(pa_stream, c->streams, s); + pa_stream_ref(s); + + return s; +} + +static void stream_free(pa_stream *s) { + pa_assert(s); + pa_assert(!s->context); + pa_assert(!s->channel_valid); + + if (s->auto_timing_update_event) { + pa_assert(s->mainloop); + s->mainloop->time_free(s->auto_timing_update_event); + } + + if (s->peek_memchunk.memblock) { + if (s->peek_data) + pa_memblock_release(s->peek_memchunk.memblock); + pa_memblock_unref(s->peek_memchunk.memblock); + } + + if (s->record_memblockq) + pa_memblockq_free(s->record_memblockq); + + pa_xfree(s->name); + pa_xfree(s->device_name); + pa_xfree(s); +} + +void pa_stream_unref(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (PA_REFCNT_DEC(s) <= 0) + stream_free(s); +} + +pa_stream* pa_stream_ref(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_REFCNT_INC(s); + return s; +} + +pa_stream_state_t pa_stream_get_state(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return s->state; +} + +pa_context* pa_stream_get_context(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return s->context; +} + +uint32_t pa_stream_get_index(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX); + + return s->stream_index; +} + +void pa_stream_set_state(pa_stream *s, pa_stream_state_t st) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (s->state == st) + return; + + pa_stream_ref(s); + + s->state = st; + if (s->state_callback) + s->state_callback(s, s->state_userdata); + + if ((st == PA_STREAM_FAILED || st == PA_STREAM_TERMINATED) && s->context) { + + /* Detach from context */ + pa_operation *o, *n; + + /* Unref all operatio object that point to us */ + for (o = s->context->operations; o; o = n) { + n = o->next; + + if (o->stream == s) + pa_operation_cancel(o); + } + + /* Drop all outstanding replies for this stream */ + if (s->context->pdispatch) + pa_pdispatch_unregister_reply(s->context->pdispatch, s); + + if (s->channel_valid) + pa_dynarray_put((s->direction == PA_STREAM_PLAYBACK) ? s->context->playback_streams : s->context->record_streams, s->channel, NULL); + + PA_LLIST_REMOVE(pa_stream, s->context->streams, s); + pa_stream_unref(s); + + s->channel = 0; + s->channel_valid = 0; + + s->context = NULL; + + s->read_callback = NULL; + s->write_callback = NULL; + s->state_callback = NULL; + s->overflow_callback = NULL; + s->underflow_callback = NULL; + s->latency_update_callback = NULL; + } + + pa_stream_unref(s); +} + +void pa_command_stream_killed(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_KILLED || command == PA_COMMAND_RECORD_STREAM_KILLED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_KILLED ? c->playback_streams : c->record_streams, channel))) + goto finish; + + pa_context_set_error(c, PA_ERR_KILLED); + pa_stream_set_state(s, PA_STREAM_FAILED); + +finish: + pa_context_unref(c); +} + +void pa_command_stream_moved(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + const char *dn; + int suspended; + uint32_t di; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_MOVED || command == PA_COMMAND_RECORD_STREAM_MOVED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 12) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_getu32(t, &di) < 0 || + pa_tagstruct_gets(t, &dn) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!dn || di == PA_INVALID_INDEX) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_MOVED ? c->playback_streams : c->record_streams, channel))) + goto finish; + + pa_xfree(s->device_name); + s->device_name = pa_xstrdup(dn); + s->device_index = di; + + s->suspended = suspended; + + if (s->moved_callback) + s->moved_callback(s, s->moved_userdata); + +finish: + pa_context_unref(c); +} + +void pa_command_stream_suspended(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_stream *s; + uint32_t channel; + int suspended; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED || command == PA_COMMAND_RECORD_STREAM_SUSPENDED); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (c->version < 12) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_dynarray_get(command == PA_COMMAND_PLAYBACK_STREAM_SUSPENDED ? c->playback_streams : c->record_streams, channel))) + goto finish; + + s->suspended = suspended; + + if (s->suspended_callback) + s->suspended_callback(s, s->suspended_userdata); + +finish: + pa_context_unref(c); +} + +void pa_command_request(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_stream *s; + pa_context *c = userdata; + uint32_t bytes, channel; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_REQUEST); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + pa_tagstruct_getu32(t, &bytes) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_dynarray_get(c->playback_streams, channel))) + goto finish; + + if (s->state == PA_STREAM_READY) { + s->requested_bytes += bytes; + + if (s->requested_bytes > 0 && s->write_callback) + s->write_callback(s, s->requested_bytes, s->write_userdata); + } + +finish: + pa_context_unref(c); +} + +void pa_command_overflow_or_underflow(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_stream *s; + pa_context *c = userdata; + uint32_t channel; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_OVERFLOW || command == PA_COMMAND_UNDERFLOW); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (!(s = pa_dynarray_get(c->playback_streams, channel))) + goto finish; + + if (s->state == PA_STREAM_READY) { + + if (command == PA_COMMAND_OVERFLOW) { + if (s->overflow_callback) + s->overflow_callback(s, s->overflow_userdata); + } else if (command == PA_COMMAND_UNDERFLOW) { + if (s->underflow_callback) + s->underflow_callback(s, s->underflow_userdata); + } + } + + finish: + pa_context_unref(c); +} + +static void request_auto_timing_update(pa_stream *s, int force) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (!(s->flags & PA_STREAM_AUTO_TIMING_UPDATE)) + return; + + if (s->state == PA_STREAM_READY && + (force || !s->auto_timing_update_requested)) { + pa_operation *o; + +/* pa_log("automatically requesting new timing data"); */ + + if ((o = pa_stream_update_timing_info(s, NULL, NULL))) { + pa_operation_unref(o); + s->auto_timing_update_requested = 1; + } + } + + if (s->auto_timing_update_event) { + struct timeval next; + pa_gettimeofday(&next); + pa_timeval_add(&next, LATENCY_IPOL_INTERVAL_USEC); + s->mainloop->time_restart(s->auto_timing_update_event, &next); + } +} + +static void invalidate_indexes(pa_stream *s, int r, int w) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + +/* pa_log("invalidate r:%u w:%u tag:%u", r, w, s->context->ctag); */ + + if (s->state != PA_STREAM_READY) + return; + + if (w) { + s->write_index_not_before = s->context->ctag; + + if (s->timing_info_valid) + s->timing_info.write_index_corrupt = 1; + +/* pa_log("write_index invalidated"); */ + } + + if (r) { + s->read_index_not_before = s->context->ctag; + + if (s->timing_info_valid) + s->timing_info.read_index_corrupt = 1; + +/* pa_log("read_index invalidated"); */ + } + + if ((s->direction == PA_STREAM_PLAYBACK && r) || + (s->direction == PA_STREAM_RECORD && w)) + s->cached_time_valid = 0; + + request_auto_timing_update(s, 1); +} + +static void auto_timing_update_callback(PA_GCC_UNUSED pa_mainloop_api *m, PA_GCC_UNUSED pa_time_event *e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { + pa_stream *s = userdata; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + +/* pa_log("time event"); */ + + pa_stream_ref(s); + request_auto_timing_update(s, 0); + pa_stream_unref(s); +} + +static void create_stream_complete(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(s->state == PA_STREAM_CREATING); + + if (s->buffer_attr_not_ready || s->timing_info_not_ready) + return; + + pa_stream_set_state(s, PA_STREAM_READY); + + if (s->requested_bytes > 0 && s->write_callback) + s->write_callback(s, s->requested_bytes, s->write_userdata); + + if (s->flags & PA_STREAM_AUTO_TIMING_UPDATE) { + struct timeval tv; + pa_gettimeofday(&tv); + tv.tv_usec += LATENCY_IPOL_INTERVAL_USEC; /* every 100 ms */ + pa_assert(!s->auto_timing_update_event); + s->auto_timing_update_event = s->mainloop->time_new(s->mainloop, &tv, &auto_timing_update_callback, s); + } +} + +static void automatic_buffer_attr(pa_buffer_attr *attr, pa_sample_spec *ss) { + pa_assert(attr); + pa_assert(ss); + + attr->tlength = pa_bytes_per_second(ss)/2; + attr->maxlength = (attr->tlength*3)/2; + attr->minreq = attr->tlength/50; + attr->prebuf = attr->tlength - attr->minreq; + attr->fragsize = attr->tlength/50; +} + +void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_stream *s = userdata; + + pa_assert(pd); + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(s->state == PA_STREAM_CREATING); + + pa_stream_ref(s); + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(s->context, command, t) < 0) + goto finish; + + pa_stream_set_state(s, PA_STREAM_FAILED); + goto finish; + } + + if (pa_tagstruct_getu32(t, &s->channel) < 0 || + ((s->direction != PA_STREAM_UPLOAD) && pa_tagstruct_getu32(t, &s->stream_index) < 0) || + ((s->direction != PA_STREAM_RECORD) && pa_tagstruct_getu32(t, &s->requested_bytes) < 0)) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (s->context->version >= 9) { + if (s->direction == PA_STREAM_PLAYBACK) { + if (pa_tagstruct_getu32(t, &s->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &s->buffer_attr.tlength) < 0 || + pa_tagstruct_getu32(t, &s->buffer_attr.prebuf) < 0 || + pa_tagstruct_getu32(t, &s->buffer_attr.minreq) < 0) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + } else if (s->direction == PA_STREAM_RECORD) { + if (pa_tagstruct_getu32(t, &s->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &s->buffer_attr.fragsize) < 0) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + } + } + + if (s->context->version >= 12 && s->direction != PA_STREAM_UPLOAD) { + pa_sample_spec ss; + pa_channel_map cm; + const char *dn = NULL; + int suspended; + + if (pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &cm) < 0 || + pa_tagstruct_getu32(t, &s->device_index) < 0 || + pa_tagstruct_gets(t, &dn) < 0 || + pa_tagstruct_get_boolean(t, &suspended) < 0) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (!dn || s->device_index == PA_INVALID_INDEX || + ss.channels != cm.channels || + !pa_channel_map_valid(&cm) || + !pa_sample_spec_valid(&ss) || + (!(s->flags & PA_STREAM_FIX_FORMAT) && ss.format != s->sample_spec.format) || + (!(s->flags & PA_STREAM_FIX_RATE) && ss.rate != s->sample_spec.rate) || + (!(s->flags & PA_STREAM_FIX_CHANNELS) && !pa_channel_map_equal(&cm, &s->channel_map))) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + pa_xfree(s->device_name); + s->device_name = pa_xstrdup(dn); + s->suspended = suspended; + + if (!s->manual_buffer_attr && pa_bytes_per_second(&ss) != pa_bytes_per_second(&s->sample_spec)) { + pa_buffer_attr attr; + pa_operation *o; + + automatic_buffer_attr(&attr, &ss); + + /* If we need to update the buffer metrics, we wait for + * the the OK for that call before we go to + * PA_STREAM_READY */ + + s->state = PA_STREAM_READY; + pa_assert_se(o = pa_stream_set_buffer_attr(s, &attr, NULL, NULL)); + pa_operation_unref(o); + s->state = PA_STREAM_CREATING; + + s->buffer_attr_not_ready = TRUE; + } + + s->channel_map = cm; + s->sample_spec = ss; + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (s->direction == PA_STREAM_RECORD) { + pa_assert(!s->record_memblockq); + + s->record_memblockq = pa_memblockq_new( + 0, + s->buffer_attr.maxlength, + 0, + pa_frame_size(&s->sample_spec), + 1, + 0, + NULL); + } + + s->channel_valid = 1; + pa_dynarray_put((s->direction == PA_STREAM_RECORD) ? s->context->record_streams : s->context->playback_streams, s->channel, s); + + if (s->direction != PA_STREAM_UPLOAD && s->flags & PA_STREAM_AUTO_TIMING_UPDATE) { + + /* If automatic timing updates are active, we wait for the + * first timing update before going to PA_STREAM_READY + * state */ + + s->state = PA_STREAM_READY; + request_auto_timing_update(s, 1); + s->state = PA_STREAM_CREATING; + + s->timing_info_not_ready = TRUE; + } + + create_stream_complete(s); + +finish: + pa_stream_unref(s); +} + +static int create_stream( + pa_stream_direction_t direction, + pa_stream *s, + const char *dev, + const pa_buffer_attr *attr, + pa_stream_flags_t flags, + const pa_cvolume *volume, + pa_stream *sync_stream) { + + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_UNCONNECTED, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, !(flags & ~((direction != PA_STREAM_UPLOAD ? + PA_STREAM_START_CORKED| + PA_STREAM_INTERPOLATE_TIMING| + PA_STREAM_NOT_MONOTONOUS| + PA_STREAM_AUTO_TIMING_UPDATE| + PA_STREAM_NO_REMAP_CHANNELS| + PA_STREAM_NO_REMIX_CHANNELS| + PA_STREAM_FIX_FORMAT| + PA_STREAM_FIX_RATE| + PA_STREAM_FIX_CHANNELS| + PA_STREAM_DONT_MOVE : 0))), PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, !volume || volume->channels == s->sample_spec.channels, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, !sync_stream || (direction == PA_STREAM_PLAYBACK && sync_stream->direction == PA_STREAM_PLAYBACK), PA_ERR_INVALID); + + pa_stream_ref(s); + + s->direction = direction; + s->flags = flags; + + if (sync_stream) + s->syncid = sync_stream->syncid; + + if (attr) { + s->buffer_attr = *attr; + s->manual_buffer_attr = TRUE; + } else { + /* half a second, with minimum request of 10 ms */ + s->buffer_attr.tlength = pa_bytes_per_second(&s->sample_spec)/2; + s->buffer_attr.maxlength = (s->buffer_attr.tlength*3)/2; + s->buffer_attr.minreq = s->buffer_attr.tlength/50; + s->buffer_attr.prebuf = s->buffer_attr.tlength - s->buffer_attr.minreq; + s->buffer_attr.fragsize = s->buffer_attr.tlength/50; + s->manual_buffer_attr = FALSE; + } + + if (!dev) + dev = s->direction == PA_STREAM_PLAYBACK ? s->context->conf->default_sink : s->context->conf->default_source; + + t = pa_tagstruct_command( + s->context, + s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CREATE_PLAYBACK_STREAM : PA_COMMAND_CREATE_RECORD_STREAM, + &tag); + + pa_tagstruct_put( + t, + PA_TAG_STRING, s->name, + PA_TAG_SAMPLE_SPEC, &s->sample_spec, + PA_TAG_CHANNEL_MAP, &s->channel_map, + PA_TAG_U32, PA_INVALID_INDEX, + PA_TAG_STRING, dev, + PA_TAG_U32, s->buffer_attr.maxlength, + PA_TAG_BOOLEAN, !!(flags & PA_STREAM_START_CORKED), + PA_TAG_INVALID); + + if (s->direction == PA_STREAM_PLAYBACK) { + pa_cvolume cv; + + pa_tagstruct_put( + t, + PA_TAG_U32, s->buffer_attr.tlength, + PA_TAG_U32, s->buffer_attr.prebuf, + PA_TAG_U32, s->buffer_attr.minreq, + PA_TAG_U32, s->syncid, + PA_TAG_INVALID); + + if (!volume) + volume = pa_cvolume_reset(&cv, s->sample_spec.channels); + + pa_tagstruct_put_cvolume(t, volume); + } else + pa_tagstruct_putu32(t, s->buffer_attr.fragsize); + + if (s->context->version >= 12 && s->direction != PA_STREAM_UPLOAD) { + pa_tagstruct_put( + t, + PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMAP_CHANNELS, + PA_TAG_BOOLEAN, flags & PA_STREAM_NO_REMIX_CHANNELS, + PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_FORMAT, + PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_RATE, + PA_TAG_BOOLEAN, flags & PA_STREAM_FIX_CHANNELS, + PA_TAG_BOOLEAN, flags & PA_STREAM_DONT_MOVE, + PA_TAG_BOOLEAN, flags & PA_STREAM_VARIABLE_RATE, + PA_TAG_INVALID); + } + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_create_stream_callback, s, NULL); + + pa_stream_set_state(s, PA_STREAM_CREATING); + + pa_stream_unref(s); + return 0; +} + +int pa_stream_connect_playback( + pa_stream *s, + const char *dev, + const pa_buffer_attr *attr, + pa_stream_flags_t flags, + pa_cvolume *volume, + pa_stream *sync_stream) { + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return create_stream(PA_STREAM_PLAYBACK, s, dev, attr, flags, volume, sync_stream); +} + +int pa_stream_connect_record( + pa_stream *s, + const char *dev, + const pa_buffer_attr *attr, + pa_stream_flags_t flags) { + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return create_stream(PA_STREAM_RECORD, s, dev, attr, flags, NULL, NULL); +} + +int pa_stream_write( + pa_stream *s, + const void *data, + size_t length, + void (*free_cb)(void *p), + int64_t offset, + pa_seek_mode_t seek) { + + pa_memchunk chunk; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(data); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || s->direction == PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, seek <= PA_SEEK_RELATIVE_END, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_PLAYBACK || (seek == PA_SEEK_RELATIVE && offset == 0), PA_ERR_INVALID); + + if (length <= 0) + return 0; + + if (free_cb) + chunk.memblock = pa_memblock_new_user(s->context->mempool, (void*) data, length, free_cb, 1); + else { + void *tdata; + chunk.memblock = pa_memblock_new(s->context->mempool, length); + tdata = pa_memblock_acquire(chunk.memblock); + memcpy(tdata, data, length); + pa_memblock_release(chunk.memblock); + } + + chunk.index = 0; + chunk.length = length; + + pa_pstream_send_memblock(s->context->pstream, s->channel, offset, seek, &chunk); + pa_memblock_unref(chunk.memblock); + + if (length < s->requested_bytes) + s->requested_bytes -= length; + else + s->requested_bytes = 0; + + if (s->direction == PA_STREAM_PLAYBACK) { + + /* Update latency request correction */ + if (s->write_index_corrections[s->current_write_index_correction].valid) { + + if (seek == PA_SEEK_ABSOLUTE) { + s->write_index_corrections[s->current_write_index_correction].corrupt = 0; + s->write_index_corrections[s->current_write_index_correction].absolute = 1; + s->write_index_corrections[s->current_write_index_correction].value = offset + length; + } else if (seek == PA_SEEK_RELATIVE) { + if (!s->write_index_corrections[s->current_write_index_correction].corrupt) + s->write_index_corrections[s->current_write_index_correction].value += offset + length; + } else + s->write_index_corrections[s->current_write_index_correction].corrupt = 1; + } + + /* Update the write index in the already available latency data */ + if (s->timing_info_valid) { + + if (seek == PA_SEEK_ABSOLUTE) { + s->timing_info.write_index_corrupt = 0; + s->timing_info.write_index = offset + length; + } else if (seek == PA_SEEK_RELATIVE) { + if (!s->timing_info.write_index_corrupt) + s->timing_info.write_index += offset + length; + } else + s->timing_info.write_index_corrupt = 1; + } + + if (!s->timing_info_valid || s->timing_info.write_index_corrupt) + request_auto_timing_update(s, 1); + } + + return 0; +} + +int pa_stream_peek(pa_stream *s, const void **data, size_t *length) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(data); + pa_assert(length); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE); + + if (!s->peek_memchunk.memblock) { + + if (pa_memblockq_peek(s->record_memblockq, &s->peek_memchunk) < 0) { + *data = NULL; + *length = 0; + return 0; + } + + s->peek_data = pa_memblock_acquire(s->peek_memchunk.memblock); + } + + pa_assert(s->peek_data); + *data = (uint8_t*) s->peek_data + s->peek_memchunk.index; + *length = s->peek_memchunk.length; + return 0; +} + +int pa_stream_drop(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->peek_memchunk.memblock, PA_ERR_BADSTATE); + + pa_memblockq_drop(s->record_memblockq, s->peek_memchunk.length); + + /* Fix the simulated local read index */ + if (s->timing_info_valid && !s->timing_info.read_index_corrupt) + s->timing_info.read_index += s->peek_memchunk.length; + + pa_assert(s->peek_data); + pa_memblock_release(s->peek_memchunk.memblock); + pa_memblock_unref(s->peek_memchunk.memblock); + s->peek_memchunk.length = 0; + s->peek_memchunk.index = 0; + s->peek_memchunk.memblock = NULL; + + return 0; +} + +size_t pa_stream_writable_size(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_RECORD, PA_ERR_BADSTATE, (size_t) -1); + + return s->requested_bytes; +} + +size_t pa_stream_readable_size(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, (size_t) -1); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction == PA_STREAM_RECORD, PA_ERR_BADSTATE, (size_t) -1); + + return pa_memblockq_get_length(s->record_memblockq); +} + +pa_operation * pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(s->context, PA_COMMAND_DRAIN_PLAYBACK_STREAM, &tag); + pa_tagstruct_putu32(t, s->channel); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + struct timeval local, remote, now; + pa_timing_info *i; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context || !o->stream) + goto finish; + + i = &o->stream->timing_info; + +/* pa_log("pre corrupt w:%u r:%u\n", !o->stream->timing_info_valid || i->write_index_corrupt,!o->stream->timing_info_valid || i->read_index_corrupt); */ + + o->stream->timing_info_valid = 0; + i->write_index_corrupt = 0; + i->read_index_corrupt = 0; + +/* pa_log("timing update %u\n", tag); */ + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + } else if (pa_tagstruct_get_usec(t, &i->sink_usec) < 0 || + pa_tagstruct_get_usec(t, &i->source_usec) < 0 || + pa_tagstruct_get_boolean(t, &i->playing) < 0 || + pa_tagstruct_get_timeval(t, &local) < 0 || + pa_tagstruct_get_timeval(t, &remote) < 0 || + pa_tagstruct_gets64(t, &i->write_index) < 0 || + pa_tagstruct_gets64(t, &i->read_index) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + + } else { + o->stream->timing_info_valid = 1; + + pa_gettimeofday(&now); + + /* Calculcate timestamps */ + if (pa_timeval_cmp(&local, &remote) <= 0 && pa_timeval_cmp(&remote, &now) <= 0) { + /* local and remote seem to have synchronized clocks */ + + if (o->stream->direction == PA_STREAM_PLAYBACK) + i->transport_usec = pa_timeval_diff(&remote, &local); + else + i->transport_usec = pa_timeval_diff(&now, &remote); + + i->synchronized_clocks = 1; + i->timestamp = remote; + } else { + /* clocks are not synchronized, let's estimate latency then */ + i->transport_usec = pa_timeval_diff(&now, &local)/2; + i->synchronized_clocks = 0; + i->timestamp = local; + pa_timeval_add(&i->timestamp, i->transport_usec); + } + + /* Invalidate read and write indexes if necessary */ + if (tag < o->stream->read_index_not_before) + i->read_index_corrupt = 1; + + if (tag < o->stream->write_index_not_before) + i->write_index_corrupt = 1; + + if (o->stream->direction == PA_STREAM_PLAYBACK) { + /* Write index correction */ + + int n, j; + uint32_t ctag = tag; + + /* Go through the saved correction values and add up the total correction.*/ + + for (n = 0, j = o->stream->current_write_index_correction+1; + n < PA_MAX_WRITE_INDEX_CORRECTIONS; + n++, j = (j + 1) % PA_MAX_WRITE_INDEX_CORRECTIONS) { + + /* Step over invalid data or out-of-date data */ + if (!o->stream->write_index_corrections[j].valid || + o->stream->write_index_corrections[j].tag < ctag) + continue; + + /* Make sure that everything is in order */ + ctag = o->stream->write_index_corrections[j].tag+1; + + /* Now fix the write index */ + if (o->stream->write_index_corrections[j].corrupt) { + /* A corrupting seek was made */ + i->write_index = 0; + i->write_index_corrupt = 1; + } else if (o->stream->write_index_corrections[j].absolute) { + /* An absolute seek was made */ + i->write_index = o->stream->write_index_corrections[j].value; + i->write_index_corrupt = 0; + } else if (!i->write_index_corrupt) { + /* A relative seek was made */ + i->write_index += o->stream->write_index_corrections[j].value; + } + } + } + + if (o->stream->direction == PA_STREAM_RECORD) { + /* Read index correction */ + + if (!i->read_index_corrupt) + i->read_index -= pa_memblockq_get_length(o->stream->record_memblockq); + } + + o->stream->cached_time_valid = 0; + } + + o->stream->auto_timing_update_requested = 0; +/* pa_log("post corrupt w:%u r:%u\n", i->write_index_corrupt || !o->stream->timing_info_valid, i->read_index_corrupt || !o->stream->timing_info_valid); */ + + /* Clear old correction entries */ + if (o->stream->direction == PA_STREAM_PLAYBACK) { + int n; + + for (n = 0; n < PA_MAX_WRITE_INDEX_CORRECTIONS; n++) { + if (!o->stream->write_index_corrections[n].valid) + continue; + + if (o->stream->write_index_corrections[n].tag <= tag) + o->stream->write_index_corrections[n].valid = 0; + } + } + + /* First, let's complete the initialization, if necessary. */ + if (o->stream->state == PA_STREAM_CREATING) { + o->stream->timing_info_not_ready = FALSE; + create_stream_complete(o->stream); + } + + if (o->stream->latency_update_callback) + o->stream->latency_update_callback(o->stream, o->stream->latency_update_userdata); + + if (o->callback && o->stream && o->stream->state == PA_STREAM_READY) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, o->stream->timing_info_valid, o->userdata); + } + +finish: + + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_stream_update_timing_info(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + uint32_t tag; + pa_operation *o; + pa_tagstruct *t; + struct timeval now; + int cidx = 0; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + if (s->direction == PA_STREAM_PLAYBACK) { + /* Find a place to store the write_index correction data for this entry */ + cidx = (s->current_write_index_correction + 1) % PA_MAX_WRITE_INDEX_CORRECTIONS; + + /* Check if we could allocate a correction slot. If not, there are too many outstanding queries */ + PA_CHECK_VALIDITY_RETURN_NULL(s->context, !s->write_index_corrections[cidx].valid, PA_ERR_INTERNAL); + } + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_GET_PLAYBACK_LATENCY : PA_COMMAND_GET_RECORD_LATENCY, + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_put_timeval(t, pa_gettimeofday(&now)); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_get_timing_info_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + if (s->direction == PA_STREAM_PLAYBACK) { + /* Fill in initial correction data */ + o->stream->current_write_index_correction = cidx; + o->stream->write_index_corrections[cidx].valid = 1; + o->stream->write_index_corrections[cidx].tag = tag; + o->stream->write_index_corrections[cidx].absolute = 0; + o->stream->write_index_corrections[cidx].value = 0; + o->stream->write_index_corrections[cidx].corrupt = 0; + } + +/* pa_log("requesting update %u\n", tag); */ + + return o; +} + +void pa_stream_disconnect_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_stream *s = userdata; + + pa_assert(pd); + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + pa_stream_ref(s); + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(s->context, command, t) < 0) + goto finish; + + pa_stream_set_state(s, PA_STREAM_FAILED); + goto finish; + } else if (!pa_tagstruct_eof(t)) { + pa_context_fail(s->context, PA_ERR_PROTOCOL); + goto finish; + } + + pa_stream_set_state(s, PA_STREAM_TERMINATED); + +finish: + pa_stream_unref(s); +} + +int pa_stream_disconnect(pa_stream *s) { + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, s->channel_valid, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->context->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + pa_stream_ref(s); + + t = pa_tagstruct_command( + s->context, + s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_DELETE_PLAYBACK_STREAM : + (s->direction == PA_STREAM_RECORD ? PA_COMMAND_DELETE_RECORD_STREAM : PA_COMMAND_DELETE_UPLOAD_STREAM), + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_disconnect_callback, s, NULL); + + pa_stream_unref(s); + return 0; +} + +void pa_stream_set_read_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->read_callback = cb; + s->read_userdata = userdata; +} + +void pa_stream_set_write_callback(pa_stream *s, pa_stream_request_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->write_callback = cb; + s->write_userdata = userdata; +} + +void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->state_callback = cb; + s->state_userdata = userdata; +} + +void pa_stream_set_overflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->overflow_callback = cb; + s->overflow_userdata = userdata; +} + +void pa_stream_set_underflow_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->underflow_callback = cb; + s->underflow_userdata = userdata; +} + +void pa_stream_set_latency_update_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->latency_update_callback = cb; + s->latency_update_userdata = userdata; +} + +void pa_stream_set_moved_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->moved_callback = cb; + s->moved_userdata = userdata; +} + +void pa_stream_set_suspended_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->suspended_callback = cb; + s->suspended_userdata = userdata; +} + +void pa_stream_simple_ack_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + success = 0; + } else if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + if (o->callback) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + +pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + s->corked = b; + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_CORK_PLAYBACK_STREAM : PA_COMMAND_CORK_RECORD_STREAM, + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_put_boolean(t, !!b); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + if (s->direction == PA_STREAM_PLAYBACK) + invalidate_indexes(s, 1, 0); + + return o; +} + +static pa_operation* stream_send_simple_command(pa_stream *s, uint32_t command, pa_stream_success_cb_t cb, void *userdata) { + pa_tagstruct *t; + pa_operation *o; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(s->context, command, &tag); + pa_tagstruct_putu32(t, s->channel); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + if ((o = stream_send_simple_command(s, s->direction == PA_STREAM_PLAYBACK ? PA_COMMAND_FLUSH_PLAYBACK_STREAM : PA_COMMAND_FLUSH_RECORD_STREAM, cb, userdata))) { + + if (s->direction == PA_STREAM_PLAYBACK) { + if (s->write_index_corrections[s->current_write_index_correction].valid) + s->write_index_corrections[s->current_write_index_correction].corrupt = 1; + + if (s->timing_info_valid) + s->timing_info.write_index_corrupt = 1; + + if (s->buffer_attr.prebuf > 0) + invalidate_indexes(s, 1, 0); + else + request_auto_timing_update(s, 1); + } else + invalidate_indexes(s, 0, 1); + } + + return o; +} + +pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); + + if ((o = stream_send_simple_command(s, PA_COMMAND_PREBUF_PLAYBACK_STREAM, cb, userdata))) + invalidate_indexes(s, 1, 0); + + return o; +} + +pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction == PA_STREAM_PLAYBACK, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->buffer_attr.prebuf > 0, PA_ERR_BADSTATE); + + if ((o = stream_send_simple_command(s, PA_COMMAND_TRIGGER_PLAYBACK_STREAM, cb, userdata))) + invalidate_indexes(s, 1, 0); + + return o; +} + +pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(name); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + s->direction == PA_STREAM_RECORD ? PA_COMMAND_SET_RECORD_STREAM_NAME : PA_COMMAND_SET_PLAYBACK_STREAM_NAME, + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_puts(t, name); + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, pa_stream_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec) { + pa_usec_t usec = 0; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_PLAYBACK || !s->timing_info.read_index_corrupt, PA_ERR_NODATA); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_RECORD || !s->timing_info.write_index_corrupt, PA_ERR_NODATA); + + if (s->cached_time_valid) + /* We alredy calculated the time value for this timing info, so let's reuse it */ + usec = s->cached_time; + else { + if (s->direction == PA_STREAM_PLAYBACK) { + /* The last byte that was written into the output device + * had this time value associated */ + usec = pa_bytes_to_usec(s->timing_info.read_index < 0 ? 0 : (uint64_t) s->timing_info.read_index, &s->sample_spec); + + if (!s->corked) { + /* Because the latency info took a little time to come + * to us, we assume that the real output time is actually + * a little ahead */ + usec += s->timing_info.transport_usec; + + /* However, the output device usually maintains a buffer + too, hence the real sample currently played is a little + back */ + if (s->timing_info.sink_usec >= usec) + usec = 0; + else + usec -= s->timing_info.sink_usec; + } + + } else if (s->direction == PA_STREAM_RECORD) { + /* The last byte written into the server side queue had + * this time value associated */ + usec = pa_bytes_to_usec(s->timing_info.write_index < 0 ? 0 : (uint64_t) s->timing_info.write_index, &s->sample_spec); + + if (!s->corked) { + /* Add transport latency */ + usec += s->timing_info.transport_usec; + + /* Add latency of data in device buffer */ + usec += s->timing_info.source_usec; + + /* If this is a monitor source, we need to correct the + * time by the playback device buffer */ + if (s->timing_info.sink_usec >= usec) + usec = 0; + else + usec -= s->timing_info.sink_usec; + } + } + + s->cached_time = usec; + s->cached_time_valid = 1; + } + + /* Interpolate if requested */ + if (s->flags & PA_STREAM_INTERPOLATE_TIMING) { + + /* We just add the time that passed since the latency info was + * current */ + if (!s->corked && s->timing_info.playing) { + struct timeval now; + usec += pa_timeval_diff(pa_gettimeofday(&now), &s->timing_info.timestamp); + } + } + + /* Make sure the time runs monotonically */ + if (!(s->flags & PA_STREAM_NOT_MONOTONOUS)) { + if (usec < s->previous_time) + usec = s->previous_time; + else + s->previous_time = usec; + } + + if (r_usec) + *r_usec = usec; + + return 0; +} + +static pa_usec_t time_counter_diff(pa_stream *s, pa_usec_t a, pa_usec_t b, int *negative) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (negative) + *negative = 0; + + if (a >= b) + return a-b; + else { + if (negative && s->direction == PA_STREAM_RECORD) { + *negative = 1; + return b-a; + } else + return 0; + } +} + +int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative) { + pa_usec_t t, c; + int r; + int64_t cindex; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(r_usec); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->timing_info_valid, PA_ERR_NODATA); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_PLAYBACK || !s->timing_info.write_index_corrupt, PA_ERR_NODATA); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_RECORD || !s->timing_info.read_index_corrupt, PA_ERR_NODATA); + + if ((r = pa_stream_get_time(s, &t)) < 0) + return r; + + if (s->direction == PA_STREAM_PLAYBACK) + cindex = s->timing_info.write_index; + else + cindex = s->timing_info.read_index; + + if (cindex < 0) + cindex = 0; + + c = pa_bytes_to_usec(cindex, &s->sample_spec); + + if (s->direction == PA_STREAM_PLAYBACK) + *r_usec = time_counter_diff(s, c, t, negative); + else + *r_usec = time_counter_diff(s, t, c, negative); + + return 0; +} + +const pa_timing_info* pa_stream_get_timing_info(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->timing_info_valid, PA_ERR_BADSTATE); + + return &s->timing_info; +} + +const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return &s->sample_spec; +} + +const pa_channel_map* pa_stream_get_channel_map(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + return &s->channel_map; +} + +const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 9, PA_ERR_NODATA); + + return &s->buffer_attr; +} + +static void stream_set_buffer_attr_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + success = 0; + } else { + + if (o->stream->direction == PA_STREAM_PLAYBACK) { + if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.tlength) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.prebuf) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.minreq) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } else if (o->stream->direction == PA_STREAM_RECORD) { + if (pa_tagstruct_getu32(t, &o->stream->buffer_attr.maxlength) < 0 || + pa_tagstruct_getu32(t, &o->stream->buffer_attr.fragsize) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + + o->stream->manual_buffer_attr = TRUE; + } + + if (o->stream->state == PA_STREAM_CREATING) { + o->stream->buffer_attr_not_ready = FALSE; + create_stream_complete(o->stream); + } + + if (o->callback) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + + +pa_operation* pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(attr); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command( + s->context, + s->direction == PA_STREAM_RECORD ? PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR : PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR, + &tag); + pa_tagstruct_putu32(t, s->channel); + + pa_tagstruct_putu32(t, attr->maxlength); + + if (s->direction == PA_STREAM_PLAYBACK) + pa_tagstruct_put( + t, + PA_TAG_U32, attr->tlength, + PA_TAG_U32, attr->prebuf, + PA_TAG_U32, attr->minreq, + PA_TAG_INVALID); + else + pa_tagstruct_putu32(t, attr->fragsize); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_set_buffer_attr_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +uint32_t pa_stream_get_device_index(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED, PA_INVALID_INDEX); + PA_CHECK_VALIDITY_RETURN_ANY(s->context, s->device_index != PA_INVALID_INDEX, PA_ERR_BADSTATE, PA_INVALID_INDEX); + + return s->device_index; +} + +const char *pa_stream_get_device_name(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->device_name, PA_ERR_BADSTATE); + + return s->device_name; +} + +int pa_stream_is_suspended(pa_stream *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + + return s->suspended; +} + +static void stream_update_sample_rate_callback(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_operation *o = userdata; + int success = 1; + + pa_assert(pd); + pa_assert(o); + pa_assert(PA_REFCNT_VALUE(o) >= 1); + + if (!o->context) + goto finish; + + if (command != PA_COMMAND_REPLY) { + if (pa_context_handle_error(o->context, command, t) < 0) + goto finish; + + success = 0; + } else { + + if (!pa_tagstruct_eof(t)) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + goto finish; + } + } + + o->stream->sample_spec.rate = PA_PTR_TO_UINT(o->private); + pa_assert(pa_sample_spec_valid(&o->stream->sample_spec)); + + if (o->callback) { + pa_stream_success_cb_t cb = (pa_stream_success_cb_t) o->callback; + cb(o->stream, success, o->userdata); + } + +finish: + pa_operation_done(o); + pa_operation_unref(o); +} + + +pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(s->context, rate > 0 && rate <= PA_RATE_MAX, PA_ERR_INVALID); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->state == PA_STREAM_READY, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->direction != PA_STREAM_UPLOAD, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->flags & PA_STREAM_VARIABLE_RATE, PA_ERR_BADSTATE); + PA_CHECK_VALIDITY_RETURN_NULL(s->context, s->context->version >= 12, PA_ERR_NOTSUPPORTED); + + o = pa_operation_new(s->context, s, (pa_operation_cb_t) cb, userdata); + o->private = PA_UINT_TO_PTR(rate); + + t = pa_tagstruct_command( + s->context, + s->direction == PA_STREAM_RECORD ? PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE : PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE, + &tag); + pa_tagstruct_putu32(t, s->channel); + pa_tagstruct_putu32(t, rate); + + pa_pstream_send_tagstruct(s->context->pstream, t); + pa_pdispatch_register_reply(s->context->pdispatch, tag, DEFAULT_TIMEOUT, stream_update_sample_rate_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; + +} diff --git a/src/pulse/stream.h b/src/pulse/stream.h new file mode 100644 index 00000000..85473227 --- /dev/null +++ b/src/pulse/stream.h @@ -0,0 +1,515 @@ +#ifndef foostreamhfoo +#define foostreamhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> +#include <pulse/def.h> +#include <pulse/cdecl.h> +#include <pulse/operation.h> + +/** \page streams Audio Streams + * + * \section overv_sec Overview + * + * Audio streams form the central functionality of the sound server. Data is + * routed, converted and mixed from several sources before it is passed along + * to a final output. Currently, there are three forms of audio streams: + * + * \li Playback streams - Data flows from the client to the server. + * \li Record streams - Data flows from the server to the client. + * \li Upload streams - Similar to playback streams, but the data is stored in + * the sample cache. See \ref scache for more information + * about controlling the sample cache. + * + * \section create_sec Creating + * + * To access a stream, a pa_stream object must be created using + * pa_stream_new(). At this point the audio sample format and mapping of + * channels must be specified. See \ref sample and \ref channelmap for more + * information about those structures. + * + * This first step will only create a client-side object, representing the + * stream. To use the stream, a server-side object must be created and + * associated with the local object. Depending on which type of stream is + * desired, a different function is needed: + * + * \li Playback stream - pa_stream_connect_playback() + * \li Record stream - pa_stream_connect_record() + * \li Upload stream - pa_stream_connect_upload() (see \ref scache) + * + * Similar to how connections are done in contexts, connecting a stream will + * not generate a pa_operation object. Also like contexts, the application + * should register a state change callback, using + * pa_stream_set_state_callback(), and wait for the stream to enter an active + * state. + * + * \subsection bufattr_subsec Buffer Attributes + * + * Playback and record streams always have a server side buffer as + * part of the data flow. The size of this buffer strikes a + * compromise between low latency and sensitivity for buffer + * overflows/underruns. + * + * The buffer metrics may be controlled by the application. They are + * described with a pa_buffer_attr structure which contains a number + * of fields: + * + * \li maxlength - The absolute maximum number of bytes that can be stored in + * the buffer. If this value is exceeded then data will be + * lost. + * \li tlength - The target length of a playback buffer. The server will only + * send requests for more data as long as the buffer has less + * than this number of bytes of data. + * \li prebuf - Number of bytes that need to be in the buffer before + * playback will commence. Start of playback can be forced using + * pa_stream_trigger() even though the prebuffer size hasn't been + * reached. If a buffer underrun occurs, this prebuffering will be + * again enabled. If the playback shall never stop in case of a buffer + * underrun, this value should be set to 0. In that case the read + * index of the output buffer overtakes the write index, and hence the + * fill level of the buffer is negative. + * \li minreq - Minimum free number of the bytes in the playback buffer before + * the server will request more data. + * \li fragsize - Maximum number of bytes that the server will push in one + * chunk for record streams. + * + * The server side playback buffers are indexed by a write and a read + * index. The application writes to the write index and the sound + * device reads from the read index. The read index is increased + * monotonically, while the write index may be freely controlled by + * the application. Substracting the read index from the write index + * will give you the current fill level of the buffer. The read/write + * indexes are 64bit values and measured in bytes, they will never + * wrap. The current read/write index may be queried using + * pa_stream_get_timing_info() (see below for more information). In + * case of a buffer underrun the read index is equal or larger than + * the write index. Unless the prebuf value is 0, PulseAudio will + * temporarily pause playback in such a case, and wait until the + * buffer is filled up to prebuf bytes again. If prebuf is 0, the + * read index may be larger than the write index, in which case + * silence is played. If the application writes data to indexes lower + * than the read index, the data is immediately lost. + * + * \section transfer_sec Transferring Data + * + * Once the stream is up, data can start flowing between the client and the + * server. Two different access models can be used to transfer the data: + * + * \li Asynchronous - The application register a callback using + * pa_stream_set_write_callback() and + * pa_stream_set_read_callback() to receive notifications + * that data can either be written or read. + * \li Polled - Query the library for available data/space using + * pa_stream_writable_size() and pa_stream_readable_size() and + * transfer data as needed. The sizes are stored locally, in the + * client end, so there is no delay when reading them. + * + * It is also possible to mix the two models freely. + * + * Once there is data/space available, it can be transferred using either + * pa_stream_write() for playback, or pa_stream_peek() / pa_stream_drop() for + * record. Make sure you do not overflow the playback buffers as data will be + * dropped. + * + * \section bufctl_sec Buffer Control + * + * The transfer buffers can be controlled through a number of operations: + * + * \li pa_stream_cork() - Start or stop the playback or recording. + * \li pa_stream_trigger() - Start playback immediatly and do not wait for + * the buffer to fill up to the set trigger level. + * \li pa_stream_prebuf() - Reenable the playback trigger level. + * \li pa_stream_drain() - Wait for the playback buffer to go empty. Will + * return a pa_operation object that will indicate when + * the buffer is completely drained. + * \li pa_stream_flush() - Drop all data from the playback buffer and do not + * wait for it to finish playing. + * + * \section seek_modes Seeking in the Playback Buffer + * + * A client application may freely seek in the playback buffer. To + * accomplish that the pa_stream_write() function takes a seek mode + * and an offset argument. The seek mode is one of: + * + * \li PA_SEEK_RELATIVE - seek relative to the current write index + * \li PA_SEEK_ABSOLUTE - seek relative to the beginning of the playback buffer, (i.e. the first that was ever played in the stream) + * \li PA_SEEK_RELATIVE_ON_READ - seek relative to the current read index. Use this to write data to the output buffer that should be played as soon as possible + * \li PA_SEEK_RELATIVE_END - seek relative to the last byte ever written. + * + * If an application just wants to append some data to the output + * buffer, PA_SEEK_RELATIVE and an offset of 0 should be used. + * + * After a call to pa_stream_write() the write index will be left at + * the position right after the last byte of the written data. + * + * \section latency_sec Latency + * + * A major problem with networked audio is the increased latency caused by + * the network. To remedy this, PulseAudio supports an advanced system of + * monitoring the current latency. + * + * To get the raw data needed to calculate latencies, call + * pa_stream_get_timing_info(). This will give you a pa_timing_info + * structure that contains everything that is known about the server + * side buffer transport delays and the backend active in the + * server. (Besides other things it contains the write and read index + * values mentioned above.) + * + * This structure is updated every time a + * pa_stream_update_timing_info() operation is executed. (i.e. before + * the first call to this function the timing information structure is + * not available!) Since it is a lot of work to keep this structure + * up-to-date manually, PulseAudio can do that automatically for you: + * if PA_STREAM_AUTO_TIMING_UPDATE is passed when connecting the + * stream PulseAudio will automatically update the structure every + * 100ms and every time a function is called that might invalidate the + * previously known timing data (such as pa_stream_write() or + * pa_stream_flush()). Please note however, that there always is a + * short time window when the data in the timing information structure + * is out-of-date. PulseAudio tries to mark these situations by + * setting the write_index_corrupt and read_index_corrupt fields + * accordingly. + * + * The raw timing data in the pa_timing_info structure is usually hard + * to deal with. Therefore a more simplistic interface is available: + * you can call pa_stream_get_time() or pa_stream_get_latency(). The + * former will return the current playback time of the hardware since + * the stream has been started. The latter returns the time a sample + * that you write now takes to be played by the hardware. These two + * functions base their calculations on the same data that is returned + * by pa_stream_get_timing_info(). Hence the same rules for keeping + * the timing data up-to-date apply here. In case the write or read + * index is corrupted, these two functions will fail with + * PA_ERR_NODATA set. + * + * Since updating the timing info structure usually requires a full + * network round trip and some applications monitor the timing very + * often PulseAudio offers a timing interpolation system. If + * PA_STREAM_INTERPOLATE_TIMING is passed when connecting the stream, + * pa_stream_get_time() and pa_stream_get_latency() will try to + * interpolate the current playback time/latency by estimating the + * number of samples that have been played back by the hardware since + * the last regular timing update. It is espcially useful to combine + * this option with PA_STREAM_AUTO_TIMING_UPDATE, which will enable + * you to monitor the current playback time/latency very precisely and + * very frequently without requiring a network round trip every time. + * + * \section flow_sec Overflow and underflow + * + * Even with the best precautions, buffers will sometime over - or + * underflow. To handle this gracefully, the application can be + * notified when this happens. Callbacks are registered using + * pa_stream_set_overflow_callback() and + * pa_stream_set_underflow_callback(). + * + * \section sync_streams Sychronizing Multiple Playback Streams + * + * PulseAudio allows applications to fully synchronize multiple + * playback streams that are connected to the same output device. That + * means the streams will always be played back sample-by-sample + * synchronously. If stream operations like pa_stream_cork() are + * issued on one of the synchronized streams, they are simultaneously + * issued on the others. + * + * To synchronize a stream to another, just pass the "master" stream + * as last argument to pa_stream_connect_playack(). To make sure that + * the freshly created stream doesn't start playback right-away, make + * sure to pass PA_STREAM_START_CORKED and - after all streams have + * been created - uncork them all with a single call to + * pa_stream_cork() for the master stream. + * + * To make sure that a particular stream doesn't stop to play when a + * server side buffer underrun happens on it while the other + * synchronized streams continue playing and hence deviate you need to + * pass a "prebuf" pa_buffer_attr of 0 when connecting it. + * + * \section disc_sec Disconnecting + * + * When a stream has served is purpose it must be disconnected with + * pa_stream_disconnect(). If you only unreference it, then it will live on + * and eat resources both locally and on the server until you disconnect the + * context. + * + */ + +/** \file + * Audio streams for input, output and sample upload */ + +PA_C_DECL_BEGIN + +/** An opaque stream for playback or recording */ +typedef struct pa_stream pa_stream; + +/** A generic callback for operation completion */ +typedef void (*pa_stream_success_cb_t) (pa_stream*s, int success, void *userdata); + +/** A generic request callback */ +typedef void (*pa_stream_request_cb_t)(pa_stream *p, size_t bytes, void *userdata); + +/** A generic notification callback */ +typedef void (*pa_stream_notify_cb_t)(pa_stream *p, void *userdata); + +/** Create a new, unconnected stream with the specified name and sample type */ +pa_stream* pa_stream_new( + pa_context *c /**< The context to create this stream in */, + const char *name /**< A name for this stream */, + const pa_sample_spec *ss /**< The desired sample format */, + const pa_channel_map *map /**< The desired channel map, or NULL for default */); + +/** Decrease the reference counter by one */ +void pa_stream_unref(pa_stream *s); + +/** Increase the reference counter by one */ +pa_stream *pa_stream_ref(pa_stream *s); + +/** Return the current state of the stream */ +pa_stream_state_t pa_stream_get_state(pa_stream *p); + +/** Return the context this stream is attached to */ +pa_context* pa_stream_get_context(pa_stream *p); + +/** Return the sink input resp. source output index this stream is + * identified in the server with. This is useful for usage with the + * introspection functions, such as pa_context_get_sink_input_info() + * resp. pa_context_get_source_output_info(). */ +uint32_t pa_stream_get_index(pa_stream *s); + +/** Return the index of the sink or source this stream is connected to + * in the server. This is useful for usage with the introspection + * functions, such as pa_context_get_sink_info_by_index() + * resp. pa_context_get_source_info_by_index(). Please note that + * streams may be moved between sinks/sources and thus it is + * recommended to use pa_stream_set_moved_callback() to be notified + * about this. This function will return with PA_ERR_NOTSUPPORTED when the + * server is older than 0.9.8. \since 0.9.8 */ +uint32_t pa_stream_get_device_index(pa_stream *s); + +/** Return the name of the sink or source this stream is connected to + * in the server. This is useful for usage with the introspection + * functions, such as pa_context_get_sink_info_by_name() + * resp. pa_context_get_source_info_by_name(). Please note that + * streams may be moved between sinks/sources and thus it is + * recommended to use pa_stream_set_moved_callback() to be notified + * about this. This function will return with PA_ERR_NOTSUPPORTED when the + * server is older than 0.9.8. \since 0.9.8 */ +const char *pa_stream_get_device_name(pa_stream *s); + +/** Return 1 if the sink or source this stream is connected to has + * been suspended. This will return 0 if not, and negative on + * error. This function will return with PA_ERR_NOTSUPPORTED when the + * server is older than 0.9.8. \since 0.9.8 */ +int pa_stream_is_suspended(pa_stream *s); + +/** Connect the stream to a sink */ +int pa_stream_connect_playback( + pa_stream *s /**< The stream to connect to a sink */, + const char *dev /**< Name of the sink to connect to, or NULL for default */ , + const pa_buffer_attr *attr /**< Buffering attributes, or NULL for default */, + pa_stream_flags_t flags /**< Additional flags, or 0 for default */, + pa_cvolume *volume /**< Initial volume, or NULL for default */, + pa_stream *sync_stream /**< Synchronize this stream with the specified one, or NULL for a standalone stream*/); + +/** Connect the stream to a source */ +int pa_stream_connect_record( + pa_stream *s /**< The stream to connect to a source */ , + const char *dev /**< Name of the source to connect to, or NULL for default */, + const pa_buffer_attr *attr /**< Buffer attributes, or NULL for default */, + pa_stream_flags_t flags /**< Additional flags, or 0 for default */); + +/** Disconnect a stream from a source/sink */ +int pa_stream_disconnect(pa_stream *s); + +/** Write some data to the server (for playback sinks), if free_cb is + * non-NULL this routine is called when all data has been written out + * and an internal reference to the specified data is kept, the data + * is not copied. If NULL, the data is copied into an internal + * buffer. The client my freely seek around in the output buffer. For + * most applications passing 0 and PA_SEEK_RELATIVE as arguments for + * offset and seek should be useful.*/ +int pa_stream_write( + pa_stream *p /**< The stream to use */, + const void *data /**< The data to write */, + size_t bytes /**< The length of the data to write in bytes*/, + pa_free_cb_t free_cb /**< A cleanup routine for the data or NULL to request an internal copy */, + int64_t offset, /**< Offset for seeking, must be 0 for upload streams */ + pa_seek_mode_t seek /**< Seek mode, must be PA_SEEK_RELATIVE for upload streams */); + +/** Read the next fragment from the buffer (for recording). + * data will point to the actual data and length will contain the size + * of the data in bytes (which can be less than a complete framgnet). + * Use pa_stream_drop() to actually remove the data from the + * buffer. If no data is available will return a NULL pointer \since 0.8 */ +int pa_stream_peek( + pa_stream *p /**< The stream to use */, + const void **data /**< Pointer to pointer that will point to data */, + size_t *bytes /**< The length of the data read in bytes */); + +/** Remove the current fragment on record streams. It is invalid to do this without first + * calling pa_stream_peek(). \since 0.8 */ +int pa_stream_drop(pa_stream *p); + +/** Return the number of bytes that may be written using pa_stream_write() */ +size_t pa_stream_writable_size(pa_stream *p); + +/** Return the number of bytes that may be read using pa_stream_read() \since 0.8 */ +size_t pa_stream_readable_size(pa_stream *p); + +/** Drain a playback stream. Use this for notification when the buffer is empty */ +pa_operation* pa_stream_drain(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); + +/** Request a timing info structure update for a stream. Use + * pa_stream_get_timing_info() to get access to the raw timing data, + * or pa_stream_get_time() or pa_stream_get_latency() to get cleaned + * up values. */ +pa_operation* pa_stream_update_timing_info(pa_stream *p, pa_stream_success_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever the state of the stream changes */ +void pa_stream_set_state_callback(pa_stream *s, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called when new data may be + * written to the stream. */ +void pa_stream_set_write_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata); + +/** Set the callback function that is called when new data is available from the stream. + * Return the number of bytes read. \since 0.8 */ +void pa_stream_set_read_callback(pa_stream *p, pa_stream_request_cb_t cb, void *userdata); + +/** Set the callback function that is called when a buffer overflow happens. (Only for playback streams) \since 0.8 */ +void pa_stream_set_overflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called when a buffer underflow happens. (Only for playback streams) \since 0.8 */ +void pa_stream_set_underflow_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever a latency + * information update happens. Useful on PA_STREAM_AUTO_TIMING_UPDATE + * streams only. (Only for playback streams) \since 0.8.2 */ +void pa_stream_set_latency_update_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever the stream is + * moved to a different sink/source. Use pa_stream_get_device_name()or + * pa_stream_get_device_index() to query the new sink/source. This + * notification is only generated when the server is at least + * 0.9.8. \since 0.9.8 */ +void pa_stream_set_moved_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Set the callback function that is called whenever the sink/source + * this stream is connected to is suspended or resumed. Use + * pa_stream_is_suspended() to query the new suspend status. Please + * note that the suspend status might also change when the stream is + * moved between devices. Thus if you call this function you very + * likely want to call pa_stream_set_moved_callback, too. This + * notification is only generated when the server is at least + * 0.9.8. \since 0.9.8 */ +void pa_stream_set_suspended_callback(pa_stream *p, pa_stream_notify_cb_t cb, void *userdata); + +/** Pause (or resume) playback of this stream temporarily. Available on both playback and recording streams. \since 0.3 */ +pa_operation* pa_stream_cork(pa_stream *s, int b, pa_stream_success_cb_t cb, void *userdata); + +/** Flush the playback buffer of this stream. Most of the time you're + * better off using the parameter delta of pa_stream_write() instead of this + * function. Available on both playback and recording streams. \since 0.3 */ +pa_operation* pa_stream_flush(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); + +/** Reenable prebuffering as specified in the pa_buffer_attr + * structure. Available for playback streams only. \since 0.6 */ +pa_operation* pa_stream_prebuf(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); + +/** Request immediate start of playback on this stream. This disables + * prebuffering as specified in the pa_buffer_attr + * structure, temporarily. Available for playback streams only. \since 0.3 */ +pa_operation* pa_stream_trigger(pa_stream *s, pa_stream_success_cb_t cb, void *userdata); + +/** Rename the stream. \since 0.5 */ +pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_success_cb_t cb, void *userdata); + +/** Return the current playback/recording time. This is based on the + * data in the timing info structure returned by + * pa_stream_get_timing_info(). This function will usually only return + * new data if a timing info update has been recieved. Only if timing + * interpolation has been requested (PA_STREAM_INTERPOLATE_TIMING) + * the data from the last timing update is used for an estimation of + * the current playback/recording time based on the local time that + * passed since the timing info structure has been acquired. The time + * value returned by this function is guaranteed to increase + * monotonically. (that means: the returned value is always greater or + * equal to the value returned on the last call) This behaviour can + * be disabled by using PA_STREAM_NOT_MONOTONOUS. This may be + * desirable to deal better with bad estimations of transport + * latencies, but may have strange effects if the application is not + * able to deal with time going 'backwards'. \since 0.6 */ +int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec); + +/** Return the total stream latency. This function is based on + * pa_stream_get_time(). In case the stream is a monitoring stream the + * result can be negative, i.e. the captured samples are not yet + * played. In this case *negative is set to 1. \since 0.6 */ +int pa_stream_get_latency(pa_stream *s, pa_usec_t *r_usec, int *negative); + +/** Return the latest raw timing data structure. The returned pointer + * points to an internal read-only instance of the timing + * structure. The user should make a copy of this structure if he + * wants to modify it. An in-place update to this data structure may + * be requested using pa_stream_update_timing_info(). If no + * pa_stream_update_timing_info() call was issued before, this + * function will fail with PA_ERR_NODATA. Please note that the + * write_index member field (and only this field) is updated on each + * pa_stream_write() call, not just when a timing update has been + * recieved. \since 0.8 */ +const pa_timing_info* pa_stream_get_timing_info(pa_stream *s); + +/** Return a pointer to the stream's sample specification. \since 0.6 */ +const pa_sample_spec* pa_stream_get_sample_spec(pa_stream *s); + +/** Return a pointer to the stream's channel map. \since 0.8 */ +const pa_channel_map* pa_stream_get_channel_map(pa_stream *s); + +/** Return the buffer metrics of the stream. Only valid after the + * stream has been connected successfuly and if the server is at least + * PulseAudio 0.9. \since 0.9.0 */ +const pa_buffer_attr* pa_stream_get_buffer_attr(pa_stream *s); + +/** Change the buffer metrics of the stream during playback. The + * server might have chosen different buffer metrics then + * requested. The selected metrics may be queried with + * pa_stream_get_buffer_attr() as soon as the callback is called. Only + * valid after the stream has been connected successfully and if the + * server is at least PulseAudio 0.9.8. \since 0.9.8 */ +pa_operation *pa_stream_set_buffer_attr(pa_stream *s, const pa_buffer_attr *attr, pa_stream_success_cb_t cb, void *userdata); + +/* Change the stream sampling rate during playback. You need to pass + * PA_STREAM_VARIABLE_RATE in the flags parameter of + * pa_stream_connect() if you plan to use this function. Only valid + * after the stream has been connected successfully and if the server + * is at least PulseAudio 0.9.8. \since 0.9.8 */ +pa_operation *pa_stream_update_sample_rate(pa_stream *s, uint32_t rate, pa_stream_success_cb_t cb, void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/subscribe.c b/src/pulse/subscribe.c new file mode 100644 index 00000000..580038cc --- /dev/null +++ b/src/pulse/subscribe.c @@ -0,0 +1,92 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> +#include <pulsecore/pstream-util.h> + +#include "internal.h" + +#include "subscribe.h" + +void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, PA_GCC_UNUSED uint32_t tag, pa_tagstruct *t, void *userdata) { + pa_context *c = userdata; + pa_subscription_event_type_t e; + uint32_t idx; + + pa_assert(pd); + pa_assert(command == PA_COMMAND_SUBSCRIBE_EVENT); + pa_assert(t); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + pa_context_ref(c); + + if (pa_tagstruct_getu32(t, &e) < 0 || + pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + pa_context_fail(c, PA_ERR_PROTOCOL); + goto finish; + } + + if (c->subscribe_callback) + c->subscribe_callback(c, e, idx, c->subscribe_userdata); + +finish: + pa_context_unref(c); +} + + +pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) { + pa_operation *o; + pa_tagstruct *t; + uint32_t tag; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, PA_ERR_BADSTATE); + + o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata); + + t = pa_tagstruct_command(c, PA_COMMAND_SUBSCRIBE, &tag); + pa_tagstruct_putu32(t, m); + pa_pstream_send_tagstruct(c->pstream, t); + pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, pa_context_simple_ack_callback, pa_operation_ref(o), (pa_free_cb_t) pa_operation_unref); + + return o; +} + +void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + c->subscribe_callback = cb; + c->subscribe_userdata = userdata; +} diff --git a/src/pulse/subscribe.h b/src/pulse/subscribe.h new file mode 100644 index 00000000..c37ead57 --- /dev/null +++ b/src/pulse/subscribe.h @@ -0,0 +1,64 @@ +#ifndef foosubscribehfoo +#define foosubscribehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/def.h> +#include <pulse/context.h> +#include <pulse/cdecl.h> + +/** \page subscribe Event Subscription + * + * \section overv_sec Overview + * + * The application can be notified, asynchronously, whenever the internal + * layout of the server changes. Possible notifications are desribed in the + * \ref pa_subscription_event_type and \ref pa_subscription_mask + * enumerations. + * + * The application sets the notification mask using pa_context_subscribe() + * and the function that will be called whenever a notification occurs using + * pa_context_set_subscribe_callback(). + */ + +/** \file + * Daemon introspection event subscription subsystem. */ + +PA_C_DECL_BEGIN + +/** Subscription event callback prototype */ +typedef void (*pa_context_subscribe_cb_t)(pa_context *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata); + +/** Enable event notification */ +pa_operation* pa_context_subscribe(pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata); + +/** Set the context specific call back function that is called whenever the state of the daemon changes */ +void pa_context_set_subscribe_callback(pa_context *c, pa_context_subscribe_cb_t cb, void *userdata); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/thread-mainloop.c b/src/pulse/thread-mainloop.c new file mode 100644 index 00000000..e8c956bb --- /dev/null +++ b/src/pulse/thread-mainloop.c @@ -0,0 +1,231 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <signal.h> +#include <stdio.h> + +#ifdef HAVE_POLL_H +#include <poll.h> +#else +#include <pulsecore/poll.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/mainloop.h> + +#include <pulsecore/log.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/thread.h> +#include <pulsecore/mutex.h> +#include <pulsecore/macro.h> + +#include "thread-mainloop.h" + +struct pa_threaded_mainloop { + pa_mainloop *real_mainloop; + int n_waiting; + + pa_thread* thread; + pa_mutex* mutex; + pa_cond* cond, *accept_cond; +}; + +static inline int in_worker(pa_threaded_mainloop *m) { + return pa_thread_self() == m->thread; +} + +static int poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) { + pa_mutex *mutex = userdata; + int r; + + pa_assert(mutex); + + /* Before entering poll() we unlock the mutex, so that + * avahi_simple_poll_quit() can succeed from another thread. */ + + pa_mutex_unlock(mutex); + r = poll(ufds, nfds, timeout); + pa_mutex_lock(mutex); + + return r; +} + +static void thread(void *userdata) { + pa_threaded_mainloop *m = userdata; + +#ifndef OS_IS_WIN32 + sigset_t mask; + + /* Make sure that signals are delivered to the main thread */ + sigfillset(&mask); + pthread_sigmask(SIG_BLOCK, &mask, NULL); +#endif + + pa_mutex_lock(m->mutex); + + pa_mainloop_run(m->real_mainloop, NULL); + + pa_mutex_unlock(m->mutex); +} + +pa_threaded_mainloop *pa_threaded_mainloop_new(void) { + pa_threaded_mainloop *m; + + m = pa_xnew(pa_threaded_mainloop, 1); + + if (!(m->real_mainloop = pa_mainloop_new())) { + pa_xfree(m); + return NULL; + } + + m->mutex = pa_mutex_new(TRUE, TRUE); + m->cond = pa_cond_new(); + m->accept_cond = pa_cond_new(); + m->thread = NULL; + + pa_mainloop_set_poll_func(m->real_mainloop, poll_func, m->mutex); + + m->n_waiting = 0; + + return m; +} + +void pa_threaded_mainloop_free(pa_threaded_mainloop* m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread */ + pa_assert((m->thread && !pa_thread_is_running(m->thread)) || !in_worker(m)); + + pa_threaded_mainloop_stop(m); + + if (m->thread) + pa_thread_free(m->thread); + + pa_mainloop_free(m->real_mainloop); + + pa_mutex_free(m->mutex); + pa_cond_free(m->cond); + pa_cond_free(m->accept_cond); + + pa_xfree(m); +} + +int pa_threaded_mainloop_start(pa_threaded_mainloop *m) { + pa_assert(m); + + pa_assert(!m->thread || !pa_thread_is_running(m->thread)); + + if (!(m->thread = pa_thread_new(thread, m))) + return -1; + + return 0; +} + +void pa_threaded_mainloop_stop(pa_threaded_mainloop *m) { + pa_assert(m); + + if (!m->thread || !pa_thread_is_running(m->thread)) + return; + + /* Make sure that this function is not called from the helper thread */ + pa_assert(!in_worker(m)); + + pa_mutex_lock(m->mutex); + pa_mainloop_quit(m->real_mainloop, 0); + pa_mutex_unlock(m->mutex); + + pa_thread_join(m->thread); +} + +void pa_threaded_mainloop_lock(pa_threaded_mainloop *m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + + pa_mutex_lock(m->mutex); +} + +void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + + pa_mutex_unlock(m->mutex); +} + +void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept) { + pa_assert(m); + + pa_cond_signal(m->cond, 1); + + if (wait_for_accept && m->n_waiting > 0) + pa_cond_wait(m->accept_cond, m->mutex); +} + +void pa_threaded_mainloop_wait(pa_threaded_mainloop *m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + + m->n_waiting ++; + + pa_cond_wait(m->cond, m->mutex); + + pa_assert(m->n_waiting > 0); + m->n_waiting --; +} + +void pa_threaded_mainloop_accept(pa_threaded_mainloop *m) { + pa_assert(m); + + /* Make sure that this function is not called from the helper thread */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + + pa_cond_signal(m->accept_cond, 0); +} + +int pa_threaded_mainloop_get_retval(pa_threaded_mainloop *m) { + pa_assert(m); + + return pa_mainloop_get_retval(m->real_mainloop); +} + +pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m) { + pa_assert(m); + + return pa_mainloop_get_api(m->real_mainloop); +} + +int pa_threaded_mainloop_in_thread(pa_threaded_mainloop *m) { + pa_assert(m); + + return m->thread && pa_thread_self() == m->thread; +} diff --git a/src/pulse/thread-mainloop.h b/src/pulse/thread-mainloop.h new file mode 100644 index 00000000..ea08f72a --- /dev/null +++ b/src/pulse/thread-mainloop.h @@ -0,0 +1,305 @@ +#ifndef foothreadmainloophfoo +#define foothreadmainloophfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/cdecl.h> + +PA_C_DECL_BEGIN + +/** \page threaded_mainloop Threaded Main Loop + * + * \section overv_sec Overview + * + * The threaded main loop implementation is a special version of the primary + * main loop implementation (see \ref mainloop). For the basic design, see + * its documentation. + * + * The added feature in the threaded main loop is that it spawns a new thread + * that runs the real main loop. This allows a synchronous application to use + * the asynchronous API without risking to stall the PulseAudio library. + * + * \section creat_sec Creation + * + * A pa_threaded_mainloop object is created using pa_threaded_mainloop_new(). + * This will only allocate the required structures though, so to use it the + * thread must also be started. This is done through + * pa_threaded_mainloop_start(), after which you can start using the main loop. + * + * \section destr_sec Destruction + * + * When the PulseAudio connection has been terminated, the thread must be + * stopped and the resources freed. Stopping the thread is done using + * pa_threaded_mainloop_stop(), which must be called without the lock (see + * below) held. When that function returns, the thread is stopped and the + * pa_threaded_mainloop object can be freed using pa_threaded_mainloop_free(). + * + * \section lock_sec Locking + * + * Since the PulseAudio API doesn't allow concurrent accesses to objects, + * a locking scheme must be used to guarantee safe usage. The threaded main + * loop API provides such a scheme through the functions + * pa_threaded_mainloop_lock() and pa_threaded_mainloop_unlock(). + * + * The lock is recursive, so it's safe to use it multiple times from the same + * thread. Just make sure you call pa_threaded_mainloop_unlock() the same + * number of times you called pa_threaded_mainloop_lock(). + * + * The lock needs to be held whenever you call any PulseAudio function that + * uses an object associated with this main loop. Make sure you do not hold + * on to the lock more than necessary though, as the threaded main loop stops + * while the lock is held. + * + * Example: + * + * \code + * void my_check_stream_func(pa_threaded_mainloop *m, pa_stream *s) { + * pa_stream_state_t state; + * + * pa_threaded_mainloop_lock(m); + * + * state = pa_stream_get_state(s); + * + * pa_threaded_mainloop_unlock(m); + * + * if (state == PA_STREAM_READY) + * printf("Stream is ready!"); + * else + * printf("Stream is not ready!"); + * } + * \endcode + * + * \section cb_sec Callbacks + * + * Callbacks in PulseAudio are asynchronous, so they require extra care when + * using them together with a threaded main loop. + * + * The easiest way to turn the callback based operations into synchronous + * ones, is to simply wait for the callback to be called and continue from + * there. This is the approach chosen in PulseAudio's threaded API. + * + * \subsection basic_subsec Basic callbacks + * + * For the basic case, where all that is required is to wait for the callback + * to be invoked, the code should look something like this: + * + * Example: + * + * \code + * static void my_drain_callback(pa_stream*s, int success, void *userdata) { + * pa_threaded_mainloop *m; + * + * m = (pa_threaded_mainloop*)userdata; + * assert(m); + * + * pa_threaded_mainloop_signal(m, 0); + * } + * + * void my_drain_stream_func(pa_threaded_mainloop *m, pa_stream *s) { + * pa_operation *o; + * + * pa_threaded_mainloop_lock(m); + * + * o = pa_stream_drain(s, my_drain_callback, m); + * assert(o); + * + * while (pa_operation_get_state(o) != OPERATION_DONE) + * pa_threaded_mainloop_wait(m); + * + * pa_operation_unref(o); + * + * pa_threaded_mainloop_unlock(m); + * } + * \endcode + * + * The main function, my_drain_stream_func(), will wait for the callback to + * be called using pa_threaded_mainloop_wait(). + * + * If your application is multi-threaded, then this waiting must be done + * inside a while loop. The reason for this is that multiple threads might be + * using pa_threaded_mainloop_wait() at the same time. Each thread must + * therefore verify that it was its callback that was invoked. + * + * The callback, my_drain_callback(), indicates to the main function that it + * has been called using pa_threaded_mainloop_signal(). + * + * As you can see, both pa_threaded_mainloop_wait() may only be called with + * the lock held. The same thing is true for pa_threaded_mainloop_signal(), + * but as the lock is held before the callback is invoked, you do not have to + * deal with that. + * + * The functions will not dead lock because the wait function will release + * the lock before waiting and then regrab it once it has been signaled. + * For those of you familiar with threads, the behaviour is that of a + * condition variable. + * + * \subsection data_subsec Data callbacks + * + * For many callbacks, simply knowing that they have been called is + * insufficient. The callback also receives some data that is desired. To + * access this data safely, we must extend our example a bit: + * + * \code + * static int *drain_result; + * + * static void my_drain_callback(pa_stream*s, int success, void *userdata) { + * pa_threaded_mainloop *m; + * + * m = (pa_threaded_mainloop*)userdata; + * assert(m); + * + * drain_result = &success; + * + * pa_threaded_mainloop_signal(m, 1); + * } + * + * void my_drain_stream_func(pa_threaded_mainloop *m, pa_stream *s) { + * pa_operation *o; + * + * pa_threaded_mainloop_lock(m); + * + * o = pa_stream_drain(s, my_drain_callback, m); + * assert(o); + * + * while (pa_operation_get_state(o) != OPERATION_DONE) + * pa_threaded_mainloop_wait(m); + * + * pa_operation_unref(o); + * + * if (*drain_result) + * printf("Success!"); + * else + * printf("Bitter defeat..."); + * + * pa_threaded_mainloop_accept(m); + * + * pa_threaded_mainloop_unlock(m); + * } + * \endcode + * + * The example is a bit silly as it would probably have been easier to just + * copy the contents of success, but for larger data structures this can be + * wasteful. + * + * The difference here compared to the basic callback is the 1 sent to + * pa_threaded_mainloop_signal() and the call to + * pa_threaded_mainloop_accept(). What will happen is that + * pa_threaded_mainloop_signal() will signal the main function and then stop. + * The main function is then free to use the data in the callback until + * pa_threaded_mainloop_accept() is called, which will allow the callback + * to continue. + * + * Note that pa_threaded_mainloop_accept() must be called some time between + * exiting the while loop and unlocking the main loop! Failure to do so will + * result in a race condition. I.e. it is not ok to release the lock and + * regrab it before calling pa_threaded_mainloop_accept(). + * + * \subsection async_subsec Asynchronous callbacks + * + * PulseAudio also has callbacks that are completely asynchronous, meaning + * that they can be called at any time. The threading main loop API provides + * the locking mechanism to handle concurrent accesses, but nothing else. + * Applications will have to handle communication from the callback to the + * main program through some own system. + * + * The callbacks that are completely asynchronous are: + * + * \li State callbacks for contexts, streams, etc. + * \li Subscription notifications + */ + +/** \file + * + * A thread based event loop implementation based on pa_mainloop. The + * event loop is run in a helper thread in the background. A few + * synchronization primitives are available to access the objects + * attached to the event loop safely. */ + +/** An opaque threaded main loop object */ +typedef struct pa_threaded_mainloop pa_threaded_mainloop; + +/** Allocate a new threaded main loop object. You have to call + * pa_threaded_mainloop_start() before the event loop thread starts + * running. */ +pa_threaded_mainloop *pa_threaded_mainloop_new(void); + +/** Free a threaded main loop object. If the event loop thread is + * still running, it is terminated using pa_threaded_mainloop_stop() + * first. */ +void pa_threaded_mainloop_free(pa_threaded_mainloop* m); + +/** Start the event loop thread. */ +int pa_threaded_mainloop_start(pa_threaded_mainloop *m); + +/** Terminate the event loop thread cleanly. Make sure to unlock the + * mainloop object before calling this function. */ +void pa_threaded_mainloop_stop(pa_threaded_mainloop *m); + +/** Lock the event loop object, effectively blocking the event loop + * thread from processing events. You can use this to enforce + * exclusive access to all objects attached to the event loop. This + * lock is recursive. This function may not be called inside the event + * loop thread. Events that are dispatched from the event loop thread + * are executed with this lock held. */ +void pa_threaded_mainloop_lock(pa_threaded_mainloop *m); + +/** Unlock the event loop object, inverse of pa_threaded_mainloop_lock() */ +void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m); + +/** Wait for an event to be signalled by the event loop thread. You + * can use this to pass data from the event loop thread to the main + * thread in synchronized fashion. This function may not be called + * inside the event loop thread. Prior to this call the event loop + * object needs to be locked using pa_threaded_mainloop_lock(). While + * waiting the lock will be released, immediately before returning it + * will be acquired again. */ +void pa_threaded_mainloop_wait(pa_threaded_mainloop *m); + +/** Signal all threads waiting for a signalling event in + * pa_threaded_mainloop_wait(). If wait_for_release is non-zero, do + * not return before the signal was accepted by a + * pa_threaded_mainloop_accept() call. While waiting for that condition + * the event loop object is unlocked. */ +void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept); + +/** Accept a signal from the event thread issued with + * pa_threaded_mainloop_signal(). This call should only be used in + * conjunction with pa_threaded_mainloop_signal() with a non-zero + * wait_for_accept value. */ +void pa_threaded_mainloop_accept(pa_threaded_mainloop *m); + +/** Return the return value as specified with the main loop's quit() routine. */ +int pa_threaded_mainloop_get_retval(pa_threaded_mainloop *m); + +/** Return the abstract main loop abstraction layer vtable for this main loop. */ +pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m); + +/** Returns non-zero when called from withing the event loop thread. \since 0.9.7 */ +int pa_threaded_mainloop_in_thread(pa_threaded_mainloop *m); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/timeval.c b/src/pulse/timeval.c new file mode 100644 index 00000000..70ceb71e --- /dev/null +++ b/src/pulse/timeval.c @@ -0,0 +1,166 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stddef.h> +#include <sys/time.h> + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#include <pulsecore/winsock.h> +#include <pulsecore/macro.h> + +#include "timeval.h" + +struct timeval *pa_gettimeofday(struct timeval *tv) { +#ifdef HAVE_GETTIMEOFDAY + pa_assert(tv); + + pa_assert_se(gettimeofday(tv, NULL) == 0); + return tv; +#elif defined(OS_IS_WIN32) + /* + * Copied from implementation by Steven Edwards (LGPL). + * Found on wine mailing list. + */ + +#if defined(_MSC_VER) || defined(__BORLANDC__) +#define EPOCHFILETIME (116444736000000000i64) +#else +#define EPOCHFILETIME (116444736000000000LL) +#endif + + FILETIME ft; + LARGE_INTEGER li; + __int64 t; + + pa_assert(tv); + + GetSystemTimeAsFileTime(&ft); + li.LowPart = ft.dwLowDateTime; + li.HighPart = ft.dwHighDateTime; + t = li.QuadPart; /* In 100-nanosecond intervals */ + t -= EPOCHFILETIME; /* Offset to the Epoch time */ + t /= 10; /* In microseconds */ + tv->tv_sec = (time_t) (t / PA_USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (t % PA_USEC_PER_SEC); + + return tv; +#else +#error "Platform lacks gettimeofday() or equivalent function." +#endif +} + +pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) { + pa_usec_t r; + + pa_assert(a); + pa_assert(b); + + /* Check which whan is the earlier time and swap the two arguments if required. */ + if (pa_timeval_cmp(a, b) < 0) { + const struct timeval *c; + c = a; + a = b; + b = c; + } + + /* Calculate the second difference*/ + r = ((pa_usec_t) a->tv_sec - b->tv_sec) * PA_USEC_PER_SEC; + + /* Calculate the microsecond difference */ + if (a->tv_usec > b->tv_usec) + r += ((pa_usec_t) a->tv_usec - b->tv_usec); + else if (a->tv_usec < b->tv_usec) + r -= ((pa_usec_t) b->tv_usec - a->tv_usec); + + return r; +} + +int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) { + pa_assert(a); + pa_assert(b); + + if (a->tv_sec < b->tv_sec) + return -1; + + if (a->tv_sec > b->tv_sec) + return 1; + + if (a->tv_usec < b->tv_usec) + return -1; + + if (a->tv_usec > b->tv_usec) + return 1; + + return 0; +} + +pa_usec_t pa_timeval_age(const struct timeval *tv) { + struct timeval now; + pa_assert(tv); + + return pa_timeval_diff(pa_gettimeofday(&now), tv); +} + +struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v) { + unsigned long secs; + pa_assert(tv); + + secs = (unsigned long) (v/PA_USEC_PER_SEC); + tv->tv_sec += secs; + v -= ((pa_usec_t) secs) * PA_USEC_PER_SEC; + + tv->tv_usec += (suseconds_t) v; + + /* Normalize */ + while (tv->tv_usec >= PA_USEC_PER_SEC) { + tv->tv_sec++; + tv->tv_usec -= PA_USEC_PER_SEC; + } + + return tv; +} + +struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v) { + pa_assert(tv); + + tv->tv_sec = v / PA_USEC_PER_SEC; + tv->tv_usec = v % PA_USEC_PER_SEC; + + return tv; +} + +pa_usec_t pa_timeval_load(const struct timeval *tv) { + pa_assert(tv); + + return + (pa_usec_t) tv->tv_sec * PA_USEC_PER_SEC + + (pa_usec_t) tv->tv_usec; +} diff --git a/src/pulse/timeval.h b/src/pulse/timeval.h new file mode 100644 index 00000000..65a0e513 --- /dev/null +++ b/src/pulse/timeval.h @@ -0,0 +1,67 @@ +#ifndef footimevalhfoo +#define footimevalhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/cdecl.h> +#include <pulse/sample.h> + +/** \file + * Utility functions for handling timeval calculations */ + +PA_C_DECL_BEGIN + +#define PA_MSEC_PER_SEC 1000 +#define PA_USEC_PER_SEC 1000000 +#define PA_NSEC_PER_SEC 1000000000 +#define PA_USEC_PER_MSEC 1000 + +struct timeval; + +/** Return the current timestamp, just like UNIX gettimeofday() */ +struct timeval *pa_gettimeofday(struct timeval *tv); + +/** Calculate the difference between the two specified timeval + * structs. */ +pa_usec_t pa_timeval_diff(const struct timeval *a, const struct timeval *b) PA_GCC_PURE; + +/** Compare the two timeval structs and return 0 when equal, negative when a < b, positive otherwse */ +int pa_timeval_cmp(const struct timeval *a, const struct timeval *b) PA_GCC_PURE; + +/** Return the time difference between now and the specified timestamp */ +pa_usec_t pa_timeval_age(const struct timeval *tv); + +/** Add the specified time inmicroseconds to the specified timeval structure */ +struct timeval* pa_timeval_add(struct timeval *tv, pa_usec_t v) PA_GCC_PURE; + +/** Store the specified uec value in the timeval struct. \since 0.9.7 */ +struct timeval* pa_timeval_store(struct timeval *tv, pa_usec_t v); + +/** Load the specified tv value and return it in usec. \since 0.9.7 */ +pa_usec_t pa_timeval_load(const struct timeval *tv); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/utf8.c b/src/pulse/utf8.c new file mode 100644 index 00000000..b2f6c3bd --- /dev/null +++ b/src/pulse/utf8.c @@ -0,0 +1,267 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +/* This file is based on the GLIB utf8 validation functions. The + * original license text follows. */ + +/* gutf8.c - Operations on UTF-8 strings. + * + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * + * This library 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. + * + * This library 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 this library; 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 <errno.h> +#include <stdlib.h> +#include <inttypes.h> +#include <string.h> + +#ifdef HAVE_ICONV +#include <iconv.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "utf8.h" + +#define FILTER_CHAR '_' + +static inline int is_unicode_valid(uint32_t ch) { + + if (ch >= 0x110000) /* End of unicode space */ + return 0; + if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ + return 0; + if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ + return 0; + if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ + return 0; + + return 1; +} + +static inline int is_continuation_char(uint8_t ch) { + if ((ch & 0xc0) != 0x80) /* 10xxxxxx */ + return 0; + return 1; +} + +static inline void merge_continuation_char(uint32_t *u_ch, uint8_t ch) { + *u_ch <<= 6; + *u_ch |= ch & 0x3f; +} + +static char* utf8_validate(const char *str, char *output) { + uint32_t val = 0; + uint32_t min = 0; + const uint8_t *p, *last; + int size; + uint8_t *o; + + pa_assert(str); + + o = (uint8_t*) output; + for (p = (const uint8_t*) str; *p; p++) { + if (*p < 128) { + if (o) + *o = *p; + } else { + last = p; + + if ((*p & 0xe0) == 0xc0) { /* 110xxxxx two-char seq. */ + size = 2; + min = 128; + val = *p & 0x1e; + goto ONE_REMAINING; + } else if ((*p & 0xf0) == 0xe0) { /* 1110xxxx three-char seq.*/ + size = 3; + min = (1 << 11); + val = *p & 0x0f; + goto TWO_REMAINING; + } else if ((*p & 0xf8) == 0xf0) { /* 11110xxx four-char seq */ + size = 4; + min = (1 << 16); + val = *p & 0x07; + } else { + size = 1; + goto error; + } + + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + +TWO_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + +ONE_REMAINING: + p++; + if (!is_continuation_char(*p)) + goto error; + merge_continuation_char(&val, *p); + + if (val < min) + goto error; + + if (!is_unicode_valid(val)) + goto error; + + if (o) { + memcpy(o, last, size); + o += size - 1; + } + + if (o) + o++; + + continue; + +error: + if (o) { + *o = FILTER_CHAR; + p = last; /* We retry at the next character */ + } else + goto failure; + } + + if (o) + o++; + } + + if (o) { + *o = '\0'; + return output; + } + + return (char*) str; + +failure: + return NULL; +} + +char* pa_utf8_valid (const char *str) { + return utf8_validate(str, NULL); +} + +char* pa_utf8_filter (const char *str) { + char *new_str; + + pa_assert(str); + new_str = pa_xnew(char, strlen(str) + 1); + return utf8_validate(str, new_str); +} + +#ifdef HAVE_ICONV + +static char* iconv_simple(const char *str, const char *to, const char *from) { + char *new_str; + size_t len, inlen; + iconv_t cd; + ICONV_CONST char *inbuf; + char *outbuf; + size_t res, inbytes, outbytes; + + pa_assert(str); + pa_assert(to); + pa_assert(from); + + cd = iconv_open(to, from); + if (cd == (iconv_t)-1) + return NULL; + + inlen = len = strlen(str) + 1; + new_str = pa_xnew(char, len); + + for (;;) { + inbuf = (ICONV_CONST char*) str; /* Brain dead prototype for iconv() */ + inbytes = inlen; + outbuf = new_str; + outbytes = len; + + res = iconv(cd, &inbuf, &inbytes, &outbuf, &outbytes); + + if (res != (size_t)-1) + break; + + if (errno != E2BIG) { + pa_xfree(new_str); + new_str = NULL; + break; + } + + pa_assert(inbytes != 0); + + len += inbytes; + new_str = pa_xrealloc(new_str, len); + } + + iconv_close(cd); + + return new_str; +} + +char* pa_utf8_to_locale (const char *str) { + return iconv_simple(str, "", "UTF-8"); +} + +char* pa_locale_to_utf8 (const char *str) { + return iconv_simple(str, "UTF-8", ""); +} + +#else + +char* pa_utf8_to_locale (const char *str) { + pa_assert(str); + return NULL; +} + +char* pa_locale_to_utf8 (const char *str) { + pa_assert(str); + return NULL; +} + +#endif diff --git a/src/pulse/utf8.h b/src/pulse/utf8.h new file mode 100644 index 00000000..1e08047c --- /dev/null +++ b/src/pulse/utf8.h @@ -0,0 +1,50 @@ +#ifndef fooutf8hfoo +#define fooutf8hfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/cdecl.h> + +/** \file + * UTF8 Validation functions + */ + +PA_C_DECL_BEGIN + +/** Test if the specified strings qualifies as valid UTF8. Return the string if so, otherwise NULL */ +char *pa_utf8_valid(const char *str) PA_GCC_PURE; + +/** Filter all invalid UTF8 characters from the specified string, returning a new fully UTF8 valid string. Don't forget to free the returned string with pa_xfree() */ +char *pa_utf8_filter(const char *str); + +/** Convert a UTF-8 string to the current locale. Free the string using pa_xfree(). */ +char* pa_utf8_to_locale (const char *str); + +/** Convert a string in the current locale to UTF-8. Free the string using pa_xfree(). */ +char* pa_locale_to_utf8 (const char *str); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/util.c b/src/pulse/util.c new file mode 100644 index 00000000..d3ac9f66 --- /dev/null +++ b/src/pulse/util.c @@ -0,0 +1,262 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <errno.h> +#include <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> +#include <sys/types.h> + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulsecore/winsock.h> +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "util.h" + +char *pa_get_user_name(char *s, size_t l) { + char *p; + char buf[1024]; + +#ifdef HAVE_PWD_H + struct passwd pw, *r; +#endif + + pa_assert(s); + pa_assert(l > 0); + + if (!(p = getenv("USER")) && !(p = getenv("LOGNAME")) && !(p = getenv("USERNAME"))) { +#ifdef HAVE_PWD_H + +#ifdef HAVE_GETPWUID_R + if (getpwuid_r(getuid(), &pw, buf, sizeof(buf), &r) != 0 || !r) { +#else + /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X) + * that do not support getpwuid_r. */ + if ((r = getpwuid(getuid())) == NULL) { +#endif + pa_snprintf(s, l, "%lu", (unsigned long) getuid()); + return s; + } + + p = r->pw_name; + +#elif defined(OS_IS_WIN32) /* HAVE_PWD_H */ + DWORD size = sizeof(buf); + + if (!GetUserName(buf, &size)) + return NULL; + + p = buf; + +#else /* HAVE_PWD_H */ + return NULL; +#endif /* HAVE_PWD_H */ + } + + return pa_strlcpy(s, p, l); +} + +char *pa_get_host_name(char *s, size_t l) { + + pa_assert(s); + pa_assert(l > 0); + + if (gethostname(s, l) < 0) { + pa_log("gethostname(): %s", pa_cstrerror(errno)); + return NULL; + } + + s[l-1] = 0; + return s; +} + +char *pa_get_home_dir(char *s, size_t l) { + char *e; + +#ifdef HAVE_PWD_H + char buf[1024]; + struct passwd pw, *r; +#endif + + pa_assert(s); + pa_assert(l > 0); + + if ((e = getenv("HOME"))) + return pa_strlcpy(s, e, l); + + if ((e = getenv("USERPROFILE"))) + return pa_strlcpy(s, e, l); + +#ifdef HAVE_PWD_H +#ifdef HAVE_GETPWUID_R + if (getpwuid_r(getuid(), &pw, buf, sizeof(buf), &r) != 0 || !r) { + pa_log("getpwuid_r() failed"); +#else + /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X) + * that do not support getpwuid_r. */ + if ((r = getpwuid(getuid())) == NULL) { + pa_log("getpwuid_r() failed"); +#endif + return NULL; + } + + return pa_strlcpy(s, r->pw_dir, l); +#else /* HAVE_PWD_H */ + return NULL; +#endif +} + +char *pa_get_binary_name(char *s, size_t l) { + + pa_assert(s); + pa_assert(l > 0); + +#if defined(OS_IS_WIN32) + { + char path[PATH_MAX]; + + if (GetModuleFileName(NULL, path, PATH_MAX)) + return pa_strlcpy(s, pa_path_get_filename(path), l); + } +#endif + +#ifdef __linux__ + { + char *rp; + /* This works on Linux only */ + + if ((rp = pa_readlink("/proc/self/exe"))) { + pa_strlcpy(s, pa_path_get_filename(rp), l); + pa_xfree(rp); + return s; + } + } + +#endif + +#if defined(HAVE_SYS_PRCTL_H) && defined(PR_GET_NAME) + { + + #ifndef TASK_COMM_LEN + /* Actually defined in linux/sched.h */ + #define TASK_COMM_LEN 16 + #endif + + char tcomm[TASK_COMM_LEN+1]; + memset(tcomm, 0, sizeof(tcomm)); + + /* This works on Linux only */ + if (prctl(PR_GET_NAME, (unsigned long) tcomm, 0, 0, 0) == 0) + return pa_strlcpy(s, tcomm, l); + + } +#endif + + return NULL; +} + +char *pa_path_get_filename(const char *p) { + char *fn; + + pa_assert(p); + + if ((fn = strrchr(p, PA_PATH_SEP_CHAR))) + return fn+1; + + return (char*) p; +} + +char *pa_get_fqdn(char *s, size_t l) { + char hn[256]; +#ifdef HAVE_GETADDRINFO + struct addrinfo *a, hints; +#endif + + pa_assert(s); + pa_assert(l > 0); + + if (!pa_get_host_name(hn, sizeof(hn))) + return NULL; + +#ifdef HAVE_GETADDRINFO + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_CANONNAME; + + if (getaddrinfo(hn, NULL, &hints, &a) < 0 || !a || !a->ai_canonname || !*a->ai_canonname) + return pa_strlcpy(s, hn, l); + + pa_strlcpy(s, a->ai_canonname, l); + freeaddrinfo(a); + return s; +#else + return pa_strlcpy(s, hn, l); +#endif +} + +int pa_msleep(unsigned long t) { +#ifdef OS_IS_WIN32 + Sleep(t); + return 0; +#elif defined(HAVE_NANOSLEEP) + struct timespec ts; + + ts.tv_sec = t/1000; + ts.tv_nsec = (t % 1000) * 1000000; + + return nanosleep(&ts, NULL); +#else +#error "Platform lacks a sleep function." +#endif +} diff --git a/src/pulse/util.h b/src/pulse/util.h new file mode 100644 index 00000000..764678e5 --- /dev/null +++ b/src/pulse/util.h @@ -0,0 +1,62 @@ +#ifndef fooutilhfoo +#define fooutilhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stddef.h> + +#include <pulse/cdecl.h> + +/** \file + * Assorted utility functions */ + +PA_C_DECL_BEGIN + +/** Return the current username in the specified string buffer. */ +char *pa_get_user_name(char *s, size_t l); + +/** Return the current hostname in the specified buffer. */ +char *pa_get_host_name(char *s, size_t l); + +/** Return the fully qualified domain name in s */ +char *pa_get_fqdn(char *s, size_t l); + +/** Return the home directory of the current user */ +char *pa_get_home_dir(char *s, size_t l); + +/** Return the binary file name of the current process. This is not + * supported on all architectures, in which case NULL is returned. */ +char *pa_get_binary_name(char *s, size_t l); + +/** Return a pointer to the filename inside a path (which is the last + * component). */ +char *pa_path_get_filename(const char *p); + +/** Wait t milliseconds */ +int pa_msleep(unsigned long t); + +PA_C_DECL_END + +#endif diff --git a/src/pulse/version.h.in b/src/pulse/version.h.in new file mode 100644 index 00000000..20c7a9c0 --- /dev/null +++ b/src/pulse/version.h.in @@ -0,0 +1,56 @@ +#ifndef fooversionhfoo /*-*-C-*-*/ +#define fooversionhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +/* WARNING: Make sure to edit the real source file version.h.in! */ + +#include <pulse/cdecl.h> + +/** \file + * Define header version */ + +PA_C_DECL_BEGIN + +/** Return the version of the header files. Keep in mind that this is +a macro and not a function, so it is impossible to get the pointer of +it. */ +#define pa_get_headers_version() ("@PACKAGE_VERSION@") + +/** Return the version of the library the current application is linked to. */ +const char* pa_get_library_version(void); + +/** The current API version. Version 6 relates to Polypaudio + * 0.6. Prior versions (i.e. Polypaudio 0.5.1 and older) have + * PA_API_VERSION undefined. */ +#define PA_API_VERSION @PA_API_VERSION@ + +/** The current protocol version. Version 8 relates to Polypaudio 0.8/PulseAudio 0.9. + * \since 0.8 */ +#define PA_PROTOCOL_VERSION @PA_PROTOCOL_VERSION@ + +PA_C_DECL_END + +#endif diff --git a/src/pulse/volume.c b/src/pulse/volume.c new file mode 100644 index 00000000..3688b847 --- /dev/null +++ b/src/pulse/volume.c @@ -0,0 +1,180 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "volume.h" + +int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) { + int i; + pa_assert(a); + pa_assert(b); + + if (a->channels != b->channels) + return 0; + + for (i = 0; i < a->channels; i++) + if (a->values[i] != b->values[i]) + return 0; + + return 1; +} + +pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v) { + int i; + + pa_assert(a); + pa_assert(channels > 0); + pa_assert(channels <= PA_CHANNELS_MAX); + + a->channels = channels; + + for (i = 0; i < a->channels; i++) + a->values[i] = v; + + return a; +} + +pa_volume_t pa_cvolume_avg(const pa_cvolume *a) { + uint64_t sum = 0; + int i; + pa_assert(a); + + for (i = 0; i < a->channels; i++) + sum += a->values[i]; + + sum /= a->channels; + + return (pa_volume_t) sum; +} + +pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) { + return pa_sw_volume_from_linear(pa_sw_volume_to_linear(a)* pa_sw_volume_to_linear(b)); +} + +#define USER_DECIBEL_RANGE 30 + +pa_volume_t pa_sw_volume_from_dB(double dB) { + if (dB <= -USER_DECIBEL_RANGE) + return PA_VOLUME_MUTED; + + return (pa_volume_t) ((dB/USER_DECIBEL_RANGE+1)*PA_VOLUME_NORM); +} + +double pa_sw_volume_to_dB(pa_volume_t v) { + if (v == PA_VOLUME_MUTED) + return PA_DECIBEL_MININFTY; + + return ((double) v/PA_VOLUME_NORM-1)*USER_DECIBEL_RANGE; +} + +pa_volume_t pa_sw_volume_from_linear(double v) { + + if (v <= 0) + return PA_VOLUME_MUTED; + + if (v > .999 && v < 1.001) + return PA_VOLUME_NORM; + + return pa_sw_volume_from_dB(20*log10(v)); +} + +double pa_sw_volume_to_linear(pa_volume_t v) { + + if (v == PA_VOLUME_MUTED) + return 0; + + return pow(10, pa_sw_volume_to_dB(v)/20); +} + +char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c) { + unsigned channel; + int first = 1; + char *e; + + pa_assert(s); + pa_assert(l > 0); + pa_assert(c); + + *(e = s) = 0; + + for (channel = 0; channel < c->channels && l > 1; channel++) { + l -= pa_snprintf(e, l, "%s%u: %3u%%", + first ? "" : " ", + channel, + (c->values[channel]*100)/PA_VOLUME_NORM); + + e = strchr(e, 0); + first = 0; + } + + return s; +} + +/** Return non-zero if the volume of all channels is equal to the specified value */ +int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) { + unsigned c; + pa_assert(a); + + for (c = 0; c < a->channels; c++) + if (a->values[c] != v) + return 0; + + return 1; +} + +pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) { + unsigned i; + + pa_assert(dest); + pa_assert(a); + pa_assert(b); + + for (i = 0; i < a->channels && i < b->channels && i < PA_CHANNELS_MAX; i++) { + + dest->values[i] = pa_sw_volume_multiply( + i < a->channels ? a->values[i] : PA_VOLUME_NORM, + i < b->channels ? b->values[i] : PA_VOLUME_NORM); + } + + dest->channels = i; + + return dest; +} + +int pa_cvolume_valid(const pa_cvolume *v) { + pa_assert(v); + + if (v->channels <= 0 || v->channels > PA_CHANNELS_MAX) + return 0; + + return 1; +} diff --git a/src/pulse/volume.h b/src/pulse/volume.h new file mode 100644 index 00000000..22e5b8a4 --- /dev/null +++ b/src/pulse/volume.h @@ -0,0 +1,175 @@ +#ifndef foovolumehfoo +#define foovolumehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/cdecl.h> +#include <pulse/sample.h> + +/** \page volume Volume Control + * + * \section overv_sec Overview + * + * Sinks, sources, sink inputs and samples can all have their own volumes. + * To deal with these, The PulseAudio libray contains a number of functions + * that ease handling. + * + * The basic volume type in PulseAudio is the \ref pa_volume_t type. Most of + * the time, applications will use the aggregated pa_cvolume structure that + * can store the volume of all channels at once. + * + * Volumes commonly span between muted (0%), and normal (100%). It is possible + * to set volumes to higher than 100%, but clipping might occur. + * + * \section calc_sec Calculations + * + * The volumes in PulseAudio are logarithmic in nature and applications + * shouldn't perform calculations with them directly. Instead, they should + * be converted to and from either dB or a linear scale: + * + * \li dB - pa_sw_volume_from_dB() / pa_sw_volume_to_dB() + * \li Linear - pa_sw_volume_from_linear() / pa_sw_volume_to_linear() + * + * For simple multiplication, pa_sw_volume_multiply() and + * pa_sw_cvolume_multiply() can be used. + * + * Calculations can only be reliably performed on software volumes + * as it is commonly unknown what scale hardware volumes relate to. + * + * The functions described above are only valid when used with + * software volumes. Hence it is usually a better idea to treat all + * volume values as opaque with a range from PA_VOLUME_MUTE (0%) to + * PA_VOLUME_NORM (100%) and to refrain from any calculations with + * them. + * + * \section conv_sec Convenience Functions + * + * To handle the pa_cvolume structure, the PulseAudio library provides a + * number of convenienc functions: + * + * \li pa_cvolume_valid() - Tests if a pa_cvolume structure is valid. + * \li pa_cvolume_equal() - Tests if two pa_cvolume structures are identical. + * \li pa_cvolume_channels_equal_to() - Tests if all channels of a pa_cvolume + * structure have a given volume. + * \li pa_cvolume_is_muted() - Tests if all channels of a pa_cvolume + * structure are muted. + * \li pa_cvolume_is_norm() - Tests if all channels of a pa_cvolume structure + * are at a normal volume. + * \li pa_cvolume_set() - Set all channels of a pa_cvolume structure to a + * certain volume. + * \li pa_cvolume_reset() - Set all channels of a pa_cvolume structure to a + * normal volume. + * \li pa_cvolume_mute() - Set all channels of a pa_cvolume structure to a + * muted volume. + * \li pa_cvolume_avg() - Return the average volume of all channels. + * \li pa_cvolume_snprint() - Pretty print a pa_cvolume structure. + */ + +/** \file + * Constants and routines for volume handling */ + +PA_C_DECL_BEGIN + +/** Volume specification: + * PA_VOLUME_MUTED: silence; + * < PA_VOLUME_NORM: decreased volume; + * PA_VOLUME_NORM: normal volume; + * > PA_VOLUME_NORM: increased volume */ +typedef uint32_t pa_volume_t; + +/** Normal volume (100%) */ +#define PA_VOLUME_NORM (0x10000) + +/** Muted volume (0%) */ +#define PA_VOLUME_MUTED (0) + +/** A structure encapsulating a per-channel volume */ +typedef struct pa_cvolume { + uint8_t channels; /**< Number of channels */ + pa_volume_t values[PA_CHANNELS_MAX]; /**< Per-channel volume */ +} pa_cvolume; + +/** Return non-zero when *a == *b */ +int pa_cvolume_equal(const pa_cvolume *a, const pa_cvolume *b) PA_GCC_PURE; + +/** Set the volume of all channels to PA_VOLUME_NORM */ +#define pa_cvolume_reset(a, n) pa_cvolume_set((a), (n), PA_VOLUME_NORM) + +/** Set the volume of all channels to PA_VOLUME_MUTED */ +#define pa_cvolume_mute(a, n) pa_cvolume_set((a), (n), PA_VOLUME_MUTED) + +/** Set the volume of all channels to the specified parameter */ +pa_cvolume* pa_cvolume_set(pa_cvolume *a, unsigned channels, pa_volume_t v); + +/** Maximum length of the strings returned by pa_cvolume_snprint() */ +#define PA_CVOLUME_SNPRINT_MAX 64 + +/** Pretty print a volume structure */ +char *pa_cvolume_snprint(char *s, size_t l, const pa_cvolume *c); + +/** Return the average volume of all channels */ +pa_volume_t pa_cvolume_avg(const pa_cvolume *a) PA_GCC_PURE; + +/** Return TRUE when the passed cvolume structure is valid, FALSE otherwise */ +int pa_cvolume_valid(const pa_cvolume *v) PA_GCC_PURE; + +/** Return non-zero if the volume of all channels is equal to the specified value */ +int pa_cvolume_channels_equal_to(const pa_cvolume *a, pa_volume_t v) PA_GCC_PURE; + +/** Return 1 if the specified volume has all channels muted */ +#define pa_cvolume_is_muted(a) pa_cvolume_channels_equal_to((a), PA_VOLUME_MUTED) + +/** Return 1 if the specified volume has all channels on normal level */ +#define pa_cvolume_is_norm(a) pa_cvolume_channels_equal_to((a), PA_VOLUME_NORM) + +/** Multiply two volumes specifications, return the result. This uses PA_VOLUME_NORM as neutral element of multiplication. This is only valid for software volumes! */ +pa_volume_t pa_sw_volume_multiply(pa_volume_t a, pa_volume_t b) PA_GCC_CONST; + +/** Multiply to per-channel volumes and return the result in *dest. This is only valid for software volumes! */ +pa_cvolume *pa_sw_cvolume_multiply(pa_cvolume *dest, const pa_cvolume *a, const pa_cvolume *b) PA_GCC_PURE; + +/** Convert a decibel value to a volume. This is only valid for software volumes! \since 0.4 */ +pa_volume_t pa_sw_volume_from_dB(double f) PA_GCC_CONST; + +/** Convert a volume to a decibel value. This is only valid for software volumes! \since 0.4 */ +double pa_sw_volume_to_dB(pa_volume_t v) PA_GCC_CONST; + +/** Convert a linear factor to a volume. This is only valid for software volumes! \since 0.8 */ +pa_volume_t pa_sw_volume_from_linear(double v) PA_GCC_CONST; + +/** Convert a volume to a linear factor. This is only valid for software volumes! \since 0.8 */ +double pa_sw_volume_to_linear(pa_volume_t v) PA_GCC_CONST; + +#ifdef INFINITY +#define PA_DECIBEL_MININFTY (-INFINITY) +#else +/** This value is used as minus infinity when using pa_volume_{to,from}_dB(). \since 0.4 */ +#define PA_DECIBEL_MININFTY (-200) +#endif + +PA_C_DECL_END + +#endif diff --git a/src/pulse/xmalloc.c b/src/pulse/xmalloc.c new file mode 100644 index 00000000..5348dda4 --- /dev/null +++ b/src/pulse/xmalloc.c @@ -0,0 +1,130 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <signal.h> +#include <unistd.h> +#include <string.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> + +#include "xmalloc.h" + +/* Make sure not to allocate more than this much memory. */ +#define MAX_ALLOC_SIZE (1024*1024*20) /* 20MB */ + +/* #undef malloc */ +/* #undef free */ +/* #undef realloc */ +/* #undef strndup */ +/* #undef strdup */ + +static void oom(void) PA_GCC_NORETURN; + +/** called in case of an OOM situation. Prints an error message and + * exits */ +static void oom(void) { + static const char e[] = "Not enough memory\n"; + pa_loop_write(STDERR_FILENO, e, sizeof(e)-1, NULL); +#ifdef SIGQUIT + raise(SIGQUIT); +#endif + _exit(1); +} + +void* pa_xmalloc(size_t size) { + void *p; + pa_assert(size > 0); + pa_assert(size < MAX_ALLOC_SIZE); + + if (!(p = malloc(size))) + oom(); + + return p; +} + +void* pa_xmalloc0(size_t size) { + void *p; + pa_assert(size > 0); + pa_assert(size < MAX_ALLOC_SIZE); + + if (!(p = calloc(1, size))) + oom(); + + return p; +} + +void *pa_xrealloc(void *ptr, size_t size) { + void *p; + pa_assert(size > 0); + pa_assert(size < MAX_ALLOC_SIZE); + + if (!(p = realloc(ptr, size))) + oom(); + return p; +} + +void* pa_xmemdup(const void *p, size_t l) { + if (!p) + return NULL; + else { + char *r = pa_xmalloc(l); + memcpy(r, p, l); + return r; + } +} + +char *pa_xstrdup(const char *s) { + if (!s) + return NULL; + + return pa_xmemdup(s, strlen(s)+1); +} + +char *pa_xstrndup(const char *s, size_t l) { + char *e, *r; + + if (!s) + return NULL; + + if ((e = memchr(s, 0, l))) + return pa_xmemdup(s, e-s+1); + + r = pa_xmalloc(l+1); + memcpy(r, s, l); + r[l] = 0; + return r; +} + +void pa_xfree(void *p) { + if (!p) + return; + + free(p); +} diff --git a/src/pulse/xmalloc.h b/src/pulse/xmalloc.h new file mode 100644 index 00000000..62a450dc --- /dev/null +++ b/src/pulse/xmalloc.h @@ -0,0 +1,89 @@ +#ifndef foomemoryhfoo +#define foomemoryhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <sys/types.h> +#include <stdlib.h> +#include <limits.h> +#include <assert.h> +#include <pulse/cdecl.h> + +/** \file + * Memory allocation functions. + */ + +PA_C_DECL_BEGIN + +/** Allocate the specified number of bytes, just like malloc() does. However, in case of OOM, terminate */ +void* pa_xmalloc(size_t l); + +/** Same as pa_xmalloc(), but initialize allocated memory to 0 */ +void *pa_xmalloc0(size_t l); + +/** The combination of pa_xmalloc() and realloc() */ +void *pa_xrealloc(void *ptr, size_t size); + +/** Free allocated memory */ +void pa_xfree(void *p); + +/** Duplicate the specified string, allocating memory with pa_xmalloc() */ +char *pa_xstrdup(const char *s); + +/** Duplicate the specified string, but truncate after l characters */ +char *pa_xstrndup(const char *s, size_t l); + +/** Duplicate the specified memory block */ +void* pa_xmemdup(const void *p, size_t l); + +/** Internal helper for pa_xnew() */ +static inline void* pa_xnew_internal(unsigned n, size_t k) { + assert(n < INT_MAX/k); + return pa_xmalloc(n*k); +} + +/** Allocate n new structures of the specified type. */ +#define pa_xnew(type, n) ((type*) pa_xnew_internal((n), sizeof(type))) + +/** Internal helper for pa_xnew0() */ +static inline void* pa_xnew0_internal(unsigned n, size_t k) { + assert(n < INT_MAX/k); + return pa_xmalloc0(n*k); +} + +/** Same as pa_xnew() but set the memory to zero */ +#define pa_xnew0(type, n) ((type*) pa_xnew0_internal((n), sizeof(type))) + +/** Internal helper for pa_xnew0() */ +static inline void* pa_xnewdup_internal(const void *p, unsigned n, size_t k) { + assert(n < INT_MAX/k); + return pa_xmemdup(p, n*k); +} + +/** Same as pa_xnew() but set the memory to zero */ +#define pa_xnewdup(type, p, n) ((type*) pa_xnewdup_internal((p), (n), sizeof(type))) + +PA_C_DECL_END + +#endif diff --git a/src/pulsecore/Makefile b/src/pulsecore/Makefile new file mode 120000 index 00000000..c110232d --- /dev/null +++ b/src/pulsecore/Makefile @@ -0,0 +1 @@ +../pulse/Makefile
\ No newline at end of file diff --git a/src/pulsecore/asyncmsgq.c b/src/pulsecore/asyncmsgq.c new file mode 100644 index 00000000..96b43a71 --- /dev/null +++ b/src/pulsecore/asyncmsgq.c @@ -0,0 +1,303 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 <unistd.h> +#include <errno.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/semaphore.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/flist.h> +#include <pulse/xmalloc.h> + +#include "asyncmsgq.h" + +PA_STATIC_FLIST_DECLARE(asyncmsgq, 0, pa_xfree); +PA_STATIC_FLIST_DECLARE(semaphores, 0, (void(*)(void*)) pa_semaphore_free); + +struct asyncmsgq_item { + int code; + pa_msgobject *object; + void *userdata; + pa_free_cb_t free_cb; + int64_t offset; + pa_memchunk memchunk; + pa_semaphore *semaphore; + int ret; +}; + +struct pa_asyncmsgq { + PA_REFCNT_DECLARE; + pa_asyncq *asyncq; + pa_mutex *mutex; /* only for the writer side */ + + struct asyncmsgq_item *current; +}; + +pa_asyncmsgq *pa_asyncmsgq_new(unsigned size) { + pa_asyncmsgq *a; + + a = pa_xnew(pa_asyncmsgq, 1); + + PA_REFCNT_INIT(a); + pa_assert_se(a->asyncq = pa_asyncq_new(size)); + pa_assert_se(a->mutex = pa_mutex_new(FALSE, TRUE)); + a->current = NULL; + + return a; +} + +static void asyncmsgq_free(pa_asyncmsgq *a) { + struct asyncmsgq_item *i; + pa_assert(a); + + while ((i = pa_asyncq_pop(a->asyncq, 0))) { + + pa_assert(!i->semaphore); + + if (i->object) + pa_msgobject_unref(i->object); + + if (i->memchunk.memblock) + pa_memblock_unref(i->memchunk.memblock); + + if (i->free_cb) + i->free_cb(i->userdata); + + if (pa_flist_push(PA_STATIC_FLIST_GET(asyncmsgq), i) < 0) + pa_xfree(i); + } + + pa_asyncq_free(a->asyncq, NULL); + pa_mutex_free(a->mutex); + pa_xfree(a); +} + +pa_asyncmsgq* pa_asyncmsgq_ref(pa_asyncmsgq *q) { + pa_assert(PA_REFCNT_VALUE(q) > 0); + + PA_REFCNT_INC(q); + return q; +} + +void pa_asyncmsgq_unref(pa_asyncmsgq* q) { + pa_assert(PA_REFCNT_VALUE(q) > 0); + + if (PA_REFCNT_DEC(q) <= 0) + asyncmsgq_free(q); +} + +void pa_asyncmsgq_post(pa_asyncmsgq *a, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *chunk, pa_free_cb_t free_cb) { + struct asyncmsgq_item *i; + pa_assert(PA_REFCNT_VALUE(a) > 0); + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(asyncmsgq)))) + i = pa_xnew(struct asyncmsgq_item, 1); + + i->code = code; + i->object = object ? pa_msgobject_ref(object) : NULL; + i->userdata = (void*) userdata; + i->free_cb = free_cb; + i->offset = offset; + if (chunk) { + pa_assert(chunk->memblock); + i->memchunk = *chunk; + pa_memblock_ref(i->memchunk.memblock); + } else + pa_memchunk_reset(&i->memchunk); + i->semaphore = NULL; + + /* This mutex makes the queue multiple-writer safe. This lock is only used on the writing side */ + pa_mutex_lock(a->mutex); + pa_assert_se(pa_asyncq_push(a->asyncq, i, 1) == 0); + pa_mutex_unlock(a->mutex); +} + +int pa_asyncmsgq_send(pa_asyncmsgq *a, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *chunk) { + struct asyncmsgq_item i; + pa_assert(PA_REFCNT_VALUE(a) > 0); + + i.code = code; + i.object = object; + i.userdata = (void*) userdata; + i.free_cb = NULL; + i.ret = -1; + i.offset = offset; + if (chunk) { + pa_assert(chunk->memblock); + i.memchunk = *chunk; + } else + pa_memchunk_reset(&i.memchunk); + + if (!(i.semaphore = pa_flist_pop(PA_STATIC_FLIST_GET(semaphores)))) + i.semaphore = pa_semaphore_new(0); + + pa_assert_se(i.semaphore); + + /* Thus mutex makes the queue multiple-writer safe. This lock is only used on the writing side */ + pa_mutex_lock(a->mutex); + pa_assert_se(pa_asyncq_push(a->asyncq, &i, 1) == 0); + pa_mutex_unlock(a->mutex); + + pa_semaphore_wait(i.semaphore); + + if (pa_flist_push(PA_STATIC_FLIST_GET(semaphores), i.semaphore) < 0) + pa_semaphore_free(i.semaphore); + + return i.ret; +} + +int pa_asyncmsgq_get(pa_asyncmsgq *a, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *chunk, int wait) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + pa_assert(!a->current); + + if (!(a->current = pa_asyncq_pop(a->asyncq, wait))) { +/* pa_log("failure"); */ + return -1; + } + +/* pa_log("success"); */ + + if (code) + *code = a->current->code; + if (userdata) + *userdata = a->current->userdata; + if (offset) + *offset = a->current->offset; + if (object) { + if ((*object = a->current->object)) + pa_msgobject_assert_ref(*object); + } + if (chunk) + *chunk = a->current->memchunk; + +/* pa_log_debug("Get q=%p object=%p (%s) code=%i data=%p chunk.length=%lu", (void*) a, (void*) a->current->object, a->current->object ? a->current->object->parent.type_name : NULL, a->current->code, (void*) a->current->userdata, (unsigned long) a->current->memchunk.length); */ + + return 0; +} + +void pa_asyncmsgq_done(pa_asyncmsgq *a, int ret) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + pa_assert(a); + pa_assert(a->current); + + if (a->current->semaphore) { + a->current->ret = ret; + pa_semaphore_post(a->current->semaphore); + } else { + + if (a->current->free_cb) + a->current->free_cb(a->current->userdata); + + if (a->current->object) + pa_msgobject_unref(a->current->object); + + if (a->current->memchunk.memblock) + pa_memblock_unref(a->current->memchunk.memblock); + + if (pa_flist_push(PA_STATIC_FLIST_GET(asyncmsgq), a->current) < 0) + pa_xfree(a->current); + } + + a->current = NULL; +} + +int pa_asyncmsgq_wait_for(pa_asyncmsgq *a, int code) { + int c; + pa_assert(PA_REFCNT_VALUE(a) > 0); + + pa_asyncmsgq_ref(a); + + do { + pa_msgobject *o; + void *data; + int64_t offset; + pa_memchunk chunk; + int ret; + + if (pa_asyncmsgq_get(a, &o, &c, &data, &offset, &chunk, 1) < 0) + return -1; + + ret = pa_asyncmsgq_dispatch(o, c, data, offset, &chunk); + pa_asyncmsgq_done(a, ret); + + } while (c != code); + + pa_asyncmsgq_unref(a); + + return 0; +} + +int pa_asyncmsgq_process_one(pa_asyncmsgq *a) { + pa_msgobject *object; + int code; + void *data; + pa_memchunk chunk; + int64_t offset; + int ret; + + pa_assert(PA_REFCNT_VALUE(a) > 0); + + if (pa_asyncmsgq_get(a, &object, &code, &data, &offset, &chunk, 0) < 0) + return 0; + + pa_asyncmsgq_ref(a); + ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk); + pa_asyncmsgq_done(a, ret); + pa_asyncmsgq_unref(a); + + return 1; +} + +int pa_asyncmsgq_get_fd(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return pa_asyncq_get_fd(a->asyncq); +} + +int pa_asyncmsgq_before_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + return pa_asyncq_before_poll(a->asyncq); +} + +void pa_asyncmsgq_after_poll(pa_asyncmsgq *a) { + pa_assert(PA_REFCNT_VALUE(a) > 0); + + pa_asyncq_after_poll(a->asyncq); +} + +int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *memchunk) { + + if (object) + return object->process_msg(object, code, userdata, offset, memchunk); + + return 0; +} diff --git a/src/pulsecore/asyncmsgq.h b/src/pulsecore/asyncmsgq.h new file mode 100644 index 00000000..5d3867ba --- /dev/null +++ b/src/pulsecore/asyncmsgq.h @@ -0,0 +1,75 @@ +#ifndef foopulseasyncmsgqhfoo +#define foopulseasyncmsgqhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <sys/types.h> + +#include <pulsecore/asyncq.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/msgobject.h> + +/* A simple asynchronous message queue, based on pa_asyncq. In + * contrast to pa_asyncq this one is multiple-writer safe, though + * still not multiple-reader safe. This queue is intended to be used + * for controlling real-time threads from normal-priority + * threads. Multiple-writer-safety is accomplished by using a mutex on + * the writer side. This queue is thus not useful for communication + * between several real-time threads. + * + * The queue takes messages consisting of: + * "Object" for which this messages is intended (may be NULL) + * A numeric message code + * Arbitrary userdata pointer (may be NULL) + * A memchunk (may be NULL) + * + * There are two functions for submitting messages: _post and + * _send. The former just enqueues the message asynchronously, the + * latter waits for completion, synchronously. */ + +enum { + PA_MESSAGE_SHUTDOWN = -1/* A generic message to inform the handler of this queue to quit */ +}; + +typedef struct pa_asyncmsgq pa_asyncmsgq; + +pa_asyncmsgq* pa_asyncmsgq_new(unsigned size); +pa_asyncmsgq* pa_asyncmsgq_ref(pa_asyncmsgq *q); +void pa_asyncmsgq_unref(pa_asyncmsgq* q); + +void pa_asyncmsgq_post(pa_asyncmsgq *q, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *memchunk, pa_free_cb_t userdata_free_cb); +int pa_asyncmsgq_send(pa_asyncmsgq *q, pa_msgobject *object, int code, const void *userdata, int64_t offset, const pa_memchunk *memchunk); + +int pa_asyncmsgq_get(pa_asyncmsgq *q, pa_msgobject **object, int *code, void **userdata, int64_t *offset, pa_memchunk *memchunk, int wait); +int pa_asyncmsgq_dispatch(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *memchunk); +void pa_asyncmsgq_done(pa_asyncmsgq *q, int ret); +int pa_asyncmsgq_wait_for(pa_asyncmsgq *a, int code); +int pa_asyncmsgq_process_one(pa_asyncmsgq *a); + +/* Just for the reading side */ +int pa_asyncmsgq_get_fd(pa_asyncmsgq *q); +int pa_asyncmsgq_before_poll(pa_asyncmsgq *a); +void pa_asyncmsgq_after_poll(pa_asyncmsgq *a); + +#endif diff --git a/src/pulsecore/asyncq.c b/src/pulsecore/asyncq.c new file mode 100644 index 00000000..75b15c0e --- /dev/null +++ b/src/pulsecore/asyncq.c @@ -0,0 +1,213 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 <unistd.h> +#include <errno.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulse/xmalloc.h> + +#include "asyncq.h" +#include "fdsem.h" + +#define ASYNCQ_SIZE 128 + +/* For debugging purposes we can define _Y to put and extra thread + * yield between each operation. */ + +/* #define PROFILE */ + +#ifdef PROFILE +#define _Y pa_thread_yield() +#else +#define _Y do { } while(0) +#endif + +struct pa_asyncq { + unsigned size; + unsigned read_idx; + unsigned write_idx; + pa_fdsem *read_fdsem, *write_fdsem; +}; + +#define PA_ASYNCQ_CELLS(x) ((pa_atomic_ptr_t*) ((uint8_t*) (x) + PA_ALIGN(sizeof(struct pa_asyncq)))) + +static int is_power_of_two(unsigned size) { + return !(size & (size - 1)); +} + +static int reduce(pa_asyncq *l, int value) { + return value & (unsigned) (l->size - 1); +} + +pa_asyncq *pa_asyncq_new(unsigned size) { + pa_asyncq *l; + + if (!size) + size = ASYNCQ_SIZE; + + pa_assert(is_power_of_two(size)); + + l = pa_xmalloc0(PA_ALIGN(sizeof(pa_asyncq)) + (sizeof(pa_atomic_ptr_t) * size)); + + l->size = size; + + if (!(l->read_fdsem = pa_fdsem_new())) { + pa_xfree(l); + return NULL; + } + + if (!(l->write_fdsem = pa_fdsem_new())) { + pa_fdsem_free(l->read_fdsem); + pa_xfree(l); + return NULL; + } + + return l; +} + +void pa_asyncq_free(pa_asyncq *l, pa_free_cb_t free_cb) { + pa_assert(l); + + if (free_cb) { + void *p; + + while ((p = pa_asyncq_pop(l, 0))) + free_cb(p); + } + + pa_fdsem_free(l->read_fdsem); + pa_fdsem_free(l->write_fdsem); + pa_xfree(l); +} + +int pa_asyncq_push(pa_asyncq*l, void *p, int wait) { + int idx; + pa_atomic_ptr_t *cells; + + pa_assert(l); + pa_assert(p); + + cells = PA_ASYNCQ_CELLS(l); + + _Y; + idx = reduce(l, l->write_idx); + + if (!pa_atomic_ptr_cmpxchg(&cells[idx], NULL, p)) { + + if (!wait) + return -1; + +/* pa_log("sleeping on push"); */ + + do { + pa_fdsem_wait(l->read_fdsem); + } while (!pa_atomic_ptr_cmpxchg(&cells[idx], NULL, p)); + } + + _Y; + l->write_idx++; + + pa_fdsem_post(l->write_fdsem); + + return 0; +} + +void* pa_asyncq_pop(pa_asyncq*l, int wait) { + int idx; + void *ret; + pa_atomic_ptr_t *cells; + + pa_assert(l); + + cells = PA_ASYNCQ_CELLS(l); + + _Y; + idx = reduce(l, l->read_idx); + + if (!(ret = pa_atomic_ptr_load(&cells[idx]))) { + + if (!wait) + return NULL; + +/* pa_log("sleeping on pop"); */ + + do { + pa_fdsem_wait(l->write_fdsem); + } while (!(ret = pa_atomic_ptr_load(&cells[idx]))); + } + + pa_assert(ret); + + /* Guaranteed to succeed if we only have a single reader */ + pa_assert_se(pa_atomic_ptr_cmpxchg(&cells[idx], ret, NULL)); + + _Y; + l->read_idx++; + + pa_fdsem_post(l->read_fdsem); + + return ret; +} + +int pa_asyncq_get_fd(pa_asyncq *q) { + pa_assert(q); + + return pa_fdsem_get(q->write_fdsem); +} + +int pa_asyncq_before_poll(pa_asyncq *l) { + int idx; + pa_atomic_ptr_t *cells; + + pa_assert(l); + + cells = PA_ASYNCQ_CELLS(l); + + _Y; + idx = reduce(l, l->read_idx); + + for (;;) { + if (pa_atomic_ptr_load(&cells[idx])) + return -1; + + if (pa_fdsem_before_poll(l->write_fdsem) >= 0) + return 0; + } + + return 0; +} + +void pa_asyncq_after_poll(pa_asyncq *l) { + pa_assert(l); + + pa_fdsem_after_poll(l->write_fdsem); +} diff --git a/src/pulsecore/asyncq.h b/src/pulsecore/asyncq.h new file mode 100644 index 00000000..53d45866 --- /dev/null +++ b/src/pulsecore/asyncq.h @@ -0,0 +1,56 @@ +#ifndef foopulseasyncqhfoo +#define foopulseasyncqhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <sys/types.h> +#include <pulse/def.h> + +/* A simple, asynchronous, lock-free (if requested also wait-free) + * queue. Not multiple-reader/multiple-writer safe. If that is + * required both sides can be protected by a mutex each. --- Which is + * not a bad thing in most cases, since this queue is intended for + * communication between a normal thread and a single real-time + * thread. Only the real-time side needs to be lock-free/wait-free. + * + * If the queue is full and another entry shall be pushed, or when the + * queue is empty and another entry shall be popped and the "wait" + * argument is non-zero, the queue will block on a UNIX FIFO object -- + * that will probably require locking on the kernel side -- which + * however is probably not problematic, because we do it only on + * starvation or overload in which case we have to block anyway. */ + +typedef struct pa_asyncq pa_asyncq; + +pa_asyncq* pa_asyncq_new(unsigned size); +void pa_asyncq_free(pa_asyncq* q, pa_free_cb_t free_cb); + +void* pa_asyncq_pop(pa_asyncq *q, int wait); +int pa_asyncq_push(pa_asyncq *q, void *p, int wait); + +int pa_asyncq_get_fd(pa_asyncq *q); +int pa_asyncq_before_poll(pa_asyncq *a); +void pa_asyncq_after_poll(pa_asyncq *a); + +#endif diff --git a/src/pulsecore/atomic.h b/src/pulsecore/atomic.h new file mode 100644 index 00000000..c2c99888 --- /dev/null +++ b/src/pulsecore/atomic.h @@ -0,0 +1,245 @@ +#ifndef foopulseatomichfoo +#define foopulseatomichfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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. +***/ + +/* + * atomic_ops guarantees us that sizeof(AO_t) == sizeof(void*). It is + * not guaranteed however, that sizeof(AO_t) == sizeof(size_t). + * however very likely. + * + * For now we do only full memory barriers. Eventually we might want + * to support more elaborate memory barriers, in which case we will add + * suffixes to the function names. + * + * On gcc >= 4.1 we use the builtin atomic functions. otherwise we use + * libatomic_ops + */ + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#ifdef HAVE_ATOMIC_BUILTINS + +/* __sync based implementation */ + +typedef struct pa_atomic { + volatile int value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +static inline int pa_atomic_load(const pa_atomic_t *a) { + __sync_synchronize(); + return a->value; +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + a->value = i; + __sync_synchronize(); +} + +/* Returns the previously set value */ +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + return __sync_fetch_and_add(&a->value, i); +} + +/* Returns the previously set value */ +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + return __sync_fetch_and_sub(&a->value, i); +} + +/* Returns the previously set value */ +static inline int pa_atomic_inc(pa_atomic_t *a) { + return pa_atomic_add(a, 1); +} + +/* Returns the previously set value */ +static inline int pa_atomic_dec(pa_atomic_t *a) { + return pa_atomic_sub(a, 1); +} + +/* Returns non-zero when the operation was successful. */ +static inline int pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + return __sync_bool_compare_and_swap(&a->value, old_i, new_i); +} + +typedef struct pa_atomic_ptr { + volatile unsigned long value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (long) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + __sync_synchronize(); + return (void*) a->value; +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + a->value = (unsigned long) p; + __sync_synchronize(); +} + +static inline int pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + return __sync_bool_compare_and_swap(&a->value, (long) old_p, (long) new_p); +} + +#elif defined(__GNUC__) && (defined(__amd64__) || defined(__x86_64__)) + +#error "The native atomic operations implementation for AMD64 has not been tested. libatomic_ops is known to not work properly on AMD64 and your gcc version is too old for the gcc-builtin atomic ops support. You have three options now: make the native atomic operations implementation for AMD64 work, fix libatomic_ops, or upgrade your GCC." + +/* Addapted from glibc */ + +typedef struct pa_atomic { + volatile int value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +static inline int pa_atomic_load(const pa_atomic_t *a) { + return a->value; +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + a->value = i; +} + +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + int result; + + __asm __volatile ("lock; xaddl %0, %1" + : "=r" (result), "=m" (a->value) + : "0" (i), "m" (a->value)); + + return result; +} + +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + return pa_atomic_add(a, -i); +} + +static inline int pa_atomic_inc(pa_atomic_t *a) { + return pa_atomic_add(a, 1); +} + +static inline int pa_atomic_dec(pa_atomic_t *a) { + return pa_atomic_sub(a, 1); +} + +static inline int pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + int result; + + __asm__ __volatile__ ("lock; cmpxchgl %2, %1" + : "=a" (result), "=m" (a->value) + : "r" (new_i), "m" (a->value), "0" (old_i)); + + return result == oldval; +} + +typedef struct pa_atomic_ptr { + volatile unsigned long value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (long) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + return (void*) a->value; +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + a->value = (unsigned long) p; +} + +static inline int pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + void *result; + + __asm__ __volatile__ ("lock; cmpxchgq %q2, %1" + : "=a" (result), "=m" (a->value) + : "r" (new_p), "m" (a->value), "0" (old_p)); + + return result; +} + +#else + +/* libatomic_ops based implementation */ + +#include <atomic_ops.h> + +typedef struct pa_atomic { + volatile AO_t value; +} pa_atomic_t; + +#define PA_ATOMIC_INIT(v) { .value = (v) } + +static inline int pa_atomic_load(const pa_atomic_t *a) { + return (int) AO_load_full((AO_t*) &a->value); +} + +static inline void pa_atomic_store(pa_atomic_t *a, int i) { + AO_store_full(&a->value, (AO_t) i); +} + +static inline int pa_atomic_add(pa_atomic_t *a, int i) { + return AO_fetch_and_add_full(&a->value, (AO_t) i); +} + +static inline int pa_atomic_sub(pa_atomic_t *a, int i) { + return AO_fetch_and_add_full(&a->value, (AO_t) -i); +} + +static inline int pa_atomic_inc(pa_atomic_t *a) { + return AO_fetch_and_add1_full(&a->value); +} + +static inline int pa_atomic_dec(pa_atomic_t *a) { + return AO_fetch_and_sub1_full(&a->value); +} + +static inline int pa_atomic_cmpxchg(pa_atomic_t *a, int old_i, int new_i) { + return AO_compare_and_swap_full(&a->value, old_i, new_i); +} + +typedef struct pa_atomic_ptr { + volatile AO_t value; +} pa_atomic_ptr_t; + +#define PA_ATOMIC_PTR_INIT(v) { .value = (AO_t) (v) } + +static inline void* pa_atomic_ptr_load(const pa_atomic_ptr_t *a) { + return (void*) AO_load_full((AO_t*) &a->value); +} + +static inline void pa_atomic_ptr_store(pa_atomic_ptr_t *a, void *p) { + AO_store_full(&a->value, (AO_t) p); +} + +static inline int pa_atomic_ptr_cmpxchg(pa_atomic_ptr_t *a, void *old_p, void* new_p) { + return AO_compare_and_swap_full(&a->value, (AO_t) old_p, (AO_t) new_p); +} + +#endif + +#endif diff --git a/src/pulsecore/authkey-prop.c b/src/pulsecore/authkey-prop.c new file mode 100644 index 00000000..54154500 --- /dev/null +++ b/src/pulsecore/authkey-prop.c @@ -0,0 +1,108 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/props.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> +#include <pulsecore/refcnt.h> + +#include "authkey-prop.h" + +struct authkey_data { + PA_REFCNT_DECLARE; + size_t length; +}; + +int pa_authkey_prop_get(pa_core *c, const char *name, void *data, size_t len) { + struct authkey_data *a; + + pa_assert(c); + pa_assert(name); + pa_assert(data); + pa_assert(len > 0); + + if (!(a = pa_property_get(c, name))) + return -1; + + pa_assert(a->length == len); + memcpy(data, (uint8_t*) a + PA_ALIGN(sizeof(struct authkey_data)), len); + + return 0; +} + +int pa_authkey_prop_put(pa_core *c, const char *name, const void *data, size_t len) { + struct authkey_data *a; + + pa_assert(c); + pa_assert(name); + + if (pa_property_get(c, name)) + return -1; + + a = pa_xmalloc(PA_ALIGN(sizeof(struct authkey_data)) + len); + PA_REFCNT_INIT(a); + a->length = len; + memcpy((uint8_t*) a + PA_ALIGN(sizeof(struct authkey_data)), data, len); + + pa_property_set(c, name, a); + + return 0; +} + +void pa_authkey_prop_ref(pa_core *c, const char *name) { + struct authkey_data *a; + + pa_assert(c); + pa_assert(name); + + a = pa_property_get(c, name); + pa_assert(a); + pa_assert(PA_REFCNT_VALUE(a) >= 1); + PA_REFCNT_INC(a); +} + +void pa_authkey_prop_unref(pa_core *c, const char *name) { + struct authkey_data *a; + + pa_assert(c); + pa_assert(name); + + a = pa_property_get(c, name); + pa_assert(a); + pa_assert(PA_REFCNT_VALUE(a) >= 1); + + if (PA_REFCNT_DEC(a) <= 0) { + pa_property_remove(c, name); + pa_xfree(a); + } +} + + diff --git a/src/pulsecore/authkey-prop.h b/src/pulsecore/authkey-prop.h new file mode 100644 index 00000000..247202f3 --- /dev/null +++ b/src/pulsecore/authkey-prop.h @@ -0,0 +1,45 @@ +#ifndef fooauthkeyprophfoo +#define fooauthkeyprophfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/core.h> + +/* The authkey-prop uses a central property to store a previously + * loaded cookie in memory. Useful for sharing the same cookie between + * several modules. */ + +/* Return the data of the specified authorization key property. Doesn't alter the refernce count of the key */ +int pa_authkey_prop_get(pa_core *c, const char *name, void *data, size_t len); + +/* Store data in the specified authorization key property. The initial reference count is set to 1 */ +int pa_authkey_prop_put(pa_core *c, const char *name, const void *data, size_t len); + +/* Increase the reference count of the specified authorization key */ +void pa_authkey_prop_ref(pa_core *c, const char *name); + +/* Decrease the reference count of the specified authorization key */ +void pa_authkey_prop_unref(pa_core *c, const char *name); + +#endif diff --git a/src/pulsecore/authkey.c b/src/pulsecore/authkey.c new file mode 100644 index 00000000..80bc8576 --- /dev/null +++ b/src/pulsecore/authkey.c @@ -0,0 +1,238 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <unistd.h> +#include <fcntl.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <inttypes.h> +#include <stdlib.h> +#include <time.h> +#include <limits.h> +#include <sys/stat.h> + +#include <pulse/util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/random.h> +#include <pulsecore/macro.h> + +#include "authkey.h" + +/* Generate a new authorization key, store it in file fd and return it in *data */ +static int generate(int fd, void *ret_data, size_t length) { + ssize_t r; + + pa_assert(fd >= 0); + pa_assert(ret_data); + pa_assert(length > 0); + + pa_random(ret_data, length); + + lseek(fd, 0, SEEK_SET); + (void) ftruncate(fd, 0); + + if ((r = pa_loop_write(fd, ret_data, length, NULL)) < 0 || (size_t) r != length) { + pa_log("Failed to write cookie file: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +#ifndef O_NOCTTY +#define O_NOCTTY 0 +#endif + +/* Load an euthorization cookie from file fn and store it in data. If + * the cookie file doesn't exist, create it */ +static int load(const char *fn, void *data, size_t length) { + int fd = -1; + int writable = 1; + int unlock = 0, ret = -1; + ssize_t r; + + pa_assert(fn); + pa_assert(data); + pa_assert(length > 0); + + if ((fd = open(fn, O_RDWR|O_CREAT|O_BINARY|O_NOCTTY, S_IRUSR|S_IWUSR)) < 0) { + + if (errno != EACCES || (fd = open(fn, O_RDONLY|O_BINARY|O_NOCTTY)) < 0) { + pa_log("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno)); + goto finish; + } else + writable = 0; + } + + unlock = pa_lock_fd(fd, 1) >= 0; + + if ((r = pa_loop_read(fd, data, length, NULL)) < 0) { + pa_log("Failed to read cookie file '%s': %s", fn, pa_cstrerror(errno)); + goto finish; + } + + if ((size_t) r != length) { + pa_log_debug("Got %d bytes from cookie file '%s', expected %d", (int) r, fn, (int) length); + + if (!writable) { + pa_log("Unable to write cookie to read only file"); + goto finish; + } + + if (generate(fd, data, length) < 0) + goto finish; + } + + ret = 0; + +finish: + + if (fd >= 0) { + + if (unlock) + pa_lock_fd(fd, 0); + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close cookie file: %s", pa_cstrerror(errno)); + ret = -1; + } + } + + return ret; +} + +/* Load a cookie from a cookie file. If the file doesn't exist, create it. */ +int pa_authkey_load(const char *path, void *data, size_t length) { + int ret; + + pa_assert(path); + pa_assert(data); + pa_assert(length > 0); + + if ((ret = load(path, data, length)) < 0) + pa_log("Failed to load authorization key '%s': %s", path, (ret < 0) ? pa_cstrerror(errno) : "File corrupt"); + + return ret; +} + +/* If the specified file path starts with / return it, otherwise + * return path prepended with home directory */ +static const char *normalize_path(const char *fn, char *s, size_t l) { + + pa_assert(fn); + pa_assert(s); + pa_assert(l > 0); + +#ifndef OS_IS_WIN32 + if (fn[0] != '/') { +#else + if (strlen(fn) < 3 || !isalpha(fn[0]) || fn[1] != ':' || fn[2] != '\\') { +#endif + char homedir[PATH_MAX]; + + if (!pa_get_home_dir(homedir, sizeof(homedir))) + return NULL; + +#ifndef OS_IS_WIN32 + pa_snprintf(s, l, "%s/%s", homedir, fn); +#else + pa_snprintf(s, l, "%s\\%s", homedir, fn); +#endif + return s; + } + + return fn; +} + +/* Load a cookie from a file in the home directory. If the specified + * path starts with /, use it as absolute path instead. */ +int pa_authkey_load_auto(const char *fn, void *data, size_t length) { + char path[PATH_MAX]; + const char *p; + + pa_assert(fn); + pa_assert(data); + pa_assert(length > 0); + + if (!(p = normalize_path(fn, path, sizeof(path)))) + return -2; + + return pa_authkey_load(p, data, length); +} + +/* Store the specified cookie in the speicified cookie file */ +int pa_authkey_save(const char *fn, const void *data, size_t length) { + int fd = -1; + int unlock = 0, ret = -1; + ssize_t r; + char path[PATH_MAX]; + const char *p; + + pa_assert(fn); + pa_assert(data); + pa_assert(length > 0); + + if (!(p = normalize_path(fn, path, sizeof(path)))) + return -2; + + if ((fd = open(p, O_RDWR|O_CREAT|O_NOCTTY, S_IRUSR|S_IWUSR)) < 0) { + pa_log("Failed to open cookie file '%s': %s", fn, pa_cstrerror(errno)); + goto finish; + } + + unlock = pa_lock_fd(fd, 1) >= 0; + + if ((r = pa_loop_write(fd, data, length, NULL)) < 0 || (size_t) r != length) { + pa_log("Failed to read cookie file '%s': %s", fn, pa_cstrerror(errno)); + goto finish; + } + + ret = 0; + +finish: + + if (fd >= 0) { + + if (unlock) + pa_lock_fd(fd, 0); + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close cookie file: %s", pa_cstrerror(errno)); + ret = -1; + } + } + + return ret; +} diff --git a/src/pulsecore/authkey.h b/src/pulsecore/authkey.h new file mode 100644 index 00000000..18e5157d --- /dev/null +++ b/src/pulsecore/authkey.h @@ -0,0 +1,34 @@ +#ifndef fooauthkeyhfoo +#define fooauthkeyhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <sys/types.h> + +int pa_authkey_load(const char *path, void *data, size_t len); +int pa_authkey_load_auto(const char *fn, void *data, size_t length); + +int pa_authkey_save(const char *path, const void *data, size_t length); + +#endif diff --git a/src/pulsecore/autoload.c b/src/pulsecore/autoload.c new file mode 100644 index 00000000..a1d3e02d --- /dev/null +++ b/src/pulsecore/autoload.c @@ -0,0 +1,204 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/sound-file.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/core-subscribe.h> + +#include "autoload.h" + +static void entry_free(pa_autoload_entry *e) { + pa_assert(e); + pa_subscription_post(e->core, PA_SUBSCRIPTION_EVENT_AUTOLOAD|PA_SUBSCRIPTION_EVENT_REMOVE, PA_INVALID_INDEX); + pa_xfree(e->name); + pa_xfree(e->module); + pa_xfree(e->argument); + pa_xfree(e); +} + +static void entry_remove_and_free(pa_autoload_entry *e) { + pa_assert(e); + pa_assert(e->core); + + pa_idxset_remove_by_data(e->core->autoload_idxset, e, NULL); + pa_hashmap_remove(e->core->autoload_hashmap, e->name); + entry_free(e); +} + +static pa_autoload_entry* entry_new(pa_core *c, const char *name) { + pa_autoload_entry *e = NULL; + + pa_core_assert_ref(c); + pa_assert(name); + + if (c->autoload_hashmap && (e = pa_hashmap_get(c->autoload_hashmap, name))) + return NULL; + + e = pa_xnew(pa_autoload_entry, 1); + e->core = c; + e->name = pa_xstrdup(name); + e->module = e->argument = NULL; + e->in_action = 0; + + if (!c->autoload_hashmap) + c->autoload_hashmap = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + pa_assert(c->autoload_hashmap); + + pa_hashmap_put(c->autoload_hashmap, e->name, e); + + if (!c->autoload_idxset) + c->autoload_idxset = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + pa_idxset_put(c->autoload_idxset, e, &e->index); + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_AUTOLOAD|PA_SUBSCRIPTION_EVENT_NEW, e->index); + + return e; +} + +int pa_autoload_add(pa_core *c, const char*name, pa_namereg_type_t type, const char*module, const char *argument, uint32_t *idx) { + pa_autoload_entry *e = NULL; + + pa_assert(c); + pa_assert(name); + pa_assert(module); + pa_assert(type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE); + + if (!(e = entry_new(c, name))) + return -1; + + e->module = pa_xstrdup(module); + e->argument = pa_xstrdup(argument); + e->type = type; + + if (idx) + *idx = e->index; + + return 0; +} + +int pa_autoload_remove_by_name(pa_core *c, const char*name, pa_namereg_type_t type) { + pa_autoload_entry *e; + + pa_assert(c); + pa_assert(name); + pa_assert(type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE); + + if (!c->autoload_hashmap || !(e = pa_hashmap_get(c->autoload_hashmap, name)) || e->type != type) + return -1; + + entry_remove_and_free(e); + return 0; +} + +int pa_autoload_remove_by_index(pa_core *c, uint32_t idx) { + pa_autoload_entry *e; + + pa_assert(c); + pa_assert(idx != PA_IDXSET_INVALID); + + if (!c->autoload_idxset || !(e = pa_idxset_get_by_index(c->autoload_idxset, idx))) + return -1; + + entry_remove_and_free(e); + return 0; +} + +void pa_autoload_request(pa_core *c, const char *name, pa_namereg_type_t type) { + pa_autoload_entry *e; + pa_module *m; + + pa_assert(c); + pa_assert(name); + + if (!c->autoload_hashmap || !(e = pa_hashmap_get(c->autoload_hashmap, name)) || (e->type != type)) + return; + + if (e->in_action) + return; + + e->in_action = 1; + + if (type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE) { + if ((m = pa_module_load(c, e->module, e->argument))) + m->auto_unload = 1; + } + + e->in_action = 0; +} + +static void free_func(void *p, PA_GCC_UNUSED void *userdata) { + pa_autoload_entry *e = p; + pa_idxset_remove_by_data(e->core->autoload_idxset, e, NULL); + entry_free(e); +} + +void pa_autoload_free(pa_core *c) { + + if (c->autoload_hashmap) { + pa_hashmap_free(c->autoload_hashmap, free_func, NULL); + c->autoload_hashmap = NULL; + } + + if (c->autoload_idxset) { + pa_idxset_free(c->autoload_idxset, NULL, NULL); + c->autoload_idxset = NULL; + } +} + +const pa_autoload_entry* pa_autoload_get_by_name(pa_core *c, const char*name, pa_namereg_type_t type) { + pa_autoload_entry *e; + + pa_core_assert_ref(c); + pa_assert(name); + + if (!c->autoload_hashmap || !(e = pa_hashmap_get(c->autoload_hashmap, name)) || e->type != type) + return NULL; + + return e; +} + +const pa_autoload_entry* pa_autoload_get_by_index(pa_core *c, uint32_t idx) { + pa_autoload_entry *e; + + pa_core_assert_ref(c); + pa_assert(idx != PA_IDXSET_INVALID); + + if (!c->autoload_idxset || !(e = pa_idxset_get_by_index(c->autoload_idxset, idx))) + return NULL; + + return e; +} diff --git a/src/pulsecore/autoload.h b/src/pulsecore/autoload.h new file mode 100644 index 00000000..8a3522a7 --- /dev/null +++ b/src/pulsecore/autoload.h @@ -0,0 +1,60 @@ +#ifndef fooautoloadhfoo +#define fooautoloadhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/namereg.h> + +/* Using the autoloading facility, modules by be loaded on-demand and + * synchronously. The user may register a "ghost sink" or "ghost + * source". Whenever this sink/source is requested but not available a + * specified module is loaded. */ + +/* An autoload entry, or "ghost" sink/source */ +typedef struct pa_autoload_entry { + pa_core *core; + uint32_t index; + char *name; + pa_namereg_type_t type; /* Type of the autoload entry */ + int in_action; /* The module is currently being loaded */ + char *module, *argument; +} pa_autoload_entry; + +/* Add a new autoload entry of the given time, with the speicified + * sink/source name, module name and argument. Return the entry's + * index in *index */ +int pa_autoload_add(pa_core *c, const char*name, pa_namereg_type_t type, const char*module, const char *argument, uint32_t *idx); + +/* Free all autoload entries */ +void pa_autoload_free(pa_core *c); +int pa_autoload_remove_by_name(pa_core *c, const char*name, pa_namereg_type_t type); +int pa_autoload_remove_by_index(pa_core *c, uint32_t idx); + +/* Request an autoload entry by its name, effectively causing a module to be loaded */ +void pa_autoload_request(pa_core *c, const char *name, pa_namereg_type_t type); + +const pa_autoload_entry* pa_autoload_get_by_name(pa_core *c, const char*name, pa_namereg_type_t type); +const pa_autoload_entry* pa_autoload_get_by_index(pa_core *c, uint32_t idx); + +#endif diff --git a/src/pulsecore/avahi-wrap.c b/src/pulsecore/avahi-wrap.c new file mode 100644 index 00000000..fae54810 --- /dev/null +++ b/src/pulsecore/avahi-wrap.c @@ -0,0 +1,197 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulse/xmalloc.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "avahi-wrap.h" + +typedef struct { + AvahiPoll api; + pa_mainloop_api *mainloop; +} pa_avahi_poll; + +struct AvahiWatch { + pa_io_event *io_event; + pa_avahi_poll *avahi_poll; + AvahiWatchEvent current_event; + AvahiWatchCallback callback; + void *userdata; +}; + +static AvahiWatchEvent translate_io_flags_back(pa_io_event_flags_t e) { + return + (e & PA_IO_EVENT_INPUT ? AVAHI_WATCH_IN : 0) | + (e & PA_IO_EVENT_OUTPUT ? AVAHI_WATCH_OUT : 0) | + (e & PA_IO_EVENT_ERROR ? AVAHI_WATCH_ERR : 0) | + (e & PA_IO_EVENT_HANGUP ? AVAHI_WATCH_HUP : 0); +} + +static pa_io_event_flags_t translate_io_flags(AvahiWatchEvent e) { + return + (e & AVAHI_WATCH_IN ? PA_IO_EVENT_INPUT : 0) | + (e & AVAHI_WATCH_OUT ? PA_IO_EVENT_OUTPUT : 0) | + (e & AVAHI_WATCH_ERR ? PA_IO_EVENT_ERROR : 0) | + (e & AVAHI_WATCH_HUP ? PA_IO_EVENT_HANGUP : 0); +} + +static void watch_callback(pa_mainloop_api*a, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { + AvahiWatch *w = userdata; + + pa_assert(a); + pa_assert(e); + pa_assert(w); + + w->current_event = translate_io_flags_back(events); + w->callback(w, fd, w->current_event, w->userdata); + w->current_event = 0; +} + +static AvahiWatch* watch_new(const AvahiPoll *api, int fd, AvahiWatchEvent event, AvahiWatchCallback callback, void *userdata) { + pa_avahi_poll *p; + AvahiWatch *w; + + pa_assert(api); + pa_assert(fd >= 0); + pa_assert(callback); + pa_assert_se(p = api->userdata); + + w = pa_xnew(AvahiWatch, 1); + w->avahi_poll = p; + w->current_event = 0; + w->callback = callback; + w->userdata = userdata; + w->io_event = p->mainloop->io_new(p->mainloop, fd, translate_io_flags(event), watch_callback, w); + + return w; +} + +static void watch_update(AvahiWatch *w, AvahiWatchEvent event) { + pa_assert(w); + + w->avahi_poll->mainloop->io_enable(w->io_event, translate_io_flags(event)); +} + +static AvahiWatchEvent watch_get_events(AvahiWatch *w) { + pa_assert(w); + + return w->current_event; +} + +static void watch_free(AvahiWatch *w) { + pa_assert(w); + + w->avahi_poll->mainloop->io_free(w->io_event); + pa_xfree(w); +} + +struct AvahiTimeout { + pa_time_event *time_event; + pa_avahi_poll *avahi_poll; + AvahiTimeoutCallback callback; + void *userdata; +}; + +static void timeout_callback(pa_mainloop_api*a, pa_time_event* e, const struct timeval *tv, void *userdata) { + AvahiTimeout *t = userdata; + + pa_assert(a); + pa_assert(e); + pa_assert(t); + + t->callback(t, t->userdata); +} + +static AvahiTimeout* timeout_new(const AvahiPoll *api, const struct timeval *tv, AvahiTimeoutCallback callback, void *userdata) { + pa_avahi_poll *p; + AvahiTimeout *t; + + pa_assert(api); + pa_assert(callback); + pa_assert_se(p = api->userdata); + + t = pa_xnew(AvahiTimeout, 1); + t->avahi_poll = p; + t->callback = callback; + t->userdata = userdata; + + t->time_event = tv ? p->mainloop->time_new(p->mainloop, tv, timeout_callback, t) : NULL; + + return t; +} + +static void timeout_update(AvahiTimeout *t, const struct timeval *tv) { + pa_assert(t); + + if (t->time_event && tv) + t->avahi_poll->mainloop->time_restart(t->time_event, tv); + else if (!t->time_event && tv) + t->time_event = t->avahi_poll->mainloop->time_new(t->avahi_poll->mainloop, tv, timeout_callback, t); + else if (t->time_event && !tv) { + t->avahi_poll->mainloop->time_free(t->time_event); + t->time_event = NULL; + } +} + +static void timeout_free(AvahiTimeout *t) { + pa_assert(t); + + if (t->time_event) + t->avahi_poll->mainloop->time_free(t->time_event); + pa_xfree(t); +} + +AvahiPoll* pa_avahi_poll_new(pa_mainloop_api *m) { + pa_avahi_poll *p; + + pa_assert(m); + + p = pa_xnew(pa_avahi_poll, 1); + + p->api.userdata = p; + p->api.watch_new = watch_new; + p->api.watch_update = watch_update; + p->api.watch_get_events = watch_get_events; + p->api.watch_free = watch_free; + p->api.timeout_new = timeout_new; + p->api.timeout_update = timeout_update; + p->api.timeout_free = timeout_free; + p->mainloop = m; + + return &p->api; +} + +void pa_avahi_poll_free(AvahiPoll *api) { + pa_avahi_poll *p; + pa_assert(api); + pa_assert_se(p = api->userdata); + + pa_xfree(p); +} + diff --git a/src/pulsecore/avahi-wrap.h b/src/pulsecore/avahi-wrap.h new file mode 100644 index 00000000..1e20ec38 --- /dev/null +++ b/src/pulsecore/avahi-wrap.h @@ -0,0 +1,34 @@ +#ifndef fooavahiwrapperhfoo +#define fooavahiwrapperhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <avahi-client/client.h> + +#include <pulse/mainloop-api.h> + +AvahiPoll* pa_avahi_poll_new(pa_mainloop_api *api); +void pa_avahi_poll_free(AvahiPoll *p); + +#endif diff --git a/src/pulsecore/cli-command.c b/src/pulsecore/cli-command.c new file mode 100644 index 00000000..3110a271 --- /dev/null +++ b/src/pulsecore/cli-command.c @@ -0,0 +1,1423 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> +#include <stdlib.h> +#include <errno.h> +#include <unistd.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/client.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/tokenizer.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/namereg.h> +#include <pulsecore/cli-text.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/sound-file.h> +#include <pulsecore/play-memchunk.h> +#include <pulsecore/autoload.h> +#include <pulsecore/sound-file-stream.h> +#include <pulsecore/props.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> + +#include "cli-command.h" + +struct command { + const char *name; + int (*proc) (pa_core *c, pa_tokenizer*t, pa_strbuf *buf, pa_bool_t *fail); + const char *help; + unsigned args; +}; + +#define META_INCLUDE ".include" +#define META_FAIL ".fail" +#define META_NOFAIL ".nofail" +#define META_IFEXISTS ".ifexists" +#define META_ELSE ".else" +#define META_ENDIF ".endif" + +enum { + IFSTATE_NONE = -1, + IFSTATE_FALSE = 0, + IFSTATE_TRUE = 1, +}; + +/* Prototypes for all available commands */ +static int pa_cli_command_exit(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_modules(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_clients(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_sources(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_sink_inputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_sink_input_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_autoload_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_autoload_add(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_autoload_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_list_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_move_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_vacuum(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); +static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail); + +/* A method table for all available commands */ + +static const struct command commands[] = { + { "exit", pa_cli_command_exit, "Terminate the daemon", 1 }, + { "help", pa_cli_command_help, "Show this help", 1 }, + { "list-modules", pa_cli_command_modules, "List loaded modules", 1 }, + { "list-sinks", pa_cli_command_sinks, "List loaded sinks", 1 }, + { "list-sources", pa_cli_command_sources, "List loaded sources", 1 }, + { "list-clients", pa_cli_command_clients, "List loaded clients", 1 }, + { "list-sink-inputs", pa_cli_command_sink_inputs, "List sink inputs", 1 }, + { "list-source-outputs", pa_cli_command_source_outputs, "List source outputs", 1 }, + { "stat", pa_cli_command_stat, "Show memory block statistics", 1 }, + { "info", pa_cli_command_info, "Show comprehensive status", 1 }, + { "ls", pa_cli_command_info, NULL, 1 }, + { "list", pa_cli_command_info, NULL, 1 }, + { "load-module", pa_cli_command_load, "Load a module (args: name, arguments)", 3}, + { "unload-module", pa_cli_command_unload, "Unload a module (args: index)", 2}, + { "set-sink-volume", pa_cli_command_sink_volume, "Set the volume of a sink (args: index|name, volume)", 3}, + { "set-sink-input-volume", pa_cli_command_sink_input_volume, "Set the volume of a sink input (args: index, volume)", 3}, + { "set-source-volume", pa_cli_command_source_volume, "Set the volume of a source (args: index|name, volume)", 3}, + { "set-sink-mute", pa_cli_command_sink_mute, "Set the mute switch of a sink (args: index|name, bool)", 3}, + { "set-sink-input-mute", pa_cli_command_sink_input_mute, "Set the mute switch of a sink input (args: index, bool)", 3}, + { "set-source-mute", pa_cli_command_source_mute, "Set the mute switch of a source (args: index|name, bool)", 3}, + { "set-default-sink", pa_cli_command_sink_default, "Set the default sink (args: index|name)", 2}, + { "set-default-source", pa_cli_command_source_default, "Set the default source (args: index|name)", 2}, + { "kill-client", pa_cli_command_kill_client, "Kill a client (args: index)", 2}, + { "kill-sink-input", pa_cli_command_kill_sink_input, "Kill a sink input (args: index)", 2}, + { "kill-source-output", pa_cli_command_kill_source_output, "Kill a source output (args: index)", 2}, + { "list-samples", pa_cli_command_scache_list, "List all entries in the sample cache", 1}, + { "play-sample", pa_cli_command_scache_play, "Play a sample from the sample cache (args: name, sink|index)", 3}, + { "remove-sample", pa_cli_command_scache_remove, "Remove a sample from the sample cache (args: name)", 2}, + { "load-sample", pa_cli_command_scache_load, "Load a sound file into the sample cache (args: name, filename)", 3}, + { "load-sample-lazy", pa_cli_command_scache_load, "Lazily load a sound file into the sample cache (args: name, filename)", 3}, + { "load-sample-dir-lazy", pa_cli_command_scache_load_dir, "Lazily load all files in a directory into the sample cache (args: pathname)", 2}, + { "play-file", pa_cli_command_play_file, "Play a sound file (args: filename, sink|index)", 3}, + { "list-autoload", pa_cli_command_autoload_list, "List autoload entries", 1}, + { "add-autoload-sink", pa_cli_command_autoload_add, "Add autoload entry for a sink (args: sink, module name, arguments)", 4}, + { "add-autoload-source", pa_cli_command_autoload_add, "Add autoload entry for a source (args: source, module name, arguments)", 4}, + { "remove-autoload-sink", pa_cli_command_autoload_remove, "Remove autoload entry for a sink (args: name)", 2}, + { "remove-autoload-source", pa_cli_command_autoload_remove, "Remove autoload entry for a source (args: name)", 2}, + { "dump", pa_cli_command_dump, "Dump daemon configuration", 1}, + { "list-props", pa_cli_command_list_props, NULL, 1}, + { "move-sink-input", pa_cli_command_move_sink_input, "Move sink input to another sink (args: index, sink)", 3}, + { "move-source-output", pa_cli_command_move_source_output, "Move source output to another source (args: index, source)", 3}, + { "vacuum", pa_cli_command_vacuum, NULL, 1}, + { "suspend-sink", pa_cli_command_suspend_sink, "Suspend sink (args: index|name, bool)", 3}, + { "suspend-source", pa_cli_command_suspend_source, "Suspend source (args: index|name, bool)", 3}, + { "suspend", pa_cli_command_suspend, "Suspend all sinks and all sources (args: bool)", 2}, + { NULL, NULL, NULL, 0 } +}; + +static const char whitespace[] = " \t\n\r"; +static const char linebreak[] = "\n\r"; + +static uint32_t parse_index(const char *n) { + uint32_t idx; + + if (pa_atou(n, &idx) < 0) + return (uint32_t) PA_IDXSET_INVALID; + + return idx; +} + +static int pa_cli_command_exit(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + c->mainloop->quit(c->mainloop, 0); + return 0; +} + +static int pa_cli_command_help(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const struct command*command; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_strbuf_puts(buf, "Available commands:\n"); + + for (command = commands; command->name; command++) + if (command->help) + pa_strbuf_printf(buf, " %-25s %s\n", command->name, command->help); + return 0; +} + +static int pa_cli_command_modules(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_module_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_clients(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_client_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_sinks(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_sink_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_sources(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_source_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_sink_inputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_sink_input_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_source_outputs(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_source_output_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + return 0; +} + +static int pa_cli_command_stat(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + char s[256]; + const pa_mempool_stat *stat; + unsigned k; + const char *def_sink, *def_source; + + static const char* const type_table[PA_MEMBLOCK_TYPE_MAX] = { + [PA_MEMBLOCK_POOL] = "POOL", + [PA_MEMBLOCK_POOL_EXTERNAL] = "POOL_EXTERNAL", + [PA_MEMBLOCK_APPENDED] = "APPENDED", + [PA_MEMBLOCK_USER] = "USER", + [PA_MEMBLOCK_FIXED] = "FIXED", + [PA_MEMBLOCK_IMPORTED] = "IMPORTED", + }; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + stat = pa_mempool_get_stat(c->mempool); + + pa_strbuf_printf(buf, "Memory blocks currently allocated: %u, size: %s.\n", + (unsigned) pa_atomic_load(&stat->n_allocated), + pa_bytes_snprint(s, sizeof(s), (size_t) pa_atomic_load(&stat->allocated_size))); + + pa_strbuf_printf(buf, "Memory blocks allocated during the whole lifetime: %u, size: %s.\n", + (unsigned) pa_atomic_load(&stat->n_accumulated), + pa_bytes_snprint(s, sizeof(s), (size_t) pa_atomic_load(&stat->accumulated_size))); + + pa_strbuf_printf(buf, "Memory blocks imported from other processes: %u, size: %s.\n", + (unsigned) pa_atomic_load(&stat->n_imported), + pa_bytes_snprint(s, sizeof(s), (size_t) pa_atomic_load(&stat->imported_size))); + + pa_strbuf_printf(buf, "Memory blocks exported to other processes: %u, size: %s.\n", + (unsigned) pa_atomic_load(&stat->n_exported), + pa_bytes_snprint(s, sizeof(s), (size_t) pa_atomic_load(&stat->exported_size))); + + pa_strbuf_printf(buf, "Total sample cache size: %s.\n", + pa_bytes_snprint(s, sizeof(s), pa_scache_total_size(c))); + + pa_strbuf_printf(buf, "Default sample spec: %s\n", + pa_sample_spec_snprint(s, sizeof(s), &c->default_sample_spec)); + + def_sink = pa_namereg_get_default_sink_name(c); + def_source = pa_namereg_get_default_source_name(c); + pa_strbuf_printf(buf, "Default sink name: %s\n" + "Default source name: %s\n", + def_sink ? def_sink : "none", + def_source ? def_source : "none"); + + for (k = 0; k < PA_MEMBLOCK_TYPE_MAX; k++) + pa_strbuf_printf(buf, + "Memory blocks of type %s: %u allocated/%u accumulated.\n", + type_table[k], + (unsigned) pa_atomic_load(&stat->n_allocated_by_type[k]), + (unsigned) pa_atomic_load(&stat->n_accumulated_by_type[k])); + + return 0; +} + +static int pa_cli_command_info(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_cli_command_stat(c, t, buf, fail); + pa_cli_command_modules(c, t, buf, fail); + pa_cli_command_sinks(c, t, buf, fail); + pa_cli_command_sources(c, t, buf, fail); + pa_cli_command_clients(c, t, buf, fail); + pa_cli_command_sink_inputs(c, t, buf, fail); + pa_cli_command_source_outputs(c, t, buf, fail); + pa_cli_command_scache_list(c, t, buf, fail); + pa_cli_command_autoload_list(c, t, buf, fail); + return 0; +} + +static int pa_cli_command_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + pa_module *m; + const char *name; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(name = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify the module name and optionally arguments.\n"); + return -1; + } + + if (!(m = pa_module_load(c, name, pa_tokenizer_get(t, 2)))) { + pa_strbuf_puts(buf, "Module load failed.\n"); + return -1; + } + + return 0; +} + +static int pa_cli_command_unload(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + pa_module *m; + uint32_t idx; + const char *i; + char *e; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(i = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify the module index.\n"); + return -1; + } + + idx = (uint32_t) strtoul(i, &e, 10); + if (*e || !(m = pa_idxset_get_by_index(c->modules, idx))) { + pa_strbuf_puts(buf, "Invalid module index.\n"); + return -1; + } + + pa_module_unload_request(m); + return 0; +} + +static int pa_cli_command_sink_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *v; + pa_sink *sink; + uint32_t volume; + pa_cvolume cvolume; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x100 is normal volume)\n"); + return -1; + } + + if (pa_atou(v, &volume) < 0) { + pa_strbuf_puts(buf, "Failed to parse volume.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + pa_cvolume_set(&cvolume, sink->sample_spec.channels, volume); + pa_sink_set_volume(sink, &cvolume); + return 0; +} + +static int pa_cli_command_sink_input_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *v; + pa_sink_input *si; + pa_volume_t volume; + pa_cvolume cvolume; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x100 is normal volume)\n"); + return -1; + } + + if (pa_atou(v, &volume) < 0) { + pa_strbuf_puts(buf, "Failed to parse volume.\n"); + return -1; + } + + if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No sink input found with this index.\n"); + return -1; + } + + pa_cvolume_set(&cvolume, si->sample_spec.channels, volume); + pa_sink_input_set_volume(si, &cvolume); + return 0; +} + +static int pa_cli_command_source_volume(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *v; + pa_source *source; + uint32_t volume; + pa_cvolume cvolume; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x100 is normal volume)\n"); + return -1; + } + + if (pa_atou(v, &volume) < 0) { + pa_strbuf_puts(buf, "Failed to parse volume.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE, 1))) { + pa_strbuf_puts(buf, "No source found by this name or index.\n"); + return -1; + } + + pa_cvolume_set(&cvolume, source->sample_spec.channels, volume); + pa_source_set_volume(source, &cvolume); + return 0; +} + +static int pa_cli_command_sink_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *m; + pa_sink *sink; + int mute; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + if (!(m = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n"); + return -1; + } + + if (pa_atoi(m, &mute) < 0) { + pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + pa_sink_set_mute(sink, mute); + return 0; +} + +static int pa_cli_command_source_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *m; + pa_source *source; + int mute; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + if (!(m = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a mute switch setting (0/1).\n"); + return -1; + } + + if (pa_atoi(m, &mute) < 0) { + pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE, 1))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + pa_source_set_mute(source, mute); + return 0; +} + +static int pa_cli_command_sink_input_mute(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *v; + pa_sink_input *si; + uint32_t idx; + int mute; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(v = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a volume >= 0. (0 is muted, 0x100 is normal volume)\n"); + return -1; + } + + if (pa_atoi(v, &mute) < 0) { + pa_strbuf_puts(buf, "Failed to parse mute switch.\n"); + return -1; + } + + if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No sink input found with this index.\n"); + return -1; + } + + pa_sink_input_set_mute(si, mute); + return 0; +} + +static int pa_cli_command_sink_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + pa_namereg_set_default(c, n, PA_NAMEREG_SINK); + return 0; +} + +static int pa_cli_command_source_default(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + pa_namereg_set_default(c, n, PA_NAMEREG_SOURCE); + return 0; +} + +static int pa_cli_command_kill_client(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n; + pa_client *client; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a client by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(client = pa_idxset_get_by_index(c->clients, idx))) { + pa_strbuf_puts(buf, "No client found by this index.\n"); + return -1; + } + + pa_client_kill(client); + return 0; +} + +static int pa_cli_command_kill_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n; + pa_sink_input *sink_input; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(sink_input = pa_idxset_get_by_index(c->sink_inputs, idx))) { + pa_strbuf_puts(buf, "No sink input found by this index.\n"); + return -1; + } + + pa_sink_input_kill(sink_input); + return 0; +} + +static int pa_cli_command_kill_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n; + pa_source_output *source_output; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source output by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(source_output = pa_idxset_get_by_index(c->source_outputs, idx))) { + pa_strbuf_puts(buf, "No source output found by this index.\n"); + return -1; + } + + pa_source_output_kill(source_output); + return 0; +} + +static int pa_cli_command_scache_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_scache_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + + return 0; +} + +static int pa_cli_command_scache_play(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *sink_name; + pa_sink *sink; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1)) || !(sink_name = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a sample name and a sink name.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, 1))) { + pa_strbuf_puts(buf, "No sink by that name.\n"); + return -1; + } + + if (pa_scache_play_item(c, n, sink, PA_VOLUME_NORM) < 0) { + pa_strbuf_puts(buf, "Failed to play sample.\n"); + return -1; + } + + return 0; +} + +static int pa_cli_command_scache_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sample name.\n"); + return -1; + } + + if (pa_scache_remove_item(c, n) < 0) { + pa_strbuf_puts(buf, "Failed to remove sample.\n"); + return -1; + } + + return 0; +} + +static int pa_cli_command_scache_load(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *fname, *n; + int r; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(fname = pa_tokenizer_get(t, 2)) || !(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a file name and a sample name.\n"); + return -1; + } + + if (strstr(pa_tokenizer_get(t, 0), "lazy")) + r = pa_scache_add_file_lazy(c, n, fname, NULL); + else + r = pa_scache_add_file(c, n, fname, NULL); + + if (r < 0) + pa_strbuf_puts(buf, "Failed to load sound file.\n"); + + return 0; +} + +static int pa_cli_command_scache_load_dir(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *pname; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(pname = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a path name.\n"); + return -1; + } + + if (pa_scache_add_directory_lazy(c, pname) < 0) { + pa_strbuf_puts(buf, "Failed to load directory.\n"); + return -1; + } + + return 0; +} + +static int pa_cli_command_play_file(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *fname, *sink_name; + pa_sink *sink; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(fname = pa_tokenizer_get(t, 1)) || !(sink_name = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a file name and a sink name.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, 1))) { + pa_strbuf_puts(buf, "No sink by that name.\n"); + return -1; + } + + + return pa_play_file(sink, fname, NULL); +} + +static int pa_cli_command_autoload_add(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *a, *b; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(a = pa_tokenizer_get(t, 1)) || !(b = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a device name, a filename or a module name and optionally module arguments\n"); + return -1; + } + + pa_autoload_add(c, a, strstr(pa_tokenizer_get(t, 0), "sink") ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE, b, pa_tokenizer_get(t, 3), NULL); + + return 0; +} + +static int pa_cli_command_autoload_remove(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *name; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(name = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a device name\n"); + return -1; + } + + if (pa_autoload_remove_by_name(c, name, strstr(pa_tokenizer_get(t, 0), "sink") ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE) < 0) { + pa_strbuf_puts(buf, "Failed to remove autload entry\n"); + return -1; + } + + return 0; +} + +static int pa_cli_command_autoload_list(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + char *s; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_assert_se(s = pa_autoload_list_to_string(c)); + pa_strbuf_puts(buf, s); + pa_xfree(s); + + return 0; +} + +static int pa_cli_command_list_props(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_property_dump(c, buf); + return 0; +} + +static int pa_cli_command_vacuum(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + pa_mempool_vacuum(c->mempool); + + return 0; +} + +static int pa_cli_command_move_sink_input(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *k; + pa_sink_input *si; + pa_sink *sink; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink input by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(k = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a sink.\n"); + return -1; + } + + if (!(si = pa_idxset_get_by_index(c->sink_inputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No sink input found with this index.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, k, PA_NAMEREG_SINK, 1))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + if (pa_sink_input_move_to(si, sink, 0) < 0) { + pa_strbuf_puts(buf, "Moved failed.\n"); + return -1; + } + return 0; +} + +static int pa_cli_command_move_source_output(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *k; + pa_source_output *so; + pa_source *source; + uint32_t idx; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source output by its index.\n"); + return -1; + } + + if ((idx = parse_index(n)) == PA_IDXSET_INVALID) { + pa_strbuf_puts(buf, "Failed to parse index.\n"); + return -1; + } + + if (!(k = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a source.\n"); + return -1; + } + + if (!(so = pa_idxset_get_by_index(c->source_outputs, (uint32_t) idx))) { + pa_strbuf_puts(buf, "No source output found with this index.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, k, PA_NAMEREG_SOURCE, 1))) { + pa_strbuf_puts(buf, "No source found by this name or index.\n"); + return -1; + } + + if (pa_source_output_move_to(so, source) < 0) { + pa_strbuf_puts(buf, "Moved failed.\n"); + return -1; + } + return 0; +} + +static int pa_cli_command_suspend_sink(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *m; + pa_sink *sink; + int suspend; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a sink either by its name or its index.\n"); + return -1; + } + + if (!(m = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a suspend switch setting (0/1).\n"); + return -1; + } + + if (pa_atoi(m, &suspend) < 0) { + pa_strbuf_puts(buf, "Failed to parse suspend switch.\n"); + return -1; + } + + if (!(sink = pa_namereg_get(c, n, PA_NAMEREG_SINK, 1))) { + pa_strbuf_puts(buf, "No sink found by this name or index.\n"); + return -1; + } + + pa_sink_suspend(sink, suspend); + return 0; +} + +static int pa_cli_command_suspend_source(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *n, *m; + pa_source *source; + int suspend; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(n = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a source either by its name or its index.\n"); + return -1; + } + + if (!(m = pa_tokenizer_get(t, 2))) { + pa_strbuf_puts(buf, "You need to specify a suspend switch setting (0/1).\n"); + return -1; + } + + if (pa_atoi(m, &suspend) < 0) { + pa_strbuf_puts(buf, "Failed to parse suspend switch.\n"); + return -1; + } + + if (!(source = pa_namereg_get(c, n, PA_NAMEREG_SOURCE, 1))) { + pa_strbuf_puts(buf, "No source found by this name or index.\n"); + return -1; + } + + pa_source_suspend(source, suspend); + return 0; +} + +static int pa_cli_command_suspend(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + const char *m; + int suspend; + int ret; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + if (!(m = pa_tokenizer_get(t, 1))) { + pa_strbuf_puts(buf, "You need to specify a suspend switch setting (0/1).\n"); + return -1; + } + + if (pa_atoi(m, &suspend) < 0) { + pa_strbuf_puts(buf, "Failed to parse suspend switch.\n"); + return -1; + } + + ret = - (pa_sink_suspend_all(c, suspend) < 0); + if (pa_source_suspend_all(c, suspend) < 0) + ret = -1; + + if (ret < 0) + pa_strbuf_puts(buf, "Failed to resume/suspend all sinks/sources.\n"); + + return 0; +} + +static int pa_cli_command_dump(pa_core *c, pa_tokenizer *t, pa_strbuf *buf, pa_bool_t *fail) { + pa_module *m; + pa_sink *sink; + pa_source *source; + int nl; + const char *p; + uint32_t idx; + char txt[256]; + time_t now; + void *i; + pa_autoload_entry *a; + + pa_core_assert_ref(c); + pa_assert(t); + pa_assert(buf); + pa_assert(fail); + + time(&now); + +#ifdef HAVE_CTIME_R + pa_strbuf_printf(buf, "### Configuration dump generated at %s\n", ctime_r(&now, txt)); +#else + pa_strbuf_printf(buf, "### Configuration dump generated at %s\n", ctime(&now)); +#endif + + for (m = pa_idxset_first(c->modules, &idx); m; m = pa_idxset_next(c->modules, &idx)) { + if (m->auto_unload) + continue; + + pa_strbuf_printf(buf, "load-module %s", m->name); + + if (m->argument) + pa_strbuf_printf(buf, " %s", m->argument); + + pa_strbuf_puts(buf, "\n"); + } + + nl = 0; + + for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) { + if (sink->module && sink->module->auto_unload) + continue; + + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = 1; + } + + pa_strbuf_printf(buf, "set-sink-volume %s 0x%03x\n", sink->name, pa_cvolume_avg(pa_sink_get_volume(sink))); + pa_strbuf_printf(buf, "set-sink-mute %s %d\n", sink->name, pa_sink_get_mute(sink)); + } + + for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) { + if (source->module && source->module->auto_unload) + continue; + + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = 1; + } + + pa_strbuf_printf(buf, "set-source-volume %s 0x%03x\n", source->name, pa_cvolume_avg(pa_source_get_volume(source))); + pa_strbuf_printf(buf, "set-source-mute %s %d\n", source->name, pa_source_get_mute(source)); + } + + + if (c->autoload_hashmap) { + nl = 0; + + i = NULL; + while ((a = pa_hashmap_iterate(c->autoload_hashmap, &i, NULL))) { + + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = 1; + } + + pa_strbuf_printf(buf, "add-autoload-%s %s %s", a->type == PA_NAMEREG_SINK ? "sink" : "source", a->name, a->module); + + if (a->argument) + pa_strbuf_printf(buf, " %s", a->argument); + + pa_strbuf_puts(buf, "\n"); + } + } + + nl = 0; + + if ((p = pa_namereg_get_default_sink_name(c))) { + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = 1; + } + pa_strbuf_printf(buf, "set-default-sink %s\n", p); + } + + if ((p = pa_namereg_get_default_source_name(c))) { + if (!nl) { + pa_strbuf_puts(buf, "\n"); + nl = 1; + } + pa_strbuf_printf(buf, "set-default-source %s\n", p); + } + + pa_strbuf_puts(buf, "\n### EOF\n"); + + return 0; +} + +int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail, int *ifstate) { + const char *cs; + + pa_assert(c); + pa_assert(s); + pa_assert(buf); + + cs = s+strspn(s, whitespace); + + if (*cs == '#' || !*cs) + return 0; + else if (*cs == '.') { + if (!strcmp(cs, META_ELSE)) { + if (!ifstate || *ifstate == IFSTATE_NONE) { + pa_strbuf_printf(buf, "Meta command %s is not valid in this context\n", cs); + return -1; + } else if (*ifstate == IFSTATE_TRUE) + *ifstate = IFSTATE_FALSE; + else + *ifstate = IFSTATE_TRUE; + return 0; + } else if (!strcmp(cs, META_ENDIF)) { + if (!ifstate || *ifstate == IFSTATE_NONE) { + pa_strbuf_printf(buf, "Meta command %s is not valid in this context\n", cs); + return -1; + } else + *ifstate = IFSTATE_NONE; + return 0; + } + if (ifstate && *ifstate == IFSTATE_FALSE) + return 0; + if (!strcmp(cs, META_FAIL)) + *fail = TRUE; + else if (!strcmp(cs, META_NOFAIL)) + *fail = FALSE; + else { + size_t l; + l = strcspn(cs, whitespace); + + if (l == sizeof(META_INCLUDE)-1 && !strncmp(cs, META_INCLUDE, l)) { + const char *filename = cs+l+strspn(cs+l, whitespace); + if (pa_cli_command_execute_file(c, filename, buf, fail) < 0) + if (*fail) + return -1; + } else if (l == sizeof(META_IFEXISTS)-1 && !strncmp(cs, META_IFEXISTS, l)) { + if (!ifstate) { + pa_strbuf_printf(buf, "Meta command %s is not valid in this context\n", cs); + return -1; + } else if (*ifstate != IFSTATE_NONE) { + pa_strbuf_printf(buf, "Nested %s commands not supported\n", cs); + return -1; + } else { + const char *filename = cs+l+strspn(cs+l, whitespace); + + *ifstate = access(filename, F_OK) == 0 ? IFSTATE_TRUE : IFSTATE_FALSE; + pa_log_debug("Checking for existance of '%s': %s", filename, *ifstate == IFSTATE_TRUE ? "success" : "failure"); + } + } else { + pa_strbuf_printf(buf, "Invalid meta command: %s\n", cs); + if (*fail) return -1; + } + } + } else { + const struct command*command; + int unknown = 1; + size_t l; + + if (ifstate && *ifstate == IFSTATE_FALSE) + return 0; + + l = strcspn(cs, whitespace); + + for (command = commands; command->name; command++) + if (strlen(command->name) == l && !strncmp(cs, command->name, l)) { + int ret; + pa_tokenizer *t = pa_tokenizer_new(cs, command->args); + pa_assert(t); + ret = command->proc(c, t, buf, fail); + pa_tokenizer_free(t); + unknown = 0; + + if (ret < 0 && *fail) + return -1; + + break; + } + + if (unknown) { + pa_strbuf_printf(buf, "Unknown command: %s\n", cs); + if (*fail) + return -1; + } + } + + return 0; +} + +int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail) { + return pa_cli_command_execute_line_stateful(c, s, buf, fail, NULL); +} + +int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_bool_t *fail) { + char line[1024]; + FILE *f = NULL; + int ifstate = IFSTATE_NONE; + int ret = -1; + + pa_assert(c); + pa_assert(fn); + pa_assert(buf); + + if (!(f = fopen(fn, "r"))) { + pa_strbuf_printf(buf, "open('%s') failed: %s\n", fn, pa_cstrerror(errno)); + if (!*fail) + ret = 0; + goto fail; + } + + while (fgets(line, sizeof(line), f)) { + char *e = line + strcspn(line, linebreak); + *e = 0; + + if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail) + goto fail; + } + + ret = 0; + +fail: + if (f) + fclose(f); + + return ret; +} + +int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail) { + const char *p; + int ifstate = IFSTATE_NONE; + + pa_assert(c); + pa_assert(s); + pa_assert(buf); + + p = s; + while (*p) { + size_t l = strcspn(p, linebreak); + char *line = pa_xstrndup(p, l); + + if (pa_cli_command_execute_line_stateful(c, line, buf, fail, &ifstate) < 0 && *fail) { + pa_xfree(line); + return -1; + } + pa_xfree(line); + + p += l; + p += strspn(p, linebreak); + } + + return 0; +} diff --git a/src/pulsecore/cli-command.h b/src/pulsecore/cli-command.h new file mode 100644 index 00000000..c90c8e08 --- /dev/null +++ b/src/pulsecore/cli-command.h @@ -0,0 +1,45 @@ +#ifndef fooclicommandhfoo +#define fooclicommandhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/strbuf.h> +#include <pulsecore/core.h> + +/* Execute a single CLI command. Write the results to the string + * buffer *buf. If *fail is non-zero the function will return -1 when + * one or more of the executed commands failed. *fail + * may be modified by the function call. */ +int pa_cli_command_execute_line(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail); + +/* Execute a whole file of CLI commands */ +int pa_cli_command_execute_file(pa_core *c, const char *fn, pa_strbuf *buf, pa_bool_t *fail); + +/* Split the specified string into lines and run pa_cli_command_execute_line() for each. */ +int pa_cli_command_execute(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail); + +/* Same as pa_cli_command_execute_line() but also take ifstate var. */ +int pa_cli_command_execute_line_stateful(pa_core *c, const char *s, pa_strbuf *buf, pa_bool_t *fail, int *ifstate); + +#endif diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c new file mode 100644 index 00000000..b64cafe2 --- /dev/null +++ b/src/pulsecore/cli-text.c @@ -0,0 +1,450 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> + +#include <pulse/volume.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/client.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/autoload.h> +#include <pulsecore/macro.h> + +#include "cli-text.h" + +char *pa_module_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_module *m; + uint32_t idx = PA_IDXSET_INVALID; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u module(s) loaded.\n", pa_idxset_size(c->modules)); + + for (m = pa_idxset_first(c->modules, &idx); m; m = pa_idxset_next(c->modules, &idx)) { + pa_strbuf_printf(s, " index: %u\n" + "\tname: <%s>\n" + "\targument: <%s>\n" + "\tused: %i\n" + "\tauto unload: %s\n", + m->index, m->name, m->argument ? m->argument : "", m->n_used, + m->auto_unload ? "yes" : "no"); + } + + return pa_strbuf_tostring_free(s); +} + +char *pa_client_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_client *client; + uint32_t idx = PA_IDXSET_INVALID; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u client(s) logged in.\n", pa_idxset_size(c->clients)); + + for (client = pa_idxset_first(c->clients, &idx); client; client = pa_idxset_next(c->clients, &idx)) { + pa_strbuf_printf(s, " index: %u\n\tname: <%s>\n\tdriver: <%s>\n", client->index, client->name, client->driver); + + if (client->owner) + pa_strbuf_printf(s, "\towner module: <%u>\n", client->owner->index); + } + + return pa_strbuf_tostring_free(s); +} + +char *pa_sink_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_sink *sink; + uint32_t idx = PA_IDXSET_INVALID; + static const char* const state_table[] = { + [PA_SINK_RUNNING] = "RUNNING", + [PA_SINK_SUSPENDED] = "SUSPENDED", + [PA_SINK_IDLE] = "IDLE", + [PA_SINK_UNLINKED] = "UNLINKED" + }; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u sink(s) available.\n", pa_idxset_size(c->sinks)); + + for (sink = pa_idxset_first(c->sinks, &idx); sink; sink = pa_idxset_next(c->sinks, &idx)) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_strbuf_printf( + s, + " %c index: %u\n" + "\tname: <%s>\n" + "\tdriver: <%s>\n" + "\tflags: %s%s%s%s\n" + "\tstate: %s\n" + "\tvolume: <%s>\n" + "\tmute: <%i>\n" + "\tlatency: <%0.0f usec>\n" + "\tmonitor source: <%u>\n" + "\tsample spec: <%s>\n" + "\tchannel map: <%s>\n" + "\tused by: <%u>\n" + "\tlinked by: <%u>\n", + c->default_sink_name && !strcmp(sink->name, c->default_sink_name) ? '*' : ' ', + sink->index, + sink->name, + sink->driver, + sink->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + sink->flags & PA_SINK_LATENCY ? "LATENCY " : "", + sink->flags & PA_SINK_HARDWARE ? "HARDWARE " : "", + sink->flags & PA_SINK_NETWORK ? "NETWORK " : "", + state_table[pa_sink_get_state(sink)], + pa_cvolume_snprint(cv, sizeof(cv), pa_sink_get_volume(sink)), + !!pa_sink_get_mute(sink), + (double) pa_sink_get_latency(sink), + sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX, + pa_sample_spec_snprint(ss, sizeof(ss), &sink->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &sink->channel_map), + pa_sink_used_by(sink), + pa_sink_linked_by(sink)); + + if (sink->module) + pa_strbuf_printf(s, "\tmodule: <%u>\n", sink->module->index); + if (sink->description) + pa_strbuf_printf(s, "\tdescription: <%s>\n", sink->description); + } + + return pa_strbuf_tostring_free(s); +} + +char *pa_source_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_source *source; + uint32_t idx = PA_IDXSET_INVALID; + static const char* const state_table[] = { + [PA_SOURCE_RUNNING] = "RUNNING", + [PA_SOURCE_SUSPENDED] = "SUSPENDED", + [PA_SOURCE_IDLE] = "IDLE", + [PA_SOURCE_UNLINKED] = "UNLINKED" + }; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u source(s) available.\n", pa_idxset_size(c->sources)); + + for (source = pa_idxset_first(c->sources, &idx); source; source = pa_idxset_next(c->sources, &idx)) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX]; + + + pa_strbuf_printf( + s, + " %c index: %u\n" + "\tname: <%s>\n" + "\tdriver: <%s>\n" + "\tflags: %s%s%s%s\n" + "\tstate: %s\n" + "\tvolume: <%s>\n" + "\tmute: <%u>\n" + "\tlatency: <%0.0f usec>\n" + "\tsample spec: <%s>\n" + "\tchannel map: <%s>\n" + "\tused by: <%u>\n" + "\tlinked by: <%u>\n", + c->default_source_name && !strcmp(source->name, c->default_source_name) ? '*' : ' ', + source->index, + source->name, + source->driver, + source->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + source->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", + source->flags & PA_SOURCE_HARDWARE ? "HARDWARE " : "", + source->flags & PA_SOURCE_NETWORK ? "NETWORK " : "", + state_table[pa_source_get_state(source)], + pa_cvolume_snprint(cv, sizeof(cv), pa_source_get_volume(source)), + !!pa_source_get_mute(source), + (double) pa_source_get_latency(source), + pa_sample_spec_snprint(ss, sizeof(ss), &source->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &source->channel_map), + pa_source_used_by(source), + pa_source_linked_by(source)); + + if (source->monitor_of) + pa_strbuf_printf(s, "\tmonitor_of: <%u>\n", source->monitor_of->index); + if (source->module) + pa_strbuf_printf(s, "\tmodule: <%u>\n", source->module->index); + if (source->description) + pa_strbuf_printf(s, "\tdescription: <%s>\n", source->description); + } + + return pa_strbuf_tostring_free(s); +} + + +char *pa_source_output_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_source_output *o; + uint32_t idx = PA_IDXSET_INVALID; + static const char* const state_table[] = { + [PA_SOURCE_OUTPUT_RUNNING] = "RUNNING", + [PA_SOURCE_OUTPUT_CORKED] = "CORKED", + [PA_SOURCE_OUTPUT_UNLINKED] = "UNLINKED" + }; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u source outputs(s) available.\n", pa_idxset_size(c->source_outputs)); + + for (o = pa_idxset_first(c->source_outputs, &idx); o; o = pa_idxset_next(c->source_outputs, &idx)) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_assert(o->source); + + pa_strbuf_printf( + s, + " index: %u\n" + "\tname: '%s'\n" + "\tdriver: <%s>\n" + "\tflags: %s%s%s%s%s%s%s\n" + "\tstate: %s\n" + "\tsource: <%u> '%s'\n" + "\tlatency: <%0.0f usec>\n" + "\tsample spec: <%s>\n" + "\tchannel map: <%s>\n" + "\tresample method: %s\n", + o->index, + o->name, + o->driver, + o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "", + o->flags & PA_SOURCE_OUTPUT_DONT_MOVE ? "DONT_MOVE " : "", + o->flags & PA_SOURCE_OUTPUT_NO_REMAP ? "NO_REMAP " : "", + o->flags & PA_SOURCE_OUTPUT_NO_REMIX ? "NO_REMIX " : "", + o->flags & PA_SOURCE_OUTPUT_FIX_FORMAT ? "FIX_FORMAT " : "", + o->flags & PA_SOURCE_OUTPUT_FIX_RATE ? "FIX_RATE " : "", + o->flags & PA_SOURCE_OUTPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "", + state_table[pa_source_output_get_state(o)], + o->source->index, o->source->name, + (double) pa_source_output_get_latency(o), + pa_sample_spec_snprint(ss, sizeof(ss), &o->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map), + pa_resample_method_to_string(pa_source_output_get_resample_method(o))); + if (o->module) + pa_strbuf_printf(s, "\towner module: <%u>\n", o->module->index); + if (o->client) + pa_strbuf_printf(s, "\tclient: <%u> '%s'\n", o->client->index, o->client->name); + } + + return pa_strbuf_tostring_free(s); +} + +char *pa_sink_input_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_sink_input *i; + uint32_t idx = PA_IDXSET_INVALID; + static const char* const state_table[] = { + [PA_SINK_INPUT_RUNNING] = "RUNNING", + [PA_SINK_INPUT_DRAINED] = "DRAINED", + [PA_SINK_INPUT_CORKED] = "CORKED", + [PA_SINK_INPUT_UNLINKED] = "UNLINKED" + }; + + pa_assert(c); + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u sink input(s) available.\n", pa_idxset_size(c->sink_inputs)); + + for (i = pa_idxset_first(c->sink_inputs, &idx); i; i = pa_idxset_next(c->sink_inputs, &idx)) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_assert(i->sink); + + pa_strbuf_printf( + s, + " index: %u\n" + "\tname: <%s>\n" + "\tdriver: <%s>\n" + "\tflags: %s%s%s%s%s%s%s\n" + "\tstate: %s\n" + "\tsink: <%u> '%s'\n" + "\tvolume: <%s>\n" + "\tmute: <%i>\n" + "\tlatency: <%0.0f usec>\n" + "\tsample spec: <%s>\n" + "\tchannel map: <%s>\n" + "\tresample method: %s\n", + i->index, + i->name, + i->driver, + i->flags & PA_SINK_INPUT_VARIABLE_RATE ? "VARIABLE_RATE " : "", + i->flags & PA_SINK_INPUT_DONT_MOVE ? "DONT_MOVE " : "", + i->flags & PA_SINK_INPUT_NO_REMAP ? "NO_REMAP " : "", + i->flags & PA_SINK_INPUT_NO_REMIX ? "NO_REMIX " : "", + i->flags & PA_SINK_INPUT_FIX_FORMAT ? "FIX_FORMAT " : "", + i->flags & PA_SINK_INPUT_FIX_RATE ? "FIX_RATE " : "", + i->flags & PA_SINK_INPUT_FIX_CHANNELS ? "FIX_CHANNELS " : "", + state_table[pa_sink_input_get_state(i)], + i->sink->index, i->sink->name, + pa_cvolume_snprint(cv, sizeof(cv), pa_sink_input_get_volume(i)), + !!pa_sink_input_get_mute(i), + (double) pa_sink_input_get_latency(i), + pa_sample_spec_snprint(ss, sizeof(ss), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + pa_resample_method_to_string(pa_sink_input_get_resample_method(i))); + + if (i->module) + pa_strbuf_printf(s, "\tmodule: <%u>\n", i->module->index); + if (i->client) + pa_strbuf_printf(s, "\tclient: <%u> '%s'\n", i->client->index, i->client->name); + } + + return pa_strbuf_tostring_free(s); +} + +char *pa_scache_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u cache entries available.\n", c->scache ? pa_idxset_size(c->scache) : 0); + + if (c->scache) { + pa_scache_entry *e; + uint32_t idx = PA_IDXSET_INVALID; + + for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(c->scache, &idx)) { + double l = 0; + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX] = "n/a", cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX] = "n/a"; + + if (e->memchunk.memblock) { + pa_sample_spec_snprint(ss, sizeof(ss), &e->sample_spec); + pa_channel_map_snprint(cm, sizeof(cm), &e->channel_map); + l = (double) e->memchunk.length / pa_bytes_per_second(&e->sample_spec); + } + + pa_strbuf_printf( + s, + " name: <%s>\n" + "\tindex: <%u>\n" + "\tsample spec: <%s>\n" + "\tchannel map: <%s>\n" + "\tlength: <%lu>\n" + "\tduration: <%0.1fs>\n" + "\tvolume: <%s>\n" + "\tlazy: %s\n" + "\tfilename: %s\n", + e->name, + e->index, + ss, + cm, + (long unsigned)(e->memchunk.memblock ? e->memchunk.length : 0), + l, + pa_cvolume_snprint(cv, sizeof(cv), &e->volume), + e->lazy ? "yes" : "no", + e->filename ? e->filename : "n/a"); + } + } + + return pa_strbuf_tostring_free(s); +} + +char *pa_autoload_list_to_string(pa_core *c) { + pa_strbuf *s; + pa_assert(c); + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, "%u autoload entries available.\n", c->autoload_hashmap ? pa_hashmap_size(c->autoload_hashmap) : 0); + + if (c->autoload_hashmap) { + pa_autoload_entry *e; + void *state = NULL; + + while ((e = pa_hashmap_iterate(c->autoload_hashmap, &state, NULL))) { + pa_strbuf_printf( + s, " name: <%s>\n\ttype: <%s>\n\tindex: <%u>\n\tmodule_name: <%s>\n\targuments: <%s>\n", + e->name, + e->type == PA_NAMEREG_SOURCE ? "source" : "sink", + e->index, + e->module, + e->argument ? e->argument : ""); + + } + } + + return pa_strbuf_tostring_free(s); +} + +char *pa_full_status_string(pa_core *c) { + pa_strbuf *s; + int i; + + s = pa_strbuf_new(); + + for (i = 0; i < 8; i++) { + char *t = NULL; + + switch (i) { + case 0: + t = pa_sink_list_to_string(c); + break; + case 1: + t = pa_source_list_to_string(c); + break; + case 2: + t = pa_sink_input_list_to_string(c); + break; + case 3: + t = pa_source_output_list_to_string(c); + break; + case 4: + t = pa_client_list_to_string(c); + break; + case 5: + t = pa_module_list_to_string(c); + break; + case 6: + t = pa_scache_list_to_string(c); + break; + case 7: + t = pa_autoload_list_to_string(c); + break; + } + + pa_strbuf_puts(s, t); + pa_xfree(t); + } + + return pa_strbuf_tostring_free(s); +} diff --git a/src/pulsecore/cli-text.h b/src/pulsecore/cli-text.h new file mode 100644 index 00000000..9e5bf081 --- /dev/null +++ b/src/pulsecore/cli-text.h @@ -0,0 +1,44 @@ +#ifndef fooclitexthfoo +#define fooclitexthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/core.h> + +/* Some functions to generate pretty formatted listings of + * entities. The returned strings have to be freed manually. */ + +char *pa_sink_input_list_to_string(pa_core *c); +char *pa_source_output_list_to_string(pa_core *c); +char *pa_sink_list_to_string(pa_core *core); +char *pa_source_list_to_string(pa_core *c); +char *pa_client_list_to_string(pa_core *c); +char *pa_module_list_to_string(pa_core *c); +char *pa_scache_list_to_string(pa_core *c); +char *pa_autoload_list_to_string(pa_core *c); + +char *pa_full_status_string(pa_core *c); + +#endif + diff --git a/src/pulsecore/cli.c b/src/pulsecore/cli.c new file mode 100644 index 00000000..85e08634 --- /dev/null +++ b/src/pulsecore/cli.c @@ -0,0 +1,155 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> +#include <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/ioline.h> +#include <pulsecore/module.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/client.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/tokenizer.h> +#include <pulsecore/strbuf.h> +#include <pulsecore/namereg.h> +#include <pulsecore/cli-text.h> +#include <pulsecore/cli-command.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "cli.h" + +#define PROMPT ">>> " + +struct pa_cli { + pa_core *core; + pa_ioline *line; + + void (*eof_callback)(pa_cli *c, void *userdata); + void *userdata; + + pa_client *client; + + pa_bool_t fail, kill_requested; + int defer_kill; +}; + +static void line_callback(pa_ioline *line, const char *s, void *userdata); +static void client_kill(pa_client *c); + +pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m) { + char cname[256]; + pa_cli *c; + pa_assert(io); + + c = pa_xnew(pa_cli, 1); + c->core = core; + pa_assert_se(c->line = pa_ioline_new(io)); + + c->userdata = NULL; + c->eof_callback = NULL; + + pa_iochannel_socket_peer_to_string(io, cname, sizeof(cname)); + pa_assert_se(c->client = pa_client_new(core, __FILE__, cname)); + c->client->kill = client_kill; + c->client->userdata = c; + c->client->owner = m; + + pa_ioline_set_callback(c->line, line_callback, c); + pa_ioline_puts(c->line, "Welcome to PulseAudio! Use \"help\" for usage information.\n"PROMPT); + + c->fail = c->kill_requested = FALSE; + c->defer_kill = 0; + + return c; +} + +void pa_cli_free(pa_cli *c) { + pa_assert(c); + + pa_ioline_close(c->line); + pa_ioline_unref(c->line); + pa_client_free(c->client); + pa_xfree(c); +} + +static void client_kill(pa_client *client) { + pa_cli *c; + + pa_assert(client); + pa_assert_se(c = client->userdata); + + pa_log_debug("CLI client killed."); + if (c->defer_kill) + c->kill_requested = TRUE; + else { + if (c->eof_callback) + c->eof_callback(c, c->userdata); + } +} + +static void line_callback(pa_ioline *line, const char *s, void *userdata) { + pa_strbuf *buf; + pa_cli *c = userdata; + char *p; + + pa_assert(line); + pa_assert(c); + + if (!s) { + pa_log_debug("CLI got EOF from user."); + if (c->eof_callback) + c->eof_callback(c, c->userdata); + + return; + } + + pa_assert_se(buf = pa_strbuf_new()); + c->defer_kill++; + pa_cli_command_execute_line(c->core, s, buf, &c->fail); + c->defer_kill--; + pa_ioline_puts(line, p = pa_strbuf_tostring_free(buf)); + pa_xfree(p); + + if (c->kill_requested) { + if (c->eof_callback) + c->eof_callback(c, c->userdata); + } else + pa_ioline_puts(line, PROMPT); +} + +void pa_cli_set_eof_callback(pa_cli *c, void (*cb)(pa_cli*c, void *userdata), void *userdata) { + pa_assert(c); + + c->eof_callback = cb; + c->userdata = userdata; +} diff --git a/src/pulsecore/cli.h b/src/pulsecore/cli.h new file mode 100644 index 00000000..2b58d458 --- /dev/null +++ b/src/pulsecore/cli.h @@ -0,0 +1,40 @@ +#ifndef fooclihfoo +#define fooclihfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/iochannel.h> +#include <pulsecore/core.h> +#include <pulsecore/module.h> + +typedef struct pa_cli pa_cli; + +/* Create a new command line session on the specified io channel owned by the specified module */ +pa_cli* pa_cli_new(pa_core *core, pa_iochannel *io, pa_module *m); +void pa_cli_free(pa_cli *cli); + +/* Set a callback function that is called whenever the command line session is terminated */ +void pa_cli_set_eof_callback(pa_cli *cli, void (*cb)(pa_cli*c, void *userdata), void *userdata); + +#endif diff --git a/src/pulsecore/client.c b/src/pulsecore/client.c new file mode 100644 index 00000000..319b8387 --- /dev/null +++ b/src/pulsecore/client.c @@ -0,0 +1,100 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/xmalloc.h> + +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "client.h" + +pa_client *pa_client_new(pa_core *core, const char *driver, const char *name) { + pa_client *c; + + pa_core_assert_ref(core); + + c = pa_xnew(pa_client, 1); + c->name = pa_xstrdup(name); + c->driver = pa_xstrdup(driver); + c->owner = NULL; + c->core = core; + + c->kill = NULL; + c->userdata = NULL; + + pa_assert_se(pa_idxset_put(core->clients, c, &c->index) >= 0); + + pa_log_info("Created %u \"%s\"", c->index, c->name); + pa_subscription_post(core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_NEW, c->index); + + pa_core_check_quit(core); + + return c; +} + +void pa_client_free(pa_client *c) { + pa_assert(c); + pa_assert(c->core); + + pa_idxset_remove_by_data(c->core->clients, c, NULL); + + pa_core_check_quit(c->core); + + pa_log_info("Freed %u \"%s\"", c->index, c->name); + pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_REMOVE, c->index); + pa_xfree(c->name); + pa_xfree(c->driver); + pa_xfree(c); +} + +void pa_client_kill(pa_client *c) { + pa_assert(c); + + if (!c->kill) { + pa_log_warn("kill() operation not implemented for client %u", c->index); + return; + } + + c->kill(c); +} + +void pa_client_set_name(pa_client *c, const char *name) { + pa_assert(c); + + pa_log_info("Client %u changed name from \"%s\" to \"%s\"", c->index, c->name, name); + + pa_xfree(c->name); + c->name = pa_xstrdup(name); + + pa_subscription_post(c->core, PA_SUBSCRIPTION_EVENT_CLIENT|PA_SUBSCRIPTION_EVENT_CHANGE, c->index); +} diff --git a/src/pulsecore/client.h b/src/pulsecore/client.h new file mode 100644 index 00000000..6d09b999 --- /dev/null +++ b/src/pulsecore/client.h @@ -0,0 +1,61 @@ +#ifndef foopulseclienthfoo +#define foopulseclienthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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> + +typedef struct pa_client pa_client; + +#include <pulsecore/core.h> +#include <pulsecore/module.h> + +/* Every connection to the server should have a pa_client + * attached. That way the user may generate a listing of all connected + * clients easily and kill them if he wants.*/ + +struct pa_client { + uint32_t index; + + pa_module *owner; + char *name, *driver; + pa_core *core; + + void (*kill)(pa_client *c); + void *userdata; +}; + +pa_client *pa_client_new(pa_core *c, const char *driver, const char *name); + +/* This function should be called only by the code that created the client */ +void pa_client_free(pa_client *c); + +/* Code that didn't create the client should call this function to + * request destruction of the client */ +void pa_client_kill(pa_client *c); + +/* Rename the client */ +void pa_client_set_name(pa_client *c, const char *name); + +#endif diff --git a/src/pulsecore/conf-parser.c b/src/pulsecore/conf-parser.c new file mode 100644 index 00000000..12ea49c2 --- /dev/null +++ b/src/pulsecore/conf-parser.c @@ -0,0 +1,201 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> +#include <stdio.h> +#include <errno.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "conf-parser.h" + +#define WHITESPACE " \t\n" +#define COMMENTS "#;\n" + +/* Run the user supplied parser for an assignment */ +static int next_assignment(const char *filename, unsigned line, const pa_config_item *t, const char *lvalue, const char *rvalue, void *userdata) { + pa_assert(filename); + pa_assert(t); + pa_assert(lvalue); + pa_assert(rvalue); + + for (; t->parse; t++) + if (!strcmp(lvalue, t->lvalue)) + return t->parse(filename, line, lvalue, rvalue, t->data, userdata); + + pa_log("[%s:%u] Unknown lvalue '%s'.", filename, line, lvalue); + + return -1; +} + +/* Returns non-zero when c is contained in s */ +static int in_string(char c, const char *s) { + pa_assert(s); + + for (; *s; s++) + if (*s == c) + return 1; + + return 0; +} + +/* Remove all whitepsapce from the beginning and the end of *s. *s may + * be modified. */ +static char *strip(char *s) { + char *b = s+strspn(s, WHITESPACE); + char *e, *l = NULL; + + for (e = b; *e; e++) + if (!in_string(*e, WHITESPACE)) + l = e; + + if (l) + *(l+1) = 0; + + return b; +} + +/* Parse a variable assignment line */ +static int parse_line(const char *filename, unsigned line, const pa_config_item *t, char *l, void *userdata) { + char *e, *c, *b = l+strspn(l, WHITESPACE); + + if ((c = strpbrk(b, COMMENTS))) + *c = 0; + + if (!*b) + return 0; + + if (!(e = strchr(b, '='))) { + pa_log("[%s:%u] Missing '='.", filename, line); + return -1; + } + + *e = 0; + e++; + + return next_assignment(filename, line, t, strip(b), strip(e), userdata); +} + +/* Go through the file and parse each line */ +int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, void *userdata) { + int r = -1; + unsigned line = 0; + int do_close = !f; + + pa_assert(filename); + pa_assert(t); + + if (!f && !(f = fopen(filename, "r"))) { + if (errno == ENOENT) { + r = 0; + goto finish; + } + + pa_log_warn("Failed to open configuration file '%s': %s", + filename, pa_cstrerror(errno)); + goto finish; + } + + while (!feof(f)) { + char l[256]; + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + pa_log_warn("Failed to read configuration file '%s': %s", + filename, pa_cstrerror(errno)); + goto finish; + } + + if (parse_line(filename, ++line, t, l, userdata) < 0) + goto finish; + } + + r = 0; + +finish: + + if (do_close && f) + fclose(f); + + return r; +} + +int pa_config_parse_int(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + int *i = data; + int32_t k; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if (pa_atoi(rvalue, &k) < 0) { + pa_log("[%s:%u] Failed to parse numeric value: %s", filename, line, rvalue); + return -1; + } + + *i = (int) k; + return 0; +} + +int pa_config_parse_bool(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + int k; + pa_bool_t *b = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + if ((k = pa_parse_boolean(rvalue)) < 0) { + pa_log("[%s:%u] Failed to parse boolean value: %s", filename, line, rvalue); + return -1; + } + + *b = !!k; + + return 0; +} + +int pa_config_parse_string(const char *filename, PA_GCC_UNUSED unsigned line, const char *lvalue, const char *rvalue, void *data, PA_GCC_UNUSED void *userdata) { + char **s = data; + + pa_assert(filename); + pa_assert(lvalue); + pa_assert(rvalue); + pa_assert(data); + + pa_xfree(*s); + *s = *rvalue ? pa_xstrdup(rvalue) : NULL; + return 0; +} diff --git a/src/pulsecore/conf-parser.h b/src/pulsecore/conf-parser.h new file mode 100644 index 00000000..b56d979e --- /dev/null +++ b/src/pulsecore/conf-parser.h @@ -0,0 +1,49 @@ +#ifndef fooconfparserhfoo +#define fooconfparserhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <stdio.h> + +/* An abstract parser for simple, line based, shallow configuration + * files consisting of variable assignments only. */ + +/* Wraps info for parsing a specific configuration variable */ +typedef struct pa_config_item { + const char *lvalue; /* name of the variable */ + int (*parse)(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, void *userdata); /* Function that is called to parse the variable's value */ + void *data; /* Where to store the variable's data */ +} pa_config_item; + +/* The configuration file parsing routine. Expects a table of + * pa_config_items in *t that is terminated by an item where lvalue is + * NULL */ +int pa_config_parse(const char *filename, FILE *f, const pa_config_item *t, void *userdata); + +/* Generic parsers for integers, booleans and strings */ +int pa_config_parse_int(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, void *userdata); +int pa_config_parse_bool(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, void *userdata); +int pa_config_parse_string(const char *filename, unsigned line, const char *lvalue, const char *rvalue, void *data, void *userdata); + +#endif diff --git a/src/pulsecore/core-def.h b/src/pulsecore/core-def.h new file mode 100644 index 00000000..4bc05137 --- /dev/null +++ b/src/pulsecore/core-def.h @@ -0,0 +1,29 @@ +#ifndef foocoredefhfoo +#define foocoredefhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +/* FIXME: Remove this shit */ + +#endif diff --git a/src/pulsecore/core-error.c b/src/pulsecore/core-error.c new file mode 100644 index 00000000..8a61e726 --- /dev/null +++ b/src/pulsecore/core-error.c @@ -0,0 +1,80 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/native-common.h> +#include <pulsecore/thread.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> + +#include "core-error.h" + +PA_STATIC_TLS_DECLARE(cstrerror, pa_xfree); + +const char* pa_cstrerror(int errnum) { + const char *original = NULL; + char *translated, *t; + char errbuf[128]; + + if ((t = PA_STATIC_TLS_GET(cstrerror))) + pa_xfree(t); + +#if defined(HAVE_STRERROR_R) && defined(__GLIBC__) + original = strerror_r(errnum, errbuf, sizeof(errbuf)); +#elif defined(HAVE_STRERROR_R) + if (strerror_r(errnum, errbuf, sizeof(errbuf)) == 0) { + errbuf[sizeof(errbuf) - 1] = 0; + original = errbuf; + } +#else + /* This might not be thread safe, but we hope for the best */ + original = strerror(errnum); +#endif + + if (!original) { + pa_snprintf(errbuf, sizeof(errbuf), "Unknown error %i", errnum); + original = errbuf; + } + + if (!(translated = pa_locale_to_utf8(original))) { + pa_log_warn("Unable to convert error string to locale, filtering."); + translated = pa_utf8_filter(original); + } + + PA_STATIC_TLS_SET(cstrerror, translated); + + return translated; +} diff --git a/src/pulsecore/core-error.h b/src/pulsecore/core-error.h new file mode 100644 index 00000000..443c4883 --- /dev/null +++ b/src/pulsecore/core-error.h @@ -0,0 +1,44 @@ +#ifndef foocoreerrorhfoo +#define foocoreerrorhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/cdecl.h> + +/** \file + * Error management */ + +PA_C_DECL_BEGIN + +/** A wrapper around the standard strerror() function that converts the + * string to UTF-8. The function is thread safe but the returned string is + * only guaranteed to exist until the thread exits or pa_cstrerror() is + * called again from the same thread. */ +const char* pa_cstrerror(int errnum); + +PA_C_DECL_END + +#endif diff --git a/src/pulsecore/core-scache.c b/src/pulsecore/core-scache.c new file mode 100644 index 00000000..46444a90 --- /dev/null +++ b/src/pulsecore/core-scache.c @@ -0,0 +1,474 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> +#include <stdio.h> +#include <sys/types.h> +#include <dirent.h> +#include <sys/stat.h> +#include <errno.h> +#include <limits.h> + +#ifdef HAVE_GLOB_H +#include <glob.h> +#endif + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#include <pulse/mainloop.h> +#include <pulse/channelmap.h> +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/volume.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/play-memchunk.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sound-file.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/macro.h> + +#include "core-scache.h" + +#define UNLOAD_POLL_TIME 2 + +static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { + pa_core *c = userdata; + struct timeval ntv; + + pa_assert(c); + pa_assert(c->mainloop == m); + pa_assert(c->scache_auto_unload_event == e); + + pa_scache_unload_unused(c); + + pa_gettimeofday(&ntv); + ntv.tv_sec += UNLOAD_POLL_TIME; + m->time_restart(e, &ntv); +} + +static void free_entry(pa_scache_entry *e) { + pa_assert(e); + + pa_namereg_unregister(e->core, e->name); + pa_subscription_post(e->core, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_REMOVE, e->index); + pa_xfree(e->name); + pa_xfree(e->filename); + if (e->memchunk.memblock) + pa_memblock_unref(e->memchunk.memblock); + pa_xfree(e); +} + +static pa_scache_entry* scache_add_item(pa_core *c, const char *name) { + pa_scache_entry *e; + + pa_assert(c); + pa_assert(name); + + if ((e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0))) { + if (e->memchunk.memblock) + pa_memblock_unref(e->memchunk.memblock); + + pa_xfree(e->filename); + + pa_assert(e->core == c); + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); + } else { + e = pa_xnew(pa_scache_entry, 1); + + if (!pa_namereg_register(c, name, PA_NAMEREG_SAMPLE, e, 1)) { + pa_xfree(e); + return NULL; + } + + e->name = pa_xstrdup(name); + e->core = c; + + if (!c->scache) { + c->scache = pa_idxset_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + pa_assert(c->scache); + } + + pa_idxset_put(c->scache, e, &e->index); + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_NEW, e->index); + } + + e->last_used_time = 0; + e->memchunk.memblock = NULL; + e->memchunk.index = e->memchunk.length = 0; + e->filename = NULL; + e->lazy = 0; + e->last_used_time = 0; + + memset(&e->sample_spec, 0, sizeof(e->sample_spec)); + pa_channel_map_init(&e->channel_map); + pa_cvolume_reset(&e->volume, PA_CHANNELS_MAX); + + return e; +} + +int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, uint32_t *idx) { + pa_scache_entry *e; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX]; + pa_channel_map tmap; + + pa_assert(c); + pa_assert(name); + pa_assert(!ss || pa_sample_spec_valid(ss)); + pa_assert(!map || (pa_channel_map_valid(map) && ss && ss->channels == map->channels)); + + if (ss && !map) + if (!(map = pa_channel_map_init_auto(&tmap, ss->channels, PA_CHANNEL_MAP_DEFAULT))) + return -1; + + if (chunk && chunk->length > PA_SCACHE_ENTRY_SIZE_MAX) + return -1; + + if (!(e = scache_add_item(c, name))) + return -1; + + memset(&e->sample_spec, 0, sizeof(e->sample_spec)); + pa_channel_map_init(&e->channel_map); + + if (ss) { + e->sample_spec = *ss; + e->volume.channels = e->sample_spec.channels; + } + + if (map) + e->channel_map = *map; + + if (chunk) { + e->memchunk = *chunk; + pa_memblock_ref(e->memchunk.memblock); + } + + if (idx) + *idx = e->index; + + pa_log_debug("Created sample \"%s\" (#%d), %lu bytes with sample spec %s", + name, e->index, (unsigned long) e->memchunk.length, + pa_sample_spec_snprint(st, sizeof(st), &e->sample_spec)); + + return 0; +} + +int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx) { + pa_sample_spec ss; + pa_channel_map map; + pa_memchunk chunk; + int r; + +#ifdef OS_IS_WIN32 + char buf[MAX_PATH]; + + if (ExpandEnvironmentStrings(filename, buf, MAX_PATH)) + filename = buf; +#endif + + pa_assert(c); + pa_assert(name); + pa_assert(filename); + + if (pa_sound_file_load(c->mempool, filename, &ss, &map, &chunk) < 0) + return -1; + + r = pa_scache_add_item(c, name, &ss, &map, &chunk, idx); + pa_memblock_unref(chunk.memblock); + + return r; +} + +int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx) { + pa_scache_entry *e; + +#ifdef OS_IS_WIN32 + char buf[MAX_PATH]; + + if (ExpandEnvironmentStrings(filename, buf, MAX_PATH)) + filename = buf; +#endif + + pa_assert(c); + pa_assert(name); + pa_assert(filename); + + if (!(e = scache_add_item(c, name))) + return -1; + + e->lazy = 1; + e->filename = pa_xstrdup(filename); + + if (!c->scache_auto_unload_event) { + struct timeval ntv; + pa_gettimeofday(&ntv); + ntv.tv_sec += UNLOAD_POLL_TIME; + c->scache_auto_unload_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, c); + } + + if (idx) + *idx = e->index; + + return 0; +} + +int pa_scache_remove_item(pa_core *c, const char *name) { + pa_scache_entry *e; + + pa_assert(c); + pa_assert(name); + + if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0))) + return -1; + + if (pa_idxset_remove_by_data(c->scache, e, NULL) != e) + pa_assert(0); + + pa_log_debug("Removed sample \"%s\"", name); + + free_entry(e); + + return 0; +} + +static void free_cb(void *p, PA_GCC_UNUSED void *userdata) { + pa_scache_entry *e = p; + pa_assert(e); + + free_entry(e); +} + +void pa_scache_free(pa_core *c) { + pa_assert(c); + + if (c->scache) { + pa_idxset_free(c->scache, free_cb, NULL); + c->scache = NULL; + } + + if (c->scache_auto_unload_event) + c->mainloop->time_free(c->scache_auto_unload_event); +} + +int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume) { + pa_scache_entry *e; + char *t; + pa_cvolume r; + + pa_assert(c); + pa_assert(name); + pa_assert(sink); + + if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 1))) + return -1; + + if (e->lazy && !e->memchunk.memblock) { + if (pa_sound_file_load(c->mempool, e->filename, &e->sample_spec, &e->channel_map, &e->memchunk) < 0) + return -1; + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); + + if (e->volume.channels > e->sample_spec.channels) + e->volume.channels = e->sample_spec.channels; + } + + if (!e->memchunk.memblock) + return -1; + + pa_log_debug("Playing sample \"%s\" on \"%s\"", name, sink->name); + + t = pa_sprintf_malloc("sample:%s", name); + + pa_cvolume_set(&r, e->volume.channels, volume); + pa_sw_cvolume_multiply(&r, &r, &e->volume); + + if (pa_play_memchunk(sink, t, &e->sample_spec, &e->channel_map, &e->memchunk, &r) < 0) { + pa_xfree(t); + return -1; + } + + pa_xfree(t); + + if (e->lazy) + time(&e->last_used_time); + + return 0; +} + +int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, int autoload) { + pa_sink *sink; + + pa_assert(c); + pa_assert(name); + + if (!(sink = pa_namereg_get(c, sink_name, PA_NAMEREG_SINK, autoload))) + return -1; + + return pa_scache_play_item(c, name, sink, volume); +} + +const char * pa_scache_get_name_by_id(pa_core *c, uint32_t id) { + pa_scache_entry *e; + + pa_assert(c); + pa_assert(id != PA_IDXSET_INVALID); + + if (!c->scache || !(e = pa_idxset_get_by_index(c->scache, id))) + return NULL; + + return e->name; +} + +uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name) { + pa_scache_entry *e; + + pa_assert(c); + pa_assert(name); + + if (!(e = pa_namereg_get(c, name, PA_NAMEREG_SAMPLE, 0))) + return PA_IDXSET_INVALID; + + return e->index; +} + +uint32_t pa_scache_total_size(pa_core *c) { + pa_scache_entry *e; + uint32_t idx, sum = 0; + + pa_assert(c); + + if (!c->scache || !pa_idxset_size(c->scache)) + return 0; + + for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(c->scache, &idx)) + if (e->memchunk.memblock) + sum += e->memchunk.length; + + return sum; +} + +void pa_scache_unload_unused(pa_core *c) { + pa_scache_entry *e; + time_t now; + uint32_t idx; + + pa_assert(c); + + if (!c->scache || !pa_idxset_size(c->scache)) + return; + + time(&now); + + for (e = pa_idxset_first(c->scache, &idx); e; e = pa_idxset_next(c->scache, &idx)) { + + if (!e->lazy || !e->memchunk.memblock) + continue; + + if (e->last_used_time + c->scache_idle_time > now) + continue; + + pa_memblock_unref(e->memchunk.memblock); + e->memchunk.memblock = NULL; + e->memchunk.index = e->memchunk.length = 0; + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE|PA_SUBSCRIPTION_EVENT_CHANGE, e->index); + } +} + +static void add_file(pa_core *c, const char *pathname) { + struct stat st; + const char *e; + + pa_core_assert_ref(c); + pa_assert(pathname); + + e = pa_path_get_filename(pathname); + + if (stat(pathname, &st) < 0) { + pa_log("stat('%s'): %s", pathname, pa_cstrerror(errno)); + return; + } + +#if defined(S_ISREG) && defined(S_ISLNK) + if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) +#endif + pa_scache_add_file_lazy(c, e, pathname, NULL); +} + +int pa_scache_add_directory_lazy(pa_core *c, const char *pathname) { + DIR *dir; + + pa_core_assert_ref(c); + pa_assert(pathname); + + /* First try to open this as directory */ + if (!(dir = opendir(pathname))) { +#ifdef HAVE_GLOB_H + glob_t p; + unsigned int i; + /* If that fails, try to open it as shell glob */ + + if (glob(pathname, GLOB_ERR|GLOB_NOSORT, NULL, &p) < 0) { + pa_log("failed to open directory '%s': %s", pathname, pa_cstrerror(errno)); + return -1; + } + + for (i = 0; i < p.gl_pathc; i++) + add_file(c, p.gl_pathv[i]); + + globfree(&p); +#else + return -1; +#endif + } else { + struct dirent *e; + + while ((e = readdir(dir))) { + char p[PATH_MAX]; + + if (e->d_name[0] == '.') + continue; + + pa_snprintf(p, sizeof(p), "%s/%s", pathname, e->d_name); + add_file(c, p); + } + } + + closedir(dir); + return 0; +} diff --git a/src/pulsecore/core-scache.h b/src/pulsecore/core-scache.h new file mode 100644 index 00000000..ab7ec0ef --- /dev/null +++ b/src/pulsecore/core-scache.h @@ -0,0 +1,68 @@ +#ifndef foocorescachehfoo +#define foocorescachehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulsecore/core.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/sink.h> + +#define PA_SCACHE_ENTRY_SIZE_MAX (1024*1024*2) + +typedef struct pa_scache_entry { + pa_core *core; + uint32_t index; + char *name; + + pa_cvolume volume; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + pa_memchunk memchunk; + + char *filename; + + int lazy; + time_t last_used_time; +} pa_scache_entry; + +int pa_scache_add_item(pa_core *c, const char *name, const pa_sample_spec *ss, const pa_channel_map *map, const pa_memchunk *chunk, uint32_t *idx); +int pa_scache_add_file(pa_core *c, const char *name, const char *filename, uint32_t *idx); +int pa_scache_add_file_lazy(pa_core *c, const char *name, const char *filename, uint32_t *idx); + +int pa_scache_add_directory_lazy(pa_core *c, const char *pathname); + +int pa_scache_remove_item(pa_core *c, const char *name); +int pa_scache_play_item(pa_core *c, const char *name, pa_sink *sink, pa_volume_t volume); +int pa_scache_play_item_by_name(pa_core *c, const char *name, const char*sink_name, pa_volume_t volume, int autoload); +void pa_scache_free(pa_core *c); + +const char *pa_scache_get_name_by_id(pa_core *c, uint32_t id); +uint32_t pa_scache_get_id_by_name(pa_core *c, const char *name); + +uint32_t pa_scache_total_size(pa_core *c); + +void pa_scache_unload_unused(pa_core *c); + +#endif diff --git a/src/pulsecore/core-subscribe.c b/src/pulsecore/core-subscribe.c new file mode 100644 index 00000000..06c5a4ad --- /dev/null +++ b/src/pulsecore/core-subscribe.c @@ -0,0 +1,266 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/xmalloc.h> + +#include <pulsecore/queue.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "core-subscribe.h" + +/* The subscription subsystem may be used to be notified whenever an + * entity (sink, source, ...) is created or deleted. Modules may + * register a callback function that is called whenever an event + * matching a subscription mask happens. The execution of the callback + * function is postponed to the next main loop iteration, i.e. is not + * called from within the stack frame the entity was created in. */ + +struct pa_subscription { + pa_core *core; + int dead; + + pa_subscription_cb_t callback; + void *userdata; + pa_subscription_mask_t mask; + + PA_LLIST_FIELDS(pa_subscription); +}; + +struct pa_subscription_event { + pa_core *core; + + pa_subscription_event_type_t type; + uint32_t index; + + PA_LLIST_FIELDS(pa_subscription_event); +}; + +static void sched_event(pa_core *c); + +/* Allocate a new subscription object for the given subscription mask. Use the specified callback function and user data */ +pa_subscription* pa_subscription_new(pa_core *c, pa_subscription_mask_t m, pa_subscription_cb_t callback, void *userdata) { + pa_subscription *s; + + pa_assert(c); + pa_assert(m); + pa_assert(callback); + + s = pa_xnew(pa_subscription, 1); + s->core = c; + s->dead = 0; + s->callback = callback; + s->userdata = userdata; + s->mask = m; + + PA_LLIST_PREPEND(pa_subscription, c->subscriptions, s); + return s; +} + +/* Free a subscription object, effectively marking it for deletion */ +void pa_subscription_free(pa_subscription*s) { + pa_assert(s); + pa_assert(!s->dead); + + s->dead = 1; + sched_event(s->core); +} + +static void free_subscription(pa_subscription *s) { + pa_assert(s); + pa_assert(s->core); + + PA_LLIST_REMOVE(pa_subscription, s->core->subscriptions, s); + pa_xfree(s); +} + +static void free_event(pa_subscription_event *s) { + pa_assert(s); + pa_assert(s->core); + + if (!s->next) + s->core->subscription_event_last = s->prev; + + PA_LLIST_REMOVE(pa_subscription_event, s->core->subscription_event_queue, s); + pa_xfree(s); +} + +/* Free all subscription objects */ +void pa_subscription_free_all(pa_core *c) { + pa_assert(c); + + while (c->subscriptions) + free_subscription(c->subscriptions); + + while (c->subscription_event_queue) + free_event(c->subscription_event_queue); + + if (c->subscription_defer_event) { + c->mainloop->defer_free(c->subscription_defer_event); + c->subscription_defer_event = NULL; + } +} + +#ifdef DEBUG +static void dump_event(const char * prefix, pa_subscription_event*e) { + const char * const fac_table[] = { + [PA_SUBSCRIPTION_EVENT_SINK] = "SINK", + [PA_SUBSCRIPTION_EVENT_SOURCE] = "SOURCE", + [PA_SUBSCRIPTION_EVENT_SINK_INPUT] = "SINK_INPUT", + [PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT] = "SOURCE_OUTPUT", + [PA_SUBSCRIPTION_EVENT_MODULE] = "MODULE", + [PA_SUBSCRIPTION_EVENT_CLIENT] = "CLIENT", + [PA_SUBSCRIPTION_EVENT_SAMPLE_CACHE] = "SAMPLE_CACHE", + [PA_SUBSCRIPTION_EVENT_SERVER] = "SERVER", + [PA_SUBSCRIPTION_EVENT_AUTOLOAD] = "AUTOLOAD" + }; + + const char * const type_table[] = { + [PA_SUBSCRIPTION_EVENT_NEW] = "NEW", + [PA_SUBSCRIPTION_EVENT_CHANGE] = "CHANGE", + [PA_SUBSCRIPTION_EVENT_REMOVE] = "REMOVE" + }; + + pa_log("%s event (%s|%s|%u)", + prefix, + fac_table[e->type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK], + type_table[e->type & PA_SUBSCRIPTION_EVENT_TYPE_MASK], + e->index); +} +#endif + +/* Deferred callback for dispatching subscirption events */ +static void defer_cb(pa_mainloop_api *m, pa_defer_event *de, void *userdata) { + pa_core *c = userdata; + pa_subscription *s; + + pa_assert(c->mainloop == m); + pa_assert(c); + pa_assert(c->subscription_defer_event == de); + + c->mainloop->defer_enable(c->subscription_defer_event, 0); + + /* Dispatch queued events */ + + while (c->subscription_event_queue) { + pa_subscription_event *e = c->subscription_event_queue; + + for (s = c->subscriptions; s; s = s->next) { + + if (!s->dead && pa_subscription_match_flags(s->mask, e->type)) + s->callback(c, e->type, e->index, s->userdata); + } + +#ifdef DEBUG + dump_event("Dispatched", e); +#endif + free_event(e); + } + + /* Remove dead subscriptions */ + + s = c->subscriptions; + while (s) { + pa_subscription *n = s->next; + if (s->dead) + free_subscription(s); + s = n; + } +} + +/* Schedule an mainloop event so that a pending subscription event is dispatched */ +static void sched_event(pa_core *c) { + pa_assert(c); + + if (!c->subscription_defer_event) { + c->subscription_defer_event = c->mainloop->defer_new(c->mainloop, defer_cb, c); + pa_assert(c->subscription_defer_event); + } + + c->mainloop->defer_enable(c->subscription_defer_event, 1); +} + +/* Append a new subscription event to the subscription event queue and schedule a main loop event */ +void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t idx) { + pa_subscription_event *e; + pa_assert(c); + + /* No need for queuing subscriptions of noone is listening */ + if (!c->subscriptions) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_NEW) { + pa_subscription_event *i, *n; + + /* Check for duplicates */ + for (i = c->subscription_event_last; i; i = n) { + n = i->prev; + + /* not the same object type */ + if (((t ^ i->type) & PA_SUBSCRIPTION_EVENT_FACILITY_MASK)) + continue; + + /* not the same object */ + if (i->index != idx) + continue; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) { + /* This object is being removed, hence there is no + * point in keeping the old events regarding this + * entry in the queue. */ + + free_event(i); + pa_log_debug("dropped redundant event."); + continue; + } + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) { + /* This object has changed. If a "new" or "change" event for + * this object is still in the queue we can exit. */ + + pa_log_debug("dropped redundant event."); + return; + } + } + } + + e = pa_xnew(pa_subscription_event, 1); + e->core = c; + e->type = t; + e->index = idx; + + PA_LLIST_INSERT_AFTER(pa_subscription_event, c->subscription_event_queue, c->subscription_event_last, e); + c->subscription_event_last = e; + +#ifdef DEBUG + dump_event("Queued", e); +#endif + + sched_event(c); +} diff --git a/src/pulsecore/core-subscribe.h b/src/pulsecore/core-subscribe.h new file mode 100644 index 00000000..2b6863f9 --- /dev/null +++ b/src/pulsecore/core-subscribe.h @@ -0,0 +1,41 @@ +#ifndef foocoresubscribehfoo +#define foocoresubscribehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + +typedef struct pa_subscription pa_subscription; +typedef struct pa_subscription_event pa_subscription_event; + +#include <pulsecore/core.h> +#include <pulsecore/native-common.h> + +typedef void (*pa_subscription_cb_t)(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata); + +pa_subscription* pa_subscription_new(pa_core *c, pa_subscription_mask_t m, pa_subscription_cb_t cb, void *userdata); +void pa_subscription_free(pa_subscription*s); +void pa_subscription_free_all(pa_core *c); + +void pa_subscription_post(pa_core *c, pa_subscription_event_type_t t, uint32_t idx); + +#endif diff --git a/src/pulsecore/core-util.c b/src/pulsecore/core-util.c new file mode 100644 index 00000000..61d04c2d --- /dev/null +++ b/src/pulsecore/core-util.c @@ -0,0 +1,1579 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2004 Joe Marcus Clarke + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdarg.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <time.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/time.h> + +#ifdef HAVE_STRTOF_L +#include <locale.h> +#endif + +#ifdef HAVE_SCHED_H +#include <sched.h> +#endif + +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#ifdef HAVE_SYS_CAPABILITY_H +#include <sys/capability.h> +#endif + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#ifdef HAVE_PTHREAD +#include <pthread.h> +#endif + +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#ifdef HAVE_PWD_H +#include <pwd.h> +#endif + +#ifdef HAVE_GRP_H +#include <grp.h> +#endif + +#ifdef HAVE_LIBSAMPLERATE +#include <samplerate.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/util.h> +#include <pulse/utf8.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/winsock.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread.h> + +#include "core-util.h" + +/* Not all platforms have this */ +#ifndef MSG_NOSIGNAL +#define MSG_NOSIGNAL 0 +#endif + +#ifndef OS_IS_WIN32 +#define PA_USER_RUNTIME_PATH_PREFIX "/tmp/pulse-" +#else +#define PA_USER_RUNTIME_PATH_PREFIX "%TEMP%\\pulse-" +#endif + +#ifdef OS_IS_WIN32 + +#define PULSE_ROOTENV "PULSE_ROOT" + +int pa_set_root(HANDLE handle) { + char library_path[MAX_PATH + sizeof(PULSE_ROOTENV) + 1], *sep; + + strcpy(library_path, PULSE_ROOTENV "="); + + if (!GetModuleFileName(handle, library_path + sizeof(PULSE_ROOTENV), MAX_PATH)) + return 0; + + sep = strrchr(library_path, PA_PATH_SEP_CHAR); + if (sep) + *sep = '\0'; + + if (_putenv(library_path) < 0) + return 0; + + return 1; +} + +#endif + +/** Make a file descriptor nonblock. Doesn't do any error checking */ +void pa_make_fd_nonblock(int fd) { + +#ifdef O_NONBLOCK + int v; + pa_assert(fd >= 0); + + pa_assert_se((v = fcntl(fd, F_GETFL)) >= 0); + + if (!(v & O_NONBLOCK)) + pa_assert_se(fcntl(fd, F_SETFL, v|O_NONBLOCK) >= 0); + +#elif defined(OS_IS_WIN32) + u_long arg = 1; + if (ioctlsocket(fd, FIONBIO, &arg) < 0) { + pa_assert_se(WSAGetLastError() == WSAENOTSOCK); + pa_log_warn("Only sockets can be made non-blocking!"); + } +#else + pa_log_warn("Non-blocking I/O not supported.!"); +#endif + +} + +/* Set the FD_CLOEXEC flag for a fd */ +void pa_make_fd_cloexec(int fd) { + +#ifdef FD_CLOEXEC + int v; + pa_assert(fd >= 0); + + pa_assert_se((v = fcntl(fd, F_GETFD, 0)) >= 0); + + if (!(v & FD_CLOEXEC)) + pa_assert_se(fcntl(fd, F_SETFD, v|FD_CLOEXEC) >= 0); +#endif + +} + +/** Creates a directory securely */ +int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid) { + struct stat st; + int r; + + pa_assert(dir); + +#ifdef OS_IS_WIN32 + r = mkdir(dir); +#else + { + mode_t u; + u = umask(~m); + r = mkdir(dir, m); + umask(u); + } +#endif + + if (r < 0 && errno != EEXIST) + return -1; + +#ifdef HAVE_CHOWN + if (uid == (uid_t)-1) + uid = getuid(); + if (gid == (gid_t)-1) + gid = getgid(); + (void) chown(dir, uid, gid); +#endif + +#ifdef HAVE_CHMOD + chmod(dir, m); +#endif + +#ifdef HAVE_LSTAT + if (lstat(dir, &st) < 0) +#else + if (stat(dir, &st) < 0) +#endif + goto fail; + +#ifndef OS_IS_WIN32 + if (!S_ISDIR(st.st_mode) || + (st.st_uid != uid) || + (st.st_gid != gid) || + ((st.st_mode & 0777) != m)) { + errno = EACCES; + goto fail; + } +#else + pa_log_warn("secure directory creation not supported on Win32."); +#endif + + return 0; + +fail: + rmdir(dir); + return -1; +} + +/* Return a newly allocated sting containing the parent directory of the specified file */ +char *pa_parent_dir(const char *fn) { + char *slash, *dir = pa_xstrdup(fn); + + if ((slash = (char*) pa_path_get_filename(dir)) == dir) { + pa_xfree(dir); + return NULL; + } + + *(slash-1) = 0; + return dir; +} + +/* Creates a the parent directory of the specified path securely */ +int pa_make_secure_parent_dir(const char *fn, mode_t m, uid_t uid, gid_t gid) { + int ret = -1; + char *dir; + + if (!(dir = pa_parent_dir(fn))) + goto finish; + + if (pa_make_secure_dir(dir, m, uid, gid) < 0) + goto finish; + + ret = 0; + +finish: + pa_xfree(dir); + return ret; +} + +/** Platform independent read function. Necessary since not all + * systems treat all file descriptors equal. If type is + * non-NULL it is used to cache the type of the fd. This is + * useful for making sure that only a single syscall is executed per + * function call. The variable pointed to should be initialized to 0 + * by the caller. */ +ssize_t pa_read(int fd, void *buf, size_t count, int *type) { + +#ifdef OS_IS_WIN32 + + if (!type || *type == 0) { + ssize_t r; + + if ((r = recv(fd, buf, count, 0)) >= 0) + return r; + + if (WSAGetLastError() != WSAENOTSOCK) { + errno = WSAGetLastError(); + return r; + } + + if (type) + *type = 1; + } + +#endif + + return read(fd, buf, count); +} + +/** Similar to pa_read(), but handles writes */ +ssize_t pa_write(int fd, const void *buf, size_t count, int *type) { + + if (!type || *type == 0) { + ssize_t r; + + if ((r = send(fd, buf, count, MSG_NOSIGNAL)) >= 0) + return r; + +#ifdef OS_IS_WIN32 + if (WSAGetLastError() != WSAENOTSOCK) { + errno = WSAGetLastError(); + return r; + } +#else + if (errno != ENOTSOCK) + return r; +#endif + + if (type) + *type = 1; + } + + return write(fd, buf, count); +} + +/** Calls read() in a loop. Makes sure that as much as 'size' bytes, + * unless EOF is reached or an error occured */ +ssize_t pa_loop_read(int fd, void*data, size_t size, int *type) { + ssize_t ret = 0; + int _type; + + pa_assert(fd >= 0); + pa_assert(data); + pa_assert(size); + + if (!type) { + _type = 0; + type = &_type; + } + + while (size > 0) { + ssize_t r; + + if ((r = pa_read(fd, data, size, type)) < 0) + return r; + + if (r == 0) + break; + + ret += r; + data = (uint8_t*) data + r; + size -= r; + } + + return ret; +} + +/** Similar to pa_loop_read(), but wraps write() */ +ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type) { + ssize_t ret = 0; + int _type; + + pa_assert(fd >= 0); + pa_assert(data); + pa_assert(size); + + if (!type) { + _type = 0; + type = &_type; + } + + while (size > 0) { + ssize_t r; + + if ((r = pa_write(fd, data, size, type)) < 0) + return r; + + if (r == 0) + break; + + ret += r; + data = (const uint8_t*) data + r; + size -= r; + } + + return ret; +} + +/** Platform independent read function. Necessary since not all + * systems treat all file descriptors equal. */ +int pa_close(int fd) { + +#ifdef OS_IS_WIN32 + int ret; + + if ((ret = closesocket(fd)) == 0) + return 0; + + if (WSAGetLastError() != WSAENOTSOCK) { + errno = WSAGetLastError(); + return ret; + } +#endif + + return close(fd); +} + +/* Print a warning messages in case that the given signal is not + * blocked or trapped */ +void pa_check_signal_is_blocked(int sig) { +#ifdef HAVE_SIGACTION + struct sigaction sa; + sigset_t set; + + /* If POSIX threads are supported use thread-aware + * pthread_sigmask() function, to check if the signal is + * blocked. Otherwise fall back to sigprocmask() */ + +#ifdef HAVE_PTHREAD + if (pthread_sigmask(SIG_SETMASK, NULL, &set) < 0) { +#endif + if (sigprocmask(SIG_SETMASK, NULL, &set) < 0) { + pa_log("sigprocmask(): %s", pa_cstrerror(errno)); + return; + } +#ifdef HAVE_PTHREAD + } +#endif + + if (sigismember(&set, sig)) + return; + + /* Check whether the signal is trapped */ + + if (sigaction(sig, NULL, &sa) < 0) { + pa_log("sigaction(): %s", pa_cstrerror(errno)); + return; + } + + if (sa.sa_handler != SIG_DFL) + return; + + pa_log_warn("%s is not trapped. This might cause malfunction!", pa_sig2str(sig)); +#else /* HAVE_SIGACTION */ + pa_log_warn("%s might not be trapped. This might cause malfunction!", pa_sig2str(sig)); +#endif +} + +/* The following function is based on an example from the GNU libc + * documentation. This function is similar to GNU's asprintf(). */ +char *pa_sprintf_malloc(const char *format, ...) { + int size = 100; + char *c = NULL; + + pa_assert(format); + + for(;;) { + int r; + va_list ap; + + c = pa_xrealloc(c, size); + + va_start(ap, format); + r = vsnprintf(c, size, format, ap); + va_end(ap); + + c[size-1] = 0; + + if (r > -1 && r < size) + return c; + + if (r > -1) /* glibc 2.1 */ + size = r+1; + else /* glibc 2.0 */ + size *= 2; + } +} + +/* Same as the previous function, but use a va_list instead of an + * ellipsis */ +char *pa_vsprintf_malloc(const char *format, va_list ap) { + int size = 100; + char *c = NULL; + + pa_assert(format); + + for(;;) { + int r; + va_list aq; + + c = pa_xrealloc(c, size); + + va_copy(aq, ap); + r = vsnprintf(c, size, format, aq); + va_end(aq); + + c[size-1] = 0; + + if (r > -1 && r < size) + return c; + + if (r > -1) /* glibc 2.1 */ + size = r+1; + else /* glibc 2.0 */ + size *= 2; + } +} + +/* Similar to OpenBSD's strlcpy() function */ +char *pa_strlcpy(char *b, const char *s, size_t l) { + pa_assert(b); + pa_assert(s); + pa_assert(l > 0); + + strncpy(b, s, l); + b[l-1] = 0; + return b; +} + +/* Make the current thread a realtime thread, and acquire the highest + * rtprio we can get that is less or equal the specified parameter. If + * the thread is already realtime, don't do anything. */ +int pa_make_realtime(int rtprio) { + +#ifdef _POSIX_PRIORITY_SCHEDULING + struct sched_param sp; + int r, policy; + + memset(&sp, 0, sizeof(sp)); + policy = 0; + + if ((r = pthread_getschedparam(pthread_self(), &policy, &sp)) != 0) { + pa_log("pthread_getschedgetparam(): %s", pa_cstrerror(r)); + return -1; + } + + if (policy == SCHED_FIFO && sp.sched_priority >= rtprio) { + pa_log_info("Thread already being scheduled with SCHED_FIFO with priority %i.", sp.sched_priority); + return 0; + } + + sp.sched_priority = rtprio; + if ((r = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) != 0) { + + while (sp.sched_priority > 1) { + sp.sched_priority --; + + if ((r = pthread_setschedparam(pthread_self(), SCHED_FIFO, &sp)) == 0) { + pa_log_info("Successfully enabled SCHED_FIFO scheduling for thread, with priority %i, which is lower than the requested %i.", sp.sched_priority, rtprio); + return 0; + } + } + + pa_log_warn("pthread_setschedparam(): %s", pa_cstrerror(r)); + return -1; + } + + pa_log_info("Successfully enabled SCHED_FIFO scheduling for thread, with priority %i.", sp.sched_priority); + return 0; +#else + return -1; +#endif +} + +/* Raise the priority of the current process as much as possible that + * is <= the specified nice level..*/ +int pa_raise_priority(int nice_level) { + +#ifdef HAVE_SYS_RESOURCE_H + if (setpriority(PRIO_PROCESS, 0, nice_level) < 0) { + int n; + + for (n = nice_level+1; n < 0; n++) { + + if (setpriority(PRIO_PROCESS, 0, n) == 0) { + pa_log_info("Successfully acquired nice level %i, which is lower than the requested %i.", n, nice_level); + return 0; + } + } + + pa_log_warn("setpriority(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_log_info("Successfully gained nice level %i.", nice_level); +#endif + +#ifdef OS_IS_WIN32 + if (nice_level < 0) { + if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) { + pa_log_warn("SetPriorityClass() failed: 0x%08X", GetLastError()); + return .-1; + } else + pa_log_info("Successfully gained high priority class."); + } +#endif + + return 0; +} + +/* Reset the priority to normal, inverting the changes made by + * pa_raise_priority() and pa_make_realtime()*/ +void pa_reset_priority(void) { +#ifdef HAVE_SYS_RESOURCE_H + struct sched_param sp; + + setpriority(PRIO_PROCESS, 0, 0); + + memset(&sp, 0, sizeof(sp)); + pa_assert_se(pthread_setschedparam(pthread_self(), SCHED_OTHER, &sp) == 0); +#endif + +#ifdef OS_IS_WIN32 + SetPriorityClass(GetCurrentProcess(), NORMAL_PRIORITY_CLASS); +#endif +} + +/* Try to parse a boolean string value.*/ +int pa_parse_boolean(const char *v) { + + if (!strcmp(v, "1") || v[0] == 'y' || v[0] == 'Y' || v[0] == 't' || v[0] == 'T' || !strcasecmp(v, "on")) + return 1; + else if (!strcmp(v, "0") || v[0] == 'n' || v[0] == 'N' || v[0] == 'f' || v[0] == 'F' || !strcasecmp(v, "off")) + return 0; + + return -1; +} + +/* Split the specified string wherever one of the strings in delimiter + * occurs. Each time it is called returns a newly allocated string + * with pa_xmalloc(). The variable state points to, should be + * initiallized to NULL before the first call. */ +char *pa_split(const char *c, const char *delimiter, const char**state) { + const char *current = *state ? *state : c; + size_t l; + + if (!*current) + return NULL; + + l = strcspn(current, delimiter); + *state = current+l; + + if (**state) + (*state)++; + + return pa_xstrndup(current, l); +} + +/* What is interpreted as whitespace? */ +#define WHITESPACE " \t\n" + +/* Split a string into words. Otherwise similar to pa_split(). */ +char *pa_split_spaces(const char *c, const char **state) { + const char *current = *state ? *state : c; + size_t l; + + if (!*current || *c == 0) + return NULL; + + current += strspn(current, WHITESPACE); + l = strcspn(current, WHITESPACE); + + *state = current+l; + + return pa_xstrndup(current, l); +} + +PA_STATIC_TLS_DECLARE(signame, pa_xfree); + +/* Return the name of an UNIX signal. Similar to Solaris sig2str() */ +const char *pa_sig2str(int sig) { + char *t; + + if (sig <= 0) + goto fail; + +#ifdef NSIG + if (sig >= NSIG) + goto fail; +#endif + +#ifdef HAVE_SIG2STR + { + char buf[SIG2STR_MAX]; + + if (sig2str(sig, buf) == 0) { + pa_xfree(PA_STATIC_TLS_GET(signame)); + t = pa_sprintf_malloc("SIG%s", buf); + PA_STATIC_TLS_SET(signame, t); + return t; + } + } +#else + + switch(sig) { +#ifdef SIGHUP + case SIGHUP: return "SIGHUP"; +#endif + case SIGINT: return "SIGINT"; +#ifdef SIGQUIT + case SIGQUIT: return "SIGQUIT"; +#endif + case SIGILL: return "SIGULL"; +#ifdef SIGTRAP + case SIGTRAP: return "SIGTRAP"; +#endif + case SIGABRT: return "SIGABRT"; +#ifdef SIGBUS + case SIGBUS: return "SIGBUS"; +#endif + case SIGFPE: return "SIGFPE"; +#ifdef SIGKILL + case SIGKILL: return "SIGKILL"; +#endif +#ifdef SIGUSR1 + case SIGUSR1: return "SIGUSR1"; +#endif + case SIGSEGV: return "SIGSEGV"; +#ifdef SIGUSR2 + case SIGUSR2: return "SIGUSR2"; +#endif +#ifdef SIGPIPE + case SIGPIPE: return "SIGPIPE"; +#endif +#ifdef SIGALRM + case SIGALRM: return "SIGALRM"; +#endif + case SIGTERM: return "SIGTERM"; +#ifdef SIGSTKFLT + case SIGSTKFLT: return "SIGSTKFLT"; +#endif +#ifdef SIGCHLD + case SIGCHLD: return "SIGCHLD"; +#endif +#ifdef SIGCONT + case SIGCONT: return "SIGCONT"; +#endif +#ifdef SIGSTOP + case SIGSTOP: return "SIGSTOP"; +#endif +#ifdef SIGTSTP + case SIGTSTP: return "SIGTSTP"; +#endif +#ifdef SIGTTIN + case SIGTTIN: return "SIGTTIN"; +#endif +#ifdef SIGTTOU + case SIGTTOU: return "SIGTTOU"; +#endif +#ifdef SIGURG + case SIGURG: return "SIGURG"; +#endif +#ifdef SIGXCPU + case SIGXCPU: return "SIGXCPU"; +#endif +#ifdef SIGXFSZ + case SIGXFSZ: return "SIGXFSZ"; +#endif +#ifdef SIGVTALRM + case SIGVTALRM: return "SIGVTALRM"; +#endif +#ifdef SIGPROF + case SIGPROF: return "SIGPROF"; +#endif +#ifdef SIGWINCH + case SIGWINCH: return "SIGWINCH"; +#endif +#ifdef SIGIO + case SIGIO: return "SIGIO"; +#endif +#ifdef SIGPWR + case SIGPWR: return "SIGPWR"; +#endif +#ifdef SIGSYS + case SIGSYS: return "SIGSYS"; +#endif + } + +#ifdef SIGRTMIN + if (sig >= SIGRTMIN && sig <= SIGRTMAX) { + pa_xfree(PA_STATIC_TLS_GET(signame)); + t = pa_sprintf_malloc("SIGRTMIN+%i", sig - SIGRTMIN); + PA_STATIC_TLS_SET(signame, t); + return t; + } +#endif + +#endif + +fail: + + pa_xfree(PA_STATIC_TLS_GET(signame)); + t = pa_sprintf_malloc("SIG%i", sig); + PA_STATIC_TLS_SET(signame, t); + return t; +} + +#ifdef HAVE_GRP_H + +/* Check whether the specified GID and the group name match */ +static int is_group(gid_t gid, const char *name) { + struct group group, *result = NULL; + long n; + void *data; + int r = -1; + +#ifdef HAVE_GETGRGID_R +#ifdef _SC_GETGR_R_SIZE_MAX + n = sysconf(_SC_GETGR_R_SIZE_MAX); +#else + n = -1; +#endif + if (n < 0) n = 512; + data = pa_xmalloc(n); + + if (getgrgid_r(gid, &group, data, n, &result) < 0 || !result) { + pa_log("getgrgid_r(%u): %s", (unsigned)gid, pa_cstrerror(errno)); + goto finish; + } + + r = strcmp(name, result->gr_name) == 0; + +finish: + pa_xfree(data); +#else + /* XXX Not thread-safe, but needed on OSes (e.g. FreeBSD 4.X) that do not + * support getgrgid_r. */ + if ((result = getgrgid(gid)) == NULL) { + pa_log("getgrgid(%u): %s", gid, pa_cstrerror(errno)); + goto finish; + } + + r = strcmp(name, result->gr_name) == 0; + +finish: +#endif + + return r; +} + +/* Check the current user is member of the specified group */ +int pa_own_uid_in_group(const char *name, gid_t *gid) { + GETGROUPS_T *gids, tgid; + int n = sysconf(_SC_NGROUPS_MAX); + int r = -1, i; + + pa_assert(n > 0); + + gids = pa_xmalloc(sizeof(GETGROUPS_T)*n); + + if ((n = getgroups(n, gids)) < 0) { + pa_log("getgroups(): %s", pa_cstrerror(errno)); + goto finish; + } + + for (i = 0; i < n; i++) { + if (is_group(gids[i], name) > 0) { + *gid = gids[i]; + r = 1; + goto finish; + } + } + + if (is_group(tgid = getgid(), name) > 0) { + *gid = tgid; + r = 1; + goto finish; + } + + r = 0; + +finish: + + pa_xfree(gids); + return r; +} + +/* Check whether the specifc user id is a member of the specified group */ +int pa_uid_in_group(uid_t uid, const char *name) { + char *g_buf, *p_buf; + long g_n, p_n; + struct group grbuf, *gr; + char **i; + int r = -1; + + g_n = sysconf(_SC_GETGR_R_SIZE_MAX); + g_buf = pa_xmalloc(g_n); + + p_n = sysconf(_SC_GETPW_R_SIZE_MAX); + p_buf = pa_xmalloc(p_n); + + if (getgrnam_r(name, &grbuf, g_buf, (size_t) g_n, &gr) != 0 || !gr) + goto finish; + + r = 0; + for (i = gr->gr_mem; *i; i++) { + struct passwd pwbuf, *pw; + + if (getpwnam_r(*i, &pwbuf, p_buf, (size_t) p_n, &pw) != 0 || !pw) + continue; + + if (pw->pw_uid == uid) { + r = 1; + break; + } + } + +finish: + pa_xfree(g_buf); + pa_xfree(p_buf); + + return r; +} + +/* Get the GID of a gfiven group, return (gid_t) -1 on failure. */ +gid_t pa_get_gid_of_group(const char *name) { + gid_t ret = (gid_t) -1; + char *g_buf; + long g_n; + struct group grbuf, *gr; + + g_n = sysconf(_SC_GETGR_R_SIZE_MAX); + g_buf = pa_xmalloc(g_n); + + if (getgrnam_r(name, &grbuf, g_buf, (size_t) g_n, &gr) != 0 || !gr) + goto finish; + + ret = gr->gr_gid; + +finish: + pa_xfree(g_buf); + return ret; +} + +int pa_check_in_group(gid_t g) { + gid_t gids[NGROUPS_MAX]; + int r; + + if ((r = getgroups(NGROUPS_MAX, gids)) < 0) + return -1; + + for (; r > 0; r--) + if (gids[r-1] == g) + return 1; + + return 0; +} + +#else /* HAVE_GRP_H */ + +int pa_own_uid_in_group(const char *name, gid_t *gid) { + return -1; + +} + +int pa_uid_in_group(uid_t uid, const char *name) { + return -1; +} + +gid_t pa_get_gid_of_group(const char *name) { + return (gid_t) -1; +} + +int pa_check_in_group(gid_t g) { + return -1; +} + +#endif + +/* Lock or unlock a file entirely. + (advisory on UNIX, mandatory on Windows) */ +int pa_lock_fd(int fd, int b) { +#ifdef F_SETLKW + struct flock flock; + + /* Try a R/W lock first */ + + flock.l_type = b ? F_WRLCK : F_UNLCK; + flock.l_whence = SEEK_SET; + flock.l_start = 0; + flock.l_len = 0; + + if (fcntl(fd, F_SETLKW, &flock) >= 0) + return 0; + + /* Perhaps the file descriptor qas opened for read only, than try again with a read lock. */ + if (b && errno == EBADF) { + flock.l_type = F_RDLCK; + if (fcntl(fd, F_SETLKW, &flock) >= 0) + return 0; + } + + pa_log("%slock: %s", !b? "un" : "", pa_cstrerror(errno)); +#endif + +#ifdef OS_IS_WIN32 + HANDLE h = (HANDLE)_get_osfhandle(fd); + + if (b && LockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF)) + return 0; + if (!b && UnlockFile(h, 0, 0, 0xFFFFFFFF, 0xFFFFFFFF)) + return 0; + + pa_log("%slock failed: 0x%08X", !b ? "un" : "", GetLastError()); +#endif + + return -1; +} + +/* Remove trailing newlines from a string */ +char* pa_strip_nl(char *s) { + pa_assert(s); + + s[strcspn(s, "\r\n")] = 0; + return s; +} + +/* Create a temporary lock file and lock it. */ +int pa_lock_lockfile(const char *fn) { + int fd = -1; + pa_assert(fn); + + for (;;) { + struct stat st; + + if ((fd = open(fn, O_CREAT|O_RDWR +#ifdef O_NOCTTY + |O_NOCTTY +#endif +#ifdef O_NOFOLLOW + |O_NOFOLLOW +#endif + , S_IRUSR|S_IWUSR)) < 0) { + pa_log_warn("Failed to create lock file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + if (pa_lock_fd(fd, 1) < 0) { + pa_log_warn("Failed to lock file '%s'.", fn); + goto fail; + } + + if (fstat(fd, &st) < 0) { + pa_log_warn("Failed to fstat() file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + /* Check wheter the file has been removed meanwhile. When yes, + * restart this loop, otherwise, we're done */ + if (st.st_nlink >= 1) + break; + + if (pa_lock_fd(fd, 0) < 0) { + pa_log_warn("Failed to unlock file '%s'.", fn); + goto fail; + } + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close file '%s': %s", fn, pa_cstrerror(errno)); + fd = -1; + goto fail; + } + + fd = -1; + } + + return fd; + +fail: + + if (fd >= 0) + pa_close(fd); + + return -1; +} + +/* Unlock a temporary lcok file */ +int pa_unlock_lockfile(const char *fn, int fd) { + int r = 0; + pa_assert(fn); + pa_assert(fd >= 0); + + if (unlink(fn) < 0) { + pa_log_warn("Unable to remove lock file '%s': %s", fn, pa_cstrerror(errno)); + r = -1; + } + + if (pa_lock_fd(fd, 0) < 0) { + pa_log_warn("Failed to unlock file '%s'.", fn); + r = -1; + } + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close '%s': %s", fn, pa_cstrerror(errno)); + r = -1; + } + + return r; +} + +/* Try to open a configuration file. If "env" is specified, open the + * value of the specified environment variable. Otherwise look for a + * file "local" in the home directory or a file "global" in global + * file system. If "result" is non-NULL, a pointer to a newly + * allocated buffer containing the used configuration file is + * stored there.*/ +FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result, const char *mode) { + const char *fn; + char h[PATH_MAX]; + +#ifdef OS_IS_WIN32 + char buf[PATH_MAX]; + + if (!getenv(PULSE_ROOTENV)) + pa_set_root(NULL); +#endif + + if (env && (fn = getenv(env))) { +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(fn, buf, PATH_MAX)) + return NULL; + fn = buf; +#endif + + if (result) + *result = pa_xstrdup(fn); + + return fopen(fn, mode); + } + + if (local) { + const char *e; + char *lfn = NULL; + + if ((e = getenv("PULSE_CONFIG_PATH"))) + fn = lfn = pa_sprintf_malloc("%s/%s", e, local); + else if (pa_get_home_dir(h, sizeof(h))) { + char *d; + + d = pa_sprintf_malloc("%s/.pulse", h); + mkdir(d, 0755); + pa_xfree(d); + + fn = lfn = pa_sprintf_malloc("%s/.pulse/%s", h, local); + } + + if (lfn) { + FILE *f; + +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(lfn, buf, PATH_MAX)) + return NULL; + fn = buf; +#endif + + f = fopen(fn, mode); + if (f != NULL) { + if (result) + *result = pa_xstrdup(fn); + pa_xfree(lfn); + return f; + } + + if (errno != ENOENT) + pa_log_warn("Failed to open configuration file '%s': %s", lfn, pa_cstrerror(errno)); + + pa_xfree(lfn); + } + } + + if (!global) { + if (result) + *result = NULL; + errno = ENOENT; + return NULL; + } + +#ifdef OS_IS_WIN32 + if (!ExpandEnvironmentStrings(global, buf, PATH_MAX)) + return NULL; + global = buf; +#endif + + if (result) + *result = pa_xstrdup(global); + + return fopen(global, mode); +} + +/* Format the specified data as a hexademical string */ +char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength) { + size_t i = 0, j = 0; + const char hex[] = "0123456789abcdef"; + + pa_assert(d); + pa_assert(s); + pa_assert(slength > 0); + + while (i < dlength && j+3 <= slength) { + s[j++] = hex[*d >> 4]; + s[j++] = hex[*d & 0xF]; + + d++; + i++; + } + + s[j < slength ? j : slength] = 0; + return s; +} + +/* Convert a hexadecimal digit to a number or -1 if invalid */ +static int hexc(char c) { + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + return -1; +} + +/* Parse a hexadecimal string as created by pa_hexstr() to a BLOB */ +size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength) { + size_t j = 0; + + pa_assert(p); + pa_assert(d); + + while (j < dlength && *p) { + int b; + + if ((b = hexc(*(p++))) < 0) + return (size_t) -1; + + d[j] = (uint8_t) (b << 4); + + if (!*p) + return (size_t) -1; + + if ((b = hexc(*(p++))) < 0) + return (size_t) -1; + + d[j] |= (uint8_t) b; + j++; + } + + return j; +} + +/* Returns nonzero when *s starts with *pfx */ +int pa_startswith(const char *s, const char *pfx) { + size_t l; + + pa_assert(s); + pa_assert(pfx); + + l = strlen(pfx); + + return strlen(s) >= l && strncmp(s, pfx, l) == 0; +} + +/* Returns nonzero when *s ends with *sfx */ +int pa_endswith(const char *s, const char *sfx) { + size_t l1, l2; + + pa_assert(s); + pa_assert(sfx); + + l1 = strlen(s); + l2 = strlen(sfx); + + return l1 >= l2 && strcmp(s+l1-l2, sfx) == 0; +} + +/* if fn is null return the PulseAudio run time path in s (/tmp/pulse) + * if fn is non-null and starts with / return fn in s + * otherwise append fn to the run time path and return it in s */ +char *pa_runtime_path(const char *fn, char *s, size_t l) { + const char *e; + +#ifndef OS_IS_WIN32 + if (fn && *fn == '/') +#else + if (fn && strlen(fn) >= 3 && isalpha(fn[0]) && fn[1] == ':' && fn[2] == '\\') +#endif + return pa_strlcpy(s, fn, l); + + if ((e = getenv("PULSE_RUNTIME_PATH"))) { + + if (fn) + pa_snprintf(s, l, "%s%c%s", e, PA_PATH_SEP_CHAR, fn); + else + pa_snprintf(s, l, "%s", e); + + } else { + char u[256]; + + if (fn) + pa_snprintf(s, l, "%s%s%c%s", PA_USER_RUNTIME_PATH_PREFIX, pa_get_user_name(u, sizeof(u)), PA_PATH_SEP_CHAR, fn); + else + pa_snprintf(s, l, "%s%s", PA_USER_RUNTIME_PATH_PREFIX, pa_get_user_name(u, sizeof(u))); + } + + +#ifdef OS_IS_WIN32 + { + char buf[l]; + strcpy(buf, s); + ExpandEnvironmentStrings(buf, s, l); + } +#endif + + return s; +} + +/* Convert the string s to a signed integer in *ret_i */ +int pa_atoi(const char *s, int32_t *ret_i) { + char *x = NULL; + long l; + + pa_assert(s); + pa_assert(ret_i); + + errno = 0; + l = strtol(s, &x, 0); + + if (!x || *x || errno != 0) + return -1; + + if ((int32_t) l != l) + return -1; + + *ret_i = (int32_t) l; + + return 0; +} + +/* Convert the string s to an unsigned integer in *ret_u */ +int pa_atou(const char *s, uint32_t *ret_u) { + char *x = NULL; + unsigned long l; + + pa_assert(s); + pa_assert(ret_u); + + errno = 0; + l = strtoul(s, &x, 0); + + if (!x || *x || errno != 0) + return -1; + + if ((uint32_t) l != l) + return -1; + + *ret_u = (uint32_t) l; + + return 0; +} + +#ifdef HAVE_STRTOF_L +static locale_t c_locale = NULL; + +static void c_locale_destroy(void) { + freelocale(c_locale); +} +#endif + +int pa_atof(const char *s, float *ret_f) { + char *x = NULL; + float f; + int r = 0; + + pa_assert(s); + pa_assert(ret_f); + + /* This should be locale independent */ + +#ifdef HAVE_STRTOF_L + + PA_ONCE_BEGIN { + + if ((c_locale = newlocale(LC_ALL_MASK, "C", NULL))) + atexit(c_locale_destroy); + + } PA_ONCE_END; + + if (c_locale) { + errno = 0; + f = strtof_l(s, &x, c_locale); + } else +#endif + { + errno = 0; +#ifdef HAVE_STRTOF + f = strtof(s, &x); +#else + f = strtod(s, &x); +#endif + } + + if (!x || *x || errno != 0) + r = -1; + else + *ret_f = f; + + return r; +} + +/* Same as snprintf, but guarantees NUL-termination on every platform */ +int pa_snprintf(char *str, size_t size, const char *format, ...) { + int ret; + va_list ap; + + pa_assert(str); + pa_assert(size > 0); + pa_assert(format); + + va_start(ap, format); + ret = vsnprintf(str, size, format, ap); + va_end(ap); + + str[size-1] = 0; + + return ret; +} + +/* Truncate the specified string, but guarantee that the string + * returned still validates as UTF8 */ +char *pa_truncate_utf8(char *c, size_t l) { + pa_assert(c); + pa_assert(pa_utf8_valid(c)); + + if (strlen(c) <= l) + return c; + + c[l] = 0; + + while (l > 0 && !pa_utf8_valid(c)) + c[--l] = 0; + + return c; +} + +char *pa_getcwd(void) { + size_t l = 128; + + for (;;) { + char *p = pa_xnew(char, l); + if (getcwd(p, l)) + return p; + + if (errno != ERANGE) + return NULL; + + pa_xfree(p); + l *= 2; + } +} + +char *pa_make_path_absolute(const char *p) { + char *r; + char *cwd; + + pa_assert(p); + + if (p[0] == '/') + return pa_xstrdup(p); + + if (!(cwd = pa_getcwd())) + return pa_xstrdup(p); + + r = pa_sprintf_malloc("%s/%s", cwd, p); + pa_xfree(cwd); + return r; +} + +void *pa_will_need(const void *p, size_t l) { +#ifdef RLIMIT_MEMLOCK + struct rlimit rlim; +#endif + const void *a; + size_t size; + int r; + size_t bs; + + pa_assert(p); + pa_assert(l > 0); + + a = PA_PAGE_ALIGN_PTR(p); + size = (const uint8_t*) p + l - (const uint8_t*) a; + +#ifdef HAVE_POSIX_MADVISE + if ((r = posix_madvise((void*) a, size, POSIX_MADV_WILLNEED)) == 0) { + pa_log_debug("posix_madvise() worked fine!"); + return (void*) p; + } +#endif + + /* Most likely the memory was not mmap()ed from a file and thus + * madvise() didn't work, so let's misuse mlock() do page this + * stuff back into RAM. Yeah, let's fuck with the MM! It's so + * inviting, the man page of mlock() tells us: "All pages that + * contain a part of the specified address range are guaranteed to + * be resident in RAM when the call returns successfully." */ + +#ifdef RLIMIT_MEMLOCK + pa_assert_se(getrlimit(RLIMIT_MEMLOCK, &rlim) == 0); + + if (rlim.rlim_cur < PA_PAGE_SIZE) { + pa_log_debug("posix_madvise() failed (or doesn't exist), resource limits don't allow mlock(), can't page in data: %s", pa_cstrerror(r)); + return (void*) p; + } + + bs = PA_PAGE_ALIGN(rlim.rlim_cur); +#else + bs = PA_PAGE_SIZE*4; +#endif + + pa_log_debug("posix_madvise() failed (or doesn't exist), trying mlock(): %s", pa_cstrerror(r)); + +#ifdef HAVE_MLOCK + while (size > 0 && bs > 0) { + + if (bs > size) + bs = size; + + if (mlock(a, bs) < 0) { + bs = PA_PAGE_ALIGN(bs / 2); + continue; + } + + pa_assert_se(munlock(a, bs) == 0); + + a = (const uint8_t*) a + bs; + size -= bs; + } +#endif + + if (bs <= 0) + pa_log_debug("mlock() failed too (or doesn't exist), giving up: %s", pa_cstrerror(errno)); + else + pa_log_debug("mlock() worked fine!"); + + return (void*) p; +} + +void pa_close_pipe(int fds[2]) { + pa_assert(fds); + + if (fds[0] >= 0) + pa_assert_se(pa_close(fds[0]) == 0); + + if (fds[1] >= 0) + pa_assert_se(pa_close(fds[1]) == 0); + + fds[0] = fds[1] = -1; +} + +char *pa_readlink(const char *p) { + size_t l = 100; + + for (;;) { + char *c; + ssize_t n; + + c = pa_xnew(char, l); + + if ((n = readlink(p, c, l-1)) < 0) { + pa_xfree(c); + return NULL; + } + + if ((size_t) n < l-1) { + c[n] = 0; + return c; + } + + pa_xfree(c); + l *= 2; + } +} diff --git a/src/pulsecore/core-util.h b/src/pulsecore/core-util.h new file mode 100644 index 00000000..c8760a1f --- /dev/null +++ b/src/pulsecore/core-util.h @@ -0,0 +1,132 @@ +#ifndef foocoreutilhfoo +#define foocoreutilhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdio.h> + +#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> + +struct timeval; + +void pa_make_fd_nonblock(int fd); +void pa_make_fd_cloexec(int fd); + +int pa_make_secure_dir(const char* dir, mode_t m, uid_t uid, gid_t gid); +int pa_make_secure_parent_dir(const char *fn, mode_t, uid_t uid, gid_t gid); + +ssize_t pa_read(int fd, void *buf, size_t count, int *type); +ssize_t pa_write(int fd, const void *buf, size_t count, int *type); +ssize_t pa_loop_read(int fd, void*data, size_t size, int *type); +ssize_t pa_loop_write(int fd, const void*data, size_t size, int *type); + +int pa_close(int fd); + +void pa_check_signal_is_blocked(int sig); + +char *pa_sprintf_malloc(const char *format, ...) PA_GCC_PRINTF_ATTR(1,2); +char *pa_vsprintf_malloc(const char *format, va_list ap); + +char *pa_strlcpy(char *b, const char *s, size_t l); + +char *pa_parent_dir(const char *fn); + +int pa_make_realtime(int rtprio); +int pa_raise_priority(int nice_level); +void pa_reset_priority(void); + +int pa_parse_boolean(const char *s) PA_GCC_PURE; + +static inline const char *pa_yes_no(pa_bool_t b) { + return b ? "yes" : "no"; +} + +char *pa_split(const char *c, const char*delimiters, const char **state); +char *pa_split_spaces(const char *c, const char **state); + +char *pa_strip_nl(char *s); + +const char *pa_sig2str(int sig) PA_GCC_PURE; + +int pa_own_uid_in_group(const char *name, gid_t *gid); +int pa_uid_in_group(uid_t uid, const char *name); +gid_t pa_get_gid_of_group(const char *name); +int pa_check_in_group(gid_t g); + +int pa_lock_fd(int fd, int b); + +int pa_lock_lockfile(const char *fn); +int pa_unlock_lockfile(const char *fn, int fd); + +FILE *pa_open_config_file(const char *global, const char *local, const char *env, char **result, const char *mode); + +char *pa_hexstr(const uint8_t* d, size_t dlength, char *s, size_t slength); +size_t pa_parsehex(const char *p, uint8_t *d, size_t dlength); + +int pa_startswith(const char *s, const char *pfx) PA_GCC_PURE; +int pa_endswith(const char *s, const char *sfx) PA_GCC_PURE; + +char *pa_runtime_path(const char *fn, char *s, size_t l); + +int pa_atoi(const char *s, int32_t *ret_i); +int pa_atou(const char *s, uint32_t *ret_u); +int pa_atof(const char *s, float *ret_f); + +int pa_snprintf(char *str, size_t size, const char *format, ...); + +char *pa_truncate_utf8(char *c, size_t l); + +char *pa_getcwd(void); +char *pa_make_path_absolute(const char *p); + +void *pa_will_need(const void *p, size_t l); + +static inline int pa_is_power_of_two(unsigned n) { + return !(n & (n - 1)); +} + +static inline unsigned pa_make_power_of_two(unsigned n) { + unsigned j = n; + + if (pa_is_power_of_two(n)) + return n; + + while (j) { + j = j >> 1; + n = n | j; + } + + return n + 1; +} + +void pa_close_pipe(int fds[2]); + +char *pa_readlink(const char *p); + +#endif diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c new file mode 100644 index 00000000..cf018509 --- /dev/null +++ b/src/pulsecore/core.c @@ -0,0 +1,220 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdio.h> +#include <signal.h> + +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/module.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/autoload.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/props.h> +#include <pulsecore/random.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "core.h" + +static PA_DEFINE_CHECK_TYPE(pa_core, pa_msgobject); + +static int core_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_core *c = PA_CORE(o); + + pa_core_assert_ref(c); + + switch (code) { + + case PA_CORE_MESSAGE_UNLOAD_MODULE: + pa_module_unload(c, userdata); + return 0; + + default: + return -1; + } +} + +static void core_free(pa_object *o); + +pa_core* pa_core_new(pa_mainloop_api *m, int shared) { + pa_core* c; + pa_mempool *pool; + int j; + + pa_assert(m); + + if (shared) { + if (!(pool = pa_mempool_new(shared))) { + pa_log_warn("failed to allocate shared memory pool. Falling back to a normal memory pool."); + shared = 0; + } + } + + if (!shared) { + if (!(pool = pa_mempool_new(shared))) { + pa_log("pa_mempool_new() failed."); + return NULL; + } + } + + c = pa_msgobject_new(pa_core); + c->parent.parent.free = core_free; + c->parent.process_msg = core_process_msg; + + c->mainloop = m; + c->clients = pa_idxset_new(NULL, NULL); + c->sinks = pa_idxset_new(NULL, NULL); + c->sources = pa_idxset_new(NULL, NULL); + c->source_outputs = pa_idxset_new(NULL, NULL); + c->sink_inputs = pa_idxset_new(NULL, NULL); + + c->default_source_name = c->default_sink_name = NULL; + + c->modules = NULL; + c->namereg = NULL; + c->scache = NULL; + c->autoload_idxset = NULL; + c->autoload_hashmap = NULL; + c->running_as_daemon = FALSE; + + c->default_sample_spec.format = PA_SAMPLE_S16NE; + c->default_sample_spec.rate = 44100; + c->default_sample_spec.channels = 2; + c->default_n_fragments = 4; + c->default_fragment_size_msec = 25; + + c->module_auto_unload_event = NULL; + c->module_defer_unload_event = NULL; + c->scache_auto_unload_event = NULL; + + c->subscription_defer_event = NULL; + PA_LLIST_HEAD_INIT(pa_subscription, c->subscriptions); + PA_LLIST_HEAD_INIT(pa_subscription_event, c->subscription_event_queue); + c->subscription_event_last = NULL; + + c->mempool = pool; + + c->quit_event = NULL; + + c->exit_idle_time = -1; + c->module_idle_time = 20; + c->scache_idle_time = 20; + + c->resample_method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 3; + + c->is_system_instance = FALSE; + c->disallow_module_loading = FALSE; + c->realtime_scheduling = FALSE; + c->realtime_priority = 5; + c->disable_remixing = FALSE; + + for (j = 0; j < PA_CORE_HOOK_MAX; j++) + pa_hook_init(&c->hooks[j], c); + + pa_property_init(c); + + pa_random(&c->cookie, sizeof(c->cookie)); + +#ifdef SIGPIPE + pa_check_signal_is_blocked(SIGPIPE); +#endif + + return c; +} + +static void core_free(pa_object *o) { + pa_core *c = PA_CORE(o); + int j; + pa_assert(c); + + pa_module_unload_all(c); + pa_assert(!c->modules); + + pa_assert(pa_idxset_isempty(c->clients)); + pa_idxset_free(c->clients, NULL, NULL); + + pa_assert(pa_idxset_isempty(c->sinks)); + pa_idxset_free(c->sinks, NULL, NULL); + + pa_assert(pa_idxset_isempty(c->sources)); + pa_idxset_free(c->sources, NULL, NULL); + + pa_assert(pa_idxset_isempty(c->source_outputs)); + pa_idxset_free(c->source_outputs, NULL, NULL); + + pa_assert(pa_idxset_isempty(c->sink_inputs)); + pa_idxset_free(c->sink_inputs, NULL, NULL); + + pa_scache_free(c); + pa_namereg_free(c); + pa_autoload_free(c); + pa_subscription_free_all(c); + + if (c->quit_event) + c->mainloop->time_free(c->quit_event); + + pa_xfree(c->default_source_name); + pa_xfree(c->default_sink_name); + + pa_mempool_free(c->mempool); + + pa_property_cleanup(c); + + for (j = 0; j < PA_CORE_HOOK_MAX; j++) + pa_hook_free(&c->hooks[j]); + + pa_xfree(c); +} + +static void quit_callback(pa_mainloop_api*m, pa_time_event *e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { + pa_core *c = userdata; + pa_assert(c->quit_event == e); + + m->quit(m, 0); +} + +void pa_core_check_quit(pa_core *c) { + pa_assert(c); + + if (!c->quit_event && c->exit_idle_time >= 0 && pa_idxset_size(c->clients) == 0) { + struct timeval tv; + pa_gettimeofday(&tv); + tv.tv_sec+= c->exit_idle_time; + c->quit_event = c->mainloop->time_new(c->mainloop, &tv, quit_callback, c); + } else if (c->quit_event && pa_idxset_size(c->clients) > 0) { + c->mainloop->time_free(c->quit_event); + c->quit_event = NULL; + } +} diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h new file mode 100644 index 00000000..ce45e300 --- /dev/null +++ b/src/pulsecore/core.h @@ -0,0 +1,142 @@ +#ifndef foocorehfoo +#define foocorehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/sample.h> + +#include <pulsecore/idxset.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/memblock.h> +#include <pulsecore/resampler.h> +#include <pulsecore/queue.h> +#include <pulsecore/llist.h> +#include <pulsecore/hook-list.h> +#include <pulsecore/asyncmsgq.h> + +typedef struct pa_core pa_core; + +#include <pulsecore/core-subscribe.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/msgobject.h> + +typedef enum pa_core_hook { + PA_CORE_HOOK_SINK_NEW_POST, + PA_CORE_HOOK_SINK_UNLINK, + PA_CORE_HOOK_SINK_UNLINK_POST, + PA_CORE_HOOK_SINK_STATE_CHANGED, + PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED, + PA_CORE_HOOK_SOURCE_NEW_POST, + PA_CORE_HOOK_SOURCE_UNLINK, + PA_CORE_HOOK_SOURCE_UNLINK_POST, + PA_CORE_HOOK_SOURCE_STATE_CHANGED, + PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED, + PA_CORE_HOOK_SINK_INPUT_NEW, + PA_CORE_HOOK_SINK_INPUT_FIXATE, + PA_CORE_HOOK_SINK_INPUT_PUT, + PA_CORE_HOOK_SINK_INPUT_UNLINK, + PA_CORE_HOOK_SINK_INPUT_UNLINK_POST, + PA_CORE_HOOK_SINK_INPUT_MOVE, + PA_CORE_HOOK_SINK_INPUT_MOVE_POST, + PA_CORE_HOOK_SINK_INPUT_NAME_CHANGED, + PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED, + PA_CORE_HOOK_SOURCE_OUTPUT_NEW, + PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE, + PA_CORE_HOOK_SOURCE_OUTPUT_PUT, + PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK, + PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST, + PA_CORE_HOOK_SOURCE_OUTPUT_MOVE, + PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST, + PA_CORE_HOOK_SOURCE_OUTPUT_NAME_CHANGED, + PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED, + PA_CORE_HOOK_MAX +} pa_core_hook_t; + +/* The core structure of PulseAudio. Every PulseAudio daemon contains + * exactly one of these. It is used for storing kind of global + * variables for the daemon. */ + +struct pa_core { + pa_msgobject parent; + + /* A random value which may be used to identify this instance of + * PulseAudio. Not cryptographically secure in any way. */ + uint32_t cookie; + + pa_mainloop_api *mainloop; + + /* idxset of all kinds of entities */ + pa_idxset *clients, *sinks, *sources, *sink_inputs, *source_outputs, *modules, *scache, *autoload_idxset; + + /* Some hashmaps for all sorts of entities */ + pa_hashmap *namereg, *autoload_hashmap, *properties; + + /* The name of the default sink/source */ + char *default_source_name, *default_sink_name; + + pa_sample_spec default_sample_spec; + unsigned default_n_fragments, default_fragment_size_msec; + + pa_time_event *module_auto_unload_event; + pa_defer_event *module_defer_unload_event; + + pa_defer_event *subscription_defer_event; + PA_LLIST_HEAD(pa_subscription, subscriptions); + PA_LLIST_HEAD(pa_subscription_event, subscription_event_queue); + pa_subscription_event *subscription_event_last; + + pa_mempool *mempool; + + int exit_idle_time, module_idle_time, scache_idle_time; + + pa_time_event *quit_event; + + pa_time_event *scache_auto_unload_event; + + pa_bool_t disallow_module_loading, running_as_daemon; + pa_resample_method_t resample_method; + pa_bool_t is_system_instance; + pa_bool_t realtime_scheduling; + int realtime_priority; + pa_bool_t disable_remixing; + + /* hooks */ + pa_hook hooks[PA_CORE_HOOK_MAX]; +}; + +PA_DECLARE_CLASS(pa_core); +#define PA_CORE(o) pa_core_cast(o) + +enum { + PA_CORE_MESSAGE_UNLOAD_MODULE, + PA_CORE_MESSAGE_MAX +}; + +pa_core* pa_core_new(pa_mainloop_api *m, int shared); + +/* Check whether noone is connected to this core */ +void pa_core_check_quit(pa_core *c); + +#endif diff --git a/src/pulsecore/creds.h b/src/pulsecore/creds.h new file mode 100644 index 00000000..51dfc33d --- /dev/null +++ b/src/pulsecore/creds.h @@ -0,0 +1,56 @@ +#ifndef foocredshfoo +#define foocredshfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 <sys/types.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +typedef struct pa_creds pa_creds; + +#if defined(SCM_CREDENTIALS) + +#define HAVE_CREDS 1 + +struct pa_creds { + gid_t gid; + uid_t uid; +}; + +#else +#undef HAVE_CREDS +#endif + +#endif diff --git a/src/pulsecore/dllmain.c b/src/pulsecore/dllmain.c new file mode 100644 index 00000000..52cbf9e2 --- /dev/null +++ b/src/pulsecore/dllmain.c @@ -0,0 +1,57 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 + +#ifdef OS_IS_WIN32 + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include <windows.h> + +extern pa_set_root(HANDLE handle); + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { + WSADATA data; + + switch (fdwReason) { + + case DLL_PROCESS_ATTACH: + if (!pa_set_root(hinstDLL)) + return FALSE; + WSAStartup(MAKEWORD(2, 0), &data); + break; + + case DLL_PROCESS_DETACH: + WSACleanup(); + break; + + } + return TRUE; +} + +#endif /* OS_IS_WIN32 */ diff --git a/src/pulsecore/dynarray.c b/src/pulsecore/dynarray.c new file mode 100644 index 00000000..8bdb46fa --- /dev/null +++ b/src/pulsecore/dynarray.c @@ -0,0 +1,111 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <stdlib.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "dynarray.h" + +/* If the array becomes to small, increase its size by 100 entries */ +#define INCREASE_BY 100 + +struct pa_dynarray { + void **data; + unsigned n_allocated, n_entries; +}; + +pa_dynarray* pa_dynarray_new(void) { + pa_dynarray *a; + a = pa_xnew(pa_dynarray, 1); + a->data = NULL; + a->n_entries = 0; + a->n_allocated = 0; + return a; +} + +void pa_dynarray_free(pa_dynarray* a, void (*func)(void *p, void *userdata), void *userdata) { + unsigned i; + pa_assert(a); + + if (func) + for (i = 0; i < a->n_entries; i++) + if (a->data[i]) + func(a->data[i], userdata); + + pa_xfree(a->data); + pa_xfree(a); +} + +void pa_dynarray_put(pa_dynarray*a, unsigned i, void *p) { + pa_assert(a); + + if (i >= a->n_allocated) { + unsigned n; + + if (!p) + return; + + n = i+INCREASE_BY; + a->data = pa_xrealloc(a->data, sizeof(void*)*n); + memset(a->data+a->n_allocated, 0, sizeof(void*)*(n-a->n_allocated)); + a->n_allocated = n; + } + + a->data[i] = p; + + if (i >= a->n_entries) + a->n_entries = i+1; +} + +unsigned pa_dynarray_append(pa_dynarray*a, void *p) { + unsigned i; + + pa_assert(a); + + i = a->n_entries; + pa_dynarray_put(a, i, p); + return i; +} + +void *pa_dynarray_get(pa_dynarray*a, unsigned i) { + pa_assert(a); + + if (i >= a->n_entries) + return NULL; + + pa_assert(a->data); + return a->data[i]; +} + +unsigned pa_dynarray_size(pa_dynarray*a) { + pa_assert(a); + + return a->n_entries; +} diff --git a/src/pulsecore/dynarray.h b/src/pulsecore/dynarray.h new file mode 100644 index 00000000..0f222e10 --- /dev/null +++ b/src/pulsecore/dynarray.h @@ -0,0 +1,51 @@ +#ifndef foodynarrayhfoo +#define foodynarrayhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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. +***/ + +typedef struct pa_dynarray pa_dynarray; + +/* Implementation of a simple dynamically sized array. The array + * expands if required, but doesn't shrink if possible. Memory + * management of the array's entries is the user's job. */ + +pa_dynarray* pa_dynarray_new(void); + +/* Free the array calling the specified function for every entry in + * the array. The function may be NULL. */ +void pa_dynarray_free(pa_dynarray* a, void (*func)(void *p, void *userdata), void *userdata); + +/* Store p at position i in the array */ +void pa_dynarray_put(pa_dynarray*a, unsigned i, void *p); + +/* Store p a the first free position in the array. Returns the index + * of that entry. If entries are removed from the array their position + * are not filled any more by this function. */ +unsigned pa_dynarray_append(pa_dynarray*a, void *p); + +void *pa_dynarray_get(pa_dynarray*a, unsigned i); + +unsigned pa_dynarray_size(pa_dynarray*a); + +#endif diff --git a/src/pulsecore/endianmacros.h b/src/pulsecore/endianmacros.h new file mode 100644 index 00000000..6b80246b --- /dev/null +++ b/src/pulsecore/endianmacros.h @@ -0,0 +1,120 @@ +#ifndef fooendianmacroshfoo +#define fooendianmacroshfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#ifdef HAVE_BYTESWAP_H +#include <byteswap.h> +#endif + +#ifdef HAVE_BYTESWAP_H +#define PA_INT16_SWAP(x) ((int16_t) bswap_16((uint16_t) x)) +#define PA_UINT16_SWAP(x) ((uint16_t) bswap_16((uint16_t) x)) +#define PA_INT32_SWAP(x) ((int32_t) bswap_32((uint32_t) x)) +#define PA_UINT32_SWAP(x) ((uint32_t) bswap_32((uint32_t) x)) +#else +#define PA_INT16_SWAP(x) ( (int16_t) ( ((uint16_t) x >> 8) | ((uint16_t) x << 8) ) ) +#define PA_UINT16_SWAP(x) ( (uint16_t) ( ((uint16_t) x >> 8) | ((uint16_t) x << 8) ) ) +#define PA_INT32_SWAP(x) ( (int32_t) ( ((uint32_t) x >> 24) | ((uint32_t) x << 24) | (((uint32_t) x & 0xFF00) << 8) | ((((uint32_t) x) >> 8) & 0xFF00) ) ) +#define PA_UINT32_SWAP(x) ( (uint32_t) ( ((uint32_t) x >> 24) | ((uint32_t) x << 24) | (((uint32_t) x & 0xFF00) << 8) | ((((uint32_t) x) >> 8) & 0xFF00) ) ) +#endif + +static inline float PA_FLOAT32_SWAP(float x) { + uint32_t i = *(uint32_t*) &x; + i = PA_UINT32_SWAP(i); + return *(float*) &i; +} + +#define PA_MAYBE_INT16_SWAP(c,x) ((c) ? PA_INT32_SWAP(x) : x) +#define PA_MAYBE_UINT16_SWAP(c,x) ((c) ? PA_UINT32_SWAP(x) : x) + +#define PA_MAYBE_INT32_SWAP(c,x) ((c) ? PA_INT32_SWAP(x) : x) +#define PA_MAYBE_UINT32_SWAP(c,x) ((c) ? PA_UINT32_SWAP(x) : x) + +#define PA_MAYBE_FLOAT32_SWAP(c,x) ((c) ? PA_FLOAT32_SWAP(x) : x) + +#ifdef WORDS_BIGENDIAN + #define PA_INT16_FROM_LE(x) PA_INT16_SWAP(x) + #define PA_INT16_FROM_BE(x) ((int16_t)(x)) + + #define PA_INT16_TO_LE(x) PA_INT16_SWAP(x) + #define PA_INT16_TO_BE(x) ((int16_t)(x)) + + #define PA_UINT16_FROM_LE(x) PA_UINT16_SWAP(x) + #define PA_UINT16_FROM_BE(x) ((uint16_t)(x)) + + #define PA_UINT16_TO_LE(x) PA_UINT16_SWAP(x) + #define PA_UINT16_TO_BE(x) ((uint16_t)(x)) + + #define PA_INT32_FROM_LE(x) PA_INT32_SWAP(x) + #define PA_INT32_FROM_BE(x) ((int32_t)(x)) + + #define PA_INT32_TO_LE(x) PA_INT32_SWAP(x) + #define PA_INT32_TO_BE(x) ((int32_t)(x)) + + #define PA_UINT32_FROM_LE(x) PA_UINT32_SWAP(x) + #define PA_UINT32_FROM_BE(x) ((uint32_t)(x)) + + #define PA_UINT32_TO_LE(x) PA_UINT32_SWAP(x) + #define PA_UINT32_TO_BE(x) ((uint32_t)(x)) + + #define PA_FLOAT32_TO_LE(x) PA_FLOAT32_SWAP(x) + #define PA_FLOAT32_TO_BE(x) ((float) (x)) +#else + #define PA_INT16_FROM_LE(x) ((int16_t)(x)) + #define PA_INT16_FROM_BE(x) PA_INT16_SWAP(x) + + #define PA_INT16_TO_LE(x) ((int16_t)(x)) + #define PA_INT16_TO_BE(x) PA_INT16_SWAP(x) + + #define PA_UINT16_FROM_LE(x) ((uint16_t)(x)) + #define PA_UINT16_FROM_BE(x) PA_UINT16_SWAP(x) + + #define PA_UINT16_TO_LE(x) ((uint16_t)(x)) + #define PA_UINT16_TO_BE(x) PA_UINT16_SWAP(x) + + #define PA_INT32_FROM_LE(x) ((int32_t)(x)) + #define PA_INT32_FROM_BE(x) PA_INT32_SWAP(x) + + #define PA_INT32_TO_LE(x) ((int32_t)(x)) + #define PA_INT32_TO_BE(x) PA_INT32_SWAP(x) + + #define PA_UINT32_FROM_LE(x) ((uint32_t)(x)) + #define PA_UINT32_FROM_BE(x) PA_UINT32_SWAP(x) + + #define PA_UINT32_TO_LE(x) ((uint32_t)(x)) + #define PA_UINT32_TO_BE(x) PA_UINT32_SWAP(x) + + #define PA_FLOAT32_TO_LE(x) ((float) (x)) + #define PA_FLOAT32_TO_BE(x) PA_FLOAT32_SWAP(x) +#endif + +#endif diff --git a/src/pulsecore/envelope.c b/src/pulsecore/envelope.c new file mode 100644 index 00000000..571f8754 --- /dev/null +++ b/src/pulsecore/envelope.c @@ -0,0 +1,783 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <stdio.h> + +#include <pulse/sample.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/endianmacros.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> +#include <pulsecore/semaphore.h> +#include <pulsecore/g711.h> + +#include "envelope.h" + +/* + Envelope subsystem for applying linear interpolated volume + envelopes on audio data. If multiple enevelopes shall be applied + at the same time, the "minimum" envelope is determined and + applied. + + Envelopes are defined in a statically allocated constant structure + pa_envelope_def. It may be activated using pa_envelope_add(). And + already active envelope may be replaced with pa_envelope_replace() + and removed with pa_envelope_remove().The combined "minimum" + envelope can be applied to audio data with pa_envelope_apply(). + + _apply() on one hand and _add()/_replace()/_remove() on the other + can be executed in seperate threads, in which case no locking is + used. +*/ + +PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree); + +struct pa_envelope_item { + PA_LLIST_FIELDS(pa_envelope_item); + const pa_envelope_def *def; + pa_usec_t start_x; + union { + int32_t i; + float f; + } start_y; + unsigned j; +}; + +enum envelope_state { + STATE_VALID0, + STATE_VALID1, + STATE_READ0, + STATE_READ1, + STATE_WAIT0, + STATE_WAIT1, + STATE_WRITE0, + STATE_WRITE1 +}; + +struct pa_envelope { + pa_sample_spec sample_spec; + + PA_LLIST_HEAD(pa_envelope_item, items); + + pa_atomic_t state; + + size_t x; + + struct { + unsigned n_points, n_allocated, n_current; + + size_t *x; + union { + int32_t *i; + float *f; + } y; + + size_t cached_dx; + int32_t cached_dy_i; + float cached_dy_dx; + pa_bool_t cached_valid; + } points[2]; + + pa_bool_t is_float; + + pa_semaphore *semaphore; +}; + +pa_envelope *pa_envelope_new(const pa_sample_spec *ss) { + pa_envelope *e; + pa_assert(ss); + + e = pa_xnew(pa_envelope, 1); + + e->sample_spec = *ss; + PA_LLIST_HEAD_INIT(pa_envelope_item, e->items); + + e->x = 0; + + e->points[0].n_points = e->points[1].n_points = 0; + e->points[0].n_allocated = e->points[1].n_allocated = 0; + e->points[0].n_current = e->points[1].n_current = 0; + e->points[0].x = e->points[1].x = NULL; + e->points[0].y.i = e->points[1].y.i = NULL; + e->points[0].cached_valid = e->points[1].cached_valid = FALSE; + + pa_atomic_store(&e->state, STATE_VALID0); + + e->is_float = + ss->format == PA_SAMPLE_FLOAT32LE || + ss->format == PA_SAMPLE_FLOAT32BE; + + e->semaphore = pa_semaphore_new(0); + + return e; +} + +void pa_envelope_free(pa_envelope *e) { + pa_assert(e); + + while (e->items) + pa_envelope_remove(e, e->items); + + pa_xfree(e->points[0].x); + pa_xfree(e->points[1].x); + pa_xfree(e->points[0].y.i); + pa_xfree(e->points[1].y.i); + + pa_semaphore_free(e->semaphore); + + pa_xfree(e); +} + +static int32_t linear_interpolate_int(pa_usec_t x1, int32_t _y1, pa_usec_t x2, int32_t y2, pa_usec_t x3) { + return (int32_t) (_y1 + (x3 - x1) * (float) (y2 - _y1) / (float) (x2 - x1)); +} + +static float linear_interpolate_float(pa_usec_t x1, float _y1, pa_usec_t x2, float y2, pa_usec_t x3) { + return _y1 + (x3 - x1) * (y2 - _y1) / (x2 - x1); +} + +static int32_t item_get_int(pa_envelope_item *i, pa_usec_t x) { + pa_assert(i); + + if (x <= i->start_x) + return i->start_y.i; + + x -= i->start_x; + + if (x <= i->def->points_x[0]) + return linear_interpolate_int(0, i->start_y.i, + i->def->points_x[0], i->def->points_y.i[0], x); + + if (x >= i->def->points_x[i->def->n_points-1]) + return i->def->points_y.i[i->def->n_points-1]; + + pa_assert(i->j > 0); + pa_assert(i->def->points_x[i->j-1] <= x); + pa_assert(x < i->def->points_x[i->j]); + + return linear_interpolate_int(i->def->points_x[i->j-1], i->def->points_y.i[i->j-1], + i->def->points_x[i->j], i->def->points_y.i[i->j], x); +} + +static float item_get_float(pa_envelope_item *i, pa_usec_t x) { + pa_assert(i); + + if (x <= i->start_x) + return i->start_y.f; + + x -= i->start_x; + + if (x <= i->def->points_x[0]) + return linear_interpolate_float(0, i->start_y.f, + i->def->points_x[0], i->def->points_y.f[0], x); + + if (x >= i->def->points_x[i->def->n_points-1]) + return i->def->points_y.f[i->def->n_points-1]; + + pa_assert(i->j > 0); + pa_assert(i->def->points_x[i->j-1] <= x); + pa_assert(x < i->def->points_x[i->j]); + + return linear_interpolate_float(i->def->points_x[i->j-1], i->def->points_y.f[i->j-1], + i->def->points_x[i->j], i->def->points_y.f[i->j], x); +} + +static void envelope_begin_write(pa_envelope *e, int *v) { + enum envelope_state new_state, old_state; + pa_bool_t wait_sem; + + pa_assert(e); + pa_assert(v); + + for (;;) { + do { + wait_sem = FALSE; + old_state = pa_atomic_load(&e->state); + + switch (old_state) { + case STATE_VALID0: + *v = 1; + new_state = STATE_WRITE0; + break; + case STATE_VALID1: + *v = 0; + new_state = STATE_WRITE1; + break; + case STATE_READ0: + new_state = STATE_WAIT0; + wait_sem = TRUE; + break; + case STATE_READ1: + new_state = STATE_WAIT1; + wait_sem = TRUE; + break; + default: + pa_assert_not_reached(); + } + } while (!pa_atomic_cmpxchg(&e->state, old_state, new_state)); + + if (!wait_sem) + break; + + pa_semaphore_wait(e->semaphore); + } +} + +static pa_bool_t envelope_commit_write(pa_envelope *e, int v) { + enum envelope_state new_state, old_state; + + pa_assert(e); + + do { + old_state = pa_atomic_load(&e->state); + + switch (old_state) { + case STATE_WRITE0: + pa_assert(v == 1); + new_state = STATE_VALID1; + break; + case STATE_WRITE1: + pa_assert(v == 0); + new_state = STATE_VALID0; + break; + case STATE_VALID0: + case STATE_VALID1: + case STATE_READ0: + case STATE_READ1: + return FALSE; + default: + pa_assert_not_reached(); + } + } while (!pa_atomic_cmpxchg(&e->state, old_state, new_state)); + + return TRUE; +} + +static void envelope_begin_read(pa_envelope *e, int *v) { + enum envelope_state new_state, old_state; + pa_assert(e); + pa_assert(v); + + do { + old_state = pa_atomic_load(&e->state); + + switch (old_state) { + case STATE_VALID0: + case STATE_WRITE0: + *v = 0; + new_state = STATE_READ0; + break; + case STATE_VALID1: + case STATE_WRITE1: + *v = 1; + new_state = STATE_READ1; + break; + default: + pa_assert_not_reached(); + } + } while (!pa_atomic_cmpxchg(&e->state, old_state, new_state)); +} + +static void envelope_commit_read(pa_envelope *e, int v) { + enum envelope_state new_state, old_state; + pa_bool_t post_sem; + + pa_assert(e); + + do { + post_sem = FALSE; + old_state = pa_atomic_load(&e->state); + + switch (old_state) { + case STATE_READ0: + pa_assert(v == 0); + new_state = STATE_VALID0; + break; + case STATE_READ1: + pa_assert(v == 1); + new_state = STATE_VALID1; + break; + case STATE_WAIT0: + pa_assert(v == 0); + new_state = STATE_VALID0; + post_sem = TRUE; + break; + case STATE_WAIT1: + pa_assert(v == 1); + new_state = STATE_VALID1; + post_sem = TRUE; + break; + default: + pa_assert_not_reached(); + } + } while (!pa_atomic_cmpxchg(&e->state, old_state, new_state)); + + if (post_sem) + pa_semaphore_post(e->semaphore); +} + +static void envelope_merge(pa_envelope *e, int v) { + + e->points[v].n_points = 0; + + if (e->items) { + pa_envelope_item *i; + pa_usec_t x = (pa_usec_t) -1; + + for (i = e->items; i; i = i->next) + i->j = 0; + + for (;;) { + pa_bool_t min_is_set; + pa_envelope_item *s = NULL; + + /* Let's find the next spot on the X axis to analyze */ + for (i = e->items; i; i = i->next) { + + for (;;) { + + if (i->j >= i->def->n_points) + break; + + if ((x != (pa_usec_t) -1) && i->start_x + i->def->points_x[i->j] <= x) { + i->j++; + continue; + } + + if (!s || (i->start_x + i->def->points_x[i->j] < s->start_x + s->def->points_x[s->j])) + s = i; + + break; + } + } + + if (!s) + break; + + if (e->points[v].n_points >= e->points[v].n_allocated) { + e->points[v].n_allocated = MAX(e->points[v].n_points*2, PA_ENVELOPE_POINTS_MAX); + + e->points[v].x = pa_xrealloc(e->points[v].x, sizeof(size_t) * e->points[v].n_allocated); + e->points[v].y.i = pa_xrealloc(e->points[v].y.i, sizeof(int32_t) * e->points[v].n_allocated); + } + + x = s->start_x + s->def->points_x[s->j]; + e->points[v].x[e->points[v].n_points] = pa_usec_to_bytes(x, &e->sample_spec); + + min_is_set = FALSE; + + /* Now let's find the lowest value */ + if (e->is_float) { + float min_f; + + for (i = e->items; i; i = i->next) { + float f = item_get_float(i, x); + if (!min_is_set || f < min_f) { + min_f = f; + min_is_set = TRUE; + } + } + + e->points[v].y.f[e->points[v].n_points] = min_f; + } else { + int32_t min_k; + + for (i = e->items; i; i = i->next) { + int32_t k = item_get_int(i, x); + if (!min_is_set || k < min_k) { + min_k = k; + min_is_set = TRUE; + } + } + + e->points[v].y.i[e->points[v].n_points] = min_k; + } + + pa_assert_se(min_is_set); + e->points[v].n_points++; + } + } + + e->points[v].n_current = 0; + e->points[v].cached_valid = FALSE; +} + +pa_envelope_item *pa_envelope_add(pa_envelope *e, const pa_envelope_def *def) { + pa_envelope_item *i; + int v; + + pa_assert(e); + pa_assert(def); + pa_assert(def->n_points > 0); + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + i = pa_xnew(pa_envelope_item, 1); + + i->def = def; + + if (e->is_float) + i->start_y.f = def->points_y.f[0]; + else + i->start_y.i = def->points_y.i[0]; + + PA_LLIST_PREPEND(pa_envelope_item, e->items, i); + + envelope_begin_write(e, &v); + + do { + + i->start_x = pa_bytes_to_usec(e->x, &e->sample_spec); + envelope_merge(e, v); + + } while (!envelope_commit_write(e, v)); + + return i; +} + +pa_envelope_item *pa_envelope_replace(pa_envelope *e, pa_envelope_item *i, const pa_envelope_def *def) { + pa_usec_t x; + int v; + + pa_assert(e); + pa_assert(i); + pa_assert(def->n_points > 0); + + envelope_begin_write(e, &v); + + for (;;) { + float saved_f; + int32_t saved_i; + uint64_t saved_start_x; + const pa_envelope_def *saved_def; + + x = pa_bytes_to_usec(e->x, &e->sample_spec); + + if (e->is_float) { + saved_f = i->start_y.f; + i->start_y.f = item_get_float(i, x); + } else { + saved_i = i->start_y.i; + i->start_y.i = item_get_int(i, x); + } + + saved_start_x = i->start_x; + saved_def = i->def; + + i->start_x = x; + i->def = def; + + envelope_merge(e, v); + + if (envelope_commit_write(e, v)) + break; + + i->start_x = saved_start_x; + i->def = saved_def; + + if (e->is_float) + i->start_y.f = saved_f; + else + i->start_y.i = saved_i; + } + + return i; +} + +void pa_envelope_remove(pa_envelope *e, pa_envelope_item *i) { + int v; + + pa_assert(e); + pa_assert(i); + + PA_LLIST_REMOVE(pa_envelope_item, e->items, i); + + if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0) + pa_xfree(i); + + envelope_begin_write(e, &v); + do { + envelope_merge(e, v); + } while (!envelope_commit_write(e, v)); +} + +static int32_t linear_get_int(pa_envelope *e, int v) { + pa_assert(e); + + /* The repeated division could be replaced by Bresenham, as an + * optimization */ + + if (e->x < e->points[v].x[0]) + return e->points[v].y.i[0]; + + for (;;) { + if (e->points[v].n_current+1 >= e->points[v].n_points) + return e->points[v].y.i[e->points[v].n_points-1]; + + if (e->x < e->points[v].x[e->points[v].n_current+1]) + break; + + e->points[v].n_current++; + e->points[v].cached_valid = FALSE; + } + + if (!e->points[v].cached_valid) { + e->points[v].cached_dx = e->points[v].x[e->points[v].n_current+1] - e->points[v].x[e->points[v].n_current]; + e->points[v].cached_dy_i = e->points[v].y.i[e->points[v].n_current+1] - e->points[v].y.i[e->points[v].n_current]; + e->points[v].cached_valid = TRUE; + } + + return e->points[v].y.i[e->points[v].n_current] + (e->points[v].cached_dy_i * (int32_t) (e->x - e->points[v].x[e->points[v].n_current])) / (int32_t) e->points[v].cached_dx; +} + +static float linear_get_float(pa_envelope *e, int v) { + pa_assert(e); + + if (e->x < e->points[v].x[0]) + return e->points[v].y.f[0]; + + for (;;) { + if (e->points[v].n_current+1 >= e->points[v].n_points) + return e->points[v].y.f[e->points[v].n_points-1]; + + if (e->x < e->points[v].x[e->points[v].n_current+1]) + break; + + e->points[v].n_current++; + e->points[v].cached_valid = FALSE; + } + + if (!e->points[v].cached_valid) { + e->points[v].cached_dy_dx = + (e->points[v].y.f[e->points[v].n_current+1] - e->points[v].y.f[e->points[v].n_current]) / + (e->points[v].x[e->points[v].n_current+1] - e->points[v].x[e->points[v].n_current]); + e->points[v].cached_valid = TRUE; + } + + return e->points[v].y.f[e->points[v].n_current] + (e->x - e->points[v].x[e->points[v].n_current]) * e->points[v].cached_dy_dx; +} + +void pa_envelope_apply(pa_envelope *e, pa_memchunk *chunk) { + int v; + + pa_assert(e); + pa_assert(chunk); + + envelope_begin_read(e, &v); + + if (e->points[v].n_points > 0) { + void *p; + size_t fs, n; + + pa_memchunk_make_writable(chunk, 0); + p = (uint8_t*) pa_memblock_acquire(chunk->memblock) + chunk->index; + fs = pa_frame_size(&e->sample_spec); + n = chunk->length; + + switch (e->sample_spec.format) { + + + + case PA_SAMPLE_U8: { + uint8_t *t; + + for (t = p; n > 0; n -= fs) { + int16_t factor = linear_get_int(e, v); + unsigned c; + e->x += fs; + + for (c = 0; c < e->sample_spec.channels; c++, t++) + *t = (uint8_t) (((factor * ((int16_t) *t - 0x80)) / 0x10000) + 0x80); + } + + break; + } + + case PA_SAMPLE_ULAW: { + uint8_t *t; + + for (t = p; n > 0; n -= fs) { + int16_t factor = linear_get_int(e, v); + unsigned c; + e->x += fs; + + for (c = 0; c < e->sample_spec.channels; c++, t++) { + int16_t k = st_ulaw2linear16(*t); + *t = (uint8_t) st_14linear2ulaw(((factor * k) / 0x10000) >> 2); + } + } + + break; + } + + case PA_SAMPLE_ALAW: { + uint8_t *t; + + for (t = p; n > 0; n -= fs) { + int16_t factor = linear_get_int(e, v); + unsigned c; + e->x += fs; + + for (c = 0; c < e->sample_spec.channels; c++, t++) { + int16_t k = st_alaw2linear16(*t); + *t = (uint8_t) st_13linear2alaw(((factor * k) / 0x10000) >> 3); + } + } + + break; + } + + case PA_SAMPLE_S16NE: { + int16_t *t; + + for (t = p; n > 0; n -= fs) { + int32_t factor = linear_get_int(e, v); + unsigned c; + e->x += fs; + + for (c = 0; c < e->sample_spec.channels; c++, t++) + *t = (factor * *t) / 0x10000; + } + + break; + } + + case PA_SAMPLE_S16RE: { + int16_t *t; + + for (t = p; n > 0; n -= fs) { + int32_t factor = linear_get_int(e, v); + unsigned c; + e->x += fs; + + for (c = 0; c < e->sample_spec.channels; c++, t++) { + int16_t r = (factor * PA_INT16_SWAP(*t)) / 0x10000; + *t = PA_INT16_SWAP(r); + } + } + + break; + } + + case PA_SAMPLE_S32NE: { + int32_t *t; + + for (t = p; n > 0; n -= fs) { + int32_t factor = linear_get_int(e, v); + unsigned c; + e->x += fs; + + for (c = 0; c < e->sample_spec.channels; c++, t++) + *t = (int32_t) (((int64_t) factor * (int64_t) *t) / 0x10000); + } + + break; + } + + case PA_SAMPLE_S32RE: { + int32_t *t; + + for (t = p; n > 0; n -= fs) { + int32_t factor = linear_get_int(e, v); + unsigned c; + e->x += fs; + + for (c = 0; c < e->sample_spec.channels; c++, t++) { + int32_t r = (int32_t) (((int64_t) factor * (int64_t) PA_INT32_SWAP(*t)) / 0x10000); + *t = PA_INT32_SWAP(r); + } + } + + break; + } + + case PA_SAMPLE_FLOAT32NE: { + float *t; + + for (t = p; n > 0; n -= fs) { + float factor = linear_get_float(e, v); + unsigned c; + e->x += fs; + + for (c = 0; c < e->sample_spec.channels; c++, t++) + *t = *t * factor; + } + + break; + } + + case PA_SAMPLE_FLOAT32RE: { + float *t; + + for (t = p; n > 0; n -= fs) { + float factor = linear_get_float(e, v); + unsigned c; + e->x += fs; + + for (c = 0; c < e->sample_spec.channels; c++, t++) { + float r = PA_FLOAT32_SWAP(*t) * factor; + *t = PA_FLOAT32_SWAP(r); + } + } + + break; + } + + case PA_SAMPLE_MAX: + case PA_SAMPLE_INVALID: + pa_assert_not_reached(); + } + + pa_memblock_release(chunk->memblock); + + e->x += chunk->length; + } else { + /* When we have no envelope to apply we reset our origin */ + e->x = 0; + } + + envelope_commit_read(e, v); +} + +void pa_envelope_rewind(pa_envelope *e, size_t n_bytes) { + int v; + + pa_assert(e); + + envelope_begin_read(e, &v); + + if (n_bytes < e->x) + e->x -= n_bytes; + else + e->x = 0; + + e->points[v].n_current = 0; + e->points[v].cached_valid = FALSE; + + envelope_commit_read(e, v); +} diff --git a/src/pulsecore/envelope.h b/src/pulsecore/envelope.h new file mode 100644 index 00000000..23be8f6a --- /dev/null +++ b/src/pulsecore/envelope.h @@ -0,0 +1,55 @@ +#ifndef foopulseenvelopehfoo +#define foopulseenvelopehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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> +#include <pulsecore/memchunk.h> + +#include <pulse/sample.h> + +#define PA_ENVELOPE_POINTS_MAX 4 + +typedef struct pa_envelope pa_envelope; +typedef struct pa_envelope_item pa_envelope_item; + +typedef struct pa_envelope_def { + unsigned n_points; + + pa_usec_t points_x[PA_ENVELOPE_POINTS_MAX]; + struct { + int32_t i[PA_ENVELOPE_POINTS_MAX]; + float f[PA_ENVELOPE_POINTS_MAX]; + } points_y; +} pa_envelope_def; + +pa_envelope *pa_envelope_new(const pa_sample_spec *ss); +void pa_envelope_free(pa_envelope *e); +pa_envelope_item *pa_envelope_add(pa_envelope *e, const pa_envelope_def *def); +pa_envelope_item *pa_envelope_replace(pa_envelope *e, pa_envelope_item *i, const pa_envelope_def *def); +void pa_envelope_remove(pa_envelope *e, pa_envelope_item *i); +void pa_envelope_apply(pa_envelope *e, pa_memchunk *chunk); +void pa_envelope_rewind(pa_envelope *e, size_t n_bytes); + +#endif diff --git a/src/pulsecore/esound.h b/src/pulsecore/esound.h new file mode 100644 index 00000000..ea6a5665 --- /dev/null +++ b/src/pulsecore/esound.h @@ -0,0 +1,211 @@ +#ifndef fooesoundhfoo +#define fooesoundhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + +/* Most of the following is blatantly stolen from esound. */ + + +/* path and name of the default EsounD domain socket */ +#define ESD_UNIX_SOCKET_DIR "/tmp/.esd" +#define ESD_UNIX_SOCKET_NAME "/tmp/.esd/socket" + +/* length of the audio buffer size */ +#define ESD_BUF_SIZE (4 * 1024) +/* maximum size we can write(). Otherwise we might overflow */ +#define ESD_MAX_WRITE_SIZE (21 * 4096) + +/* length of the authorization key, octets */ +#define ESD_KEY_LEN (16) + +/* default port for the EsounD server */ +#define ESD_DEFAULT_PORT (16001) + +/* default sample rate for the EsounD server */ +#define ESD_DEFAULT_RATE (44100) + +/* maximum length of a stream/sample name */ +#define ESD_NAME_MAX (128) + +/* a magic number to identify the relative endianness of a client */ +#define ESD_ENDIAN_KEY ((uint32_t) (('E' << 24) + ('N' << 16) + ('D' << 8) + ('N'))) + +#define ESD_VOLUME_BASE (256) + + +/*************************************/ +/* what can we do to/with the EsounD */ +enum esd_proto { + ESD_PROTO_CONNECT, /* implied on inital client connection */ + + /* pseudo "security" functionality */ + ESD_PROTO_LOCK, /* disable "foreign" client connections */ + ESD_PROTO_UNLOCK, /* enable "foreign" client connections */ + + /* stream functionality: play, record, monitor */ + ESD_PROTO_STREAM_PLAY, /* play all following data as a stream */ + ESD_PROTO_STREAM_REC, /* record data from card as a stream */ + ESD_PROTO_STREAM_MON, /* send mixed buffer output as a stream */ + + /* sample functionality: cache, free, play, loop, EOL, kill */ + ESD_PROTO_SAMPLE_CACHE, /* cache a sample in the server */ + ESD_PROTO_SAMPLE_FREE, /* release a sample in the server */ + ESD_PROTO_SAMPLE_PLAY, /* play a cached sample */ + ESD_PROTO_SAMPLE_LOOP, /* loop a cached sample, til eoloop */ + ESD_PROTO_SAMPLE_STOP, /* stop a looping sample when done */ + ESD_PROTO_SAMPLE_KILL, /* stop the looping sample immed. */ + + /* free and reclaim /dev/dsp functionality */ + ESD_PROTO_STANDBY, /* release /dev/dsp and ignore all data */ + ESD_PROTO_RESUME, /* reclaim /dev/dsp and play sounds again */ + + /* TODO: move these to a more logical place. NOTE: will break the protocol */ + ESD_PROTO_SAMPLE_GETID, /* get the ID for an already-cached sample */ + ESD_PROTO_STREAM_FILT, /* filter mixed buffer output as a stream */ + + /* esd remote management */ + ESD_PROTO_SERVER_INFO, /* get server info (ver, sample rate, format) */ + ESD_PROTO_ALL_INFO, /* get all info (server info, players, samples) */ + ESD_PROTO_SUBSCRIBE, /* track new and removed players and samples */ + ESD_PROTO_UNSUBSCRIBE, /* stop tracking updates */ + + /* esd remote control */ + ESD_PROTO_STREAM_PAN, /* set stream panning */ + ESD_PROTO_SAMPLE_PAN, /* set default sample panning */ + + /* esd status */ + ESD_PROTO_STANDBY_MODE, /* see if server is in standby, autostandby, etc */ + + /* esd latency */ + ESD_PROTO_LATENCY, /* retrieve latency between write()'s and output */ + + ESD_PROTO_MAX /* for bounds checking */ +}; + +/******************/ +/* The EsounD api */ + +/* the properties of a sound buffer are logically or'd */ + +/* bits of stream/sample data */ +#define ESD_MASK_BITS ( 0x000F ) +#define ESD_BITS8 ( 0x0000 ) +#define ESD_BITS16 ( 0x0001 ) + +/* how many interleaved channels of data */ +#define ESD_MASK_CHAN ( 0x00F0 ) +#define ESD_MONO ( 0x0010 ) +#define ESD_STEREO ( 0x0020 ) + +/* whether it's a stream or a sample */ +#define ESD_MASK_MODE ( 0x0F00 ) +#define ESD_STREAM ( 0x0000 ) +#define ESD_SAMPLE ( 0x0100 ) +#define ESD_ADPCM ( 0x0200 ) /* TODO: anyone up for this? =P */ + +/* the function of the stream/sample, and common functions */ +#define ESD_MASK_FUNC ( 0xF000 ) +#define ESD_PLAY ( 0x1000 ) +/* functions for streams only */ +#define ESD_MONITOR ( 0x0000 ) +#define ESD_RECORD ( 0x2000 ) +/* functions for samples only */ +#define ESD_STOP ( 0x0000 ) +#define ESD_LOOP ( 0x2000 ) + +typedef int esd_format_t; +typedef int esd_proto_t; + +/*******************************************************************/ +/* esdmgr.c - functions to implement a "sound manager" for esd */ + +/* structures to retrieve information about streams/samples from the server */ +typedef struct esd_server_info { + + int version; /* server version encoded as an int */ + esd_format_t format; /* magic int with the format info */ + int rate; /* sample rate */ + +} esd_server_info_t; + +typedef struct esd_player_info { + + struct esd_player_info *next; /* point to next entry in list */ + esd_server_info_t *server; /* the server that contains this stream */ + + int source_id; /* either a stream fd or sample id */ + char name[ ESD_NAME_MAX ]; /* name of stream for remote control */ + int rate; /* sample rate */ + int left_vol_scale; /* volume scaling */ + int right_vol_scale; + + esd_format_t format; /* magic int with the format info */ + +} esd_player_info_t; + +typedef struct esd_sample_info { + + struct esd_sample_info *next; /* point to next entry in list */ + esd_server_info_t *server; /* the server that contains this sample */ + + int sample_id; /* either a stream fd or sample id */ + char name[ ESD_NAME_MAX ]; /* name of stream for remote control */ + int rate; /* sample rate */ + int left_vol_scale; /* volume scaling */ + int right_vol_scale; + + esd_format_t format; /* magic int with the format info */ + int length; /* total buffer length */ + +} esd_sample_info_t; + +typedef struct esd_info { + + esd_server_info_t *server; + esd_player_info_t *player_list; + esd_sample_info_t *sample_list; + +} esd_info_t; + +enum esd_standby_mode { + ESM_ERROR, ESM_ON_STANDBY, ESM_ON_AUTOSTANDBY, ESM_RUNNING +}; +typedef int esd_standby_mode_t; + +enum esd_client_state { + ESD_STREAMING_DATA, /* data from here on is streamed data */ + ESD_CACHING_SAMPLE, /* midway through caching a sample */ + ESD_NEEDS_REQDATA, /* more data needed to complere request */ + ESD_NEXT_REQUEST, /* proceed to next request */ + ESD_CLIENT_STATE_MAX /* place holder */ +}; +typedef int esd_client_state_t; + +/* the endian key is transferred in binary, if it's read into int, */ +/* and matches ESD_ENDIAN_KEY (ENDN), then the endianness of the */ +/* server and the client match; if it's SWAP_ENDIAN_KEY, swap data */ +#define ESD_SWAP_ENDIAN_KEY (PA_UINT32_SWAP(ESD_ENDIAN_KEY)) + + +#endif diff --git a/src/pulsecore/fdsem.c b/src/pulsecore/fdsem.c new file mode 100644 index 00000000..59eec18e --- /dev/null +++ b/src/pulsecore/fdsem.c @@ -0,0 +1,280 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 + +#ifdef HAVE_SYS_SYSCALL_H +#include <sys/syscall.h> +#endif + +#include <unistd.h> +#include <errno.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulse/xmalloc.h> + +#ifndef HAVE_PIPE +#include <pulsecore/pipe.h> +#endif + +#ifdef __linux__ + +#if !defined(__NR_eventfd) && defined(__i386__) +#define __NR_eventfd 323 +#endif + +#if !defined(__NR_eventfd) && defined(__x86_64__) +#define __NR_eventfd 284 +#endif + +#if !defined(__NR_eventfd) && defined(__arm__) +#define __NR_eventfd (__NR_SYSCALL_BASE+351) +#endif + +#if !defined(SYS_eventfd) && defined(__NR_eventfd) +#define SYS_eventfd __NR_eventfd +#endif + +#ifdef SYS_eventfd +#define HAVE_EVENTFD + +static inline long eventfd(unsigned count) { + return syscall(SYS_eventfd, count); +} + +#endif +#endif + +#include "fdsem.h" + +struct pa_fdsem { + int fds[2]; +#ifdef HAVE_EVENTFD + int efd; +#endif + pa_atomic_t waiting; + pa_atomic_t signalled; + pa_atomic_t in_pipe; +}; + +pa_fdsem *pa_fdsem_new(void) { + pa_fdsem *f; + + f = pa_xnew(pa_fdsem, 1); + +#ifdef HAVE_EVENTFD + if ((f->efd = eventfd(0)) >= 0) { + pa_make_fd_cloexec(f->efd); + f->fds[0] = f->fds[1] = -1; + + } else +#endif + { + if (pipe(f->fds) < 0) { + pa_xfree(f); + return NULL; + } + + pa_make_fd_cloexec(f->fds[0]); + pa_make_fd_cloexec(f->fds[1]); + } + + pa_atomic_store(&f->waiting, 0); + pa_atomic_store(&f->signalled, 0); + pa_atomic_store(&f->in_pipe, 0); + + return f; +} + +void pa_fdsem_free(pa_fdsem *f) { + pa_assert(f); + +#ifdef HAVE_EVENTFD + if (f->efd >= 0) + pa_close(f->efd); +#endif + pa_close_pipe(f->fds); + + pa_xfree(f); +} + +static void flush(pa_fdsem *f) { + ssize_t r; + pa_assert(f); + + if (pa_atomic_load(&f->in_pipe) <= 0) + return; + + do { + char x[10]; + +#ifdef HAVE_EVENTFD + if (f->efd >= 0) { + uint64_t u; + + if ((r = read(f->efd, &u, sizeof(u))) != sizeof(u)) { + pa_assert(r < 0 && errno == EINTR); + continue; + } + r = (ssize_t) u; + } else +#endif + + if ((r = read(f->fds[0], &x, sizeof(x))) <= 0) { + pa_assert(r < 0 && errno == EINTR); + continue; + } + + } while (pa_atomic_sub(&f->in_pipe, r) > r); +} + +void pa_fdsem_post(pa_fdsem *f) { + pa_assert(f); + + if (pa_atomic_cmpxchg(&f->signalled, 0, 1)) { + + if (pa_atomic_load(&f->waiting)) { + ssize_t r; + char x = 'x'; + + pa_atomic_inc(&f->in_pipe); + + for (;;) { + +#ifdef HAVE_EVENTFD + if (f->efd >= 0) { + uint64_t u = 1; + + if ((r = write(f->efd, &u, sizeof(u))) != sizeof(u)) { + pa_assert(r < 0 && errno == EINTR); + continue; + } + } else +#endif + + if ((r = write(f->fds[1], &x, 1)) != 1) { + pa_assert(r < 0 && errno == EINTR); + continue; + } + + break; + } + } + } +} + +void pa_fdsem_wait(pa_fdsem *f) { + pa_assert(f); + + flush(f); + + if (pa_atomic_cmpxchg(&f->signalled, 1, 0)) + return; + + pa_atomic_inc(&f->waiting); + + while (!pa_atomic_cmpxchg(&f->signalled, 1, 0)) { + char x[10]; + ssize_t r; + +#ifdef HAVE_EVENTFD + if (f->efd >= 0) { + uint64_t u; + + if ((r = read(f->efd, &u, sizeof(u))) != sizeof(u)) { + pa_assert(r < 0 && errno == EINTR); + continue; + } + + r = (ssize_t) u; + } else +#endif + + if ((r = read(f->fds[0], &x, sizeof(x))) <= 0) { + pa_assert(r < 0 && errno == EINTR); + continue; + } + + pa_atomic_sub(&f->in_pipe, r); + } + + pa_assert_se(pa_atomic_dec(&f->waiting) >= 1); +} + +int pa_fdsem_try(pa_fdsem *f) { + pa_assert(f); + + flush(f); + + if (pa_atomic_cmpxchg(&f->signalled, 1, 0)) + return 1; + + return 0; +} + +int pa_fdsem_get(pa_fdsem *f) { + pa_assert(f); + +#ifdef HAVE_EVENTFD + if (f->efd >= 0) + return f->efd; +#endif + + return f->fds[0]; +} + +int pa_fdsem_before_poll(pa_fdsem *f) { + pa_assert(f); + + flush(f); + + if (pa_atomic_cmpxchg(&f->signalled, 1, 0)) + return -1; + + pa_atomic_inc(&f->waiting); + + if (pa_atomic_cmpxchg(&f->signalled, 1, 0)) { + pa_assert_se(pa_atomic_dec(&f->waiting) >= 1); + return -1; + } + return 0; +} + +int pa_fdsem_after_poll(pa_fdsem *f) { + pa_assert(f); + + pa_assert_se(pa_atomic_dec(&f->waiting) >= 1); + + flush(f); + + if (pa_atomic_cmpxchg(&f->signalled, 1, 0)) + return 1; + + return 0; +} diff --git a/src/pulsecore/fdsem.h b/src/pulsecore/fdsem.h new file mode 100644 index 00000000..f38ef205 --- /dev/null +++ b/src/pulsecore/fdsem.h @@ -0,0 +1,49 @@ +#ifndef foopulsefdsemhfoo +#define foopulsefdsemhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <sys/types.h> +#include <pulse/def.h> + +/* A simple, asynchronous semaphore which uses fds for sleeping. In + * the best case all functions are lock-free unless sleeping is + * required. */ + +typedef struct pa_fdsem pa_fdsem; + +pa_fdsem *pa_fdsem_new(void); +void pa_fdsem_free(pa_fdsem *f); + +void pa_fdsem_post(pa_fdsem *f); +void pa_fdsem_wait(pa_fdsem *f); +int pa_fdsem_try(pa_fdsem *f); + +int pa_fdsem_get(pa_fdsem *f); + +int pa_fdsem_before_poll(pa_fdsem *f); +int pa_fdsem_after_poll(pa_fdsem *f); + + +#endif diff --git a/src/pulsecore/ffmpeg/Makefile b/src/pulsecore/ffmpeg/Makefile new file mode 100644 index 00000000..316beb72 --- /dev/null +++ b/src/pulsecore/ffmpeg/Makefile @@ -0,0 +1,13 @@ +# This is a dirty trick just to ease compilation with emacs +# +# This file is not intended to be distributed or anything +# +# So: don't touch it, even better ignore it! + +all: + $(MAKE) -C ../.. + +clean: + $(MAKE) -C ../.. clean + +.PHONY: all clean diff --git a/src/pulsecore/ffmpeg/avcodec.h b/src/pulsecore/ffmpeg/avcodec.h new file mode 100644 index 00000000..696fc986 --- /dev/null +++ b/src/pulsecore/ffmpeg/avcodec.h @@ -0,0 +1,82 @@ +/* + * copyright (c) 2001 Fabrice Bellard + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVCODEC_H +#define AVCODEC_H + +/* Just a heavily bastardized version of the original file from + * ffmpeg, just enough to get resample2.c to compile without + * modification -- Lennart */ + +#if !defined(PACKAGE) && defined(HAVE_CONFIG_H) +#include <config.h> +#endif + +#include <sys/types.h> +#include <inttypes.h> +#include <math.h> +#include <string.h> +#include <stdlib.h> +#include <assert.h> + +#define av_mallocz(l) calloc(1, (l)) +#define av_malloc(l) malloc(l) +#define av_realloc(p,l) realloc((p),(l)) +#define av_free(p) free(p) + +static inline void av_freep(void *k) { + void **p = k; + + if (p) { + free(*p); + *p = NULL; + } +} + +static inline int av_clip(int a, int amin, int amax) +{ + if (a < amin) return amin; + else if (a > amax) return amax; + else return a; +} + +#define av_log(a,b,c) + +#define FFABS(a) ((a) >= 0 ? (a) : (-(a))) +#define FFSIGN(a) ((a) > 0 ? 1 : -1) + +#define FFMAX(a,b) ((a) > (b) ? (a) : (b)) +#define FFMIN(a,b) ((a) > (b) ? (b) : (a)) + +struct AVResampleContext; +struct AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_length, int log2_phase_count, int linear, double cutoff); +int av_resample(struct AVResampleContext *c, short *dst, short *src, int *consumed, int src_size, int dst_size, int update_ctx); +void av_resample_compensate(struct AVResampleContext *c, int sample_delta, int compensation_distance); +void av_resample_close(struct AVResampleContext *c); +void av_build_filter(int16_t *filter, double factor, int tap_count, int phase_count, int scale, int type); + +/* + * crude lrintf for non-C99 systems. + */ +#ifndef HAVE_LRINTF +#define lrintf(x) ((long int)(x)) +#endif + +#endif /* AVCODEC_H */ diff --git a/src/pulsecore/ffmpeg/dsputil.h b/src/pulsecore/ffmpeg/dsputil.h new file mode 100644 index 00000000..8da742d0 --- /dev/null +++ b/src/pulsecore/ffmpeg/dsputil.h @@ -0,0 +1 @@ +/* empty file, just here to allow us to compile an unmodified resampler2.c */ diff --git a/src/pulsecore/ffmpeg/resample2.c b/src/pulsecore/ffmpeg/resample2.c new file mode 100644 index 00000000..da1443d9 --- /dev/null +++ b/src/pulsecore/ffmpeg/resample2.c @@ -0,0 +1,324 @@ +/* + * audio resampling + * Copyright (c) 2004 Michael Niedermayer <michaelni@gmx.at> + * + * This file is part of FFmpeg. + * + * FFmpeg 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. + * + * FFmpeg 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 FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file resample2.c + * audio resampling + * @author Michael Niedermayer <michaelni@gmx.at> + */ + +#include "avcodec.h" +#include "dsputil.h" + +#ifndef CONFIG_RESAMPLE_HP +#define FILTER_SHIFT 15 + +#define FELEM int16_t +#define FELEM2 int32_t +#define FELEML int64_t +#define FELEM_MAX INT16_MAX +#define FELEM_MIN INT16_MIN +#define WINDOW_TYPE 9 +#elif !defined(CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE) +#define FILTER_SHIFT 30 + +#define FELEM int32_t +#define FELEM2 int64_t +#define FELEML int64_t +#define FELEM_MAX INT32_MAX +#define FELEM_MIN INT32_MIN +#define WINDOW_TYPE 12 +#else +#define FILTER_SHIFT 0 + +#define FELEM double +#define FELEM2 double +#define FELEML double +#define WINDOW_TYPE 24 +#endif + + +typedef struct AVResampleContext{ + FELEM *filter_bank; + int filter_length; + int ideal_dst_incr; + int dst_incr; + int index; + int frac; + int src_incr; + int compensation_distance; + int phase_shift; + int phase_mask; + int linear; +}AVResampleContext; + +/** + * 0th order modified bessel function of the first kind. + */ +static double bessel(double x){ + double v=1; + double t=1; + int i; + + x= x*x/4; + for(i=1; i<50; i++){ + t *= x/(i*i); + v += t; + } + return v; +} + +/** + * builds a polyphase filterbank. + * @param factor resampling factor + * @param scale wanted sum of coefficients for each filter + * @param type 0->cubic, 1->blackman nuttall windowed sinc, 2..16->kaiser windowed sinc beta=2..16 + */ +void av_build_filter(FELEM *filter, double factor, int tap_count, int phase_count, int scale, int type){ + int ph, i; + double x, y, w, tab[tap_count]; + const int center= (tap_count-1)/2; + + /* if upsampling, only need to interpolate, no filter */ + if (factor > 1.0) + factor = 1.0; + + for(ph=0;ph<phase_count;ph++) { + double norm = 0; + for(i=0;i<tap_count;i++) { + x = M_PI * ((double)(i - center) - (double)ph / phase_count) * factor; + if (x == 0) y = 1.0; + else y = sin(x) / x; + switch(type){ + case 0:{ + const float d= -0.5; //first order derivative = -0.5 + x = fabs(((double)(i - center) - (double)ph / phase_count) * factor); + if(x<1.0) y= 1 - 3*x*x + 2*x*x*x + d*( -x*x + x*x*x); + else y= d*(-4 + 8*x - 5*x*x + x*x*x); + break;} + case 1: + w = 2.0*x / (factor*tap_count) + M_PI; + y *= 0.3635819 - 0.4891775 * cos(w) + 0.1365995 * cos(2*w) - 0.0106411 * cos(3*w); + break; + default: + w = 2.0*x / (factor*tap_count*M_PI); + y *= bessel(type*sqrt(FFMAX(1-w*w, 0))); + break; + } + + tab[i] = y; + norm += y; + } + + /* normalize so that an uniform color remains the same */ + for(i=0;i<tap_count;i++) { +#ifdef CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE + filter[ph * tap_count + i] = tab[i] / norm; +#else + filter[ph * tap_count + i] = av_clip(lrintf(tab[i] * scale / norm), FELEM_MIN, FELEM_MAX); +#endif + } + } +#if 0 + { +#define LEN 1024 + int j,k; + double sine[LEN + tap_count]; + double filtered[LEN]; + double maxff=-2, minff=2, maxsf=-2, minsf=2; + for(i=0; i<LEN; i++){ + double ss=0, sf=0, ff=0; + for(j=0; j<LEN+tap_count; j++) + sine[j]= cos(i*j*M_PI/LEN); + for(j=0; j<LEN; j++){ + double sum=0; + ph=0; + for(k=0; k<tap_count; k++) + sum += filter[ph * tap_count + k] * sine[k+j]; + filtered[j]= sum / (1<<FILTER_SHIFT); + ss+= sine[j + center] * sine[j + center]; + ff+= filtered[j] * filtered[j]; + sf+= sine[j + center] * filtered[j]; + } + ss= sqrt(2*ss/LEN); + ff= sqrt(2*ff/LEN); + sf= 2*sf/LEN; + maxff= FFMAX(maxff, ff); + minff= FFMIN(minff, ff); + maxsf= FFMAX(maxsf, sf); + minsf= FFMIN(minsf, sf); + if(i%11==0){ + av_log(NULL, AV_LOG_ERROR, "i:%4d ss:%f ff:%13.6e-%13.6e sf:%13.6e-%13.6e\n", i, ss, maxff, minff, maxsf, minsf); + minff=minsf= 2; + maxff=maxsf= -2; + } + } + } +#endif +} + +/** + * Initializes an audio resampler. + * Note, if either rate is not an integer then simply scale both rates up so they are. + */ +AVResampleContext *av_resample_init(int out_rate, int in_rate, int filter_size, int phase_shift, int linear, double cutoff){ + AVResampleContext *c= av_mallocz(sizeof(AVResampleContext)); + double factor= FFMIN(out_rate * cutoff / in_rate, 1.0); + int phase_count= 1<<phase_shift; + + c->phase_shift= phase_shift; + c->phase_mask= phase_count-1; + c->linear= linear; + + c->filter_length= FFMAX((int)ceil(filter_size/factor), 1); + c->filter_bank= av_mallocz(c->filter_length*(phase_count+1)*sizeof(FELEM)); + av_build_filter(c->filter_bank, factor, c->filter_length, phase_count, 1<<FILTER_SHIFT, WINDOW_TYPE); + memcpy(&c->filter_bank[c->filter_length*phase_count+1], c->filter_bank, (c->filter_length-1)*sizeof(FELEM)); + c->filter_bank[c->filter_length*phase_count]= c->filter_bank[c->filter_length - 1]; + + c->src_incr= out_rate; + c->ideal_dst_incr= c->dst_incr= in_rate * phase_count; + c->index= -phase_count*((c->filter_length-1)/2); + + return c; +} + +void av_resample_close(AVResampleContext *c){ + av_freep(&c->filter_bank); + av_freep(&c); +} + +/** + * Compensates samplerate/timestamp drift. The compensation is done by changing + * the resampler parameters, so no audible clicks or similar distortions ocur + * @param compensation_distance distance in output samples over which the compensation should be performed + * @param sample_delta number of output samples which should be output less + * + * example: av_resample_compensate(c, 10, 500) + * here instead of 510 samples only 500 samples would be output + * + * note, due to rounding the actual compensation might be slightly different, + * especially if the compensation_distance is large and the in_rate used during init is small + */ +void av_resample_compensate(AVResampleContext *c, int sample_delta, int compensation_distance){ +// sample_delta += (c->ideal_dst_incr - c->dst_incr)*(int64_t)c->compensation_distance / c->ideal_dst_incr; + c->compensation_distance= compensation_distance; + c->dst_incr = c->ideal_dst_incr - c->ideal_dst_incr * (int64_t)sample_delta / compensation_distance; +} + +/** + * resamples. + * @param src an array of unconsumed samples + * @param consumed the number of samples of src which have been consumed are returned here + * @param src_size the number of unconsumed samples available + * @param dst_size the amount of space in samples available in dst + * @param update_ctx if this is 0 then the context wont be modified, that way several channels can be resampled with the same context + * @return the number of samples written in dst or -1 if an error occured + */ +int av_resample(AVResampleContext *c, short *dst, short *src, int *consumed, int src_size, int dst_size, int update_ctx){ + int dst_index, i; + int index= c->index; + int frac= c->frac; + int dst_incr_frac= c->dst_incr % c->src_incr; + int dst_incr= c->dst_incr / c->src_incr; + int compensation_distance= c->compensation_distance; + + if(compensation_distance == 0 && c->filter_length == 1 && c->phase_shift==0){ + int64_t index2= ((int64_t)index)<<32; + int64_t incr= (1LL<<32) * c->dst_incr / c->src_incr; + dst_size= FFMIN(dst_size, (src_size-1-index) * (int64_t)c->src_incr / c->dst_incr); + + for(dst_index=0; dst_index < dst_size; dst_index++){ + dst[dst_index] = src[index2>>32]; + index2 += incr; + } + frac += dst_index * dst_incr_frac; + index += dst_index * dst_incr; + index += frac / c->src_incr; + frac %= c->src_incr; + }else{ + for(dst_index=0; dst_index < dst_size; dst_index++){ + FELEM *filter= c->filter_bank + c->filter_length*(index & c->phase_mask); + int sample_index= index >> c->phase_shift; + FELEM2 val=0; + + if(sample_index < 0){ + for(i=0; i<c->filter_length; i++) + val += src[FFABS(sample_index + i) % src_size] * filter[i]; + }else if(sample_index + c->filter_length > src_size){ + break; + }else if(c->linear){ + FELEM2 v2=0; + for(i=0; i<c->filter_length; i++){ + val += src[sample_index + i] * (FELEM2)filter[i]; + v2 += src[sample_index + i] * (FELEM2)filter[i + c->filter_length]; + } + val+=(v2-val)*(FELEML)frac / c->src_incr; + }else{ + for(i=0; i<c->filter_length; i++){ + val += src[sample_index + i] * (FELEM2)filter[i]; + } + } + +#ifdef CONFIG_RESAMPLE_AUDIOPHILE_KIDDY_MODE + dst[dst_index] = av_clip_int16(lrintf(val)); +#else + val = (val + (1<<(FILTER_SHIFT-1)))>>FILTER_SHIFT; + dst[dst_index] = (unsigned)(val + 32768) > 65535 ? (val>>31) ^ 32767 : val; +#endif + + frac += dst_incr_frac; + index += dst_incr; + if(frac >= c->src_incr){ + frac -= c->src_incr; + index++; + } + + if(dst_index + 1 == compensation_distance){ + compensation_distance= 0; + dst_incr_frac= c->ideal_dst_incr % c->src_incr; + dst_incr= c->ideal_dst_incr / c->src_incr; + } + } + } + *consumed= FFMAX(index, 0) >> c->phase_shift; + if(index>=0) index &= c->phase_mask; + + if(compensation_distance){ + compensation_distance -= dst_index; + assert(compensation_distance > 0); + } + if(update_ctx){ + c->frac= frac; + c->index= index; + c->dst_incr= dst_incr_frac + c->src_incr*dst_incr; + c->compensation_distance= compensation_distance; + } +#if 0 + if(update_ctx && !c->compensation_distance){ +#undef rand + av_resample_compensate(c, rand() % (8000*2) - 8000, 8000*2); +av_log(NULL, AV_LOG_DEBUG, "%d %d %d\n", c->dst_incr, c->ideal_dst_incr, c->compensation_distance); + } +#endif + + return dst_index; +} diff --git a/src/pulsecore/flist.c b/src/pulsecore/flist.c new file mode 100644 index 00000000..d9740777 --- /dev/null +++ b/src/pulsecore/flist.c @@ -0,0 +1,234 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 <pulse/xmalloc.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "flist.h" + +/* Algorithm is not perfect, in a few corner cases it will fail to pop + * from the flist although it isn't empty, and fail to push into the + * flist, although it isn't full. + * + * We keep a fixed size array of entries, each item is either marked + * UNUSED, USED or BUSY and contains a user data pointer. When pushing + * into the queue we look for an UNUSED cell and mark it BUSY with a + * CAS operation. If successful we use it and mark it USED, otherwise + * we go on and look for the next UNUSED cell. The algorithm for + * popping an item from the queue is practically inverse: look for a + * USED cell and and mark it BUSY with a CAS operation, after reading + * from it mark it UNUSED again. + * + * To accelerate finding of used/unused cells we maintain a read and a + * write index which is used like a ring buffer. After each push we + * increase the write index and after each pop we increase the read + * index. + * + * The indexes are incremented atomically and are never truncated to + * the buffer size. Instead we assume that the buffer size is a power + * of two and that the truncation can thus be done by applying a + * simple AND on read. + * + * To make sure that we do not look for empty cells indefinitely we + * maintain a length value which stores the number of used cells. From + * this value the number of unused cells is easily calculated. Please + * note that the length value is not updated atomically with the read + * and write index and might thus be a few cells off the real + * value. To deal with this we always look for N_EXTRA_SCAN extra + * cells when pushing/popping entries. + * + * It might make sense to replace this implementation with a link list + * stack or queue, which however requires DCAS to be simple. Patches + * welcome. + * + * Please note that this algorithm is home grown.*/ + +#define FLIST_SIZE 128 +#define N_EXTRA_SCAN 2 + +/* For debugging purposes we can define _Y to put and extra thread + * yield between each operation. */ + +#ifdef PROFILE +#define _Y pa_thread_yield() +#else +#define _Y do { } while(0) +#endif + +enum { + STATE_UNUSED, + STATE_USED, + STATE_BUSY +}; + +struct cell { + pa_atomic_t state; + void *data; +}; + +struct pa_flist { + unsigned size; + pa_atomic_t length; + pa_atomic_t read_idx; + pa_atomic_t write_idx; +}; + +#define PA_FLIST_CELLS(x) ((struct cell*) ((uint8_t*) (x) + PA_ALIGN(sizeof(struct pa_flist)))) + +pa_flist *pa_flist_new(unsigned size) { + pa_flist *l; + + if (!size) + size = FLIST_SIZE; + + pa_assert(pa_is_power_of_two(size)); + + l = pa_xmalloc0(PA_ALIGN(sizeof(pa_flist)) + (sizeof(struct cell) * size)); + + l->size = size; + + pa_atomic_store(&l->read_idx, 0); + pa_atomic_store(&l->write_idx, 0); + pa_atomic_store(&l->length, 0); + + return l; +} + +static int reduce(pa_flist *l, int value) { + return value & (unsigned) (l->size - 1); +} + +void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb) { + pa_assert(l); + + if (free_cb) { + struct cell *cells; + int len, idx; + + cells = PA_FLIST_CELLS(l); + + idx = reduce(l, pa_atomic_load(&l->read_idx)); + len = pa_atomic_load(&l->length); + + for (; len > 0; len--) { + + if (pa_atomic_load(&cells[idx].state) == STATE_USED) + free_cb(cells[idx].data); + + idx = reduce(l, idx + 1); + } + } + + pa_xfree(l); +} + +int pa_flist_push(pa_flist*l, void *p) { + int idx, len, n; + struct cell *cells; + + pa_assert(l); + pa_assert(p); + + cells = PA_FLIST_CELLS(l); + + n = len = (int) l->size - pa_atomic_load(&l->length) + N_EXTRA_SCAN; + _Y; + idx = reduce(l, pa_atomic_load(&l->write_idx)); + + for (; n > 0 ; n--) { + _Y; + + if (pa_atomic_cmpxchg(&cells[idx].state, STATE_UNUSED, STATE_BUSY)) { + _Y; + pa_atomic_inc(&l->write_idx); + _Y; + cells[idx].data = p; + _Y; + pa_atomic_store(&cells[idx].state, STATE_USED); + _Y; + pa_atomic_inc(&l->length); + return 0; + } + + _Y; + idx = reduce(l, idx + 1); + } + +#ifdef PROFILE + if (len > N_EXTRA_SCAN) + pa_log_warn("Didn't find free cell after %u iterations.", len); +#endif + + return -1; +} + +void* pa_flist_pop(pa_flist*l) { + int idx, len, n; + struct cell *cells; + + pa_assert(l); + + cells = PA_FLIST_CELLS(l); + + n = len = pa_atomic_load(&l->length) + N_EXTRA_SCAN; + _Y; + idx = reduce(l, pa_atomic_load(&l->read_idx)); + + for (; n > 0 ; n--) { + _Y; + + if (pa_atomic_cmpxchg(&cells[idx].state, STATE_USED, STATE_BUSY)) { + void *p; + _Y; + pa_atomic_inc(&l->read_idx); + _Y; + p = cells[idx].data; + _Y; + pa_atomic_store(&cells[idx].state, STATE_UNUSED); + _Y; + + pa_atomic_dec(&l->length); + return p; + } + + _Y; + idx = reduce(l, idx+1); + } + +#ifdef PROFILE + if (len > N_EXTRA_SCAN) + pa_log_warn("Didn't find used cell after %u iterations.", len); +#endif + + return NULL; +} diff --git a/src/pulsecore/flist.h b/src/pulsecore/flist.h new file mode 100644 index 00000000..daf0fec4 --- /dev/null +++ b/src/pulsecore/flist.h @@ -0,0 +1,68 @@ +#ifndef foopulseflisthfoo +#define foopulseflisthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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/def.h> + +#include <pulsecore/once.h> +#include <pulsecore/gccmacro.h> + +/* A multiple-reader multipler-write lock-free free list implementation */ + +typedef struct pa_flist pa_flist; + +/* Size is required to be a power of two, or 0 for the default size */ +pa_flist * pa_flist_new(unsigned size); +void pa_flist_free(pa_flist *l, pa_free_cb_t free_cb); + +/* Please note that this routine might fail! */ +int pa_flist_push(pa_flist*l, void *p); +void* pa_flist_pop(pa_flist*l); + +/* Please not that the destructor stuff is not really necesary, we do + * this just to make valgrind output more useful. */ + +#define PA_STATIC_FLIST_DECLARE(name, size, free_cb) \ + static struct { \ + pa_flist *flist; \ + pa_once once; \ + } name##_flist = { NULL, PA_ONCE_INIT }; \ + static void name##_flist_init(void) { \ + name##_flist.flist = pa_flist_new(size); \ + } \ + static inline pa_flist* name##_flist_get(void) { \ + pa_run_once(&name##_flist.once, name##_flist_init); \ + return name##_flist.flist; \ + } \ + static void name##_flist_destructor(void) PA_GCC_DESTRUCTOR; \ + static void name##_flist_destructor(void) { \ + if (name##_flist.flist) \ + pa_flist_free(name##_flist.flist, (free_cb)); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_STATIC_FLIST_GET(name) (name##_flist_get()) + +#endif diff --git a/src/pulsecore/g711.c b/src/pulsecore/g711.c new file mode 100644 index 00000000..aa2d703a --- /dev/null +++ b/src/pulsecore/g711.c @@ -0,0 +1,2531 @@ +/* + * This source code is a product of Sun Microsystems, Inc. and is provided + * for unrestricted use. Users may copy or modify this source code without + * charge. + * + * SUN SOURCE CODE IS PROVIDED AS IS WITH NO WARRANTIES OF ANY KIND INCLUDING + * THE WARRANTIES OF DESIGN, MERCHANTIBILITY AND FITNESS FOR A PARTICULAR + * PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE OR TRADE PRACTICE. + * + * Sun source code is provided with no support and without any obligation on + * the part of Sun Microsystems, Inc. to assist in its use, correction, + * modification or enhancement. + * + * SUN MICROSYSTEMS, INC. SHALL HAVE NO LIABILITY WITH RESPECT TO THE + * INFRINGEMENT OF COPYRIGHTS, TRADE SECRETS OR ANY PATENTS BY THIS SOFTWARE + * OR ANY PART THEREOF. + * + * In no event will Sun Microsystems, Inc. be liable for any lost revenue + * or profits or other special, indirect and consequential damages, even if + * Sun has been advised of the possibility of such damages. + * + * Sun Microsystems, Inc. + * 2550 Garcia Avenue + * Mountain View, California 94043 + */ + +/* + * g711.c + * + * u-law, A-law and linear PCM conversions. + */ + +/* + * December 30, 1994: + * Functions linear2alaw, linear2ulaw have been updated to correctly + * convert unquantized 16 bit values. + * Tables for direct u- to A-law and A- to u-law conversions have been + * corrected. + * Borge Lindberg, Center for PersonKommunikation, Aalborg University. + * bli@cpk.auc.dk + * + */ + +#include "g711.h" + +#define SIGN_BIT (0x80) /* Sign bit for a A-law byte. */ +#define QUANT_MASK (0xf) /* Quantization field mask. */ +#define NSEGS (8) /* Number of A-law segments. */ +#define SEG_SHIFT (4) /* Left shift for segment number. */ +#define SEG_MASK (0x70) /* Segment field mask. */ + +#if !defined(FAST_ALAW_CONVERSION) || !defined(FAST_ULAW_CONVERSION) +static int16_t seg_aend[8] = {0x1F, 0x3F, 0x7F, 0xFF, + 0x1FF, 0x3FF, 0x7FF, 0xFFF}; +static int16_t seg_uend[8] = {0x3F, 0x7F, 0xFF, 0x1FF, + 0x3FF, 0x7FF, 0xFFF, 0x1FFF}; + +static int16_t search( + int16_t val, + int16_t *table, + int size) +{ + int i; + + for (i = 0; i < size; i++) { + if (val <= *table++) + return (i); + } + return (size); +} +#endif /* !FAST_*_CONVERSION */ + +#ifndef FAST_ALAW_CONVERSION +/* + * linear2alaw() accepts an 13-bit signed integer and encodes it as A-law data + * stored in a unsigned char. This function should only be called with + * the data shifted such that it only contains information in the lower + * 13-bits. + * + * Linear Input Code Compressed Code + * ------------------------ --------------- + * 0000000wxyza 000wxyz + * 0000001wxyza 001wxyz + * 000001wxyzab 010wxyz + * 00001wxyzabc 011wxyz + * 0001wxyzabcd 100wxyz + * 001wxyzabcde 101wxyz + * 01wxyzabcdef 110wxyz + * 1wxyzabcdefg 111wxyz + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +unsigned char st_13linear2alaw( + int16_t pcm_val) /* 2's complement (13-bit range) */ +{ + int16_t mask; + short seg; + unsigned char aval; + + /* Have calling software do it since its already doing a shift + * from 32-bits down to 16-bits. + */ + /* pcm_val = pcm_val >> 3; */ + + /* A-law using even bit inversion */ + if (pcm_val >= 0) { + mask = 0xD5; /* sign (7th) bit = 1 */ + } else { + mask = 0x55; /* sign bit = 0 */ + pcm_val = -pcm_val - 1; + } + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_aend, 8); + + /* Combine the sign, segment, and quantization bits. */ + + if (seg >= 8) /* out of range, return maximum value. */ + return (unsigned char) (0x7F ^ mask); + else { + aval = (unsigned char) seg << SEG_SHIFT; + if (seg < 2) + aval |= (pcm_val >> 1) & QUANT_MASK; + else + aval |= (pcm_val >> seg) & QUANT_MASK; + return (aval ^ mask); + } +} + +/* + * alaw2linear() - Convert an A-law value to 16-bit signed linear PCM + * + */ +int16_t st_alaw2linear16( + unsigned char a_val) +{ + int16_t t; + int16_t seg; + + a_val ^= 0x55; + + t = (a_val & QUANT_MASK) << 4; + seg = ((unsigned)a_val & SEG_MASK) >> SEG_SHIFT; + switch (seg) { + case 0: + t += 8; + break; + case 1: + t += 0x108; + break; + default: + t += 0x108; + t <<= seg - 1; + } + return ((a_val & SIGN_BIT) ? t : -t); +} +#endif /* !FAST_ALAW_CONVERSION */ + +#define BIAS (0x84) /* Bias for linear code. */ +#define CLIP 8159 + +#ifndef FAST_ULAW_CONVERSION +/* + * linear2ulaw() accepts a 14-bit signed integer and encodes it as u-law data + * stored in a unsigned char. This function should only be called with + * the data shifted such that it only contains information in the lower + * 14-bits. + * + * In order to simplify the encoding process, the original linear magnitude + * is biased by adding 33 which shifts the encoding range from (0 - 8158) to + * (33 - 8191). The result can be seen in the following encoding table: + * + * Biased Linear Input Code Compressed Code + * ------------------------ --------------- + * 00000001wxyza 000wxyz + * 0000001wxyzab 001wxyz + * 000001wxyzabc 010wxyz + * 00001wxyzabcd 011wxyz + * 0001wxyzabcde 100wxyz + * 001wxyzabcdef 101wxyz + * 01wxyzabcdefg 110wxyz + * 1wxyzabcdefgh 111wxyz + * + * Each biased linear code has a leading 1 which identifies the segment + * number. The value of the segment number is equal to 7 minus the number + * of leading 0's. The quantization interval is directly available as the + * four bits wxyz. * The trailing bits (a - h) are ignored. + * + * Ordinarily the complement of the resulting code word is used for + * transmission, and so the code word is complemented before it is returned. + * + * For further information see John C. Bellamy's Digital Telephony, 1982, + * John Wiley & Sons, pps 98-111 and 472-476. + */ +unsigned char st_14linear2ulaw( + int16_t pcm_val) /* 2's complement (14-bit range) */ +{ + int16_t mask; + int16_t seg; + unsigned char uval; + + /* Have calling software do it since its already doing a shift + * from 32-bits down to 16-bits. + */ + /* pcm_val = pcm_val >> 2; */ + + /* u-law inverts all bits */ + /* Get the sign and the magnitude of the value. */ + if (pcm_val < 0) { + pcm_val = -pcm_val; + mask = 0x7F; + } else { + mask = 0xFF; + } + if ( pcm_val > CLIP ) pcm_val = CLIP; /* clip the magnitude */ + pcm_val += (BIAS >> 2); + + /* Convert the scaled magnitude to segment number. */ + seg = search(pcm_val, seg_uend, 8); + + /* + * Combine the sign, segment, quantization bits; + * and complement the code word. + */ + if (seg >= 8) /* out of range, return maximum value. */ + return (unsigned char) (0x7F ^ mask); + else { + uval = (unsigned char) (seg << 4) | ((pcm_val >> (seg + 1)) & 0xF); + return (uval ^ mask); + } + +} + +/* + * ulaw2linear() - Convert a u-law value to 16-bit linear PCM + * + * First, a biased linear code is derived from the code word. An unbiased + * output can then be obtained by subtracting 33 from the biased code. + * + * Note that this function expects to be passed the complement of the + * original code word. This is in keeping with ISDN conventions. + */ +int16_t st_ulaw2linear16( + unsigned char u_val) +{ + int16_t t; + + /* Complement to obtain normal u-law value. */ + u_val = ~u_val; + + /* + * Extract and bias the quantization bits. Then + * shift up by the segment number and subtract out the bias. + */ + t = ((u_val & QUANT_MASK) << 3) + BIAS; + t <<= ((unsigned)u_val & SEG_MASK) >> SEG_SHIFT; + + return ((u_val & SIGN_BIT) ? (BIAS - t) : (t - BIAS)); +} +#endif /* !FAST_ULAW_CONVERSION */ + +#ifdef FAST_ALAW_CONVERSION + +int16_t _st_alaw2linear16[256] = { + -5504, -5248, -6016, -5760, -4480, -4224, -4992, + -4736, -7552, -7296, -8064, -7808, -6528, -6272, + -7040, -6784, -2752, -2624, -3008, -2880, -2240, + -2112, -2496, -2368, -3776, -3648, -4032, -3904, + -3264, -3136, -3520, -3392, -22016, -20992, -24064, + -23040, -17920, -16896, -19968, -18944, -30208, -29184, + -32256, -31232, -26112, -25088, -28160, -27136, -11008, + -10496, -12032, -11520, -8960, -8448, -9984, -9472, + -15104, -14592, -16128, -15616, -13056, -12544, -14080, + -13568, -344, -328, -376, -360, -280, -264, + -312, -296, -472, -456, -504, -488, -408, + -392, -440, -424, -88, -72, -120, -104, + -24, -8, -56, -40, -216, -200, -248, + -232, -152, -136, -184, -168, -1376, -1312, + -1504, -1440, -1120, -1056, -1248, -1184, -1888, + -1824, -2016, -1952, -1632, -1568, -1760, -1696, + -688, -656, -752, -720, -560, -528, -624, + -592, -944, -912, -1008, -976, -816, -784, + -880, -848, 5504, 5248, 6016, 5760, 4480, + 4224, 4992, 4736, 7552, 7296, 8064, 7808, + 6528, 6272, 7040, 6784, 2752, 2624, 3008, + 2880, 2240, 2112, 2496, 2368, 3776, 3648, + 4032, 3904, 3264, 3136, 3520, 3392, 22016, + 20992, 24064, 23040, 17920, 16896, 19968, 18944, + 30208, 29184, 32256, 31232, 26112, 25088, 28160, + 27136, 11008, 10496, 12032, 11520, 8960, 8448, + 9984, 9472, 15104, 14592, 16128, 15616, 13056, + 12544, 14080, 13568, 344, 328, 376, 360, + 280, 264, 312, 296, 472, 456, 504, + 488, 408, 392, 440, 424, 88, 72, + 120, 104, 24, 8, 56, 40, 216, + 200, 248, 232, 152, 136, 184, 168, + 1376, 1312, 1504, 1440, 1120, 1056, 1248, + 1184, 1888, 1824, 2016, 1952, 1632, 1568, + 1760, 1696, 688, 656, 752, 720, 560, + 528, 624, 592, 944, 912, 1008, 976, + 816, 784, 880, 848 +}; + +uint8_t _st_13linear2alaw[0x2000] = { + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, + 0x6b, 0x6b, 0x6b, 0x6b, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, 0x68, + 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x6e, 0x6e, 0x6e, 0x6e, + 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, + 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, + 0x6d, 0x6d, 0x6d, 0x6d, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x60, 0x60, 0x60, 0x60, + 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, + 0x67, 0x67, 0x67, 0x67, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, 0x64, + 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x7a, 0x7a, 0x7a, 0x7a, + 0x7b, 0x7b, 0x7b, 0x7b, 0x78, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x79, + 0x7e, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f, 0x7f, 0x7f, 0x7c, 0x7c, 0x7c, 0x7c, + 0x7d, 0x7d, 0x7d, 0x7d, 0x72, 0x72, 0x72, 0x72, 0x73, 0x73, 0x73, 0x73, + 0x70, 0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x71, 0x76, 0x76, 0x76, 0x76, + 0x77, 0x77, 0x77, 0x77, 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x75, + 0x4a, 0x4a, 0x4b, 0x4b, 0x48, 0x48, 0x49, 0x49, 0x4e, 0x4e, 0x4f, 0x4f, + 0x4c, 0x4c, 0x4d, 0x4d, 0x42, 0x42, 0x43, 0x43, 0x40, 0x40, 0x41, 0x41, + 0x46, 0x46, 0x47, 0x47, 0x44, 0x44, 0x45, 0x45, 0x5a, 0x5a, 0x5b, 0x5b, + 0x58, 0x58, 0x59, 0x59, 0x5e, 0x5e, 0x5f, 0x5f, 0x5c, 0x5c, 0x5d, 0x5d, + 0x52, 0x52, 0x53, 0x53, 0x50, 0x50, 0x51, 0x51, 0x56, 0x56, 0x57, 0x57, + 0x54, 0x54, 0x55, 0x55, 0xd5, 0xd5, 0xd4, 0xd4, 0xd7, 0xd7, 0xd6, 0xd6, + 0xd1, 0xd1, 0xd0, 0xd0, 0xd3, 0xd3, 0xd2, 0xd2, 0xdd, 0xdd, 0xdc, 0xdc, + 0xdf, 0xdf, 0xde, 0xde, 0xd9, 0xd9, 0xd8, 0xd8, 0xdb, 0xdb, 0xda, 0xda, + 0xc5, 0xc5, 0xc4, 0xc4, 0xc7, 0xc7, 0xc6, 0xc6, 0xc1, 0xc1, 0xc0, 0xc0, + 0xc3, 0xc3, 0xc2, 0xc2, 0xcd, 0xcd, 0xcc, 0xcc, 0xcf, 0xcf, 0xce, 0xce, + 0xc9, 0xc9, 0xc8, 0xc8, 0xcb, 0xcb, 0xca, 0xca, 0xf5, 0xf5, 0xf5, 0xf5, + 0xf4, 0xf4, 0xf4, 0xf4, 0xf7, 0xf7, 0xf7, 0xf7, 0xf6, 0xf6, 0xf6, 0xf6, + 0xf1, 0xf1, 0xf1, 0xf1, 0xf0, 0xf0, 0xf0, 0xf0, 0xf3, 0xf3, 0xf3, 0xf3, + 0xf2, 0xf2, 0xf2, 0xf2, 0xfd, 0xfd, 0xfd, 0xfd, 0xfc, 0xfc, 0xfc, 0xfc, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xf9, 0xf9, 0xf9, 0xf9, + 0xf8, 0xf8, 0xf8, 0xf8, 0xfb, 0xfb, 0xfb, 0xfb, 0xfa, 0xfa, 0xfa, 0xfa, + 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, 0xe4, 0xe4, 0xe4, + 0xe4, 0xe4, 0xe4, 0xe4, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, + 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe1, 0xe1, 0xe1, 0xe1, + 0xe1, 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, + 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, + 0xe2, 0xe2, 0xe2, 0xe2, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, + 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xef, 0xef, 0xef, 0xef, + 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, + 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, + 0xe8, 0xe8, 0xe8, 0xe8, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, + 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0xea, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa +}; + +#endif /* FAST_ALAW_CONVERSION */ + +#ifdef FAST_ULAW_CONVERSION + +int16_t _st_ulaw2linear16[256] = { + -32124, -31100, -30076, -29052, -28028, -27004, -25980, + -24956, -23932, -22908, -21884, -20860, -19836, -18812, + -17788, -16764, -15996, -15484, -14972, -14460, -13948, + -13436, -12924, -12412, -11900, -11388, -10876, -10364, + -9852, -9340, -8828, -8316, -7932, -7676, -7420, + -7164, -6908, -6652, -6396, -6140, -5884, -5628, + -5372, -5116, -4860, -4604, -4348, -4092, -3900, + -3772, -3644, -3516, -3388, -3260, -3132, -3004, + -2876, -2748, -2620, -2492, -2364, -2236, -2108, + -1980, -1884, -1820, -1756, -1692, -1628, -1564, + -1500, -1436, -1372, -1308, -1244, -1180, -1116, + -1052, -988, -924, -876, -844, -812, -780, + -748, -716, -684, -652, -620, -588, -556, + -524, -492, -460, -428, -396, -372, -356, + -340, -324, -308, -292, -276, -260, -244, + -228, -212, -196, -180, -164, -148, -132, + -120, -112, -104, -96, -88, -80, -72, + -64, -56, -48, -40, -32, -24, -16, + -8, 0, 32124, 31100, 30076, 29052, 28028, + 27004, 25980, 24956, 23932, 22908, 21884, 20860, + 19836, 18812, 17788, 16764, 15996, 15484, 14972, + 14460, 13948, 13436, 12924, 12412, 11900, 11388, + 10876, 10364, 9852, 9340, 8828, 8316, 7932, + 7676, 7420, 7164, 6908, 6652, 6396, 6140, + 5884, 5628, 5372, 5116, 4860, 4604, 4348, + 4092, 3900, 3772, 3644, 3516, 3388, 3260, + 3132, 3004, 2876, 2748, 2620, 2492, 2364, + 2236, 2108, 1980, 1884, 1820, 1756, 1692, + 1628, 1564, 1500, 1436, 1372, 1308, 1244, + 1180, 1116, 1052, 988, 924, 876, 844, + 812, 780, 748, 716, 684, 652, 620, + 588, 556, 524, 492, 460, 428, 396, + 372, 356, 340, 324, 308, 292, 276, + 260, 244, 228, 212, 196, 180, 164, + 148, 132, 120, 112, 104, 96, 88, + 80, 72, 64, 56, 48, 40, 32, + 24, 16, 8, 0 +}; + +uint8_t _st_14linear2ulaw[0x4000] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, + 0x03, 0x03, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, + 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, + 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, + 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, + 0x09, 0x09, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, + 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, + 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0b, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, + 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, + 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, + 0x0f, 0x0f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, + 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, 0x12, + 0x12, 0x12, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, + 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, + 0x15, 0x15, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, + 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x16, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, + 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, + 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x19, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, 0x1b, + 0x1b, 0x1b, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, + 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, + 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, + 0x1e, 0x1e, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, + 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, + 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, 0x23, + 0x23, 0x23, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, + 0x24, 0x24, 0x24, 0x24, 0x24, 0x24, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, + 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x25, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, + 0x26, 0x26, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, + 0x27, 0x27, 0x27, 0x27, 0x27, 0x27, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, + 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, 0x29, + 0x29, 0x29, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, + 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2a, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, + 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2b, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, 0x2c, + 0x2c, 0x2c, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, + 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2e, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, + 0x2f, 0x2f, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, + 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, + 0x32, 0x32, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, + 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, 0x35, + 0x35, 0x35, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, + 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, + 0x37, 0x37, 0x37, 0x37, 0x37, 0x37, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, + 0x38, 0x38, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, + 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, + 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, 0x3b, + 0x3b, 0x3b, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, + 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3c, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, + 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3d, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, 0x3e, + 0x3e, 0x3e, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, + 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x40, 0x40, + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x40, 0x40, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, + 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, + 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x43, 0x43, + 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, 0x43, + 0x43, 0x43, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, + 0x44, 0x44, 0x44, 0x44, 0x44, 0x44, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, + 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x45, 0x46, 0x46, + 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46, + 0x46, 0x46, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, + 0x47, 0x47, 0x47, 0x47, 0x47, 0x47, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, + 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x49, 0x49, + 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, + 0x49, 0x49, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, + 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, + 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c, + 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, + 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, + 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4d, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, + 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4e, 0x4f, 0x4f, + 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, 0x4f, + 0x4f, 0x4f, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x50, 0x51, 0x51, + 0x51, 0x51, 0x51, 0x51, 0x51, 0x51, 0x52, 0x52, 0x52, 0x52, 0x52, 0x52, + 0x52, 0x52, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x54, 0x54, + 0x54, 0x54, 0x54, 0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, + 0x55, 0x55, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x56, 0x57, 0x57, + 0x57, 0x57, 0x57, 0x57, 0x57, 0x57, 0x58, 0x58, 0x58, 0x58, 0x58, 0x58, + 0x58, 0x58, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x59, 0x5a, 0x5a, + 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, 0x5b, + 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, + 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, 0x5e, + 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x60, 0x60, + 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x62, 0x63, 0x63, + 0x63, 0x63, 0x64, 0x64, 0x64, 0x64, 0x65, 0x65, 0x65, 0x65, 0x66, 0x66, + 0x66, 0x66, 0x67, 0x67, 0x67, 0x67, 0x68, 0x68, 0x68, 0x68, 0x69, 0x69, + 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6b, 0x6b, 0x6c, 0x6c, + 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, + 0x6f, 0x6f, 0x70, 0x70, 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x74, 0x74, + 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, + 0x7b, 0x7b, 0x7c, 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0xff, 0xfe, 0xfe, 0xfd, + 0xfd, 0xfc, 0xfc, 0xfb, 0xfb, 0xfa, 0xfa, 0xf9, 0xf9, 0xf8, 0xf8, 0xf7, + 0xf7, 0xf6, 0xf6, 0xf5, 0xf5, 0xf4, 0xf4, 0xf3, 0xf3, 0xf2, 0xf2, 0xf1, + 0xf1, 0xf0, 0xf0, 0xef, 0xef, 0xef, 0xef, 0xee, 0xee, 0xee, 0xee, 0xed, + 0xed, 0xed, 0xed, 0xec, 0xec, 0xec, 0xec, 0xeb, 0xeb, 0xeb, 0xeb, 0xea, + 0xea, 0xea, 0xea, 0xe9, 0xe9, 0xe9, 0xe9, 0xe8, 0xe8, 0xe8, 0xe8, 0xe7, + 0xe7, 0xe7, 0xe7, 0xe6, 0xe6, 0xe6, 0xe6, 0xe5, 0xe5, 0xe5, 0xe5, 0xe4, + 0xe4, 0xe4, 0xe4, 0xe3, 0xe3, 0xe3, 0xe3, 0xe2, 0xe2, 0xe2, 0xe2, 0xe1, + 0xe1, 0xe1, 0xe1, 0xe0, 0xe0, 0xe0, 0xe0, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, + 0xdf, 0xdf, 0xdf, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdd, + 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc, + 0xdc, 0xdc, 0xdc, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xda, + 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, + 0xd9, 0xd9, 0xd9, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd7, + 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, + 0xd6, 0xd6, 0xd6, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd4, + 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, + 0xd3, 0xd3, 0xd3, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd1, + 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, + 0xd0, 0xd0, 0xd0, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, + 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xce, 0xce, 0xce, 0xce, 0xce, + 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, + 0xcd, 0xcd, 0xcd, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, + 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, + 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xca, + 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, 0xca, + 0xca, 0xca, 0xca, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, + 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, + 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc7, + 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, + 0xc7, 0xc7, 0xc7, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, + 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, + 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc4, + 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, + 0xc4, 0xc4, 0xc4, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, + 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, + 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, + 0xbf, 0xbf, 0xbf, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, + 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, + 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, + 0xbc, 0xbc, 0xbc, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, + 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, + 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xba, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, + 0xb9, 0xb9, 0xb9, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, + 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, + 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, + 0xb6, 0xb6, 0xb6, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, + 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb5, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, + 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, + 0xb3, 0xb3, 0xb3, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, + 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, + 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, + 0xb0, 0xb0, 0xb0, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, + 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, + 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xae, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, + 0xad, 0xad, 0xad, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, + 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xac, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, + 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, + 0xaa, 0xaa, 0xaa, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, + 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, + 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, 0xa7, + 0xa7, 0xa7, 0xa7, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, + 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, + 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4, + 0xa4, 0xa4, 0xa4, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, + 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, + 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, + 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, 0x9f, + 0x9f, 0x9f, 0x9f, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, + 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, + 0x9c, 0x9c, 0x9c, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, + 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, + 0x99, 0x99, 0x99, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, + 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, + 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x97, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, 0x96, + 0x96, 0x96, 0x96, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, + 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, 0x93, + 0x93, 0x93, 0x93, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, + 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x92, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, + 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x91, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, + 0x90, 0x90, 0x90, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, + 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8f, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, + 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, 0x8d, + 0x8d, 0x8d, 0x8d, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, + 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8c, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, + 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, 0x8a, + 0x8a, 0x8a, 0x8a, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, + 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x89, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, + 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x88, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, 0x87, + 0x87, 0x87, 0x87, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, + 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x86, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, + 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, + 0x84, 0x84, 0x84, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, + 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x83, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, + 0x81, 0x81, 0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0x80 +}; + +#endif /* FAST_ULAW_CONVERSION */ + +/* The following code was used to generate the lookup tables */ +#if 0 +int main() +{ + int x, y, find2a = 0; + + y = 0; + printf("int16_t _st_alaw2linear16[256] = {\n "); + for (x = 0; x < 256; x++) + { + printf("%8d,", st_alaw2linear16(x)); + y++; + if (y == 7) + { + y = 0; + printf("\n "); + } + } + + printf("\n};\n\nuint8_t _st_13linear2alaw[0x2000] = {\n "); + y = 0; + for (x = 0; x < 0x2000; x++) + { + printf(" 0x%02x,", st_13linear2alaw((-0x1000)+x)); + y++; + if (y == 12) + { + y = 0; + printf("\n "); + } + } + + printf("\n};\n\nint16_t _st_ulaw2linear16[256] = {\n "); + y = 0; + for (x = 0; x < 256; x++) + { + printf("%8d,", st_ulaw2linear16(x)); + y++; + if (y == 7) + { + y = 0; + printf("\n "); + } + } + + printf("\n};\n\nuint8_t _st_14linear2ulaw[0x4000] = {\n "); + y = 0; + for (x = 0; x < 0x4000; x++) + { + printf(" 0x%02x,", st_14linear2ulaw((-0x2000)+x)); + y++; + if (y == 12) + { + y = 0; + printf("\n "); + } + } + printf("\n};\n"); + +} +#endif + +/* The following is not used by SoX but kept for reference */ +#if 0 +/* copy from CCITT G.711 specifications */ +unsigned char _u2a[128] = { /* u- to A-law conversions */ + 1, 1, 2, 2, 3, 3, 4, 4, + 5, 5, 6, 6, 7, 7, 8, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 27, 29, 31, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, + 46, 48, 49, 50, 51, 52, 53, 54, + 55, 56, 57, 58, 59, 60, 61, 62, + 64, 65, 66, 67, 68, 69, 70, 71, + 72, 73, 74, 75, 76, 77, 78, 79, +/* corrected: + 81, 82, 83, 84, 85, 86, 87, 88, + should be: */ + 80, 82, 83, 84, 85, 86, 87, 88, + 89, 90, 91, 92, 93, 94, 95, 96, + 97, 98, 99, 100, 101, 102, 103, 104, + 105, 106, 107, 108, 109, 110, 111, 112, + 113, 114, 115, 116, 117, 118, 119, 120, + 121, 122, 123, 124, 125, 126, 127, 128}; + +unsigned char _a2u[128] = { /* A- to u-law conversions */ + 1, 3, 5, 7, 9, 11, 13, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 32, 33, 33, 34, 34, 35, 35, + 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 48, 49, 49, + 50, 51, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 62, 63, 64, 64, + 65, 66, 67, 68, 69, 70, 71, 72, +/* corrected: + 73, 74, 75, 76, 77, 78, 79, 79, + should be: */ + 73, 74, 75, 76, 77, 78, 79, 80, + + 80, 81, 82, 83, 84, 85, 86, 87, + 88, 89, 90, 91, 92, 93, 94, 95, + 96, 97, 98, 99, 100, 101, 102, 103, + 104, 105, 106, 107, 108, 109, 110, 111, + 112, 113, 114, 115, 116, 117, 118, 119, + 120, 121, 122, 123, 124, 125, 126, 127}; + +/* A-law to u-law conversion */ +unsigned char st_alaw2ulaw( + unsigned char aval) +{ + aval &= 0xff; + return (unsigned char) ((aval & 0x80) ? (0xFF ^ _a2u[aval ^ 0xD5]) : + (0x7F ^ _a2u[aval ^ 0x55])); +} + +/* u-law to A-law conversion */ +unsigned char st_ulaw2alaw( + unsigned char uval) +{ + uval &= 0xff; + return (unsigned char) ((uval & 0x80) ? (0xD5 ^ (_u2a[0xFF ^ uval] - 1)) : + (unsigned char) (0x55 ^ (_u2a[0x7F ^ uval] - 1))); +} +#endif diff --git a/src/pulsecore/g711.h b/src/pulsecore/g711.h new file mode 100644 index 00000000..37ebcf72 --- /dev/null +++ b/src/pulsecore/g711.h @@ -0,0 +1,40 @@ +#ifndef foog711hfoo +#define foog711hfoo + +/* g711.h - include for G711 u-law and a-law conversion routines +** +** Copyright (C) 2001 Chris Bagwell +** +** Permission to use, copy, modify, and distribute this software and its +** documentation for any purpose and without fee is hereby granted, provided +** that the above copyright notice appear in all copies and that both that +** copyright notice and this permission notice appear in supporting +** documentation. This software is provided "as is" without express or +** implied warranty. +*/ + +/** Copied from sox -- Lennart Poettering */ + +#include <inttypes.h> + +#ifdef FAST_ALAW_CONVERSION +extern uint8_t _st_13linear2alaw[0x2000]; +extern int16_t _st_alaw2linear16[256]; +#define st_13linear2alaw(sw) (_st_13linear2alaw[(sw + 0x1000)]) +#define st_alaw2linear16(uc) (_st_alaw2linear16[uc]) +#else +unsigned char st_13linear2alaw(int16_t pcm_val); +int16_t st_alaw2linear16(unsigned char); +#endif + +#ifdef FAST_ULAW_CONVERSION +extern uint8_t _st_14linear2ulaw[0x4000]; +extern int16_t _st_ulaw2linear16[256]; +#define st_14linear2ulaw(sw) (_st_14linear2ulaw[(sw + 0x2000)]) +#define st_ulaw2linear16(uc) (_st_ulaw2linear16[uc]) +#else +unsigned char st_14linear2ulaw(int16_t pcm_val); +int16_t st_ulaw2linear16(unsigned char); +#endif + +#endif diff --git a/src/pulsecore/gccmacro.h b/src/pulsecore/gccmacro.h new file mode 100644 index 00000000..f94a8c45 --- /dev/null +++ b/src/pulsecore/gccmacro.h @@ -0,0 +1,90 @@ +#ifndef foopulsegccmacrohfoo +#define foopulsegccmacrohfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 __GNUC__ +#define PA_GCC_PRINTF_ATTR(a,b) __attribute__ ((format (printf, a, b))) +#else +/** If we're in GNU C, use some magic for detecting invalid format strings */ +#define PA_GCC_PRINTF_ATTR(a,b) +#endif + +#if defined(__GNUC__) && (__GNUC__ >= 4) +#define PA_GCC_SENTINEL __attribute__ ((sentinel)) +#else +/** Macro for usage of GCC's sentinel compilation warnings */ +#define PA_GCC_SENTINEL +#endif + +#ifdef __GNUC__ +#define PA_GCC_NORETURN __attribute__((noreturn)) +#else +/** Macro for no-return functions */ +#define PA_GCC_NORETURN +#endif + +#ifdef __GNUC__ +#define PA_GCC_UNUSED __attribute__ ((unused)) +#else +/** Macro for not used parameter */ +#define PA_GCC_UNUSED +#endif + +#ifdef __GNUC__ +#define PA_GCC_DESTRUCTOR __attribute__ ((destructor)) +#else +/** Call this function when process terminates */ +#define PA_GCC_DESTRUCTOR +#endif + +#ifndef PA_GCC_PURE +#ifdef __GNUC__ +#define PA_GCC_PURE __attribute__ ((pure)) +#else +/** This function's return value depends only the arguments list and global state **/ +#define PA_GCC_PURE +#endif +#endif + +#ifndef PA_GCC_CONST +#ifdef __GNUC__ +#define PA_GCC_CONST __attribute__ ((const)) +#else +/** This function's return value depends only the arguments list (stricter version of PA_GCC_PURE) **/ +#define PA_GCC_CONST +#endif +#endif + +#ifndef PA_LIKELY +#ifdef __GNUC__ +#define PA_LIKELY(x) (__builtin_expect(!!(x),1)) +#define PA_UNLIKELY(x) (__builtin_expect((x),0)) +#else +#define PA_LIKELY(x) (x) +#define PA_UNLIKELY(x) (x) +#endif +#endif + +#endif diff --git a/src/pulsecore/hashmap.c b/src/pulsecore/hashmap.c new file mode 100644 index 00000000..f5589664 --- /dev/null +++ b/src/pulsecore/hashmap.c @@ -0,0 +1,234 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/idxset.h> +#include <pulsecore/log.h> +#include <pulsecore/flist.h> +#include <pulsecore/macro.h> + +#include "hashmap.h" + +#define BUCKETS 127 + +struct hashmap_entry { + struct hashmap_entry *next, *previous, *bucket_next, *bucket_previous; + unsigned hash; + const void *key; + void *value; +}; + +struct pa_hashmap { + unsigned size; + struct hashmap_entry **data; + struct hashmap_entry *first_entry; + + unsigned n_entries; + pa_hash_func_t hash_func; + pa_compare_func_t compare_func; +}; + +PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree); + +pa_hashmap *pa_hashmap_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) { + pa_hashmap *h; + + h = pa_xnew(pa_hashmap, 1); + h->data = pa_xnew0(struct hashmap_entry*, h->size = BUCKETS); + h->first_entry = NULL; + h->n_entries = 0; + h->hash_func = hash_func ? hash_func : pa_idxset_trivial_hash_func; + h->compare_func = compare_func ? compare_func : pa_idxset_trivial_compare_func; + + return h; +} + +static void remove(pa_hashmap *h, struct hashmap_entry *e) { + pa_assert(h); + pa_assert(e); + + if (e->next) + e->next->previous = e->previous; + if (e->previous) + e->previous->next = e->next; + else + h->first_entry = e->next; + + if (e->bucket_next) + e->bucket_next->bucket_previous = e->bucket_previous; + if (e->bucket_previous) + e->bucket_previous->bucket_next = e->bucket_next; + else { + pa_assert(e->hash < h->size); + h->data[e->hash] = e->bucket_next; + } + + if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0) + pa_xfree(e); + + h->n_entries--; +} + +void pa_hashmap_free(pa_hashmap*h, void (*free_func)(void *p, void *userdata), void *userdata) { + pa_assert(h); + + while (h->first_entry) { + if (free_func) + free_func(h->first_entry->value, userdata); + remove(h, h->first_entry); + } + + pa_xfree(h->data); + pa_xfree(h); +} + +static struct hashmap_entry *get(pa_hashmap *h, unsigned hash, const void *key) { + struct hashmap_entry *e; + pa_assert(h); + pa_assert(hash < h->size); + + for (e = h->data[hash]; e; e = e->bucket_next) + if (h->compare_func(e->key, key) == 0) + return e; + + return NULL; +} + +int pa_hashmap_put(pa_hashmap *h, const void *key, void *value) { + struct hashmap_entry *e; + unsigned hash; + pa_assert(h); + + hash = h->hash_func(key) % h->size; + + if ((e = get(h, hash, key))) + return -1; + + if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries)))) + e = pa_xnew(struct hashmap_entry, 1); + + e->hash = hash; + e->key = key; + e->value = value; + + e->previous = NULL; + e->next = h->first_entry; + if (h->first_entry) + h->first_entry->previous = e; + h->first_entry = e; + + e->bucket_previous = NULL; + e->bucket_next = h->data[hash]; + if (h->data[hash]) + h->data[hash]->bucket_previous = e; + h->data[hash] = e; + + h->n_entries ++; + return 0; +} + +void* pa_hashmap_get(pa_hashmap *h, const void *key) { + unsigned hash; + struct hashmap_entry *e; + + pa_assert(h); + + hash = h->hash_func(key) % h->size; + + if (!(e = get(h, hash, key))) + return NULL; + + return e->value; +} + +void* pa_hashmap_remove(pa_hashmap *h, const void *key) { + struct hashmap_entry *e; + unsigned hash; + void *data; + + pa_assert(h); + + hash = h->hash_func(key) % h->size; + + if (!(e = get(h, hash, key))) + return NULL; + + data = e->value; + remove(h, e); + return data; +} + +unsigned pa_hashmap_size(pa_hashmap *h) { + return h->n_entries; +} + +void *pa_hashmap_iterate(pa_hashmap *h, void **state, const void **key) { + pa_assert(h); + pa_assert(state); + + if (!*state) + *state = h->first_entry; + else + *state = ((struct hashmap_entry*) *state)->next; + + if (!*state) { + if (key) + *key = NULL; + return NULL; + } + + if (key) + *key = ((struct hashmap_entry*) *state)->key; + + return ((struct hashmap_entry*) *state)->value; +} + +void* pa_hashmap_steal_first(pa_hashmap *h) { + void *data; + + pa_assert(h); + + if (!h->first_entry) + return NULL; + + data = h->first_entry->value; + remove(h, h->first_entry); + return data; +} + +void *pa_hashmap_get_first(pa_hashmap *h) { + pa_assert(h); + + if (!h->first_entry) + return NULL; + + return h->first_entry->value; +} diff --git a/src/pulsecore/hashmap.h b/src/pulsecore/hashmap.h new file mode 100644 index 00000000..98df4502 --- /dev/null +++ b/src/pulsecore/hashmap.h @@ -0,0 +1,63 @@ +#ifndef foohashmaphfoo +#define foohashmaphfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/idxset.h> + +/* Simple Implementation of a hash table. Memory management is the + * user's job. It's a good idea to have the key pointer point to a + * string in the value data. */ + +typedef struct pa_hashmap pa_hashmap; + +typedef void (*pa_free2_cb_t)(void *p, void *userdata); + +/* Create a new hashmap. Use the specified functions for hashing and comparing objects in the map */ +pa_hashmap *pa_hashmap_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func); + +/* Free the hash table. Calls the specified function for every value in the table. The function may be NULL */ +void pa_hashmap_free(pa_hashmap*, pa_free2_cb_t free_cb, void *userdata); + +/* Returns non-zero when the entry already exists */ +int pa_hashmap_put(pa_hashmap *h, const void *key, void *value); +void* pa_hashmap_get(pa_hashmap *h, const void *key); + +/* Returns the data of the entry while removing */ +void* pa_hashmap_remove(pa_hashmap *h, const void *key); + +unsigned pa_hashmap_size(pa_hashmap *h); + +/* May be used to iterate through the hashmap. Initially the opaque + pointer *state has to be set to NULL. The hashmap may not be + modified during iteration. The key of the entry is returned in + *key, if key is non-NULL. After the last entry in the hashmap NULL + is returned. */ +void *pa_hashmap_iterate(pa_hashmap *h, void **state, const void**key); + +void *pa_hashmap_steal_first(pa_hashmap *h); + +void *pa_hashmap_get_first(pa_hashmap *h); + +#endif diff --git a/src/pulsecore/hook-list.c b/src/pulsecore/hook-list.c new file mode 100644 index 00000000..3a6874c4 --- /dev/null +++ b/src/pulsecore/hook-list.c @@ -0,0 +1,120 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/macro.h> + +#include "hook-list.h" + +void pa_hook_init(pa_hook *hook, void *data) { + pa_assert(hook); + + PA_LLIST_HEAD_INIT(pa_hook_slot, hook->slots); + hook->last = NULL; + hook->n_dead = hook->firing = 0; + hook->data = data; +} + +static void slot_free(pa_hook *hook, pa_hook_slot *slot) { + pa_assert(hook); + pa_assert(slot); + + if (hook->last == slot) + hook->last = slot->prev; + + PA_LLIST_REMOVE(pa_hook_slot, hook->slots, slot); + + pa_xfree(slot); +} + +void pa_hook_free(pa_hook *hook) { + pa_assert(hook); + pa_assert(!hook->firing); + + while (hook->slots) + slot_free(hook, hook->slots); + + pa_hook_init(hook, NULL); +} + +pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_cb_t cb, void *data) { + pa_hook_slot *slot; + + pa_assert(cb); + + slot = pa_xnew(pa_hook_slot, 1); + slot->hook = hook; + slot->dead = 0; + slot->callback = cb; + slot->data = data; + + PA_LLIST_INSERT_AFTER(pa_hook_slot, hook->slots, hook->last, slot); + hook->last = slot; + + return slot; +} + +void pa_hook_slot_free(pa_hook_slot *slot) { + pa_assert(slot); + pa_assert(!slot->dead); + + if (slot->hook->firing > 0) { + slot->dead = 1; + slot->hook->n_dead++; + } else + slot_free(slot->hook, slot); +} + +pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data) { + pa_hook_slot *slot, *next; + pa_hook_result_t result = PA_HOOK_OK; + + pa_assert(hook); + + hook->firing ++; + + for (slot = hook->slots; slot; slot = slot->next) { + if (slot->dead) + continue; + + if ((result = slot->callback(hook->data, data, slot->data)) != PA_HOOK_OK) + break; + } + + hook->firing --; + + for (slot = hook->slots; hook->n_dead > 0 && slot; slot = next) { + next = slot->next; + + if (slot->dead) { + slot_free(hook, slot); + hook->n_dead--; + } + } + + return result; +} + diff --git a/src/pulsecore/hook-list.h b/src/pulsecore/hook-list.h new file mode 100644 index 00000000..b3bd600a --- /dev/null +++ b/src/pulsecore/hook-list.h @@ -0,0 +1,69 @@ +#ifndef foohooklistfoo +#define foohooklistfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/llist.h> +#include <pulse/xmalloc.h> +#include <pulsecore/gccmacro.h> + +typedef struct pa_hook_slot pa_hook_slot; +typedef struct pa_hook pa_hook; + +typedef enum pa_hook_result { + PA_HOOK_OK = 0, + PA_HOOK_STOP = 1, + PA_HOOK_CANCEL = -1 +} pa_hook_result_t; + +typedef pa_hook_result_t (*pa_hook_cb_t)( + void *hook_data, + void *call_data, + void *slot_data); + +struct pa_hook_slot { + int dead; + pa_hook *hook; + pa_hook_cb_t callback; + void *data; + PA_LLIST_FIELDS(pa_hook_slot); +}; + +struct pa_hook { + PA_LLIST_HEAD(pa_hook_slot, slots); + pa_hook_slot *last; + int firing, n_dead; + + void *data; +}; + +void pa_hook_init(pa_hook *hook, void *data); +void pa_hook_free(pa_hook *hook); + +pa_hook_slot* pa_hook_connect(pa_hook *hook, pa_hook_cb_t, void *data); +void pa_hook_slot_free(pa_hook_slot *slot); + +pa_hook_result_t pa_hook_fire(pa_hook *hook, void *data); + +#endif diff --git a/src/pulsecore/idxset.c b/src/pulsecore/idxset.c new file mode 100644 index 00000000..8a88471f --- /dev/null +++ b/src/pulsecore/idxset.c @@ -0,0 +1,423 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> + +#include "idxset.h" + +struct idxset_entry { + void *data; + uint32_t index; + unsigned hash_value; + + struct idxset_entry *hash_prev, *hash_next; + struct idxset_entry* iterate_prev, *iterate_next; +}; + +struct pa_idxset { + pa_hash_func_t hash_func; + pa_compare_func_t compare_func; + + unsigned hash_table_size, n_entries; + struct idxset_entry **hash_table, **array, *iterate_list_head, *iterate_list_tail; + uint32_t index, start_index, array_size; +}; + +PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree); + +unsigned pa_idxset_string_hash_func(const void *p) { + unsigned hash = 0; + const char *c; + + for (c = p; *c; c++) + hash = 31 * hash + *c; + + return hash; +} + +int pa_idxset_string_compare_func(const void *a, const void *b) { + return strcmp(a, b); +} + +unsigned pa_idxset_trivial_hash_func(const void *p) { + return PA_PTR_TO_UINT(p); +} + +int pa_idxset_trivial_compare_func(const void *a, const void *b) { + return a != b; +} + +pa_idxset* pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func) { + pa_idxset *s; + + s = pa_xnew(pa_idxset, 1); + s->hash_func = hash_func ? hash_func : pa_idxset_trivial_hash_func; + s->compare_func = compare_func ? compare_func : pa_idxset_trivial_compare_func; + s->hash_table_size = 127; + s->hash_table = pa_xnew0(struct idxset_entry*, s->hash_table_size); + s->array = NULL; + s->array_size = 0; + s->index = 0; + s->start_index = 0; + s->n_entries = 0; + + s->iterate_list_head = s->iterate_list_tail = NULL; + + return s; +} + +void pa_idxset_free(pa_idxset *s, void (*free_func) (void *p, void *userdata), void *userdata) { + pa_assert(s); + + while (s->iterate_list_head) { + struct idxset_entry *e = s->iterate_list_head; + s->iterate_list_head = s->iterate_list_head->iterate_next; + + if (free_func) + free_func(e->data, userdata); + + if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0) + pa_xfree(e); + } + + pa_xfree(s->hash_table); + pa_xfree(s->array); + pa_xfree(s); +} + +static struct idxset_entry* hash_scan(pa_idxset *s, struct idxset_entry* e, const void *p) { + pa_assert(p); + + pa_assert(s->compare_func); + for (; e; e = e->hash_next) + if (s->compare_func(e->data, p) == 0) + return e; + + return NULL; +} + +static void extend_array(pa_idxset *s, uint32_t idx) { + uint32_t i, j, l; + struct idxset_entry** n; + + pa_assert(s); + pa_assert(idx >= s->start_index); + + if (idx < s->start_index + s->array_size) + return; + + for (i = 0; i < s->array_size; i++) + if (s->array[i]) + break; + + l = idx - s->start_index - i + 100; + n = pa_xnew0(struct idxset_entry*, l); + + for (j = 0; j < s->array_size-i; j++) + n[j] = s->array[i+j]; + + pa_xfree(s->array); + + s->array = n; + s->array_size = l; + s->start_index += i; +} + +static struct idxset_entry** array_index(pa_idxset*s, uint32_t idx) { + pa_assert(s); + + if (idx >= s->start_index + s->array_size) + return NULL; + + if (idx < s->start_index) + return NULL; + + return s->array + idx - s->start_index; +} + +int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx) { + unsigned h; + struct idxset_entry *e, **a; + + pa_assert(s); + pa_assert(p); + + pa_assert(s->hash_func); + h = s->hash_func(p) % s->hash_table_size; + + pa_assert(s->hash_table); + if ((e = hash_scan(s, s->hash_table[h], p))) { + if (idx) + *idx = e->index; + + return -1; + } + + if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries)))) + e = pa_xnew(struct idxset_entry, 1); + e->data = p; + e->index = s->index++; + e->hash_value = h; + + /* Insert into hash table */ + e->hash_next = s->hash_table[h]; + e->hash_prev = NULL; + if (s->hash_table[h]) + s->hash_table[h]->hash_prev = e; + s->hash_table[h] = e; + + /* Insert into array */ + extend_array(s, e->index); + a = array_index(s, e->index); + pa_assert(a && !*a); + *a = e; + + /* Insert into linked list */ + e->iterate_next = NULL; + e->iterate_prev = s->iterate_list_tail; + if (s->iterate_list_tail) { + pa_assert(s->iterate_list_head); + s->iterate_list_tail->iterate_next = e; + } else { + pa_assert(!s->iterate_list_head); + s->iterate_list_head = e; + } + s->iterate_list_tail = e; + + s->n_entries++; + pa_assert(s->n_entries >= 1); + + if (idx) + *idx = e->index; + + return 0; +} + +void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx) { + struct idxset_entry **a; + pa_assert(s); + + if (!(a = array_index(s, idx))) + return NULL; + + if (!*a) + return NULL; + + return (*a)->data; +} + +void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx) { + unsigned h; + struct idxset_entry *e; + + pa_assert(s); + pa_assert(p); + + pa_assert(s->hash_func); + h = s->hash_func(p) % s->hash_table_size; + + pa_assert(s->hash_table); + if (!(e = hash_scan(s, s->hash_table[h], p))) + return NULL; + + if (idx) + *idx = e->index; + + return e->data; +} + +static void remove_entry(pa_idxset *s, struct idxset_entry *e) { + struct idxset_entry **a; + + pa_assert(s); + pa_assert(e); + + /* Remove from array */ + a = array_index(s, e->index); + pa_assert(a && *a && *a == e); + *a = NULL; + + /* Remove from linked list */ + if (e->iterate_next) + e->iterate_next->iterate_prev = e->iterate_prev; + else + s->iterate_list_tail = e->iterate_prev; + + if (e->iterate_prev) + e->iterate_prev->iterate_next = e->iterate_next; + else + s->iterate_list_head = e->iterate_next; + + /* Remove from hash table */ + if (e->hash_next) + e->hash_next->hash_prev = e->hash_prev; + + if (e->hash_prev) + e->hash_prev->hash_next = e->hash_next; + else + s->hash_table[e->hash_value] = e->hash_next; + + if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0) + pa_xfree(e); + + pa_assert(s->n_entries >= 1); + s->n_entries--; +} + +void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx) { + struct idxset_entry **a; + void *data; + + pa_assert(s); + + if (!(a = array_index(s, idx))) + return NULL; + + if (!*a) + return NULL; + + data = (*a)->data; + remove_entry(s, *a); + + return data; +} + +void* pa_idxset_remove_by_data(pa_idxset*s, const void *data, uint32_t *idx) { + struct idxset_entry *e; + unsigned h; + void *r; + + pa_assert(s); + + pa_assert(s->hash_func); + h = s->hash_func(data) % s->hash_table_size; + + pa_assert(s->hash_table); + if (!(e = hash_scan(s, s->hash_table[h], data))) + return NULL; + + r = e->data; + if (idx) + *idx = e->index; + + remove_entry(s, e); + + return r; +} + +void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx) { + struct idxset_entry **a, *e = NULL; + + pa_assert(s); + pa_assert(idx); + + if ((a = array_index(s, *idx)) && *a) + e = (*a)->iterate_next; + + if (!e) + e = s->iterate_list_head; + + if (!e) + return NULL; + + *idx = e->index; + return e->data; +} + +void* pa_idxset_first(pa_idxset *s, uint32_t *idx) { + pa_assert(s); + + if (!s->iterate_list_head) + return NULL; + + if (idx) + *idx = s->iterate_list_head->index; + return s->iterate_list_head->data; +} + +void *pa_idxset_next(pa_idxset *s, uint32_t *idx) { + struct idxset_entry **a, *e = NULL; + + pa_assert(s); + pa_assert(idx); + + if ((a = array_index(s, *idx)) && *a) + e = (*a)->iterate_next; + + if (e) { + *idx = e->index; + return e->data; + } else { + *idx = PA_IDXSET_INVALID; + return NULL; + } +} + +int pa_idxset_foreach(pa_idxset*s, int (*func)(void *p, uint32_t idx, int *del, void*userdata), void *userdata) { + struct idxset_entry *e; + + pa_assert(s); + pa_assert(func); + + e = s->iterate_list_head; + while (e) { + int del = 0, r; + struct idxset_entry *n = e->iterate_next; + + r = func(e->data, e->index, &del, userdata); + + if (del) + remove_entry(s, e); + + if (r < 0) + return r; + + e = n; + } + + return 0; +} + +unsigned pa_idxset_size(pa_idxset*s) { + pa_assert(s); + + return s->n_entries; +} + +int pa_idxset_isempty(pa_idxset *s) { + pa_assert(s); + + return s->n_entries == 0; +} + diff --git a/src/pulsecore/idxset.h b/src/pulsecore/idxset.h new file mode 100644 index 00000000..5b55cec2 --- /dev/null +++ b/src/pulsecore/idxset.h @@ -0,0 +1,100 @@ +#ifndef fooidxsethfoo +#define fooidxsethfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <inttypes.h> + +/* A combination of a set and a dynamic array. Entries are indexable + * both through a numeric automatically generated index and the entry's + * data pointer. As usual, memory management is the user's job. */ + +/* A special index value denoting the invalid index. */ +#define PA_IDXSET_INVALID ((uint32_t) -1) + +/* Generic implementations for hash and comparison functions. Just + * compares the pointer or calculates the hash value directly from the + * pointer value. */ +unsigned pa_idxset_trivial_hash_func(const void *p); +int pa_idxset_trivial_compare_func(const void *a, const void *b); + +/* Generic implementations for hash and comparison functions for strings. */ +unsigned pa_idxset_string_hash_func(const void *p); +int pa_idxset_string_compare_func(const void *a, const void *b); + +typedef unsigned (*pa_hash_func_t)(const void *p); +typedef int (*pa_compare_func_t)(const void *a, const void *b); + +typedef struct pa_idxset pa_idxset; + +/* Instantiate a new idxset with the specified hash and comparison functions */ +pa_idxset* pa_idxset_new(pa_hash_func_t hash_func, pa_compare_func_t compare_func); + +/* Free the idxset. When the idxset is not empty the specified function is called for every entry contained */ +void pa_idxset_free(pa_idxset *s, void (*free_func) (void *p, void *userdata), void *userdata); + +/* Store a new item in the idxset. The index of the item is returned in *idx */ +int pa_idxset_put(pa_idxset*s, void *p, uint32_t *idx); + +/* Get the entry by its idx */ +void* pa_idxset_get_by_index(pa_idxset*s, uint32_t idx); + +/* Get the entry by its data. The idx is returned in *index */ +void* pa_idxset_get_by_data(pa_idxset*s, const void *p, uint32_t *idx); + +/* Similar to pa_idxset_get_by_index(), but removes the entry from the idxset. */ +void* pa_idxset_remove_by_index(pa_idxset*s, uint32_t idx); + +/* Similar to pa_idxset_get_by_data(), but removes the entry from the idxset */ +void* pa_idxset_remove_by_data(pa_idxset*s, const void *p, uint32_t *idx); + +/* This may be used to iterate through all entries. When called with + an invalid index value it returns the first entry, otherwise the + next following. The function is best called with *idx = + PA_IDXSET_VALID first. It is safe to manipulate the idxset between + the calls. It is not guaranteed that all entries have already been + returned before the an entry is returned the second time.*/ +void* pa_idxset_rrobin(pa_idxset *s, uint32_t *idx); + +/* Return the oldest entry in the idxset. Fill in its index in *idx. */ +void* pa_idxset_first(pa_idxset *s, uint32_t *idx); + +/* Return the entry following the entry indexed by *idx. After the + * call *index contains the index of the returned + * object. pa_idxset_first() and pa_idxset_next() may be used to + * iterate through the set.*/ +void *pa_idxset_next(pa_idxset *s, uint32_t *idx); + +/* Call a function for every item in the set. If the callback function + returns -1, the loop is terminated. If *del is set to non-zero that + specific item is removed. It is not safe to call any other + functions on the idxset while pa_idxset_foreach is executed. */ +int pa_idxset_foreach(pa_idxset*s, int (*func)(void *p, uint32_t idx, int *del, void*userdata), void *userdata); + +unsigned pa_idxset_size(pa_idxset*s); + +int pa_idxset_isempty(pa_idxset *s); + +#endif diff --git a/src/pulsecore/inet_ntop.c b/src/pulsecore/inet_ntop.c new file mode 100644 index 00000000..4a4f7aac --- /dev/null +++ b/src/pulsecore/inet_ntop.c @@ -0,0 +1,81 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdio.h> +#include <errno.h> + +#ifndef HAVE_INET_NTOP + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#include "winsock.h" + +#include "inet_ntop.h" + +const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt) { + struct in_addr *in = (struct in_addr*)src; + struct in6_addr *in6 = (struct in6_addr*)src; + + assert(src && dst); + + switch (af) { + case AF_INET: + pa_snprintf(dst, cnt, "%d.%d.%d.%d", +#ifdef WORDS_BIGENDIAN + (int)(in->s_addr >> 24) & 0xff, + (int)(in->s_addr >> 16) & 0xff, + (int)(in->s_addr >> 8) & 0xff, + (int)(in->s_addr >> 0) & 0xff); +#else + (int)(in->s_addr >> 0) & 0xff, + (int)(in->s_addr >> 8) & 0xff, + (int)(in->s_addr >> 16) & 0xff, + (int)(in->s_addr >> 24) & 0xff); +#endif + break; + case AF_INET6: + pa_snprintf(dst, cnt, "%x:%x:%x:%x:%x:%x:%x:%x", + in6->s6_addr[ 0] << 8 | in6->s6_addr[ 1], + in6->s6_addr[ 2] << 8 | in6->s6_addr[ 3], + in6->s6_addr[ 4] << 8 | in6->s6_addr[ 5], + in6->s6_addr[ 6] << 8 | in6->s6_addr[ 7], + in6->s6_addr[ 8] << 8 | in6->s6_addr[ 9], + in6->s6_addr[10] << 8 | in6->s6_addr[11], + in6->s6_addr[12] << 8 | in6->s6_addr[13], + in6->s6_addr[14] << 8 | in6->s6_addr[15]); + break; + default: + errno = EAFNOSUPPORT; + return NULL; + } + + return dst; +} + +#endif /* INET_NTOP */ diff --git a/src/pulsecore/inet_ntop.h b/src/pulsecore/inet_ntop.h new file mode 100644 index 00000000..7fb67b44 --- /dev/null +++ b/src/pulsecore/inet_ntop.h @@ -0,0 +1,12 @@ +#ifndef fooinet_ntophfoo +#define fooinet_ntophfoo + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#include "winsock.h" + +const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt); + +#endif diff --git a/src/pulsecore/inet_pton.c b/src/pulsecore/inet_pton.c new file mode 100644 index 00000000..84d0c0ea --- /dev/null +++ b/src/pulsecore/inet_pton.c @@ -0,0 +1,63 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdio.h> +#include <errno.h> + +#ifndef HAVE_INET_PTON + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#include "winsock.h" + +#include "inet_pton.h" + +int inet_pton(int af, const char *src, void *dst) { + struct in_addr *in = (struct in_addr*)dst; + struct in6_addr *in6 = (struct in6_addr*)dst; + + assert(src && dst); + + switch (af) { + case AF_INET: + in->s_addr = inet_addr(src); + if (in->s_addr == INADDR_NONE) + return 0; + break; + case AF_INET6: + /* FIXME */ + default: + errno = EAFNOSUPPORT; + return -1; + } + + return 1; +} + +#endif /* INET_PTON */ diff --git a/src/pulsecore/inet_pton.h b/src/pulsecore/inet_pton.h new file mode 100644 index 00000000..111b4a07 --- /dev/null +++ b/src/pulsecore/inet_pton.h @@ -0,0 +1,12 @@ +#ifndef fooinet_ptonhfoo +#define fooinet_ptonhfoo + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#include "winsock.h" + +int inet_pton(int af, const char *src, void *dst); + +#endif diff --git a/src/pulsecore/iochannel.c b/src/pulsecore/iochannel.c new file mode 100644 index 00000000..63ab2ad7 --- /dev/null +++ b/src/pulsecore/iochannel.c @@ -0,0 +1,426 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdlib.h> +#include <fcntl.h> +#include <unistd.h> +#include <errno.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif + +#include "winsock.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 "iochannel.h" + +struct pa_iochannel { + int ifd, ofd; + int ifd_type, ofd_type; + pa_mainloop_api* mainloop; + + pa_iochannel_cb_t callback; + void*userdata; + + pa_bool_t readable; + pa_bool_t writable; + pa_bool_t hungup; + + pa_bool_t no_close; + + pa_io_event* input_event, *output_event; +}; + +static void enable_mainloop_sources(pa_iochannel *io) { + pa_assert(io); + + if (io->input_event == io->output_event && io->input_event) { + pa_io_event_flags_t f = PA_IO_EVENT_NULL; + pa_assert(io->input_event); + + if (!io->readable) + f |= PA_IO_EVENT_INPUT; + if (!io->writable) + f |= PA_IO_EVENT_OUTPUT; + + io->mainloop->io_enable(io->input_event, f); + } else { + if (io->input_event) + io->mainloop->io_enable(io->input_event, io->readable ? PA_IO_EVENT_NULL : PA_IO_EVENT_INPUT); + if (io->output_event) + io->mainloop->io_enable(io->output_event, io->writable ? PA_IO_EVENT_NULL : PA_IO_EVENT_OUTPUT); + } +} + +static void callback(pa_mainloop_api* m, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + pa_iochannel *io = userdata; + pa_bool_t changed = FALSE; + + pa_assert(m); + pa_assert(e); + pa_assert(fd >= 0); + pa_assert(userdata); + + if ((f & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) && !io->hungup) { + io->hungup = TRUE; + changed = TRUE; + } + + if ((f & PA_IO_EVENT_INPUT) && !io->readable) { + io->readable = TRUE; + changed = TRUE; + pa_assert(e == io->input_event); + } + + if ((f & PA_IO_EVENT_OUTPUT) && !io->writable) { + io->writable = TRUE; + changed = TRUE; + pa_assert(e == io->output_event); + } + + if (changed) { + enable_mainloop_sources(io); + + if (io->callback) + io->callback(io, io->userdata); + } +} + +pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd) { + pa_iochannel *io; + + pa_assert(m); + pa_assert(ifd >= 0 || ofd >= 0); + + io = pa_xnew(pa_iochannel, 1); + io->ifd = ifd; + io->ofd = ofd; + io->ifd_type = io->ofd_type = 0; + io->mainloop = m; + + io->userdata = NULL; + io->callback = NULL; + io->readable = FALSE; + io->writable = FALSE; + io->hungup = FALSE; + io->no_close = FALSE; + + io->input_event = io->output_event = NULL; + + if (ifd == ofd) { + pa_assert(ifd >= 0); + pa_make_fd_nonblock(io->ifd); + io->input_event = io->output_event = m->io_new(m, ifd, PA_IO_EVENT_INPUT|PA_IO_EVENT_OUTPUT, callback, io); + } else { + + if (ifd >= 0) { + pa_make_fd_nonblock(io->ifd); + io->input_event = m->io_new(m, ifd, PA_IO_EVENT_INPUT, callback, io); + } + + if (ofd >= 0) { + pa_make_fd_nonblock(io->ofd); + io->output_event = m->io_new(m, ofd, PA_IO_EVENT_OUTPUT, callback, io); + } + } + + return io; +} + +void pa_iochannel_free(pa_iochannel*io) { + pa_assert(io); + + if (io->input_event) + io->mainloop->io_free(io->input_event); + + if (io->output_event && (io->output_event != io->input_event)) + io->mainloop->io_free(io->output_event); + + if (!io->no_close) { + if (io->ifd >= 0) + pa_close(io->ifd); + if (io->ofd >= 0 && io->ofd != io->ifd) + pa_close(io->ofd); + } + + pa_xfree(io); +} + +pa_bool_t pa_iochannel_is_readable(pa_iochannel*io) { + pa_assert(io); + + return io->readable || io->hungup; +} + +pa_bool_t pa_iochannel_is_writable(pa_iochannel*io) { + pa_assert(io); + + return io->writable && !io->hungup; +} + +pa_bool_t pa_iochannel_is_hungup(pa_iochannel*io) { + pa_assert(io); + + return io->hungup; +} + +ssize_t pa_iochannel_write(pa_iochannel*io, const void*data, size_t l) { + ssize_t r; + + pa_assert(io); + pa_assert(data); + pa_assert(l); + pa_assert(io->ofd >= 0); + + if ((r = pa_write(io->ofd, data, l, &io->ofd_type)) >= 0) { + io->writable = FALSE; + enable_mainloop_sources(io); + } + + return r; +} + +ssize_t pa_iochannel_read(pa_iochannel*io, void*data, size_t l) { + ssize_t r; + + pa_assert(io); + pa_assert(data); + pa_assert(io->ifd >= 0); + + if ((r = pa_read(io->ifd, data, l, &io->ifd_type)) >= 0) { + io->readable = FALSE; + enable_mainloop_sources(io); + } + + return r; +} + +#ifdef HAVE_CREDS + +pa_bool_t pa_iochannel_creds_supported(pa_iochannel *io) { + struct sockaddr_un sa; + socklen_t l; + + pa_assert(io); + pa_assert(io->ifd >= 0); + pa_assert(io->ofd == io->ifd); + + l = sizeof(sa); + + if (getsockname(io->ifd, (struct sockaddr*) &sa, &l) < 0) + return 0; + + return sa.sun_family == AF_UNIX; +} + +int pa_iochannel_creds_enable(pa_iochannel *io) { + int t = 1; + + pa_assert(io); + pa_assert(io->ifd >= 0); + + if (setsockopt(io->ifd, SOL_SOCKET, SO_PASSCRED, &t, sizeof(t)) < 0) { + pa_log_error("setsockopt(SOL_SOCKET, SO_PASSCRED): %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +ssize_t pa_iochannel_write_with_creds(pa_iochannel*io, const void*data, size_t l, const pa_creds *ucred) { + ssize_t r; + struct msghdr mh; + struct iovec iov; + union { + struct cmsghdr hdr; + uint8_t data[CMSG_SPACE(sizeof(struct ucred))]; + } cmsg; + struct ucred *u; + + pa_assert(io); + pa_assert(data); + pa_assert(l); + pa_assert(io->ofd >= 0); + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = (void*) data; + iov.iov_len = l; + + memset(&cmsg, 0, sizeof(cmsg)); + cmsg.hdr.cmsg_len = CMSG_LEN(sizeof(struct ucred)); + cmsg.hdr.cmsg_level = SOL_SOCKET; + cmsg.hdr.cmsg_type = SCM_CREDENTIALS; + + u = (struct ucred*) CMSG_DATA(&cmsg.hdr); + + u->pid = getpid(); + if (ucred) { + u->uid = ucred->uid; + u->gid = ucred->gid; + } else { + u->uid = getuid(); + u->gid = getgid(); + } + + memset(&mh, 0, sizeof(mh)); + mh.msg_name = NULL; + mh.msg_namelen = 0; + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &cmsg; + mh.msg_controllen = sizeof(cmsg); + mh.msg_flags = 0; + + if ((r = sendmsg(io->ofd, &mh, MSG_NOSIGNAL)) >= 0) { + io->writable = FALSE; + enable_mainloop_sources(io); + } + + return r; +} + +ssize_t pa_iochannel_read_with_creds(pa_iochannel*io, void*data, size_t l, pa_creds *creds, pa_bool_t *creds_valid) { + ssize_t r; + struct msghdr mh; + struct iovec iov; + union { + struct cmsghdr hdr; + uint8_t data[CMSG_SPACE(sizeof(struct ucred))]; + } cmsg; + + pa_assert(io); + pa_assert(data); + pa_assert(l); + pa_assert(io->ifd >= 0); + pa_assert(creds); + pa_assert(creds_valid); + + memset(&iov, 0, sizeof(iov)); + iov.iov_base = data; + iov.iov_len = l; + + memset(&cmsg, 0, sizeof(cmsg)); + + memset(&mh, 0, sizeof(mh)); + mh.msg_name = NULL; + mh.msg_namelen = 0; + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &cmsg; + mh.msg_controllen = sizeof(cmsg); + mh.msg_flags = 0; + + if ((r = recvmsg(io->ifd, &mh, 0)) >= 0) { + struct cmsghdr *cmh; + + *creds_valid = 0; + + for (cmh = CMSG_FIRSTHDR(&mh); cmh; cmh = CMSG_NXTHDR(&mh, cmh)) { + + if (cmh->cmsg_level == SOL_SOCKET && cmh->cmsg_type == SCM_CREDENTIALS) { + struct ucred u; + pa_assert(cmh->cmsg_len == CMSG_LEN(sizeof(struct ucred))); + memcpy(&u, CMSG_DATA(cmh), sizeof(struct ucred)); + + creds->gid = u.gid; + creds->uid = u.uid; + *creds_valid = TRUE; + break; + } + } + + io->readable = FALSE; + enable_mainloop_sources(io); + } + + return r; +} + +#endif /* HAVE_CREDS */ + +void pa_iochannel_set_callback(pa_iochannel*io, pa_iochannel_cb_t _callback, void *userdata) { + pa_assert(io); + + io->callback = _callback; + io->userdata = userdata; +} + +void pa_iochannel_set_noclose(pa_iochannel*io, pa_bool_t b) { + pa_assert(io); + + io->no_close = !!b; +} + +void pa_iochannel_socket_peer_to_string(pa_iochannel*io, char*s, size_t l) { + pa_assert(io); + pa_assert(s); + pa_assert(l); + + pa_socket_peer_to_string(io->ifd, s, l); +} + +int pa_iochannel_socket_set_rcvbuf(pa_iochannel *io, size_t l) { + pa_assert(io); + + return pa_socket_set_rcvbuf(io->ifd, l); +} + +int pa_iochannel_socket_set_sndbuf(pa_iochannel *io, size_t l) { + pa_assert(io); + + return pa_socket_set_sndbuf(io->ofd, l); +} + +pa_mainloop_api* pa_iochannel_get_mainloop_api(pa_iochannel *io) { + pa_assert(io); + + return io->mainloop; +} + +int pa_iochannel_get_recv_fd(pa_iochannel *io) { + pa_assert(io); + + return io->ifd; +} + +int pa_iochannel_get_send_fd(pa_iochannel *io) { + pa_assert(io); + + return io->ofd; +} diff --git a/src/pulsecore/iochannel.h b/src/pulsecore/iochannel.h new file mode 100644 index 00000000..c9794d99 --- /dev/null +++ b/src/pulsecore/iochannel.h @@ -0,0 +1,93 @@ +#ifndef fooiochannelhfoo +#define fooiochannelhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#include <sys/types.h> + +#include <pulse/mainloop-api.h> +#include <pulsecore/creds.h> +#include <pulsecore/macro.h> + +/* A wrapper around UNIX file descriptors for attaching them to the a + main event loop. Everytime new data may be read or be written to + the channel a callback function is called. It is safe to destroy + the calling iochannel object from the callback */ + +/* When pa_iochannel_is_readable() returns non-zero, the user has to + * call this function in a loop until it is no longer set or EOF + * reached. Otherwise strange things may happen when an EOF is + * reached. */ + +typedef struct pa_iochannel pa_iochannel; + +/* Create a new IO channel for the specified file descriptors for +input resp. output. It is safe to pass the same file descriptor for +both parameters (in case of full-duplex channels). For a simplex +channel specify -1 for the other direction. */ + +pa_iochannel* pa_iochannel_new(pa_mainloop_api*m, int ifd, int ofd); +void pa_iochannel_free(pa_iochannel*io); + +ssize_t pa_iochannel_write(pa_iochannel*io, const void*data, size_t l); +ssize_t pa_iochannel_read(pa_iochannel*io, void*data, size_t l); + +#ifdef HAVE_CREDS +pa_bool_t pa_iochannel_creds_supported(pa_iochannel *io); +int pa_iochannel_creds_enable(pa_iochannel *io); + +ssize_t pa_iochannel_write_with_creds(pa_iochannel*io, const void*data, size_t l, const pa_creds *ucred); +ssize_t pa_iochannel_read_with_creds(pa_iochannel*io, void*data, size_t l, pa_creds *ucred, pa_bool_t *creds_valid); +#endif + +pa_bool_t pa_iochannel_is_readable(pa_iochannel*io); +pa_bool_t pa_iochannel_is_writable(pa_iochannel*io); +pa_bool_t pa_iochannel_is_hungup(pa_iochannel*io); + +/* Don't close the file descirptors when the io channel is freed. By + * default the file descriptors are closed. */ +void pa_iochannel_set_noclose(pa_iochannel*io, pa_bool_t b); + +/* Set the callback function that is called whenever data becomes available for read or write */ +typedef void (*pa_iochannel_cb_t)(pa_iochannel*io, void *userdata); +void pa_iochannel_set_callback(pa_iochannel*io, pa_iochannel_cb_t callback, void *userdata); + +/* In case the file descriptor is a socket, return a pretty-printed string in *s which describes the peer connected */ +void pa_iochannel_socket_peer_to_string(pa_iochannel*io, char*s, size_t l); + +/* Use setsockopt() to tune the recieve and send buffers of TCP sockets */ +int pa_iochannel_socket_set_rcvbuf(pa_iochannel*io, size_t l); +int pa_iochannel_socket_set_sndbuf(pa_iochannel*io, size_t l); + +pa_mainloop_api* pa_iochannel_get_mainloop_api(pa_iochannel *io); + +int pa_iochannel_get_recv_fd(pa_iochannel *io); +int pa_iochannel_get_send_fd(pa_iochannel *io); + +#endif diff --git a/src/pulsecore/ioline.c b/src/pulsecore/ioline.c new file mode 100644 index 00000000..5fd2189b --- /dev/null +++ b/src/pulsecore/ioline.c @@ -0,0 +1,415 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/winsock.h> +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/refcnt.h> + +#include "ioline.h" + +#define BUFFER_LIMIT (64*1024) +#define READ_SIZE (1024) + +struct pa_ioline { + PA_REFCNT_DECLARE; + + pa_iochannel *io; + pa_defer_event *defer_event; + pa_mainloop_api *mainloop; + int dead; + + char *wbuf; + size_t wbuf_length, wbuf_index, wbuf_valid_length; + + char *rbuf; + size_t rbuf_length, rbuf_index, rbuf_valid_length; + + void (*callback)(pa_ioline*io, const char *s, void *userdata); + void *userdata; + + int defer_close; +}; + +static void io_callback(pa_iochannel*io, void *userdata); +static void defer_callback(pa_mainloop_api*m, pa_defer_event*e, void *userdata); + +pa_ioline* pa_ioline_new(pa_iochannel *io) { + pa_ioline *l; + pa_assert(io); + + l = pa_xnew(pa_ioline, 1); + PA_REFCNT_INIT(l); + l->io = io; + l->dead = 0; + + l->wbuf = NULL; + l->wbuf_length = l->wbuf_index = l->wbuf_valid_length = 0; + + l->rbuf = NULL; + l->rbuf_length = l->rbuf_index = l->rbuf_valid_length = 0; + + l->callback = NULL; + l->userdata = NULL; + + l->mainloop = pa_iochannel_get_mainloop_api(io); + + l->defer_event = l->mainloop->defer_new(l->mainloop, defer_callback, l); + l->mainloop->defer_enable(l->defer_event, 0); + + l->defer_close = 0; + + pa_iochannel_set_callback(io, io_callback, l); + + return l; +} + +static void ioline_free(pa_ioline *l) { + pa_assert(l); + + if (l->io) + pa_iochannel_free(l->io); + + if (l->defer_event) + l->mainloop->defer_free(l->defer_event); + + pa_xfree(l->wbuf); + pa_xfree(l->rbuf); + pa_xfree(l); +} + +void pa_ioline_unref(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + if (PA_REFCNT_DEC(l) <= 0) + ioline_free(l); +} + +pa_ioline* pa_ioline_ref(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + PA_REFCNT_INC(l); + return l; +} + +void pa_ioline_close(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + l->dead = 1; + + if (l->io) { + pa_iochannel_free(l->io); + l->io = NULL; + } + + if (l->defer_event) { + l->mainloop->defer_free(l->defer_event); + l->defer_event = NULL; + } + + if (l->callback) + l->callback = NULL; +} + +void pa_ioline_puts(pa_ioline *l, const char *c) { + size_t len; + + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + pa_assert(c); + + if (l->dead) + return; + + len = strlen(c); + if (len > BUFFER_LIMIT - l->wbuf_valid_length) + len = BUFFER_LIMIT - l->wbuf_valid_length; + + if (len) { + pa_assert(l->wbuf_length >= l->wbuf_valid_length); + + /* In case the allocated buffer is too small, enlarge it. */ + if (l->wbuf_valid_length + len > l->wbuf_length) { + size_t n = l->wbuf_valid_length+len; + char *new = pa_xmalloc(n); + if (l->wbuf) { + memcpy(new, l->wbuf+l->wbuf_index, l->wbuf_valid_length); + pa_xfree(l->wbuf); + } + l->wbuf = new; + l->wbuf_length = n; + l->wbuf_index = 0; + } else if (l->wbuf_index + l->wbuf_valid_length + len > l->wbuf_length) { + + /* In case the allocated buffer fits, but the current index is too far from the start, move it to the front. */ + memmove(l->wbuf, l->wbuf+l->wbuf_index, l->wbuf_valid_length); + l->wbuf_index = 0; + } + + pa_assert(l->wbuf_index + l->wbuf_valid_length + len <= l->wbuf_length); + + /* Append the new string */ + memcpy(l->wbuf + l->wbuf_index + l->wbuf_valid_length, c, len); + l->wbuf_valid_length += len; + + l->mainloop->defer_enable(l->defer_event, 1); + } +} + +void pa_ioline_set_callback(pa_ioline*l, void (*callback)(pa_ioline*io, const char *s, void *userdata), void *userdata) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + l->callback = callback; + l->userdata = userdata; +} + +static void failure(pa_ioline *l, int process_leftover) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + pa_assert(!l->dead); + + if (process_leftover && l->rbuf_valid_length > 0) { + /* Pass the last missing bit to the client */ + + if (l->callback) { + char *p = pa_xstrndup(l->rbuf+l->rbuf_index, l->rbuf_valid_length); + l->callback(l, p, l->userdata); + pa_xfree(p); + } + } + + if (l->callback) { + l->callback(l, NULL, l->userdata); + l->callback = NULL; + } + + pa_ioline_close(l); +} + +static void scan_for_lines(pa_ioline *l, size_t skip) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + pa_assert(skip < l->rbuf_valid_length); + + while (!l->dead && l->rbuf_valid_length > skip) { + char *e, *p; + size_t m; + + if (!(e = memchr(l->rbuf + l->rbuf_index + skip, '\n', l->rbuf_valid_length - skip))) + break; + + *e = 0; + + p = l->rbuf + l->rbuf_index; + m = strlen(p); + + l->rbuf_index += m+1; + l->rbuf_valid_length -= m+1; + + /* A shortcut for the next time */ + if (l->rbuf_valid_length == 0) + l->rbuf_index = 0; + + if (l->callback) + l->callback(l, p, l->userdata); + + skip = 0; + } + + /* If the buffer became too large and still no newline was found, drop it. */ + if (l->rbuf_valid_length >= BUFFER_LIMIT) + l->rbuf_index = l->rbuf_valid_length = 0; +} + +static int do_write(pa_ioline *l); + +static int do_read(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + while (!l->dead && pa_iochannel_is_readable(l->io)) { + ssize_t r; + size_t len; + + len = l->rbuf_length - l->rbuf_index - l->rbuf_valid_length; + + /* Check if we have to enlarge the read buffer */ + if (len < READ_SIZE) { + size_t n = l->rbuf_valid_length+READ_SIZE; + + if (n >= BUFFER_LIMIT) + n = BUFFER_LIMIT; + + if (l->rbuf_length >= n) { + /* The current buffer is large enough, let's just move the data to the front */ + if (l->rbuf_valid_length) + memmove(l->rbuf, l->rbuf+l->rbuf_index, l->rbuf_valid_length); + } else { + /* Enlarge the buffer */ + char *new = pa_xmalloc(n); + if (l->rbuf_valid_length) + memcpy(new, l->rbuf+l->rbuf_index, l->rbuf_valid_length); + pa_xfree(l->rbuf); + l->rbuf = new; + l->rbuf_length = n; + } + + l->rbuf_index = 0; + } + + len = l->rbuf_length - l->rbuf_index - l->rbuf_valid_length; + + pa_assert(len >= READ_SIZE); + + /* Read some data */ + if ((r = pa_iochannel_read(l->io, l->rbuf+l->rbuf_index+l->rbuf_valid_length, len)) <= 0) { + if (r < 0 && errno != ECONNRESET) { + pa_log("read(): %s", pa_cstrerror(errno)); + failure(l, 0); + } else + failure(l, 1); + + return -1; + } + + l->rbuf_valid_length += r; + + /* Look if a line has been terminated in the newly read data */ + scan_for_lines(l, l->rbuf_valid_length - r); + } + + return 0; +} + +/* Try to flush the buffer */ +static int do_write(pa_ioline *l) { + ssize_t r; + + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + while (!l->dead && pa_iochannel_is_writable(l->io) && l->wbuf_valid_length) { + + if ((r = pa_iochannel_write(l->io, l->wbuf+l->wbuf_index, l->wbuf_valid_length)) <= 0) { + + if (r < 0 && errno != EPIPE) + pa_log("write(): %s", pa_cstrerror(errno)); + + failure(l, 0); + + return -1; + } + + l->wbuf_index += r; + l->wbuf_valid_length -= r; + + /* A shortcut for the next time */ + if (l->wbuf_valid_length == 0) + l->wbuf_index = 0; + } + + return 0; +} + +/* Try to flush read/write data */ +static void do_work(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + pa_ioline_ref(l); + + l->mainloop->defer_enable(l->defer_event, 0); + + if (!l->dead) + do_read(l); + + if (!l->dead) + do_write(l); + + if (l->defer_close && !l->wbuf_valid_length) + failure(l, 1); + + pa_ioline_unref(l); +} + +static void io_callback(pa_iochannel*io, void *userdata) { + pa_ioline *l = userdata; + + pa_assert(io); + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + do_work(l); +} + +static void defer_callback(pa_mainloop_api*m, pa_defer_event*e, void *userdata) { + pa_ioline *l = userdata; + + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + pa_assert(l->mainloop == m); + pa_assert(l->defer_event == e); + + do_work(l); +} + +void pa_ioline_defer_close(pa_ioline *l) { + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + l->defer_close = 1; + + if (!l->wbuf_valid_length) + l->mainloop->defer_enable(l->defer_event, 1); +} + +void pa_ioline_printf(pa_ioline *l, const char *format, ...) { + char *t; + va_list ap; + + pa_assert(l); + pa_assert(PA_REFCNT_VALUE(l) >= 1); + + va_start(ap, format); + t = pa_vsprintf_malloc(format, ap); + va_end(ap); + + pa_ioline_puts(l, t); + pa_xfree(t); +} diff --git a/src/pulsecore/ioline.h b/src/pulsecore/ioline.h new file mode 100644 index 00000000..8475b798 --- /dev/null +++ b/src/pulsecore/ioline.h @@ -0,0 +1,53 @@ +#ifndef fooiolinehfoo +#define fooiolinehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/iochannel.h> +#include <pulsecore/core-util.h> + +/* An ioline wraps an iochannel for line based communication. A + * callback function is called whenever a new line has been recieved + * from the client */ + +typedef struct pa_ioline pa_ioline; + +pa_ioline* pa_ioline_new(pa_iochannel *io); +void pa_ioline_unref(pa_ioline *l); +pa_ioline* pa_ioline_ref(pa_ioline *l); +void pa_ioline_close(pa_ioline *l); + +/* Write a string to the channel */ +void pa_ioline_puts(pa_ioline *s, const char *c); + +/* Write a string to the channel */ +void pa_ioline_printf(pa_ioline *s, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); + +/* Set the callback function that is called for every recieved line */ +void pa_ioline_set_callback(pa_ioline*io, void (*callback)(pa_ioline*io, const char *s, void *userdata), void *userdata); + +/* Make sure to close the ioline object as soon as the send buffer is emptied */ +void pa_ioline_defer_close(pa_ioline *io); + +#endif diff --git a/src/pulsecore/ipacl.c b/src/pulsecore/ipacl.c new file mode 100644 index 00000000..9b22e8f5 --- /dev/null +++ b/src/pulsecore/ipacl.c @@ -0,0 +1,239 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> +#include <sys/types.h> +#include <string.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN_SYSTM_H +#include <netinet/in_systm.h> +#endif +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/llist.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/winsock.h> + +#ifndef HAVE_INET_PTON +#include "inet_pton.h" +#endif + +#include "ipacl.h" + +struct acl_entry { + PA_LLIST_FIELDS(struct acl_entry); + int family; + struct in_addr address_ipv4; + struct in6_addr address_ipv6; + int bits; +}; + +struct pa_ip_acl { + PA_LLIST_HEAD(struct acl_entry, entries); +}; + +pa_ip_acl* pa_ip_acl_new(const char *s) { + const char *state = NULL; + char *a; + pa_ip_acl *acl; + + pa_assert(s); + + acl = pa_xnew(pa_ip_acl, 1); + PA_LLIST_HEAD_INIT(struct acl_entry, acl->entries); + + while ((a = pa_split(s, ";", &state))) { + char *slash; + struct acl_entry e, *n; + uint32_t bits; + + if ((slash = strchr(a, '/'))) { + *slash = 0; + slash++; + if (pa_atou(slash, &bits) < 0) { + pa_log_warn("Failed to parse number of bits: %s", slash); + goto fail; + } + } else + bits = (uint32_t) -1; + + if (inet_pton(AF_INET, a, &e.address_ipv4) > 0) { + + e.bits = bits == (uint32_t) -1 ? 32 : (int) bits; + + if (e.bits > 32) { + pa_log_warn("Number of bits out of range: %i", e.bits); + goto fail; + } + + e.family = AF_INET; + + if (e.bits < 32 && (uint32_t) (ntohl(e.address_ipv4.s_addr) << e.bits) != 0) + pa_log_warn("Host part of ACL entry '%s/%u' is not zero!", a, e.bits); + + } else if (inet_pton(AF_INET6, a, &e.address_ipv6) > 0) { + + e.bits = bits == (uint32_t) -1 ? 128 : (int) bits; + + if (e.bits > 128) { + pa_log_warn("Number of bits out of range: %i", e.bits); + goto fail; + } + e.family = AF_INET6; + + if (e.bits < 128) { + int t = 0, i; + + for (i = 0, bits = e.bits; i < 16; i++) { + + if (bits >= 8) + bits -= 8; + else { + if ((uint8_t) ((e.address_ipv6.s6_addr[i]) << bits) != 0) { + t = 1; + break; + } + bits = 0; + } + } + + if (t) + pa_log_warn("Host part of ACL entry '%s/%u' is not zero!", a, e.bits); + } + + } else { + pa_log_warn("Failed to parse address: %s", a); + goto fail; + } + + n = pa_xmemdup(&e, sizeof(struct acl_entry)); + PA_LLIST_PREPEND(struct acl_entry, acl->entries, n); + + pa_xfree(a); + } + + return acl; + +fail: + pa_xfree(a); + pa_ip_acl_free(acl); + + return NULL; +} + +void pa_ip_acl_free(pa_ip_acl *acl) { + pa_assert(acl); + + while (acl->entries) { + struct acl_entry *e = acl->entries; + PA_LLIST_REMOVE(struct acl_entry, acl->entries, e); + pa_xfree(e); + } + + pa_xfree(acl); +} + +int pa_ip_acl_check(pa_ip_acl *acl, int fd) { + struct sockaddr_storage sa; + struct acl_entry *e; + socklen_t salen; + + pa_assert(acl); + pa_assert(fd >= 0); + + salen = sizeof(sa); + if (getpeername(fd, (struct sockaddr*) &sa, &salen) < 0) + return -1; + + if (sa.ss_family != AF_INET && sa.ss_family != AF_INET6) + return -1; + + if (sa.ss_family == AF_INET && salen != sizeof(struct sockaddr_in)) + return -1; + + if (sa.ss_family == AF_INET6 && salen != sizeof(struct sockaddr_in6)) + return -1; + + for (e = acl->entries; e; e = e->next) { + + if (e->family != sa.ss_family) + continue; + + if (e->family == AF_INET) { + struct sockaddr_in *sai = (struct sockaddr_in*) &sa; + + if (e->bits == 0 || /* this needs special handling because >> takes the right-hand side modulo 32 */ + (ntohl(sai->sin_addr.s_addr ^ e->address_ipv4.s_addr) >> (32 - e->bits)) == 0) + return 1; + } else if (e->family == AF_INET6) { + int i, bits ; + struct sockaddr_in6 *sai = (struct sockaddr_in6*) &sa; + + if (e->bits == 128) + return memcmp(&sai->sin6_addr, &e->address_ipv6, 16) == 0; + + if (e->bits == 0) + return 1; + + for (i = 0, bits = e->bits; i < 16; i++) { + + if (bits >= 8) { + if (sai->sin6_addr.s6_addr[i] != e->address_ipv6.s6_addr[i]) + break; + + bits -= 8; + } else { + if ((sai->sin6_addr.s6_addr[i] ^ e->address_ipv6.s6_addr[i]) >> (8 - bits) != 0) + break; + + bits = 0; + } + + if (bits == 0) + return 1; + } + } + } + + return 0; +} diff --git a/src/pulsecore/ipacl.h b/src/pulsecore/ipacl.h new file mode 100644 index 00000000..175f54e0 --- /dev/null +++ b/src/pulsecore/ipacl.h @@ -0,0 +1,34 @@ +#ifndef fooparseaddrhfoo +#define fooparseaddrhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +typedef struct pa_ip_acl pa_ip_acl; + +pa_ip_acl* pa_ip_acl_new(const char *s); +void pa_ip_acl_free(pa_ip_acl *acl); +int pa_ip_acl_check(pa_ip_acl *acl, int fd); + +#endif diff --git a/src/pulsecore/llist.h b/src/pulsecore/llist.h new file mode 100644 index 00000000..e62f15b4 --- /dev/null +++ b/src/pulsecore/llist.h @@ -0,0 +1,109 @@ +#ifndef foollistfoo +#define foollistfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/macro.h> + +/* Some macros for maintaining doubly linked lists */ + +/* The head of the linked list. Use this in the structure that shall + * contain the head of the linked list */ +#define PA_LLIST_HEAD(t,name) \ + t *name + +/* The pointers in the linked list's items. Use this in the item structure */ +#define PA_LLIST_FIELDS(t) \ + t *next, *prev + +/* Initialize the list's head */ +#define PA_LLIST_HEAD_INIT(t,item) \ + do { \ + (item) = (t*) NULL; } \ + while(0) + +/* Initialize a list item */ +#define PA_LLIST_INIT(t,item) \ + do { \ + t *_item = (item); \ + pa_assert(_item); \ + _item->prev = _item->next = NULL; \ + } while(0) + +/* Prepend an item to the list */ +#define PA_LLIST_PREPEND(t,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + pa_assert(_item); \ + if ((_item->next = *_head)) \ + _item->next->prev = _item; \ + _item->prev = NULL; \ + *_head = _item; \ + } while (0) + +/* Remove an item from the list */ +#define PA_LLIST_REMOVE(t,head,item) \ + do { \ + t **_head = &(head), *_item = (item); \ + pa_assert(_item); \ + if (_item->next) \ + _item->next->prev = _item->prev; \ + if (_item->prev) \ + _item->prev->next = _item->next; \ + else { \ + pa_assert(*_head == _item); \ + *_head = _item->next; \ + } \ + _item->next = _item->prev = NULL; \ + } while(0) + +/* Find the head of the list */ +#define PA_LLIST_FIND_HEAD(t,item,head) \ + do { \ + t **_head = (head), *_item = (item); \ + *_head = _item; \ + pa_assert(_head); \ + while ((*_head)->prev) \ + *_head = (*_head)->prev; \ + } while (0) + +/* Insert an item after another one (a = where, b = what) */ +#define PA_LLIST_INSERT_AFTER(t,head,a,b) \ + do { \ + t **_head = &(head), *_a = (a), *_b = (b); \ + pa_assert(_b); \ + if (!_a) { \ + if ((_b->next = *_head)) \ + _b->next->prev = _b; \ + _b->prev = NULL; \ + *_head = _b; \ + } else { \ + if ((_b->next = _a->next)) \ + _b->next->prev = _b; \ + _b->prev = _a; \ + _a->next = _b; \ + } \ + } while (0) + +#endif diff --git a/src/pulsecore/log.c b/src/pulsecore/log.c new file mode 100644 index 00000000..c824e84d --- /dev/null +++ b/src/pulsecore/log.c @@ -0,0 +1,234 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdarg.h> +#include <stdio.h> +#include <unistd.h> +#include <string.h> + +#ifdef HAVE_SYSLOG_H +#include <syslog.h> +#endif + +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "log.h" + +#define ENV_LOGLEVEL "PULSE_LOG" +#define ENV_LOGMETA "PULSE_LOG_META" + +static char *log_ident = NULL, *log_ident_local = NULL; +static pa_log_target_t log_target = PA_LOG_STDERR; +static void (*user_log_func)(pa_log_level_t l, const char *s) = NULL; +static pa_log_level_t maximal_level = PA_LOG_NOTICE; + +#ifdef HAVE_SYSLOG_H +static const int level_to_syslog[] = { + [PA_LOG_ERROR] = LOG_ERR, + [PA_LOG_WARN] = LOG_WARNING, + [PA_LOG_NOTICE] = LOG_NOTICE, + [PA_LOG_INFO] = LOG_INFO, + [PA_LOG_DEBUG] = LOG_DEBUG +}; +#endif + +static const char level_to_char[] = { + [PA_LOG_ERROR] = 'E', + [PA_LOG_WARN] = 'W', + [PA_LOG_NOTICE] = 'N', + [PA_LOG_INFO] = 'I', + [PA_LOG_DEBUG] = 'D' +}; + +void pa_log_set_ident(const char *p) { + pa_xfree(log_ident); + pa_xfree(log_ident_local); + + log_ident = pa_xstrdup(p); + if (!(log_ident_local = pa_utf8_to_locale(log_ident))) + log_ident_local = pa_xstrdup(log_ident); +} + +/* To make valgrind shut up. */ +static void ident_destructor(void) PA_GCC_DESTRUCTOR; +static void ident_destructor(void) { + pa_xfree(log_ident); + pa_xfree(log_ident_local); +} + +void pa_log_set_maximal_level(pa_log_level_t l) { + pa_assert(l < PA_LOG_LEVEL_MAX); + + maximal_level = l; +} + +void pa_log_set_target(pa_log_target_t t, void (*func)(pa_log_level_t l, const char*s)) { + pa_assert(t == PA_LOG_USER || !func); + + log_target = t; + user_log_func = func; +} + +void pa_log_levelv_meta( + pa_log_level_t level, + const char*file, + int line, + const char *func, + const char *format, + va_list ap) { + + const char *e; + char *text, *t, *n, *location; + + pa_assert(level < PA_LOG_LEVEL_MAX); + pa_assert(format); + + if ((e = getenv(ENV_LOGLEVEL))) + maximal_level = atoi(e); + + if (level > maximal_level) + return; + + text = pa_vsprintf_malloc(format, ap); + + if (getenv(ENV_LOGMETA) && file && line > 0 && func) + location = pa_sprintf_malloc("[%s:%i %s()] ", file, line, func); + else if (file) + location = pa_sprintf_malloc("%s: ", pa_path_get_filename(file)); + else + location = pa_xstrdup(""); + + if (!pa_utf8_valid(text)) + pa_log_level(level, __FILE__": invalid UTF-8 string following below:"); + + for (t = text; t; t = n) { + if ((n = strchr(t, '\n'))) { + *n = 0; + n++; + } + + if (!*t) + continue; + + switch (log_target) { + case PA_LOG_STDERR: { + const char *prefix = "", *suffix = ""; + char *local_t; + +#ifndef OS_IS_WIN32 + /* Yes indeed. Useless, but fun! */ + if (isatty(STDERR_FILENO)) { + if (level <= PA_LOG_ERROR) { + prefix = "\x1B[1;31m"; + suffix = "\x1B[0m"; + } else if (level <= PA_LOG_WARN) { + prefix = "\x1B[1m"; + suffix = "\x1B[0m"; + } + } +#endif + + local_t = pa_utf8_to_locale(t); + if (!local_t) + fprintf(stderr, "%c: %s%s%s%s\n", level_to_char[level], location, prefix, t, suffix); + else { + fprintf(stderr, "%c: %s%s%s%s\n", level_to_char[level], location, prefix, local_t, suffix); + pa_xfree(local_t); + } + + break; + } + +#ifdef HAVE_SYSLOG_H + case PA_LOG_SYSLOG: { + char *local_t; + + openlog(log_ident_local ? log_ident_local : "???", LOG_PID, LOG_USER); + + local_t = pa_utf8_to_locale(t); + if (!local_t) + syslog(level_to_syslog[level], "%s%s", location, t); + else { + syslog(level_to_syslog[level], "%s%s", location, local_t); + pa_xfree(local_t); + } + + closelog(); + break; + } +#endif + + case PA_LOG_USER: { + char *x; + + x = pa_sprintf_malloc("%s%s", location, t); + user_log_func(level, x); + pa_xfree(x); + + break; + } + + case PA_LOG_NULL: + default: + break; + } + } + + pa_xfree(text); + pa_xfree(location); +} + +void pa_log_level_meta( + pa_log_level_t level, + const char*file, + int line, + const char *func, + const char *format, ...) { + + va_list ap; + va_start(ap, format); + pa_log_levelv_meta(level, file, line, func, format, ap); + va_end(ap); +} + +void pa_log_levelv(pa_log_level_t level, const char *format, va_list ap) { + pa_log_levelv_meta(level, NULL, 0, NULL, format, ap); +} + +void pa_log_level(pa_log_level_t level, const char *format, ...) { + va_list ap; + + va_start(ap, format); + pa_log_levelv_meta(level, NULL, 0, NULL, format, ap); + va_end(ap); +} diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h new file mode 100644 index 00000000..b0711dca --- /dev/null +++ b/src/pulsecore/log.h @@ -0,0 +1,107 @@ +#ifndef foologhfoo +#define foologhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdarg.h> +#include <stdlib.h> +#include <pulsecore/gccmacro.h> + +/* A simple logging subsystem */ + +/* Where to log to */ +typedef enum pa_log_target { + PA_LOG_STDERR, /* default */ + PA_LOG_SYSLOG, + PA_LOG_USER, /* to user specified function */ + PA_LOG_NULL /* to /dev/null */ +} pa_log_target_t; + +typedef enum pa_log_level { + PA_LOG_ERROR = 0, /* Error messages */ + PA_LOG_WARN = 1, /* Warning messages */ + PA_LOG_NOTICE = 2, /* Notice messages */ + PA_LOG_INFO = 3, /* Info messages */ + PA_LOG_DEBUG = 4, /* debug message */ + PA_LOG_LEVEL_MAX +} pa_log_level_t; + +/* Set an identification for the current daemon. Used when logging to syslog. */ +void pa_log_set_ident(const char *p); + +/* Set another log target. If t is PA_LOG_USER you may specify a function that is called every log string */ +void pa_log_set_target(pa_log_target_t t, void (*func)(pa_log_level_t t, const char*s)); + +/* Minimal log level */ +void pa_log_set_maximal_level(pa_log_level_t l); + +void pa_log_level_meta( + pa_log_level_t level, + const char*file, + int line, + const char *func, + const char *format, ...) PA_GCC_PRINTF_ATTR(5,6); +void pa_log_levelv_meta( + pa_log_level_t level, + const char*file, + int line, + const char *func, + const char *format, + va_list ap); + +void pa_log_level(pa_log_level_t level, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); +void pa_log_levelv(pa_log_level_t level, const char *format, va_list ap); + +#if __STDC_VERSION__ >= 199901L + +/* ISO varargs available */ + +#define pa_log_debug(...) pa_log_level_meta(PA_LOG_DEBUG, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_log_info(...) pa_log_level_meta(PA_LOG_INFO, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_log_notice(...) pa_log_level_meta(PA_LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_log_warn(...) pa_log_level_meta(PA_LOG_WARN, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_log_error(...) pa_log_level_meta(PA_LOG_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__) + +#else + +#define LOG_FUNC(suffix, level) \ +PA_GCC_UNUSED static void pa_log_##suffix(const char *format, ...) { \ + va_list ap; \ + va_start(ap, format); \ + pa_log_levelv_meta(level, NULL, 0, NULL, format, ap); \ + va_end(ap); \ +} + +LOG_FUNC(debug, PA_LOG_DEBUG) +LOG_FUNC(info, PA_LOG_INFO) +LOG_FUNC(notice, PA_LOG_NOTICE) +LOG_FUNC(warn, PA_LOG_WARN) +LOG_FUNC(error, PA_LOG_ERROR) + +#endif + +#define pa_log pa_log_error + +#endif diff --git a/src/pulsecore/ltdl-helper.c b/src/pulsecore/ltdl-helper.c new file mode 100644 index 00000000..711396d8 --- /dev/null +++ b/src/pulsecore/ltdl-helper.c @@ -0,0 +1,64 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <ctype.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "ltdl-helper.h" + +pa_void_func_t pa_load_sym(lt_dlhandle handle, const char *module, const char *symbol) { + char *sn, *c; + pa_void_func_t f; + + pa_assert(handle); + pa_assert(module); + pa_assert(symbol); + + if ((f = ((pa_void_func_t) (long) lt_dlsym(handle, symbol)))) + return f; + + /* As the .la files might have been cleansed from the system, we should + * try with the ltdl prefix as well. */ + + sn = pa_sprintf_malloc("%s_LTX_%s", module, symbol); + + for (c = sn; *c; c++) + if (!isalnum(*c)) + *c = '_'; + + f = (pa_void_func_t) (long) lt_dlsym(handle, sn); + pa_xfree(sn); + + return f; +} diff --git a/src/pulsecore/ltdl-helper.h b/src/pulsecore/ltdl-helper.h new file mode 100644 index 00000000..5c7388a1 --- /dev/null +++ b/src/pulsecore/ltdl-helper.h @@ -0,0 +1,34 @@ +#ifndef foopulsecoreltdlhelperhfoo +#define foopulsecoreltdlhelperhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <ltdl.h> + +typedef void (*pa_void_func_t)(void); + +pa_void_func_t pa_load_sym(lt_dlhandle handle, const char*module, const char *symbol); + +#endif + diff --git a/src/pulsecore/macro.h b/src/pulsecore/macro.h new file mode 100644 index 00000000..41af19c9 --- /dev/null +++ b/src/pulsecore/macro.h @@ -0,0 +1,159 @@ +#ifndef foopulsemacrohfoo +#define foopulsemacrohfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <sys/types.h> +#include <unistd.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> + +#include <pulsecore/log.h> +#include <pulsecore/gccmacro.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +#if defined(PAGE_SIZE) +#define PA_PAGE_SIZE ((size_t) PAGE_SIZE) +#elif defined(PAGESIZE) +#define PA_PAGE_SIZE ((size_t) PAGESIZE) +#elif defined(HAVE_SYSCONF) +#define PA_PAGE_SIZE ((size_t) (sysconf(_SC_PAGE_SIZE))) +#else +/* Let's hope it's like x86. */ +#define PA_PAGE_SIZE ((size_t) 4096) +#endif + +static inline size_t pa_align(size_t l) { + return (((l + sizeof(void*) - 1) / sizeof(void*)) * sizeof(void*)); +} +#define PA_ALIGN(x) (pa_align(x)) + +static inline void* pa_page_align_ptr(const void *p) { + return (void*) (((size_t) p) & ~(PA_PAGE_SIZE-1)); +} +#define PA_PAGE_ALIGN_PTR(x) (pa_page_align_ptr(x)) + +static inline size_t pa_page_align(size_t l) { + return l & ~(PA_PAGE_SIZE-1); +} +#define PA_PAGE_ALIGN(x) (pa_page_align(x)) + +#define PA_ELEMENTSOF(x) (sizeof(x)/sizeof((x)[0])) + +#ifndef MAX +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef CLAMP +#define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) +#endif + +#define PA_CLAMP_UNLIKELY(x, low, high) (PA_UNLIKELY((x) > (high)) ? (high) : (PA_UNLIKELY((x) < (low)) ? (low) : (x))) +/* We don't define a PA_CLAMP_LIKELY here, because it doesn't really + * make sense: we cannot know if it is more likely that the values is + * lower or greater than the boundaries.*/ + +/* This type is not intended to be used in exported APIs! Use classic "int" there! */ +#ifdef HAVE_STD_BOOL +typedef _Bool pa_bool_t; +#else +typedef int pa_bool_t; +#endif + +#ifndef FALSE +#define FALSE ((pa_bool_t) 0) +#endif + +#ifndef TRUE +#define TRUE (!FALSE) +#endif + +#ifdef __GNUC__ +#define PA_PRETTY_FUNCTION __PRETTY_FUNCTION__ +#else +#define PA_PRETTY_FUNCTION "" +#endif + +#define pa_return_if_fail(expr) \ + do { \ + if (!(expr)) { \ + pa_log_debug("%s: Assertion <%s> failed.\n", PA_PRETTY_FUNCTION, #expr ); \ + return; \ + } \ + } while(0) + +#define pa_return_val_if_fail(expr, val) \ + do { \ + if (!(expr)) { \ + pa_log_debug("%s: Assertion <%s> failed.\n", PA_PRETTY_FUNCTION, #expr ); \ + return (val); \ + } \ + } while(0) + +#define pa_return_null_if_fail(expr) pa_return_val_if_fail(expr, NULL) + +#define pa_assert assert + +#define pa_assert_not_reached() pa_assert(!"Should not be reached.") + +/* An assert which guarantees side effects of x */ +#ifdef NDEBUG +#define pa_assert_se(x) x +#else +#define pa_assert_se(x) pa_assert(x) +#endif + +#define PA_PTR_TO_UINT(p) ((unsigned int) (unsigned long) (p)) +#define PA_UINT_TO_PTR(u) ((void*) (unsigned long) (u)) + +#define PA_PTR_TO_UINT32(p) ((uint32_t) PA_PTR_TO_UINT(p)) +#define PA_UINT32_TO_PTR(u) PA_UINT_TO_PTR((uint32_t) u) + +#define PA_PTR_TO_INT(p) ((int) PA_PTR_TO_UINT(p)) +#define PA_INT_TO_PTR(u) PA_UINT_TO_PTR((int) u) + +#define PA_PTR_TO_INT32(p) ((int32_t) PA_PTR_TO_UINT(p)) +#define PA_INT32_TO_PTR(u) PA_UINT_TO_PTR((int32_t) u) + +#ifdef OS_IS_WIN32 +#define PA_PATH_SEP "\\" +#define PA_PATH_SEP_CHAR '\\' +#else +#define PA_PATH_SEP "/" +#define PA_PATH_SEP_CHAR '/' +#endif + +static inline const char *pa_strnull(const char *x) { + return x ? x : "(null)"; +} + +#endif diff --git a/src/pulsecore/mcalign.c b/src/pulsecore/mcalign.c new file mode 100644 index 00000000..8ca7c962 --- /dev/null +++ b/src/pulsecore/mcalign.c @@ -0,0 +1,213 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "mcalign.h" + +struct pa_mcalign { + size_t base; + pa_memchunk leftover, current; +}; + +pa_mcalign *pa_mcalign_new(size_t base) { + pa_mcalign *m; + pa_assert(base); + + m = pa_xnew(pa_mcalign, 1); + + m->base = base; + pa_memchunk_reset(&m->leftover); + pa_memchunk_reset(&m->current); + + return m; +} + +void pa_mcalign_free(pa_mcalign *m) { + pa_assert(m); + + if (m->leftover.memblock) + pa_memblock_unref(m->leftover.memblock); + + if (m->current.memblock) + pa_memblock_unref(m->current.memblock); + + pa_xfree(m); +} + +void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c) { + pa_assert(m); + pa_assert(c); + + pa_assert(c->memblock); + pa_assert(c->length > 0); + + pa_assert(!m->current.memblock); + + /* Append to the leftover memory block */ + if (m->leftover.memblock) { + + /* Try to merge */ + if (m->leftover.memblock == c->memblock && + m->leftover.index + m->leftover.length == c->index) { + + /* Merge */ + m->leftover.length += c->length; + + /* If the new chunk is larger than m->base, move it to current */ + if (m->leftover.length >= m->base) { + m->current = m->leftover; + pa_memchunk_reset(&m->leftover); + } + + } else { + size_t l; + void *lo_data, *m_data; + + /* We have to copy */ + pa_assert(m->leftover.length < m->base); + l = m->base - m->leftover.length; + + if (l > c->length) + l = c->length; + + /* Can we use the current block? */ + pa_memchunk_make_writable(&m->leftover, m->base); + + lo_data = pa_memblock_acquire(m->leftover.memblock); + m_data = pa_memblock_acquire(c->memblock); + memcpy((uint8_t*) lo_data + m->leftover.index + m->leftover.length, (uint8_t*) m_data + c->index, l); + pa_memblock_release(m->leftover.memblock); + pa_memblock_release(c->memblock); + m->leftover.length += l; + + pa_assert(m->leftover.length <= m->base); + pa_assert(m->leftover.length <= pa_memblock_get_length(m->leftover.memblock)); + + if (c->length > l) { + /* Save the remainder of the memory block */ + m->current = *c; + m->current.index += l; + m->current.length -= l; + pa_memblock_ref(m->current.memblock); + } + } + } else { + /* Nothing to merge or copy, just store it */ + + if (c->length >= m->base) + m->current = *c; + else + m->leftover = *c; + + pa_memblock_ref(c->memblock); + } +} + +int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c) { + pa_assert(m); + pa_assert(c); + + /* First test if there's a leftover memory block available */ + if (m->leftover.memblock) { + pa_assert(m->leftover.length > 0); + pa_assert(m->leftover.length <= m->base); + + /* The leftover memory block is not yet complete */ + if (m->leftover.length < m->base) + return -1; + + /* Return the leftover memory block */ + *c = m->leftover; + pa_memchunk_reset(&m->leftover); + + /* If the current memblock is too small move it the leftover */ + if (m->current.memblock && m->current.length < m->base) { + m->leftover = m->current; + pa_memchunk_reset(&m->current); + } + + return 0; + } + + /* Now let's see if there is other data available */ + if (m->current.memblock) { + size_t l; + pa_assert(m->current.length >= m->base); + + /* The length of the returned memory block */ + l = m->current.length; + l /= m->base; + l *= m->base; + pa_assert(l > 0); + + /* Prepare the returned block */ + *c = m->current; + pa_memblock_ref(c->memblock); + c->length = l; + + /* Drop that from the current memory block */ + pa_assert(l <= m->current.length); + m->current.index += l; + m->current.length -= l; + + /* In case the whole block was dropped ... */ + if (m->current.length == 0) + pa_memblock_unref(m->current.memblock); + else { + /* Move the raimainder to leftover */ + pa_assert(m->current.length < m->base && !m->leftover.memblock); + + m->leftover = m->current; + } + + pa_memchunk_reset(&m->current); + + return 0; + } + + /* There's simply nothing */ + return -1; + +} + +size_t pa_mcalign_csize(pa_mcalign *m, size_t l) { + pa_assert(m); + pa_assert(l > 0); + + pa_assert(!m->current.memblock); + + if (m->leftover.memblock) + l += m->leftover.length; + + return (l/m->base)*m->base; +} diff --git a/src/pulsecore/mcalign.h b/src/pulsecore/mcalign.h new file mode 100644 index 00000000..6ff8f94e --- /dev/null +++ b/src/pulsecore/mcalign.h @@ -0,0 +1,82 @@ +#ifndef foomcalignhfoo +#define foomcalignhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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/memblock.h> +#include <pulsecore/memchunk.h> + +/* An alignment object, used for aligning memchunks to multiples of + * the frame size. */ + +/* Method of operation: the user creates a new mcalign object by + * calling pa_mcalign_new() with the appropriate aligning + * granularity. After that he may call pa_mcalign_push() for an input + * memchunk. After exactly one memchunk the user has to call + * pa_mcalign_pop() until it returns -1. If pa_mcalign_pop() returns + * 0, the memchunk *c is valid and aligned to the granularity. Some + * pseudocode illustrating this: + * + * pa_mcalign *a = pa_mcalign_new(4, NULL); + * + * for (;;) { + * pa_memchunk input; + * + * ... fill input ... + * + * pa_mcalign_push(m, &input); + * pa_memblock_unref(input.memblock); + * + * for (;;) { + * pa_memchunk output; + * + * if (pa_mcalign_pop(m, &output) < 0) + * break; + * + * ... consume output ... + * + * pa_memblock_unref(output.memblock); + * } + * } + * + * pa_memchunk_free(a); + * */ + +typedef struct pa_mcalign pa_mcalign; + +pa_mcalign *pa_mcalign_new(size_t base); +void pa_mcalign_free(pa_mcalign *m); + +/* Push a new memchunk into the aligner. The caller of this routine + * has to free the memchunk by himself. */ +void pa_mcalign_push(pa_mcalign *m, const pa_memchunk *c); + +/* Pop a new memchunk from the aligner. Returns 0 when sucessful, + * nonzero otherwise. */ +int pa_mcalign_pop(pa_mcalign *m, pa_memchunk *c); + +/* If we pass l bytes in now, how many bytes would we get out? */ +size_t pa_mcalign_csize(pa_mcalign *m, size_t l); + +#endif diff --git a/src/pulsecore/memblock.c b/src/pulsecore/memblock.c new file mode 100644 index 00000000..99b5a13f --- /dev/null +++ b/src/pulsecore/memblock.c @@ -0,0 +1,1117 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <signal.h> +#include <errno.h> + +#include <pulse/xmalloc.h> +#include <pulse/def.h> + +#include <pulsecore/shm.h> +#include <pulsecore/log.h> +#include <pulsecore/hashmap.h> +#include <pulsecore/semaphore.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> +#include <pulsecore/core-util.h> + +#include "memblock.h" + +#define PA_MEMPOOL_SLOTS_MAX 128 +#define PA_MEMPOOL_SLOT_SIZE (16*1024) + +#define PA_MEMEXPORT_SLOTS_MAX 128 + +#define PA_MEMIMPORT_SLOTS_MAX 128 +#define PA_MEMIMPORT_SEGMENTS_MAX 16 + +struct pa_memblock { + PA_REFCNT_DECLARE; /* the reference counter */ + pa_mempool *pool; + + pa_memblock_type_t type; + int read_only; /* boolean */ + + pa_atomic_ptr_t data; + size_t length; + + pa_atomic_t n_acquired; + pa_atomic_t please_signal; + + union { + struct { + /* If type == PA_MEMBLOCK_USER this points to a function for freeing this memory block */ + pa_free_cb_t free_cb; + } user; + + struct { + uint32_t id; + pa_memimport_segment *segment; + } imported; + } per_type; +}; + +struct pa_memimport_segment { + pa_memimport *import; + pa_shm memory; + unsigned n_blocks; +}; + +struct pa_memimport { + pa_mutex *mutex; + + pa_mempool *pool; + pa_hashmap *segments; + pa_hashmap *blocks; + + /* Called whenever an imported memory block is no longer + * needed. */ + pa_memimport_release_cb_t release_cb; + void *userdata; + + PA_LLIST_FIELDS(pa_memimport); +}; + +struct memexport_slot { + PA_LLIST_FIELDS(struct memexport_slot); + pa_memblock *block; +}; + +struct pa_memexport { + pa_mutex *mutex; + pa_mempool *pool; + + struct memexport_slot slots[PA_MEMEXPORT_SLOTS_MAX]; + + PA_LLIST_HEAD(struct memexport_slot, free_slots); + PA_LLIST_HEAD(struct memexport_slot, used_slots); + unsigned n_init; + + /* Called whenever a client from which we imported a memory block + which we in turn exported to another client dies and we need to + revoke the memory block accordingly */ + pa_memexport_revoke_cb_t revoke_cb; + void *userdata; + + PA_LLIST_FIELDS(pa_memexport); +}; + +struct mempool_slot { + PA_LLIST_FIELDS(struct mempool_slot); + /* the actual data follows immediately hereafter */ +}; + +struct pa_mempool { + pa_semaphore *semaphore; + pa_mutex *mutex; + + pa_shm memory; + size_t block_size; + unsigned n_blocks; + + pa_atomic_t n_init; + + PA_LLIST_HEAD(pa_memimport, imports); + PA_LLIST_HEAD(pa_memexport, exports); + + /* A list of free slots that may be reused */ + pa_flist *free_slots; + + pa_mempool_stat stat; +}; + +static void segment_detach(pa_memimport_segment *seg); + +PA_STATIC_FLIST_DECLARE(unused_memblocks, 0, pa_xfree); + +/* No lock necessary */ +static void stat_add(pa_memblock*b) { + pa_assert(b); + pa_assert(b->pool); + + pa_atomic_inc(&b->pool->stat.n_allocated); + pa_atomic_add(&b->pool->stat.allocated_size, b->length); + + pa_atomic_inc(&b->pool->stat.n_accumulated); + pa_atomic_add(&b->pool->stat.accumulated_size, b->length); + + if (b->type == PA_MEMBLOCK_IMPORTED) { + pa_atomic_inc(&b->pool->stat.n_imported); + pa_atomic_add(&b->pool->stat.imported_size, b->length); + } + + pa_atomic_inc(&b->pool->stat.n_allocated_by_type[b->type]); + pa_atomic_inc(&b->pool->stat.n_accumulated_by_type[b->type]); +} + +/* No lock necessary */ +static void stat_remove(pa_memblock *b) { + pa_assert(b); + pa_assert(b->pool); + + pa_assert(pa_atomic_load(&b->pool->stat.n_allocated) > 0); + pa_assert(pa_atomic_load(&b->pool->stat.allocated_size) >= (int) b->length); + + pa_atomic_dec(&b->pool->stat.n_allocated); + pa_atomic_sub(&b->pool->stat.allocated_size, b->length); + + if (b->type == PA_MEMBLOCK_IMPORTED) { + pa_assert(pa_atomic_load(&b->pool->stat.n_imported) > 0); + pa_assert(pa_atomic_load(&b->pool->stat.imported_size) >= (int) b->length); + + pa_atomic_dec(&b->pool->stat.n_imported); + pa_atomic_sub(&b->pool->stat.imported_size, b->length); + } + + pa_atomic_dec(&b->pool->stat.n_allocated_by_type[b->type]); +} + +static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length); + +/* No lock necessary */ +pa_memblock *pa_memblock_new(pa_mempool *p, size_t length) { + pa_memblock *b; + + pa_assert(p); + pa_assert(length > 0); + + if (!(b = pa_memblock_new_pool(p, length))) + b = memblock_new_appended(p, length); + + return b; +} + +/* No lock necessary */ +static pa_memblock *memblock_new_appended(pa_mempool *p, size_t length) { + pa_memblock *b; + + pa_assert(p); + pa_assert(length > 0); + + /* If -1 is passed as length we choose the size for the caller. */ + + if (length == (size_t) -1) + length = p->block_size - PA_ALIGN(sizeof(struct mempool_slot)) - PA_ALIGN(sizeof(pa_memblock)); + + b = pa_xmalloc(PA_ALIGN(sizeof(pa_memblock)) + length); + PA_REFCNT_INIT(b); + b->pool = p; + b->type = PA_MEMBLOCK_APPENDED; + b->read_only = 0; + pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock))); + b->length = length; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + + stat_add(b); + return b; +} + +/* No lock necessary */ +static struct mempool_slot* mempool_allocate_slot(pa_mempool *p) { + struct mempool_slot *slot; + pa_assert(p); + + if (!(slot = pa_flist_pop(p->free_slots))) { + int idx; + + /* The free list was empty, we have to allocate a new entry */ + + if ((unsigned) (idx = pa_atomic_inc(&p->n_init)) >= p->n_blocks) + pa_atomic_dec(&p->n_init); + else + slot = (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (p->block_size * idx)); + + if (!slot) { + pa_log_debug("Pool full"); + pa_atomic_inc(&p->stat.n_pool_full); + return NULL; + } + } + + return slot; +} + +/* No lock necessary */ +static void* mempool_slot_data(struct mempool_slot *slot) { + pa_assert(slot); + + return (uint8_t*) slot + PA_ALIGN(sizeof(struct mempool_slot)); +} + +/* No lock necessary */ +static unsigned mempool_slot_idx(pa_mempool *p, void *ptr) { + pa_assert(p); + + pa_assert((uint8_t*) ptr >= (uint8_t*) p->memory.ptr); + pa_assert((uint8_t*) ptr < (uint8_t*) p->memory.ptr + p->memory.size); + + return ((uint8_t*) ptr - (uint8_t*) p->memory.ptr) / p->block_size; +} + +/* No lock necessary */ +static struct mempool_slot* mempool_slot_by_ptr(pa_mempool *p, void *ptr) { + unsigned idx; + + if ((idx = mempool_slot_idx(p, ptr)) == (unsigned) -1) + return NULL; + + return (struct mempool_slot*) ((uint8_t*) p->memory.ptr + (idx * p->block_size)); +} + +/* No lock necessary */ +pa_memblock *pa_memblock_new_pool(pa_mempool *p, size_t length) { + pa_memblock *b = NULL; + struct mempool_slot *slot; + + pa_assert(p); + pa_assert(length > 0); + + /* If -1 is passed as length we choose the size for the caller: we + * take the largest size that fits in one of our slots. */ + + if (length == (size_t) -1) + length = pa_mempool_block_size_max(p); + + if (p->block_size - PA_ALIGN(sizeof(struct mempool_slot)) >= PA_ALIGN(sizeof(pa_memblock)) + length) { + + if (!(slot = mempool_allocate_slot(p))) + return NULL; + + b = mempool_slot_data(slot); + b->type = PA_MEMBLOCK_POOL; + pa_atomic_ptr_store(&b->data, (uint8_t*) b + PA_ALIGN(sizeof(pa_memblock))); + + } else if (p->block_size - PA_ALIGN(sizeof(struct mempool_slot)) >= length) { + + if (!(slot = mempool_allocate_slot(p))) + return NULL; + + if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) + b = pa_xnew(pa_memblock, 1); + + b->type = PA_MEMBLOCK_POOL_EXTERNAL; + pa_atomic_ptr_store(&b->data, mempool_slot_data(slot)); + + } else { + pa_log_debug("Memory block too large for pool: %lu > %lu", (unsigned long) length, (unsigned long) (p->block_size - PA_ALIGN(sizeof(struct mempool_slot)))); + pa_atomic_inc(&p->stat.n_too_large_for_pool); + return NULL; + } + + PA_REFCNT_INIT(b); + b->pool = p; + b->read_only = 0; + b->length = length; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + + stat_add(b); + return b; +} + +/* No lock necessary */ +pa_memblock *pa_memblock_new_fixed(pa_mempool *p, void *d, size_t length, int read_only) { + pa_memblock *b; + + pa_assert(p); + pa_assert(d); + pa_assert(length != (size_t) -1); + pa_assert(length > 0); + + if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) + b = pa_xnew(pa_memblock, 1); + PA_REFCNT_INIT(b); + b->pool = p; + b->type = PA_MEMBLOCK_FIXED; + b->read_only = read_only; + pa_atomic_ptr_store(&b->data, d); + b->length = length; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + + stat_add(b); + return b; +} + +/* No lock necessary */ +pa_memblock *pa_memblock_new_user(pa_mempool *p, void *d, size_t length, void (*free_cb)(void *p), int read_only) { + pa_memblock *b; + + pa_assert(p); + pa_assert(d); + pa_assert(length > 0); + pa_assert(length != (size_t) -1); + pa_assert(free_cb); + + if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) + b = pa_xnew(pa_memblock, 1); + PA_REFCNT_INIT(b); + b->pool = p; + b->type = PA_MEMBLOCK_USER; + b->read_only = read_only; + pa_atomic_ptr_store(&b->data, d); + b->length = length; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + + b->per_type.user.free_cb = free_cb; + + stat_add(b); + return b; +} + +/* No lock necessary */ +int pa_memblock_is_read_only(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + return b->read_only && PA_REFCNT_VALUE(b) == 1; +} + +/* No lock necessary */ +int pa_memblock_ref_is_one(pa_memblock *b) { + int r; + + pa_assert(b); + + r = PA_REFCNT_VALUE(b); + pa_assert(r > 0); + + return r == 1; +} + +/* No lock necessary */ +void* pa_memblock_acquire(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + pa_atomic_inc(&b->n_acquired); + + return pa_atomic_ptr_load(&b->data); +} + +/* No lock necessary, in corner cases locks by its own */ +void pa_memblock_release(pa_memblock *b) { + int r; + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + r = pa_atomic_dec(&b->n_acquired); + pa_assert(r >= 1); + + /* Signal a waiting thread that this memblock is no longer used */ + if (r == 1 && pa_atomic_load(&b->please_signal)) + pa_semaphore_post(b->pool->semaphore); +} + +size_t pa_memblock_get_length(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + return b->length; +} + +pa_mempool* pa_memblock_get_pool(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + return b->pool; +} + +/* No lock necessary */ +pa_memblock* pa_memblock_ref(pa_memblock*b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + PA_REFCNT_INC(b); + return b; +} + +static void memblock_free(pa_memblock *b) { + pa_assert(b); + + pa_assert(pa_atomic_load(&b->n_acquired) == 0); + + stat_remove(b); + + switch (b->type) { + case PA_MEMBLOCK_USER : + pa_assert(b->per_type.user.free_cb); + b->per_type.user.free_cb(pa_atomic_ptr_load(&b->data)); + + /* Fall through */ + + case PA_MEMBLOCK_FIXED: + case PA_MEMBLOCK_APPENDED : + if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0) + pa_xfree(b); + + break; + + case PA_MEMBLOCK_IMPORTED : { + pa_memimport_segment *segment; + pa_memimport *import; + + /* FIXME! This should be implemented lock-free */ + + segment = b->per_type.imported.segment; + pa_assert(segment); + import = segment->import; + pa_assert(import); + + pa_mutex_lock(import->mutex); + pa_hashmap_remove(import->blocks, PA_UINT32_TO_PTR(b->per_type.imported.id)); + if (-- segment->n_blocks <= 0) + segment_detach(segment); + + pa_mutex_unlock(import->mutex); + + import->release_cb(import, b->per_type.imported.id, import->userdata); + + if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0) + pa_xfree(b); + break; + } + + case PA_MEMBLOCK_POOL_EXTERNAL: + case PA_MEMBLOCK_POOL: { + struct mempool_slot *slot; + int call_free; + + slot = mempool_slot_by_ptr(b->pool, pa_atomic_ptr_load(&b->data)); + pa_assert(slot); + + call_free = b->type == PA_MEMBLOCK_POOL_EXTERNAL; + + /* The free list dimensions should easily allow all slots + * to fit in, hence try harder if pushing this slot into + * the free list fails */ + while (pa_flist_push(b->pool->free_slots, slot) < 0) + ; + + if (call_free) + if (pa_flist_push(PA_STATIC_FLIST_GET(unused_memblocks), b) < 0) + pa_xfree(b); + + break; + } + + case PA_MEMBLOCK_TYPE_MAX: + default: + pa_assert_not_reached(); + } +} + +/* No lock necessary */ +void pa_memblock_unref(pa_memblock*b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + if (PA_REFCNT_DEC(b) > 0) + return; + + memblock_free(b); +} + +/* Self locked */ +static void memblock_wait(pa_memblock *b) { + pa_assert(b); + + if (pa_atomic_load(&b->n_acquired) > 0) { + /* We need to wait until all threads gave up access to the + * memory block before we can go on. Unfortunately this means + * that we have to lock and wait here. Sniff! */ + + pa_atomic_inc(&b->please_signal); + + while (pa_atomic_load(&b->n_acquired) > 0) + pa_semaphore_wait(b->pool->semaphore); + + pa_atomic_dec(&b->please_signal); + } +} + +/* No lock necessary. This function is not multiple caller safe! */ +static void memblock_make_local(pa_memblock *b) { + pa_assert(b); + + pa_atomic_dec(&b->pool->stat.n_allocated_by_type[b->type]); + + if (b->length <= b->pool->block_size - PA_ALIGN(sizeof(struct mempool_slot))) { + struct mempool_slot *slot; + + if ((slot = mempool_allocate_slot(b->pool))) { + void *new_data; + /* We can move it into a local pool, perfect! */ + + new_data = mempool_slot_data(slot); + memcpy(new_data, pa_atomic_ptr_load(&b->data), b->length); + pa_atomic_ptr_store(&b->data, new_data); + + b->type = PA_MEMBLOCK_POOL_EXTERNAL; + b->read_only = 0; + + goto finish; + } + } + + /* Humm, not enough space in the pool, so lets allocate the memory with malloc() */ + b->per_type.user.free_cb = pa_xfree; + pa_atomic_ptr_store(&b->data, pa_xmemdup(pa_atomic_ptr_load(&b->data), b->length)); + + b->type = PA_MEMBLOCK_USER; + b->read_only = 0; + +finish: + pa_atomic_inc(&b->pool->stat.n_allocated_by_type[b->type]); + pa_atomic_inc(&b->pool->stat.n_accumulated_by_type[b->type]); + memblock_wait(b); +} + +/* No lock necessary. This function is not multiple caller safe*/ +void pa_memblock_unref_fixed(pa_memblock *b) { + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + pa_assert(b->type == PA_MEMBLOCK_FIXED); + + if (PA_REFCNT_VALUE(b) > 1) + memblock_make_local(b); + + pa_memblock_unref(b); +} + +/* No lock necessary. */ +pa_memblock *pa_memblock_will_need(pa_memblock *b) { + void *p; + + pa_assert(b); + pa_assert(PA_REFCNT_VALUE(b) > 0); + + p = pa_memblock_acquire(b); + pa_will_need(p, b->length); + pa_memblock_release(b); + + return b; +} + +/* Self-locked. This function is not multiple-caller safe */ +static void memblock_replace_import(pa_memblock *b) { + pa_memimport_segment *seg; + + pa_assert(b); + pa_assert(b->type == PA_MEMBLOCK_IMPORTED); + + pa_assert(pa_atomic_load(&b->pool->stat.n_imported) > 0); + pa_assert(pa_atomic_load(&b->pool->stat.imported_size) >= (int) b->length); + pa_atomic_dec(&b->pool->stat.n_imported); + pa_atomic_sub(&b->pool->stat.imported_size, b->length); + + seg = b->per_type.imported.segment; + pa_assert(seg); + pa_assert(seg->import); + + pa_mutex_lock(seg->import->mutex); + + pa_hashmap_remove( + seg->import->blocks, + PA_UINT32_TO_PTR(b->per_type.imported.id)); + + memblock_make_local(b); + + if (-- seg->n_blocks <= 0) { + pa_mutex_unlock(seg->import->mutex); + segment_detach(seg); + } else + pa_mutex_unlock(seg->import->mutex); +} + +pa_mempool* pa_mempool_new(int shared) { + pa_mempool *p; + + p = pa_xnew(pa_mempool, 1); + + p->mutex = pa_mutex_new(TRUE, TRUE); + p->semaphore = pa_semaphore_new(0); + + p->block_size = PA_PAGE_ALIGN(PA_MEMPOOL_SLOT_SIZE); + if (p->block_size < PA_PAGE_SIZE) + p->block_size = PA_PAGE_SIZE; + + p->n_blocks = PA_MEMPOOL_SLOTS_MAX; + + pa_assert(p->block_size > PA_ALIGN(sizeof(struct mempool_slot))); + + if (pa_shm_create_rw(&p->memory, p->n_blocks * p->block_size, shared, 0700) < 0) { + pa_xfree(p); + return NULL; + } + + memset(&p->stat, 0, sizeof(p->stat)); + pa_atomic_store(&p->n_init, 0); + + PA_LLIST_HEAD_INIT(pa_memimport, p->imports); + PA_LLIST_HEAD_INIT(pa_memexport, p->exports); + + p->free_slots = pa_flist_new(p->n_blocks*2); + + return p; +} + +void pa_mempool_free(pa_mempool *p) { + pa_assert(p); + + pa_mutex_lock(p->mutex); + + while (p->imports) + pa_memimport_free(p->imports); + + while (p->exports) + pa_memexport_free(p->exports); + + pa_mutex_unlock(p->mutex); + + pa_flist_free(p->free_slots, NULL); + + if (pa_atomic_load(&p->stat.n_allocated) > 0) { +/* raise(SIGTRAP); */ + pa_log_warn("Memory pool destroyed but not all memory blocks freed! %u remain.", pa_atomic_load(&p->stat.n_allocated)); + } + + pa_shm_free(&p->memory); + + pa_mutex_free(p->mutex); + pa_semaphore_free(p->semaphore); + + pa_xfree(p); +} + +/* No lock necessary */ +const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p) { + pa_assert(p); + + return &p->stat; +} + +/* No lock necessary */ +size_t pa_mempool_block_size_max(pa_mempool *p) { + pa_assert(p); + + return p->block_size - PA_ALIGN(sizeof(struct mempool_slot)) - PA_ALIGN(sizeof(pa_memblock)); +} + +/* No lock necessary */ +void pa_mempool_vacuum(pa_mempool *p) { + struct mempool_slot *slot; + pa_flist *list; + + pa_assert(p); + + list = pa_flist_new(p->n_blocks*2); + + while ((slot = pa_flist_pop(p->free_slots))) + while (pa_flist_push(list, slot) < 0) + ; + + while ((slot = pa_flist_pop(list))) { + pa_shm_punch(&p->memory, + (uint8_t*) slot - (uint8_t*) p->memory.ptr + PA_ALIGN(sizeof(struct mempool_slot)), + p->block_size - PA_ALIGN(sizeof(struct mempool_slot))); + + while (pa_flist_push(p->free_slots, slot)) + ; + } + + pa_flist_free(list, NULL); +} + +/* No lock necessary */ +int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id) { + pa_assert(p); + + if (!p->memory.shared) + return -1; + + *id = p->memory.id; + + return 0; +} + +/* No lock necessary */ +int pa_mempool_is_shared(pa_mempool *p) { + pa_assert(p); + + return !!p->memory.shared; +} + +/* For recieving blocks from other nodes */ +pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void *userdata) { + pa_memimport *i; + + pa_assert(p); + pa_assert(cb); + + i = pa_xnew(pa_memimport, 1); + i->mutex = pa_mutex_new(TRUE, TRUE); + i->pool = p; + i->segments = pa_hashmap_new(NULL, NULL); + i->blocks = pa_hashmap_new(NULL, NULL); + i->release_cb = cb; + i->userdata = userdata; + + pa_mutex_lock(p->mutex); + PA_LLIST_PREPEND(pa_memimport, p->imports, i); + pa_mutex_unlock(p->mutex); + + return i; +} + +static void memexport_revoke_blocks(pa_memexport *e, pa_memimport *i); + +/* Should be called locked */ +static pa_memimport_segment* segment_attach(pa_memimport *i, uint32_t shm_id) { + pa_memimport_segment* seg; + + if (pa_hashmap_size(i->segments) >= PA_MEMIMPORT_SEGMENTS_MAX) + return NULL; + + seg = pa_xnew(pa_memimport_segment, 1); + + if (pa_shm_attach_ro(&seg->memory, shm_id) < 0) { + pa_xfree(seg); + return NULL; + } + + seg->import = i; + seg->n_blocks = 0; + + pa_hashmap_put(i->segments, PA_UINT32_TO_PTR(shm_id), seg); + return seg; +} + +/* Should be called locked */ +static void segment_detach(pa_memimport_segment *seg) { + pa_assert(seg); + + pa_hashmap_remove(seg->import->segments, PA_UINT32_TO_PTR(seg->memory.id)); + pa_shm_free(&seg->memory); + pa_xfree(seg); +} + +/* Self-locked. Not multiple-caller safe */ +void pa_memimport_free(pa_memimport *i) { + pa_memexport *e; + pa_memblock *b; + + pa_assert(i); + + pa_mutex_lock(i->mutex); + + while ((b = pa_hashmap_get_first(i->blocks))) + memblock_replace_import(b); + + pa_assert(pa_hashmap_size(i->segments) == 0); + + pa_mutex_unlock(i->mutex); + + pa_mutex_lock(i->pool->mutex); + + /* If we've exported this block further we need to revoke that export */ + for (e = i->pool->exports; e; e = e->next) + memexport_revoke_blocks(e, i); + + PA_LLIST_REMOVE(pa_memimport, i->pool->imports, i); + + pa_mutex_unlock(i->pool->mutex); + + pa_hashmap_free(i->blocks, NULL, NULL); + pa_hashmap_free(i->segments, NULL, NULL); + + pa_mutex_free(i->mutex); + + pa_xfree(i); +} + +/* Self-locked */ +pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_id, size_t offset, size_t size) { + pa_memblock *b = NULL; + pa_memimport_segment *seg; + + pa_assert(i); + + pa_mutex_lock(i->mutex); + + if (pa_hashmap_size(i->blocks) >= PA_MEMIMPORT_SLOTS_MAX) + goto finish; + + if (!(seg = pa_hashmap_get(i->segments, PA_UINT32_TO_PTR(shm_id)))) + if (!(seg = segment_attach(i, shm_id))) + goto finish; + + if (offset+size > seg->memory.size) + goto finish; + + if (!(b = pa_flist_pop(PA_STATIC_FLIST_GET(unused_memblocks)))) + b = pa_xnew(pa_memblock, 1); + + PA_REFCNT_INIT(b); + b->pool = i->pool; + b->type = PA_MEMBLOCK_IMPORTED; + b->read_only = 1; + pa_atomic_ptr_store(&b->data, (uint8_t*) seg->memory.ptr + offset); + b->length = size; + pa_atomic_store(&b->n_acquired, 0); + pa_atomic_store(&b->please_signal, 0); + b->per_type.imported.id = block_id; + b->per_type.imported.segment = seg; + + pa_hashmap_put(i->blocks, PA_UINT32_TO_PTR(block_id), b); + + seg->n_blocks++; + +finish: + pa_mutex_unlock(i->mutex); + + if (b) + stat_add(b); + + return b; +} + +int pa_memimport_process_revoke(pa_memimport *i, uint32_t id) { + pa_memblock *b; + int ret = 0; + pa_assert(i); + + pa_mutex_lock(i->mutex); + + if (!(b = pa_hashmap_get(i->blocks, PA_UINT32_TO_PTR(id)))) { + ret = -1; + goto finish; + } + + memblock_replace_import(b); + +finish: + pa_mutex_unlock(i->mutex); + + return ret; +} + +/* For sending blocks to other nodes */ +pa_memexport* pa_memexport_new(pa_mempool *p, pa_memexport_revoke_cb_t cb, void *userdata) { + pa_memexport *e; + + pa_assert(p); + pa_assert(cb); + + if (!p->memory.shared) + return NULL; + + e = pa_xnew(pa_memexport, 1); + e->mutex = pa_mutex_new(TRUE, TRUE); + e->pool = p; + PA_LLIST_HEAD_INIT(struct memexport_slot, e->free_slots); + PA_LLIST_HEAD_INIT(struct memexport_slot, e->used_slots); + e->n_init = 0; + e->revoke_cb = cb; + e->userdata = userdata; + + pa_mutex_lock(p->mutex); + PA_LLIST_PREPEND(pa_memexport, p->exports, e); + pa_mutex_unlock(p->mutex); + return e; +} + +void pa_memexport_free(pa_memexport *e) { + pa_assert(e); + + pa_mutex_lock(e->mutex); + while (e->used_slots) + pa_memexport_process_release(e, e->used_slots - e->slots); + pa_mutex_unlock(e->mutex); + + pa_mutex_lock(e->pool->mutex); + PA_LLIST_REMOVE(pa_memexport, e->pool->exports, e); + pa_mutex_unlock(e->pool->mutex); + + pa_mutex_free(e->mutex); + pa_xfree(e); +} + +/* Self-locked */ +int pa_memexport_process_release(pa_memexport *e, uint32_t id) { + pa_memblock *b; + + pa_assert(e); + + pa_mutex_lock(e->mutex); + + if (id >= e->n_init) + goto fail; + + if (!e->slots[id].block) + goto fail; + + b = e->slots[id].block; + e->slots[id].block = NULL; + + PA_LLIST_REMOVE(struct memexport_slot, e->used_slots, &e->slots[id]); + PA_LLIST_PREPEND(struct memexport_slot, e->free_slots, &e->slots[id]); + + pa_mutex_unlock(e->mutex); + +/* pa_log("Processing release for %u", id); */ + + pa_assert(pa_atomic_load(&e->pool->stat.n_exported) > 0); + pa_assert(pa_atomic_load(&e->pool->stat.exported_size) >= (int) b->length); + + pa_atomic_dec(&e->pool->stat.n_exported); + pa_atomic_sub(&e->pool->stat.exported_size, b->length); + + pa_memblock_unref(b); + + return 0; + +fail: + pa_mutex_unlock(e->mutex); + + return -1; +} + +/* Self-locked */ +static void memexport_revoke_blocks(pa_memexport *e, pa_memimport *i) { + struct memexport_slot *slot, *next; + pa_assert(e); + pa_assert(i); + + pa_mutex_lock(e->mutex); + + for (slot = e->used_slots; slot; slot = next) { + uint32_t idx; + next = slot->next; + + if (slot->block->type != PA_MEMBLOCK_IMPORTED || + slot->block->per_type.imported.segment->import != i) + continue; + + idx = slot - e->slots; + e->revoke_cb(e, idx, e->userdata); + pa_memexport_process_release(e, idx); + } + + pa_mutex_unlock(e->mutex); +} + +/* No lock necessary */ +static pa_memblock *memblock_shared_copy(pa_mempool *p, pa_memblock *b) { + pa_memblock *n; + + pa_assert(p); + pa_assert(b); + + if (b->type == PA_MEMBLOCK_IMPORTED || + b->type == PA_MEMBLOCK_POOL || + b->type == PA_MEMBLOCK_POOL_EXTERNAL) { + pa_assert(b->pool == p); + return pa_memblock_ref(b); + } + + if (!(n = pa_memblock_new_pool(p, b->length))) + return NULL; + + memcpy(pa_atomic_ptr_load(&n->data), pa_atomic_ptr_load(&b->data), b->length); + return n; +} + +/* Self-locked */ +int pa_memexport_put(pa_memexport *e, pa_memblock *b, uint32_t *block_id, uint32_t *shm_id, size_t *offset, size_t * size) { + pa_shm *memory; + struct memexport_slot *slot; + void *data; + + pa_assert(e); + pa_assert(b); + pa_assert(block_id); + pa_assert(shm_id); + pa_assert(offset); + pa_assert(size); + pa_assert(b->pool == e->pool); + + if (!(b = memblock_shared_copy(e->pool, b))) + return -1; + + pa_mutex_lock(e->mutex); + + if (e->free_slots) { + slot = e->free_slots; + PA_LLIST_REMOVE(struct memexport_slot, e->free_slots, slot); + } else if (e->n_init < PA_MEMEXPORT_SLOTS_MAX) + slot = &e->slots[e->n_init++]; + else { + pa_mutex_unlock(e->mutex); + pa_memblock_unref(b); + return -1; + } + + PA_LLIST_PREPEND(struct memexport_slot, e->used_slots, slot); + slot->block = b; + *block_id = slot - e->slots; + + pa_mutex_unlock(e->mutex); +/* pa_log("Got block id %u", *block_id); */ + + data = pa_memblock_acquire(b); + + if (b->type == PA_MEMBLOCK_IMPORTED) { + pa_assert(b->per_type.imported.segment); + memory = &b->per_type.imported.segment->memory; + } else { + pa_assert(b->type == PA_MEMBLOCK_POOL || b->type == PA_MEMBLOCK_POOL_EXTERNAL); + pa_assert(b->pool); + memory = &b->pool->memory; + } + + pa_assert(data >= memory->ptr); + pa_assert((uint8_t*) data + b->length <= (uint8_t*) memory->ptr + memory->size); + + *shm_id = memory->id; + *offset = (uint8_t*) data - (uint8_t*) memory->ptr; + *size = b->length; + + pa_memblock_release(b); + + pa_atomic_inc(&e->pool->stat.n_exported); + pa_atomic_add(&e->pool->stat.exported_size, b->length); + + return 0; +} diff --git a/src/pulsecore/memblock.h b/src/pulsecore/memblock.h new file mode 100644 index 00000000..c704014a --- /dev/null +++ b/src/pulsecore/memblock.h @@ -0,0 +1,139 @@ +#ifndef foopulsememblockhfoo +#define foopulsememblockhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> +#include <inttypes.h> + +#include <pulse/def.h> +#include <pulsecore/llist.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/atomic.h> + +/* A pa_memblock is a reference counted memory block. PulseAudio + * passed references to pa_memblocks around instead of copying + * data. See pa_memchunk for a structure that describes parts of + * memory blocks. */ + +/* The type of memory this block points to */ +typedef enum pa_memblock_type { + PA_MEMBLOCK_POOL, /* Memory is part of the memory pool */ + PA_MEMBLOCK_POOL_EXTERNAL, /* Data memory is part of the memory pool but the pa_memblock structure itself not */ + PA_MEMBLOCK_APPENDED, /* the data is appended to the memory block */ + PA_MEMBLOCK_USER, /* User supplied memory, to be freed with free_cb */ + PA_MEMBLOCK_FIXED, /* data is a pointer to fixed memory that needs not to be freed */ + PA_MEMBLOCK_IMPORTED, /* Memory is imported from another process via shm */ + PA_MEMBLOCK_TYPE_MAX +} pa_memblock_type_t; + +typedef struct pa_memblock pa_memblock; +typedef struct pa_mempool pa_mempool; +typedef struct pa_mempool_stat pa_mempool_stat; +typedef struct pa_memimport_segment pa_memimport_segment; +typedef struct pa_memimport pa_memimport; +typedef struct pa_memexport pa_memexport; + +typedef void (*pa_memimport_release_cb_t)(pa_memimport *i, uint32_t block_id, void *userdata); +typedef void (*pa_memexport_revoke_cb_t)(pa_memexport *e, uint32_t block_id, void *userdata); + +/* Please note that updates to this structure are not locked, + * i.e. n_allocated might be updated at a point in time where + * n_accumulated is not yet. Take these values with a grain of salt, + * they are here for purely statistical reasons.*/ +struct pa_mempool_stat { + pa_atomic_t n_allocated; + pa_atomic_t n_accumulated; + pa_atomic_t n_imported; + pa_atomic_t n_exported; + pa_atomic_t allocated_size; + pa_atomic_t accumulated_size; + pa_atomic_t imported_size; + pa_atomic_t exported_size; + + pa_atomic_t n_too_large_for_pool; + pa_atomic_t n_pool_full; + + pa_atomic_t n_allocated_by_type[PA_MEMBLOCK_TYPE_MAX]; + pa_atomic_t n_accumulated_by_type[PA_MEMBLOCK_TYPE_MAX]; +}; + +/* Allocate a new memory block of type PA_MEMBLOCK_MEMPOOL or PA_MEMBLOCK_APPENDED, depending on the size */ +pa_memblock *pa_memblock_new(pa_mempool *, size_t length); + +/* Allocate a new memory block of type PA_MEMBLOCK_MEMPOOL. If the requested size is too large, return NULL */ +pa_memblock *pa_memblock_new_pool(pa_mempool *, size_t length); + +/* Allocate a new memory block of type PA_MEMBLOCK_USER */ +pa_memblock *pa_memblock_new_user(pa_mempool *, void *data, size_t length, void (*free_cb)(void *p), int read_only); + +/* A special case of pa_memblock_new_user: take a memory buffer previously allocated with pa_xmalloc() */ +#define pa_memblock_new_malloced(p,data,length) pa_memblock_new_user(p, data, length, pa_xfree, 0) + +/* Allocate a new memory block of type PA_MEMBLOCK_FIXED */ +pa_memblock *pa_memblock_new_fixed(pa_mempool *, void *data, size_t length, int read_only); + +void pa_memblock_unref(pa_memblock*b); +pa_memblock* pa_memblock_ref(pa_memblock*b); + +/* This special unref function has to be called by the owner of the +memory of a static memory block when he wants to release all +references to the memory. This causes the memory to be copied and +converted into a pool or malloc'ed memory block. Please note that this +function is not multiple caller safe, i.e. needs to be locked +manually if called from more than one thread at the same time. */ +void pa_memblock_unref_fixed(pa_memblock*b); + +int pa_memblock_is_read_only(pa_memblock *b); +int pa_memblock_ref_is_one(pa_memblock *b); +void* pa_memblock_acquire(pa_memblock *b); +void pa_memblock_release(pa_memblock *b); +size_t pa_memblock_get_length(pa_memblock *b); +pa_mempool * pa_memblock_get_pool(pa_memblock *b); + +pa_memblock *pa_memblock_will_need(pa_memblock *b); + +/* The memory block manager */ +pa_mempool* pa_mempool_new(int shared); +void pa_mempool_free(pa_mempool *p); +const pa_mempool_stat* pa_mempool_get_stat(pa_mempool *p); +void pa_mempool_vacuum(pa_mempool *p); +int pa_mempool_get_shm_id(pa_mempool *p, uint32_t *id); +int pa_mempool_is_shared(pa_mempool *p); +size_t pa_mempool_block_size_max(pa_mempool *p); + +/* For recieving blocks from other nodes */ +pa_memimport* pa_memimport_new(pa_mempool *p, pa_memimport_release_cb_t cb, void *userdata); +void pa_memimport_free(pa_memimport *i); +pa_memblock* pa_memimport_get(pa_memimport *i, uint32_t block_id, uint32_t shm_id, size_t offset, size_t size); +int pa_memimport_process_revoke(pa_memimport *i, uint32_t block_id); + +/* For sending blocks to other nodes */ +pa_memexport* pa_memexport_new(pa_mempool *p, pa_memexport_revoke_cb_t cb, void *userdata); +void pa_memexport_free(pa_memexport *e); +int pa_memexport_put(pa_memexport *e, pa_memblock *b, uint32_t *block_id, uint32_t *shm_id, size_t *offset, size_t *size); +int pa_memexport_process_release(pa_memexport *e, uint32_t id); + +#endif diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c new file mode 100644 index 00000000..8247feab --- /dev/null +++ b/src/pulsecore/memblockq.c @@ -0,0 +1,739 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <sys/time.h> +#include <time.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/log.h> +#include <pulsecore/mcalign.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> + +#include "memblockq.h" + +struct list_item { + struct list_item *next, *prev; + int64_t index; + pa_memchunk chunk; +}; + +PA_STATIC_FLIST_DECLARE(list_items, 0, pa_xfree); + +struct pa_memblockq { + struct list_item *blocks, *blocks_tail; + unsigned n_blocks; + size_t maxlength, tlength, base, prebuf, minreq; + int64_t read_index, write_index; + pa_bool_t in_prebuf; + pa_memblock *silence; + pa_mcalign *mcalign; + int64_t missing; + size_t requested; +}; + +pa_memblockq* pa_memblockq_new( + int64_t idx, + size_t maxlength, + size_t tlength, + size_t base, + size_t prebuf, + size_t minreq, + pa_memblock *silence) { + + pa_memblockq* bq; + + pa_assert(base > 0); + + bq = pa_xnew(pa_memblockq, 1); + bq->blocks = bq->blocks_tail = NULL; + bq->n_blocks = 0; + + bq->base = base; + bq->read_index = bq->write_index = idx; + + pa_log_debug("memblockq requested: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu", + (unsigned long) maxlength, (unsigned long) tlength, (unsigned long) base, (unsigned long) prebuf, (unsigned long) minreq); + + bq->missing = bq->requested = bq->maxlength = bq->tlength = bq->prebuf = bq->minreq = 0; + bq->in_prebuf = TRUE; + + pa_memblockq_set_maxlength(bq, maxlength); + pa_memblockq_set_tlength(bq, tlength); + pa_memblockq_set_prebuf(bq, prebuf); + pa_memblockq_set_minreq(bq, minreq); + + pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu", + (unsigned long)bq->maxlength, (unsigned long)bq->tlength, (unsigned long)bq->base, (unsigned long)bq->prebuf, (unsigned long)bq->minreq); + + bq->silence = silence ? pa_memblock_ref(silence) : NULL; + bq->mcalign = NULL; + + return bq; +} + +void pa_memblockq_free(pa_memblockq* bq) { + pa_assert(bq); + + pa_memblockq_flush(bq); + + if (bq->silence) + pa_memblock_unref(bq->silence); + + if (bq->mcalign) + pa_mcalign_free(bq->mcalign); + + pa_xfree(bq); +} + +static void drop_block(pa_memblockq *bq, struct list_item *q) { + pa_assert(bq); + pa_assert(q); + + pa_assert(bq->n_blocks >= 1); + + if (q->prev) + q->prev->next = q->next; + else + bq->blocks = q->next; + + if (q->next) + q->next->prev = q->prev; + else + bq->blocks_tail = q->prev; + + pa_memblock_unref(q->chunk.memblock); + + if (pa_flist_push(PA_STATIC_FLIST_GET(list_items), q) < 0) + pa_xfree(q); + + bq->n_blocks--; +} + +static pa_bool_t can_push(pa_memblockq *bq, size_t l) { + int64_t end; + + pa_assert(bq); + + if (bq->read_index > bq->write_index) { + size_t d = bq->read_index - bq->write_index; + + if (l > d) + l -= d; + else + return TRUE; + } + + end = bq->blocks_tail ? bq->blocks_tail->index + bq->blocks_tail->chunk.length : 0; + + /* Make sure that the list doesn't get too long */ + if (bq->write_index + (int64_t)l > end) + if (bq->write_index + l - bq->read_index > bq->maxlength) + return FALSE; + + return TRUE; +} + +int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *uchunk) { + struct list_item *q, *n; + pa_memchunk chunk; + int64_t old, delta; + + pa_assert(bq); + pa_assert(uchunk); + pa_assert(uchunk->memblock); + pa_assert(uchunk->length > 0); + pa_assert(uchunk->index + uchunk->length <= pa_memblock_get_length(uchunk->memblock)); + + if (uchunk->length % bq->base) + return -1; + + if (!can_push(bq, uchunk->length)) + return -1; + + old = bq->write_index; + chunk = *uchunk; + + if (bq->read_index > bq->write_index) { + + /* We currently have a buffer underflow, we need to drop some + * incoming data */ + + size_t d = bq->read_index - bq->write_index; + + if (chunk.length > d) { + chunk.index += d; + chunk.length -= d; + bq->write_index += d; + } else { + /* We drop the incoming data completely */ + bq->write_index += chunk.length; + goto finish; + } + } + + /* We go from back to front to look for the right place to add + * this new entry. Drop data we will overwrite on the way */ + + q = bq->blocks_tail; + while (q) { + + if (bq->write_index >= q->index + (int64_t) q->chunk.length) + /* We found the entry where we need to place the new entry immediately after */ + break; + else if (bq->write_index + (int64_t) chunk.length <= q->index) { + /* This entry isn't touched at all, let's skip it */ + q = q->prev; + } else if (bq->write_index <= q->index && + bq->write_index + chunk.length >= q->index + q->chunk.length) { + + /* This entry is fully replaced by the new entry, so let's drop it */ + + struct list_item *p; + p = q; + q = q->prev; + drop_block(bq, p); + } else if (bq->write_index >= q->index) { + /* The write index points into this memblock, so let's + * truncate or split it */ + + if (bq->write_index + chunk.length < q->index + q->chunk.length) { + + /* We need to save the end of this memchunk */ + struct list_item *p; + size_t d; + + /* Create a new list entry for the end of thie memchunk */ + if (!(p = pa_flist_pop(PA_STATIC_FLIST_GET(list_items)))) + p = pa_xnew(struct list_item, 1); + + p->chunk = q->chunk; + pa_memblock_ref(p->chunk.memblock); + + /* Calculate offset */ + d = bq->write_index + chunk.length - q->index; + pa_assert(d > 0); + + /* Drop it from the new entry */ + p->index = q->index + d; + p->chunk.length -= d; + + /* Add it to the list */ + p->prev = q; + if ((p->next = q->next)) + q->next->prev = p; + else + bq->blocks_tail = p; + q->next = p; + + bq->n_blocks++; + } + + /* Truncate the chunk */ + if (!(q->chunk.length = bq->write_index - q->index)) { + struct list_item *p; + p = q; + q = q->prev; + drop_block(bq, p); + } + + /* We had to truncate this block, hence we're now at the right position */ + break; + } else { + size_t d; + + pa_assert(bq->write_index + (int64_t)chunk.length > q->index && + bq->write_index + (int64_t)chunk.length < q->index + (int64_t)q->chunk.length && + bq->write_index < q->index); + + /* The job overwrites the current entry at the end, so let's drop the beginning of this entry */ + + d = bq->write_index + chunk.length - q->index; + q->index += d; + q->chunk.index += d; + q->chunk.length -= d; + + q = q->prev; + } + } + + if (q) { + pa_assert(bq->write_index >= q->index + (int64_t)q->chunk.length); + pa_assert(!q->next || (bq->write_index + (int64_t)chunk.length <= q->next->index)); + + /* Try to merge memory blocks */ + + if (q->chunk.memblock == chunk.memblock && + q->chunk.index + (int64_t)q->chunk.length == chunk.index && + bq->write_index == q->index + (int64_t)q->chunk.length) { + + q->chunk.length += chunk.length; + bq->write_index += chunk.length; + goto finish; + } + } else + pa_assert(!bq->blocks || (bq->write_index + (int64_t)chunk.length <= bq->blocks->index)); + + if (!(n = pa_flist_pop(PA_STATIC_FLIST_GET(list_items)))) + n = pa_xnew(struct list_item, 1); + + n->chunk = chunk; + pa_memblock_ref(n->chunk.memblock); + n->index = bq->write_index; + bq->write_index += n->chunk.length; + + n->next = q ? q->next : bq->blocks; + n->prev = q; + + if (n->next) + n->next->prev = n; + else + bq->blocks_tail = n; + + if (n->prev) + n->prev->next = n; + else + bq->blocks = n; + + bq->n_blocks++; + +finish: + + delta = bq->write_index - old; + + if (delta >= bq->requested) { + delta -= bq->requested; + bq->requested = 0; + } else { + bq->requested -= delta; + delta = 0; + } + + bq->missing -= delta; + + return 0; +} + +static pa_bool_t memblockq_check_prebuf(pa_memblockq *bq) { + pa_assert(bq); + + if (bq->in_prebuf) { + + if (pa_memblockq_get_length(bq) < bq->prebuf) + return TRUE; + + bq->in_prebuf = FALSE; + return FALSE; + } else { + + if (bq->prebuf > 0 && bq->read_index >= bq->write_index) { + bq->in_prebuf = TRUE; + return TRUE; + } + + return FALSE; + } +} + +int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk) { + pa_assert(bq); + pa_assert(chunk); + + /* We need to pre-buffer */ + if (memblockq_check_prebuf(bq)) + return -1; + + /* Do we need to spit out silence? */ + if (!bq->blocks || bq->blocks->index > bq->read_index) { + + size_t length; + + /* How much silence shall we return? */ + length = bq->blocks ? bq->blocks->index - bq->read_index : 0; + + /* We need to return silence, since no data is yet available */ + if (bq->silence) { + chunk->memblock = pa_memblock_ref(bq->silence); + + if (!length || length > pa_memblock_get_length(chunk->memblock)) + length = pa_memblock_get_length(chunk->memblock); + + chunk->length = length; + } else { + + /* If the memblockq is empty, return -1, otherwise return + * the time to sleep */ + if (!bq->blocks) + return -1; + + chunk->memblock = NULL; + chunk->length = length; + } + + chunk->index = 0; + return 0; + } + + /* Ok, let's pass real data to the caller */ + pa_assert(bq->blocks->index == bq->read_index); + + *chunk = bq->blocks->chunk; + pa_memblock_ref(chunk->memblock); + + return 0; +} + +void pa_memblockq_drop(pa_memblockq *bq, size_t length) { + int64_t old, delta; + pa_assert(bq); + pa_assert(length % bq->base == 0); + + old = bq->read_index; + + while (length > 0) { + + /* Do not drop any data when we are in prebuffering mode */ + if (memblockq_check_prebuf(bq)) + break; + + if (bq->blocks) { + size_t d; + + pa_assert(bq->blocks->index >= bq->read_index); + + d = (size_t) (bq->blocks->index - bq->read_index); + + if (d >= length) { + /* The first block is too far in the future */ + + bq->read_index += length; + break; + } else { + + length -= d; + bq->read_index += d; + } + + pa_assert(bq->blocks->index == bq->read_index); + + if (bq->blocks->chunk.length <= length) { + /* We need to drop the full block */ + + length -= bq->blocks->chunk.length; + bq->read_index += bq->blocks->chunk.length; + + drop_block(bq, bq->blocks); + } else { + /* Only the start of this block needs to be dropped */ + + bq->blocks->chunk.index += length; + bq->blocks->chunk.length -= length; + bq->blocks->index += length; + bq->read_index += length; + break; + } + + } else { + + /* The list is empty, there's nothing we could drop */ + bq->read_index += length; + break; + } + } + + delta = bq->read_index - old; + bq->missing += delta; +} + +int pa_memblockq_is_readable(pa_memblockq *bq) { + pa_assert(bq); + + if (memblockq_check_prebuf(bq)) + return 0; + + if (pa_memblockq_get_length(bq) <= 0) + return 0; + + return 1; +} + +size_t pa_memblockq_get_length(pa_memblockq *bq) { + pa_assert(bq); + + if (bq->write_index <= bq->read_index) + return 0; + + return (size_t) (bq->write_index - bq->read_index); +} + +size_t pa_memblockq_missing(pa_memblockq *bq) { + size_t l; + pa_assert(bq); + + if ((l = pa_memblockq_get_length(bq)) >= bq->tlength) + return 0; + + l = bq->tlength - l; + + return l >= bq->minreq ? l : 0; +} + +size_t pa_memblockq_get_minreq(pa_memblockq *bq) { + pa_assert(bq); + + return bq->minreq; +} + +void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek) { + int64_t old, delta; + pa_assert(bq); + + old = bq->write_index; + + switch (seek) { + case PA_SEEK_RELATIVE: + bq->write_index += offset; + break; + case PA_SEEK_ABSOLUTE: + bq->write_index = offset; + break; + case PA_SEEK_RELATIVE_ON_READ: + bq->write_index = bq->read_index + offset; + break; + case PA_SEEK_RELATIVE_END: + bq->write_index = (bq->blocks_tail ? bq->blocks_tail->index + (int64_t) bq->blocks_tail->chunk.length : bq->read_index) + offset; + break; + default: + pa_assert_not_reached(); + } + + delta = bq->write_index - old; + + if (delta >= bq->requested) { + delta -= bq->requested; + bq->requested = 0; + } else if (delta >= 0) { + bq->requested -= delta; + delta = 0; + } + + bq->missing -= delta; +} + +void pa_memblockq_flush(pa_memblockq *bq) { + int64_t old, delta; + pa_assert(bq); + + while (bq->blocks) + drop_block(bq, bq->blocks); + + pa_assert(bq->n_blocks == 0); + + old = bq->write_index; + bq->write_index = bq->read_index; + + pa_memblockq_prebuf_force(bq); + + delta = bq->write_index - old; + + if (delta > bq->requested) { + delta -= bq->requested; + bq->requested = 0; + } else if (delta >= 0) { + bq->requested -= delta; + delta = 0; + } + + bq->missing -= delta; +} + +size_t pa_memblockq_get_tlength(pa_memblockq *bq) { + pa_assert(bq); + + return bq->tlength; +} + +int64_t pa_memblockq_get_read_index(pa_memblockq *bq) { + pa_assert(bq); + return bq->read_index; +} + +int64_t pa_memblockq_get_write_index(pa_memblockq *bq) { + pa_assert(bq); + return bq->write_index; +} + +int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk) { + pa_memchunk rchunk; + + pa_assert(bq); + pa_assert(chunk); + + if (bq->base == 1) + return pa_memblockq_push(bq, chunk); + + if (!bq->mcalign) + bq->mcalign = pa_mcalign_new(bq->base); + + if (!can_push(bq, pa_mcalign_csize(bq->mcalign, chunk->length))) + return -1; + + pa_mcalign_push(bq->mcalign, chunk); + + while (pa_mcalign_pop(bq->mcalign, &rchunk) >= 0) { + int r; + r = pa_memblockq_push(bq, &rchunk); + pa_memblock_unref(rchunk.memblock); + + if (r < 0) + return -1; + } + + return 0; +} + +void pa_memblockq_shorten(pa_memblockq *bq, size_t length) { + size_t l; + pa_assert(bq); + + l = pa_memblockq_get_length(bq); + + if (l > length) + pa_memblockq_drop(bq, l - length); +} + +void pa_memblockq_prebuf_disable(pa_memblockq *bq) { + pa_assert(bq); + + bq->in_prebuf = FALSE; +} + +void pa_memblockq_prebuf_force(pa_memblockq *bq) { + pa_assert(bq); + + if (!bq->in_prebuf && bq->prebuf > 0) + bq->in_prebuf = TRUE; +} + +size_t pa_memblockq_get_maxlength(pa_memblockq *bq) { + pa_assert(bq); + + return bq->maxlength; +} + +size_t pa_memblockq_get_prebuf(pa_memblockq *bq) { + pa_assert(bq); + + return bq->prebuf; +} + +size_t pa_memblockq_pop_missing(pa_memblockq *bq) { + size_t l; + + pa_assert(bq); + +/* pa_log("pop: %lli", bq->missing); */ + + if (bq->missing <= 0) + return 0; + + l = (size_t) bq->missing; + bq->missing = 0; + bq->requested += l; + + return l; +} + +void pa_memblockq_set_maxlength(pa_memblockq *bq, size_t maxlength) { + pa_assert(bq); + + bq->maxlength = ((maxlength+bq->base-1)/bq->base)*bq->base; + + if (bq->maxlength < bq->base) + bq->maxlength = bq->base; + + if (bq->tlength > bq->maxlength) + pa_memblockq_set_tlength(bq, bq->maxlength); + + if (bq->prebuf > bq->maxlength) + pa_memblockq_set_prebuf(bq, bq->maxlength); +} + +void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) { + size_t old_tlength; + pa_assert(bq); + + old_tlength = bq->tlength; + + if (tlength <= 0) + tlength = bq->maxlength; + + bq->tlength = ((tlength+bq->base-1)/bq->base)*bq->base; + + if (bq->tlength > bq->maxlength) + bq->tlength = bq->maxlength; + + if (bq->minreq > bq->tlength - bq->prebuf) + pa_memblockq_set_minreq(bq, bq->tlength - bq->prebuf); + + bq->missing += (int64_t) bq->tlength - (int64_t) old_tlength; +} + +void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) { + pa_assert(bq); + + bq->prebuf = (prebuf == (size_t) -1) ? bq->tlength/2 : prebuf; + bq->prebuf = ((bq->prebuf+bq->base-1)/bq->base)*bq->base; + + if (prebuf > 0 && bq->prebuf < bq->base) + bq->prebuf = bq->base; + + if (bq->prebuf > bq->maxlength) + bq->prebuf = bq->maxlength; + + if (bq->prebuf <= 0 || pa_memblockq_get_length(bq) >= bq->prebuf) + bq->in_prebuf = FALSE; + + if (bq->minreq > bq->tlength - bq->prebuf) + pa_memblockq_set_minreq(bq, bq->tlength - bq->prebuf); +} + +void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) { + pa_assert(bq); + + bq->minreq = (minreq/bq->base)*bq->base; + + if (bq->minreq > bq->tlength - bq->prebuf) + bq->minreq = bq->tlength - bq->prebuf; + + if (bq->minreq < bq->base) + bq->minreq = bq->base; +} diff --git a/src/pulsecore/memblockq.h b/src/pulsecore/memblockq.h new file mode 100644 index 00000000..46637f10 --- /dev/null +++ b/src/pulsecore/memblockq.h @@ -0,0 +1,151 @@ +#ifndef foomemblockqhfoo +#define foomemblockqhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <sys/types.h> +#include <inttypes.h> + +#include <pulsecore/memblock.h> +#include <pulsecore/memchunk.h> +#include <pulse/def.h> + +/* A memblockq is a queue of pa_memchunks (yepp, the name is not + * perfect). It is similar to the ring buffers used by most other + * audio software. In contrast to a ring buffer this memblockq data + * type doesn't need to copy any data around, it just maintains + * references to reference counted memory blocks. */ + +typedef struct pa_memblockq pa_memblockq; + + +/* Parameters: + + - idx: start value for both read and write index + + - maxlength: maximum length of queue. If more data is pushed into + the queue, the operation will fail. Must not be 0. + + - tlength: the target length of the queue. Pass 0 for the default. + + - base: a base value for all metrics. Only multiples of this value + are popped from the queue or should be pushed into + it. Must not be 0. + + - prebuf: If the queue runs empty wait until this many bytes are in + queue again before passing the first byte out. If set + to 0 pa_memblockq_pop() will return a silence memblock + if no data is in the queue and will never fail. Pass + (size_t) -1 for the default. + + - minreq: pa_memblockq_missing() will only return values greater + than this value. Pass 0 for the default. + + - silence: return this memblock when reading unitialized data +*/ +pa_memblockq* pa_memblockq_new( + int64_t idx, + size_t maxlength, + size_t tlength, + size_t base, + size_t prebuf, + size_t minreq, + pa_memblock *silence); + +void pa_memblockq_free(pa_memblockq*bq); + +/* Push a new memory chunk into the queue. */ +int pa_memblockq_push(pa_memblockq* bq, const pa_memchunk *chunk); + +/* Push a new memory chunk into the queue, but filter it through a + * pa_mcalign object. Don't mix this with pa_memblockq_seek() unless + * you know what you do. */ +int pa_memblockq_push_align(pa_memblockq* bq, const pa_memchunk *chunk); + +/* Return a copy of the next memory chunk in the queue. It is not + * removed from the queue. There are two reasons this function might + * fail: 1. prebuffering is active, 2. queue is empty and no silence + * memblock was passed at initialization. If the queue is not empty, + * but we're currently at a hole in the queue and no silence memblock + * was passed we return the length of the hole in chunk->length. */ +int pa_memblockq_peek(pa_memblockq* bq, pa_memchunk *chunk); + +/* Drop the specified bytes from the queue. */ +void pa_memblockq_drop(pa_memblockq *bq, size_t length); + +/* Test if the pa_memblockq is currently readable, that is, more data than base */ +int pa_memblockq_is_readable(pa_memblockq *bq); + +/* Return the length of the queue in bytes */ +size_t pa_memblockq_get_length(pa_memblockq *bq); + +/* Return how many bytes are missing in queue to the specified fill amount */ +size_t pa_memblockq_missing(pa_memblockq *bq); + +/* Return the number of bytes that are missing since the last call to + * this function, reset the internal counter to 0. */ +size_t pa_memblockq_pop_missing(pa_memblockq *bq); + +/* Returns the minimal request value */ +size_t pa_memblockq_get_minreq(pa_memblockq *bq); + +/* Manipulate the write pointer */ +void pa_memblockq_seek(pa_memblockq *bq, int64_t offset, pa_seek_mode_t seek); + +/* Set the queue to silence, set write index to read index */ +void pa_memblockq_flush(pa_memblockq *bq); + +/* Get Target length */ +size_t pa_memblockq_get_tlength(pa_memblockq *bq); + +/* Return the current read index */ +int64_t pa_memblockq_get_read_index(pa_memblockq *bq); + +/* Return the current write index */ +int64_t pa_memblockq_get_write_index(pa_memblockq *bq); + +/* Shorten the pa_memblockq to the specified length by dropping data + * at the read end of the queue. The read index is increased until the + * queue has the specified length */ +void pa_memblockq_shorten(pa_memblockq *bq, size_t length); + +/* Ignore prebuf for now */ +void pa_memblockq_prebuf_disable(pa_memblockq *bq); + +/* Force prebuf */ +void pa_memblockq_prebuf_force(pa_memblockq *bq); + +/* Return the maximum length of the queue in bytes */ +size_t pa_memblockq_get_maxlength(pa_memblockq *bq); + +/* Return the prebuffer length in bytes */ +size_t pa_memblockq_get_prebuf(pa_memblockq *bq); + +/* Change metrics. */ +void pa_memblockq_set_maxlength(pa_memblockq *memblockq, size_t maxlength); +void pa_memblockq_set_tlength(pa_memblockq *memblockq, size_t tlength); +void pa_memblockq_set_prebuf(pa_memblockq *memblockq, size_t prebuf); +void pa_memblockq_set_minreq(pa_memblockq *memblockq, size_t minreq); + +#endif diff --git a/src/pulsecore/memchunk.c b/src/pulsecore/memchunk.c new file mode 100644 index 00000000..4e73b636 --- /dev/null +++ b/src/pulsecore/memchunk.c @@ -0,0 +1,92 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "memchunk.h" + +pa_memchunk* pa_memchunk_make_writable(pa_memchunk *c, size_t min) { + pa_memblock *n; + size_t l; + void *tdata, *sdata; + + pa_assert(c); + pa_assert(c->memblock); + + if (pa_memblock_ref_is_one(c->memblock) && + !pa_memblock_is_read_only(c->memblock) && + pa_memblock_get_length(c->memblock) >= c->index+min) + return c; + + l = c->length; + if (l < min) + l = min; + + n = pa_memblock_new(pa_memblock_get_pool(c->memblock), l); + tdata = pa_memblock_acquire(n); + sdata = pa_memblock_acquire(c->memblock); + memcpy(tdata, (uint8_t*) sdata + c->index, c->length); + pa_memblock_release(n); + pa_memblock_release(c->memblock); + pa_memblock_unref(c->memblock); + c->memblock = n; + c->index = 0; + + return c; +} + +pa_memchunk* pa_memchunk_reset(pa_memchunk *c) { + pa_assert(c); + + c->memblock = NULL; + c->length = c->index = 0; + + return c; +} + +pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c) { + void *p; + + pa_assert(c); + pa_assert(c->memblock); + + /* A version of pa_memblock_will_need() that works on memchunks + * instead of memblocks */ + + p = (uint8_t*) pa_memblock_acquire(c->memblock) + c->index; + pa_will_need(p, c->length); + pa_memblock_release(c->memblock); + + return (pa_memchunk*) c; +} diff --git a/src/pulsecore/memchunk.h b/src/pulsecore/memchunk.h new file mode 100644 index 00000000..e6105ace --- /dev/null +++ b/src/pulsecore/memchunk.h @@ -0,0 +1,52 @@ +#ifndef foomemchunkhfoo +#define foomemchunkhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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/memblock.h> + +/* A memchunk describes a part of a memblock. In contrast to the memblock, a + * memchunk is not allocated dynamically or reference counted, instead + * it is usually stored on the stack and copied around */ + +typedef struct pa_memchunk { + pa_memblock *memblock; + size_t index, length; +} pa_memchunk; + +/* Make a memchunk writable, i.e. make sure that the caller may have + * exclusive access to the memblock and it is not read_only. If needed + * the memblock in the structure is replaced by a copy. If min is not + * 0 it is made sure that the returned memblock is at least of the + * specified size, i.e. is enlarged if necessary. */ +pa_memchunk* pa_memchunk_make_writable(pa_memchunk *c, size_t min); + +/* Invalidate a memchunk. This does not free the cotaining memblock, + * but sets all members to zero. */ +pa_memchunk* pa_memchunk_reset(pa_memchunk *c); + +/* Map a memory chunk back into memory if it was swapped out */ +pa_memchunk *pa_memchunk_will_need(const pa_memchunk *c); + +#endif diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c new file mode 100644 index 00000000..0dab254b --- /dev/null +++ b/src/pulsecore/modargs.c @@ -0,0 +1,324 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <ctype.h> +#include <stdlib.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/hashmap.h> +#include <pulsecore/idxset.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/sink.h> +#include <pulsecore/source.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "modargs.h" + +struct entry { + char *key, *value; +}; + +static int add_key_value(pa_hashmap *map, char *key, char *value, const char* const valid_keys[]) { + struct entry *e; + + pa_assert(map); + pa_assert(key); + pa_assert(value); + + if (valid_keys) { + const char*const* v; + for (v = valid_keys; *v; v++) + if (strcmp(*v, key) == 0) + break; + + if (!*v) { + pa_xfree(key); + pa_xfree(value); + return -1; + } + } + + e = pa_xnew(struct entry, 1); + e->key = key; + e->value = value; + pa_hashmap_put(map, key, e); + + return 0; +} + +pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) { + pa_hashmap *map = NULL; + + map = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + if (args) { + enum { WHITESPACE, KEY, VALUE_START, VALUE_SIMPLE, VALUE_DOUBLE_QUOTES, VALUE_TICKS } state; + const char *p, *key, *value; + size_t key_len = 0, value_len = 0; + + key = value = NULL; + state = WHITESPACE; + for (p = args; *p; p++) { + switch (state) { + case WHITESPACE: + if (*p == '=') + goto fail; + else if (!isspace(*p)) { + key = p; + state = KEY; + key_len = 1; + } + break; + case KEY: + if (*p == '=') + state = VALUE_START; + else + key_len++; + break; + case VALUE_START: + if (*p == '\'') { + state = VALUE_TICKS; + value = p+1; + value_len = 0; + } else if (*p == '"') { + state = VALUE_DOUBLE_QUOTES; + value = p+1; + value_len = 0; + } else if (isspace(*p)) { + if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys) < 0) + goto fail; + state = WHITESPACE; + } else { + state = VALUE_SIMPLE; + value = p; + value_len = 1; + } + break; + case VALUE_SIMPLE: + if (isspace(*p)) { + if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + goto fail; + state = WHITESPACE; + } else + value_len++; + break; + case VALUE_DOUBLE_QUOTES: + if (*p == '"') { + if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + goto fail; + state = WHITESPACE; + } else + value_len++; + break; + case VALUE_TICKS: + if (*p == '\'') { + if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrndup(value, value_len), valid_keys) < 0) + goto fail; + state = WHITESPACE; + } else + value_len++; + break; + } + } + + if (state == VALUE_START) { + if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys) < 0) + goto fail; + } else if (state == VALUE_SIMPLE) { + if (add_key_value(map, pa_xstrndup(key, key_len), pa_xstrdup(value), valid_keys) < 0) + goto fail; + } else if (state != WHITESPACE) + goto fail; + } + + return (pa_modargs*) map; + +fail: + + if (map) + pa_modargs_free((pa_modargs*) map); + + return NULL; +} + +static void free_func(void *p, PA_GCC_UNUSED void*userdata) { + struct entry *e = p; + pa_assert(e); + + pa_xfree(e->key); + pa_xfree(e->value); + pa_xfree(e); +} + +void pa_modargs_free(pa_modargs*ma) { + pa_hashmap *map = (pa_hashmap*) ma; + pa_hashmap_free(map, free_func, NULL); +} + +const char *pa_modargs_get_value(pa_modargs *ma, const char *key, const char *def) { + pa_hashmap *map = (pa_hashmap*) ma; + struct entry*e; + + if (!(e = pa_hashmap_get(map, key))) + return def; + + return e->value; +} + +int pa_modargs_get_value_u32(pa_modargs *ma, const char *key, uint32_t *value) { + const char *v; + + pa_assert(ma); + pa_assert(key); + pa_assert(value); + + if (!(v = pa_modargs_get_value(ma, key, NULL))) + return 0; + + if (pa_atou(v, value) < 0) + return -1; + + return 0; +} + +int pa_modargs_get_value_s32(pa_modargs *ma, const char *key, int32_t *value) { + const char *v; + + pa_assert(ma); + pa_assert(key); + pa_assert(value); + + if (!(v = pa_modargs_get_value(ma, key, NULL))) + return 0; + + if (pa_atoi(v, value) < 0) + return -1; + + return 0; +} + +int pa_modargs_get_value_boolean(pa_modargs *ma, const char *key, pa_bool_t *value) { + const char *v; + int r; + + pa_assert(ma); + pa_assert(key); + pa_assert(value); + + if (!(v = pa_modargs_get_value(ma, key, NULL))) + return 0; + + if (!*v) + return -1; + + if ((r = pa_parse_boolean(v)) < 0) + return -1; + + *value = r; + return 0; +} + +int pa_modargs_get_sample_spec(pa_modargs *ma, pa_sample_spec *rss) { + const char *format; + uint32_t channels; + pa_sample_spec ss; + + pa_assert(ma); + pa_assert(rss); + + ss = *rss; + if ((pa_modargs_get_value_u32(ma, "rate", &ss.rate)) < 0) + return -1; + + channels = ss.channels; + if ((pa_modargs_get_value_u32(ma, "channels", &channels)) < 0) + return -1; + ss.channels = (uint8_t) channels; + + if ((format = pa_modargs_get_value(ma, "format", NULL))) + if ((ss.format = pa_parse_sample_format(format)) < 0) + return -1; + + if (!pa_sample_spec_valid(&ss)) + return -1; + + *rss = ss; + + return 0; +} + +int pa_modargs_get_channel_map(pa_modargs *ma, const char *name, pa_channel_map *rmap) { + pa_channel_map map; + const char *cm; + + pa_assert(ma); + pa_assert(rmap); + + map = *rmap; + + if ((cm = pa_modargs_get_value(ma, name ? name : "channel_map", NULL))) + if (!pa_channel_map_parse(&map, cm)) + return -1; + + if (!pa_channel_map_valid(&map)) + return -1; + + *rmap = map; + return 0; +} + +int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *rss, pa_channel_map *rmap, pa_channel_map_def_t def) { + pa_sample_spec ss; + pa_channel_map map; + + pa_assert(ma); + pa_assert(rss); + pa_assert(rmap); + + ss = *rss; + + if (pa_modargs_get_sample_spec(ma, &ss) < 0) + return -1; + + if (!pa_channel_map_init_auto(&map, ss.channels, def)) + map.channels = 0; + + if (pa_modargs_get_channel_map(ma, NULL, &map) < 0) + return -1; + + if (map.channels != ss.channels) + return -1; + + *rmap = map; + *rss = ss; + + return 0; +} diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h new file mode 100644 index 00000000..504b9cd7 --- /dev/null +++ b/src/pulsecore/modargs.h @@ -0,0 +1,63 @@ +#ifndef foomodargshfoo +#define foomodargshfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulsecore/core.h> +#include <pulsecore/macro.h> + +typedef struct pa_modargs pa_modargs; + +/* A generic parser for module arguments */ + +/* Parse the string args. The NULL-terminated array keys contains all valid arguments. */ +pa_modargs *pa_modargs_new(const char *args, const char* const keys[]); +void pa_modargs_free(pa_modargs*ma); + +/* Return the module argument for the specified name as a string. If + * the argument was not specified, return def instead.*/ +const char *pa_modargs_get_value(pa_modargs *ma, const char *key, const char *def); + +/* Return a module argument as unsigned 32bit value in *value */ +int pa_modargs_get_value_u32(pa_modargs *ma, const char *key, uint32_t *value); +int pa_modargs_get_value_s32(pa_modargs *ma, const char *key, int32_t *value); +int pa_modargs_get_value_boolean(pa_modargs *ma, const char *key, pa_bool_t *value); + +/* Return sample spec data from the three arguments "rate", "format" and "channels" */ +int pa_modargs_get_sample_spec(pa_modargs *ma, pa_sample_spec *ss); + +/* Return channel map data from the argument "channel_map" if name is NULL, otherwise read from the specified argument */ +int pa_modargs_get_channel_map(pa_modargs *ma, const char *name, pa_channel_map *map); + +/* Combination of pa_modargs_get_sample_spec() and +pa_modargs_get_channel_map(). Not always suitable, since this routine +initializes the map parameter based on the channels field of the ss +structure if no channel_map is found, using pa_channel_map_init_auto() */ + +int pa_modargs_get_sample_spec_and_channel_map(pa_modargs *ma, pa_sample_spec *ss, pa_channel_map *map, pa_channel_map_def_t def); + +#endif diff --git a/src/pulsecore/modinfo.c b/src/pulsecore/modinfo.c new file mode 100644 index 00000000..d1a78fbb --- /dev/null +++ b/src/pulsecore/modinfo.c @@ -0,0 +1,97 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <ltdl.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/ltdl-helper.h> + +#include "modinfo.h" + +#define PA_SYMBOL_AUTHOR "pa__get_author" +#define PA_SYMBOL_DESCRIPTION "pa__get_description" +#define PA_SYMBOL_USAGE "pa__get_usage" +#define PA_SYMBOL_VERSION "pa__get_version" +#define PA_SYMBOL_LOAD_ONCE "pa__load_once" + +pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name) { + pa_modinfo *i; + const char* (*func)(void); + pa_bool_t (*func2) (void); + + pa_assert(dl); + + i = pa_xnew0(pa_modinfo, 1); + + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_AUTHOR))) + i->author = pa_xstrdup(func()); + + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_DESCRIPTION))) + i->description = pa_xstrdup(func()); + + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_USAGE))) + i->usage = pa_xstrdup(func()); + + if ((func = (const char* (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_VERSION))) + i->version = pa_xstrdup(func()); + + if ((func2 = (pa_bool_t (*)(void)) pa_load_sym(dl, module_name, PA_SYMBOL_LOAD_ONCE))) + i->load_once = func2(); + + return i; +} + +pa_modinfo *pa_modinfo_get_by_name(const char *name) { + lt_dlhandle dl; + pa_modinfo *i; + + pa_assert(name); + + if (!(dl = lt_dlopenext(name))) { + pa_log("Failed to open module \"%s\": %s", name, lt_dlerror()); + return NULL; + } + + i = pa_modinfo_get_by_handle(dl, name); + lt_dlclose(dl); + + return i; +} + +void pa_modinfo_free(pa_modinfo *i) { + pa_assert(i); + + pa_xfree(i->author); + pa_xfree(i->description); + pa_xfree(i->usage); + pa_xfree(i->version); + pa_xfree(i); +} diff --git a/src/pulsecore/modinfo.h b/src/pulsecore/modinfo.h new file mode 100644 index 00000000..da6d5428 --- /dev/null +++ b/src/pulsecore/modinfo.h @@ -0,0 +1,47 @@ +#ifndef foomodinfohfoo +#define foomodinfohfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + +/* Some functions for reading module meta data from PulseAudio modules */ +#include <pulsecore/macro.h> + +typedef struct pa_modinfo { + char *author; + char *description; + char *usage; + char *version; + pa_bool_t load_once; +} pa_modinfo; + +/* Read meta data from an libtool handle */ +pa_modinfo *pa_modinfo_get_by_handle(lt_dlhandle dl, const char *module_name); + +/* Read meta data from a module file */ +pa_modinfo *pa_modinfo_get_by_name(const char *name); + +/* Free meta data */ +void pa_modinfo_free(pa_modinfo *i); + +#endif diff --git a/src/pulsecore/module.c b/src/pulsecore/module.c new file mode 100644 index 00000000..ae140ff4 --- /dev/null +++ b/src/pulsecore/module.c @@ -0,0 +1,308 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <limits.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> + +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/ltdl-helper.h> + +#include "module.h" + +#define PA_SYMBOL_INIT "pa__init" +#define PA_SYMBOL_DONE "pa__done" +#define PA_SYMBOL_LOAD_ONCE "pa__load_once" + +#define UNLOAD_POLL_TIME 2 + +static void timeout_callback(pa_mainloop_api *m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { + pa_core *c = PA_CORE(userdata); + struct timeval ntv; + + pa_core_assert_ref(c); + pa_assert(c->mainloop == m); + pa_assert(c->module_auto_unload_event == e); + + pa_module_unload_unused(c); + + pa_gettimeofday(&ntv); + pa_timeval_add(&ntv, UNLOAD_POLL_TIME*1000000); + m->time_restart(e, &ntv); +} + +pa_module* pa_module_load(pa_core *c, const char *name, const char *argument) { + pa_module *m = NULL; + pa_bool_t (*load_once)(void); + + pa_assert(c); + pa_assert(name); + + if (c->disallow_module_loading) + goto fail; + + m = pa_xnew(pa_module, 1); + m->name = pa_xstrdup(name); + m->argument = pa_xstrdup(argument); + + if (!(m->dl = lt_dlopenext(name))) { + pa_log("Failed to open module \"%s\": %s", name, lt_dlerror()); + goto fail; + } + + if ((load_once = (pa_bool_t (*)(void)) pa_load_sym(m->dl, name, PA_SYMBOL_LOAD_ONCE))) { + + if (load_once() && c->modules) { + pa_module *i; + uint32_t idx; + /* OK, the module only wants to be loaded once, let's make sure it is */ + + for (i = pa_idxset_first(c->modules, &idx); i; i = pa_idxset_next(c->modules, &idx)) { + if (strcmp(name, i->name) == 0) { + pa_log("Module \"%s\" should be loaded once at most. Refusing to load.", name); + goto fail; + } + } + } + } + + if (!(m->init = (int (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_INIT))) { + pa_log("Failed to load module \"%s\": symbol \""PA_SYMBOL_INIT"\" not found.", name); + goto fail; + } + + m->done = (void (*)(pa_module*_m)) pa_load_sym(m->dl, name, PA_SYMBOL_DONE); + m->userdata = NULL; + m->core = c; + m->n_used = -1; + m->auto_unload = 0; + m->unload_requested = 0; + + if (m->init(m) < 0) { + pa_log_error("Failed to load module \"%s\" (argument: \"%s\"): initialization failed.", name, argument ? argument : ""); + goto fail; + } + + if (!c->modules) + c->modules = pa_idxset_new(NULL, NULL); + + if (!c->module_auto_unload_event) { + struct timeval ntv; + pa_gettimeofday(&ntv); + pa_timeval_add(&ntv, UNLOAD_POLL_TIME*1000000); + c->module_auto_unload_event = c->mainloop->time_new(c->mainloop, &ntv, timeout_callback, c); + } + + pa_assert_se(pa_idxset_put(c->modules, m, &m->index) >= 0); + pa_assert(m->index != PA_IDXSET_INVALID); + + pa_log_info("Loaded \"%s\" (index: #%u; argument: \"%s\").", m->name, m->index, m->argument ? m->argument : ""); + + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_NEW, m->index); + + return m; + +fail: + + if (m) { + pa_xfree(m->argument); + pa_xfree(m->name); + + if (m->dl) + lt_dlclose(m->dl); + + pa_xfree(m); + } + + return NULL; +} + +static void pa_module_free(pa_module *m) { + pa_assert(m); + pa_assert(m->core); + + if (m->core->disallow_module_loading) + return; + + pa_log_info("Unloading \"%s\" (index: #%u).", m->name, m->index); + + if (m->done) + m->done(m); + + lt_dlclose(m->dl); + + pa_log_info("Unloaded \"%s\" (index: #%u).", m->name, m->index); + + pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_REMOVE, m->index); + + pa_xfree(m->name); + pa_xfree(m->argument); + pa_xfree(m); +} + +void pa_module_unload(pa_core *c, pa_module *m) { + pa_assert(c); + pa_assert(m); + + pa_assert(c->modules); + if (!(m = pa_idxset_remove_by_data(c->modules, m, NULL))) + return; + + pa_module_free(m); +} + +void pa_module_unload_by_index(pa_core *c, uint32_t idx) { + pa_module *m; + pa_assert(c); + pa_assert(idx != PA_IDXSET_INVALID); + + if (!(m = pa_idxset_remove_by_index(c->modules, idx))) + return; + + pa_module_free(m); +} + +static void free_callback(void *p, PA_GCC_UNUSED void *userdata) { + pa_module *m = p; + pa_assert(m); + pa_module_free(m); +} + +void pa_module_unload_all(pa_core *c) { + pa_module *m; + + pa_assert(c); + + if (!c->modules) + return; + + while ((m = pa_idxset_first(c->modules, NULL))) + pa_module_unload(c, m); + + pa_idxset_free(c->modules, free_callback, NULL); + c->modules = NULL; + + if (c->module_auto_unload_event) { + c->mainloop->time_free(c->module_auto_unload_event); + c->module_auto_unload_event = NULL; + } + + if (c->module_defer_unload_event) { + c->mainloop->defer_free(c->module_defer_unload_event); + c->module_defer_unload_event = NULL; + } +} + +static int unused_callback(void *p, PA_GCC_UNUSED uint32_t idx, int *del, void *userdata) { + pa_module *m = p; + time_t *now = userdata; + + pa_assert(m); + pa_assert(del); + pa_assert(now); + + if (m->n_used == 0 && m->auto_unload && m->last_used_time+m->core->module_idle_time <= *now) { + pa_module_free(m); + *del = 1; + } + + return 0; +} + +void pa_module_unload_unused(pa_core *c) { + time_t now; + pa_assert(c); + + if (!c->modules) + return; + + time(&now); + pa_idxset_foreach(c->modules, unused_callback, &now); +} + +static int unload_callback(void *p, PA_GCC_UNUSED uint32_t idx, int *del, PA_GCC_UNUSED void *userdata) { + pa_module *m = p; + pa_assert(m); + + if (m->unload_requested) { + pa_module_free(m); + *del = 1; + } + + return 0; +} + +static void defer_cb(pa_mainloop_api*api, pa_defer_event *e, void *userdata) { + pa_core *core = PA_CORE(userdata); + + pa_core_assert_ref(core); + api->defer_enable(e, 0); + + if (!core->modules) + return; + + pa_idxset_foreach(core->modules, unload_callback, NULL); +} + +void pa_module_unload_request(pa_module *m) { + pa_assert(m); + + m->unload_requested = 1; + + if (!m->core->module_defer_unload_event) + m->core->module_defer_unload_event = m->core->mainloop->defer_new(m->core->mainloop, defer_cb, m->core); + + m->core->mainloop->defer_enable(m->core->module_defer_unload_event, 1); +} + +void pa_module_set_used(pa_module*m, int used) { + pa_assert(m); + + if (m->n_used != used) + pa_subscription_post(m->core, PA_SUBSCRIPTION_EVENT_MODULE|PA_SUBSCRIPTION_EVENT_CHANGE, m->index); + + if (used == 0 && m->n_used > 0) + time(&m->last_used_time); + + m->n_used = used; +} + +pa_modinfo *pa_module_get_info(pa_module *m) { + pa_assert(m); + + return pa_modinfo_get_by_handle(m->dl, m->name); +} diff --git a/src/pulsecore/module.h b/src/pulsecore/module.h new file mode 100644 index 00000000..25f122d1 --- /dev/null +++ b/src/pulsecore/module.h @@ -0,0 +1,87 @@ +#ifndef foomodulehfoo +#define foomodulehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <ltdl.h> + +typedef struct pa_module pa_module; + +#include <pulsecore/core.h> +#include <pulsecore/modinfo.h> + +struct pa_module { + pa_core *core; + char *name, *argument; + uint32_t index; + + lt_dlhandle dl; + + int (*init)(pa_module*m); + void (*done)(pa_module*m); + + void *userdata; + + int n_used; + int auto_unload; + time_t last_used_time; + + int unload_requested; +}; + +pa_module* pa_module_load(pa_core *c, const char *name, const char*argument); +void pa_module_unload(pa_core *c, pa_module *m); +void pa_module_unload_by_index(pa_core *c, uint32_t idx); + +void pa_module_unload_all(pa_core *c); +void pa_module_unload_unused(pa_core *c); + +void pa_module_unload_request(pa_module *m); + +void pa_module_set_used(pa_module*m, int used); + +#define PA_MODULE_AUTHOR(s) \ + const char *pa__get_author(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_MODULE_DESCRIPTION(s) \ + const char *pa__get_description(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_MODULE_USAGE(s) \ + const char *pa__get_usage(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_MODULE_VERSION(s) \ + const char * pa__get_version(void) { return s; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_MODULE_LOAD_ONCE(b) \ + pa_bool_t pa__load_once(void) { return b; } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +pa_modinfo *pa_module_get_info(pa_module *m); + +#endif diff --git a/src/pulsecore/msgobject.c b/src/pulsecore/msgobject.c new file mode 100644 index 00000000..f54e69f2 --- /dev/null +++ b/src/pulsecore/msgobject.c @@ -0,0 +1,49 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 "msgobject.h" + +PA_DEFINE_CHECK_TYPE(pa_msgobject, pa_object); + +pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)) { + pa_msgobject *o; + + pa_assert(size > sizeof(pa_msgobject)); + pa_assert(type_name); + + if (!check_type) + check_type = pa_msgobject_check_type; + + pa_assert(check_type(type_name)); + pa_assert(check_type("pa_object")); + pa_assert(check_type("pa_msgobject")); + + o = PA_MSGOBJECT(pa_object_new_internal(size, type_name, check_type)); + o->process_msg = NULL; + return o; +} diff --git a/src/pulsecore/msgobject.h b/src/pulsecore/msgobject.h new file mode 100644 index 00000000..8221cc33 --- /dev/null +++ b/src/pulsecore/msgobject.h @@ -0,0 +1,54 @@ +#ifndef foopulsemsgobjecthfoo +#define foopulsemsgobjecthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/macro.h> +#include <pulsecore/object.h> +#include <pulsecore/memchunk.h> + +typedef struct pa_msgobject pa_msgobject; + +struct pa_msgobject { + pa_object parent; + int (*process_msg)(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); +}; + +pa_msgobject *pa_msgobject_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)); + +int pa_msgobject_check_type(const char *type); + +#define pa_msgobject_new(type) ((type*) pa_msgobject_new_internal(sizeof(type), #type, type##_check_type)) +#define pa_msgobject_free ((void (*) (pa_msgobject* o)) pa_object_free) + +#define PA_MSGOBJECT(o) pa_msgobject_cast(o) + +PA_DECLARE_CLASS(pa_msgobject); + +#endif diff --git a/src/pulsecore/mutex-posix.c b/src/pulsecore/mutex-posix.c new file mode 100644 index 00000000..6ac98484 --- /dev/null +++ b/src/pulsecore/mutex-posix.c @@ -0,0 +1,142 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pthread.h> +#include <errno.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> + +#include "mutex.h" + +struct pa_mutex { + pthread_mutex_t mutex; +}; + +struct pa_cond { + pthread_cond_t cond; +}; + +pa_mutex* pa_mutex_new(pa_bool_t recursive, pa_bool_t inherit_priority) { + pa_mutex *m; + pthread_mutexattr_t attr; + int r; + + pa_assert_se(pthread_mutexattr_init(&attr) == 0); + + if (recursive) + pa_assert_se(pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) == 0); + +#ifdef HAVE_PTHREAD_PRIO_INHERIT + if (inherit_priority) + pa_assert_se(pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT) == 0); +#endif + + m = pa_xnew(pa_mutex, 1); + +#ifndef HAVE_PTHREAD_PRIO_INHERIT + pa_assert_se(pthread_mutex_init(&m->mutex, &attr) == 0); + +#else + if ((r = pthread_mutex_init(&m->mutex, &attr))) { + + /* If this failed, then this was probably due to non-available + * priority inheritance. In which case we fall back to normal + * mutexes. */ + pa_assert(r == ENOTSUP && inherit_priority); + + pa_assert_se(pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_NONE) == 0); + pa_assert_se(pthread_mutex_init(&m->mutex, &attr) == 0); + } +#endif + + return m; +} + +void pa_mutex_free(pa_mutex *m) { + pa_assert(m); + + pa_assert_se(pthread_mutex_destroy(&m->mutex) == 0); + pa_xfree(m); +} + +void pa_mutex_lock(pa_mutex *m) { + pa_assert(m); + + pa_assert_se(pthread_mutex_lock(&m->mutex) == 0); +} + +pa_bool_t pa_mutex_try_lock(pa_mutex *m) { + int r; + pa_assert(m); + + if ((r = pthread_mutex_trylock(&m->mutex)) != 0) { + pa_assert(r == EBUSY); + return FALSE; + } + + return TRUE; +} + +void pa_mutex_unlock(pa_mutex *m) { + pa_assert(m); + + pa_assert_se(pthread_mutex_unlock(&m->mutex) == 0); +} + +pa_cond *pa_cond_new(void) { + pa_cond *c; + + c = pa_xnew(pa_cond, 1); + pa_assert_se(pthread_cond_init(&c->cond, NULL) == 0); + return c; +} + +void pa_cond_free(pa_cond *c) { + pa_assert(c); + + pa_assert_se(pthread_cond_destroy(&c->cond) == 0); + pa_xfree(c); +} + +void pa_cond_signal(pa_cond *c, int broadcast) { + pa_assert(c); + + if (broadcast) + pa_assert_se(pthread_cond_broadcast(&c->cond) == 0); + else + pa_assert_se(pthread_cond_signal(&c->cond) == 0); +} + +int pa_cond_wait(pa_cond *c, pa_mutex *m) { + pa_assert(c); + pa_assert(m); + + return pthread_cond_wait(&c->cond, &m->mutex); +} diff --git a/src/pulsecore/mutex-win32.c b/src/pulsecore/mutex-win32.c new file mode 100644 index 00000000..77d63d15 --- /dev/null +++ b/src/pulsecore/mutex-win32.c @@ -0,0 +1,135 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <windows.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/hashmap.h> + +#include "mutex.h" + +struct pa_mutex { + CRITICAL_SECTION mutex; +}; + +struct pa_cond { + pa_hashmap *wait_events; +}; + +pa_mutex* pa_mutex_new(pa_bool_t recursive, pa_bool_t inherit_priority) { + pa_mutex *m; + + m = pa_xnew(pa_mutex, 1); + + InitializeCriticalSection(&m->mutex); + + return m; +} + +void pa_mutex_free(pa_mutex *m) { + assert(m); + + DeleteCriticalSection(&m->mutex); + pa_xfree(m); +} + +void pa_mutex_lock(pa_mutex *m) { + assert(m); + + EnterCriticalSection(&m->mutex); +} + +void pa_mutex_unlock(pa_mutex *m) { + assert(m); + + LeaveCriticalSection(&m->mutex); +} + +pa_cond *pa_cond_new(void) { + pa_cond *c; + + c = pa_xnew(pa_cond, 1); + c->wait_events = pa_hashmap_new(NULL, NULL); + assert(c->wait_events); + + return c; +} + +void pa_cond_free(pa_cond *c) { + assert(c); + + pa_hashmap_free(c->wait_events, NULL, NULL); + pa_xfree(c); +} + +void pa_cond_signal(pa_cond *c, int broadcast) { + assert(c); + + if (pa_hashmap_size(c->wait_events) == 0) + return; + + if (broadcast) + SetEvent(pa_hashmap_get_first(c->wait_events)); + else { + void *iter; + const void *key; + HANDLE event; + + iter = NULL; + while (1) { + pa_hashmap_iterate(c->wait_events, &iter, &key); + if (key == NULL) + break; + event = (HANDLE)pa_hashmap_get(c->wait_events, key); + SetEvent(event); + } + } +} + +int pa_cond_wait(pa_cond *c, pa_mutex *m) { + HANDLE event; + + assert(c); + assert(m); + + event = CreateEvent(NULL, FALSE, FALSE, NULL); + assert(event); + + pa_hashmap_put(c->wait_events, event, event); + + pa_mutex_unlock(m); + + WaitForSingleObject(event, INFINITE); + + pa_mutex_lock(m); + + pa_hashmap_remove(c->wait_events, event); + + CloseHandle(event); + + return 0; +} diff --git a/src/pulsecore/mutex.h b/src/pulsecore/mutex.h new file mode 100644 index 00000000..9ca8fae5 --- /dev/null +++ b/src/pulsecore/mutex.h @@ -0,0 +1,50 @@ +#ifndef foopulsemutexhfoo +#define foopulsemutexhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/macro.h> + +typedef struct pa_mutex pa_mutex; + +/* Please think twice before enabling priority inheritance. This is no + * magic wand! Use it only when the potentially priorized threads are + * good candidates for it. Don't use this blindly! Also, note that + * only very few operating systems actually implement this, hence this + * is merely a hint. */ +pa_mutex* pa_mutex_new(pa_bool_t recursive, pa_bool_t inherit_priority); + +void pa_mutex_free(pa_mutex *m); +void pa_mutex_lock(pa_mutex *m); +pa_bool_t pa_mutex_try_lock(pa_mutex *m); +void pa_mutex_unlock(pa_mutex *m); + +typedef struct pa_cond pa_cond; + +pa_cond *pa_cond_new(void); +void pa_cond_free(pa_cond *c); +void pa_cond_signal(pa_cond *c, int broadcast); +int pa_cond_wait(pa_cond *c, pa_mutex *m); + +#endif diff --git a/src/pulsecore/namereg.c b/src/pulsecore/namereg.c new file mode 100644 index 00000000..fe520384 --- /dev/null +++ b/src/pulsecore/namereg.c @@ -0,0 +1,300 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> +#include <string.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/autoload.h> +#include <pulsecore/source.h> +#include <pulsecore/sink.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "namereg.h" + +struct namereg_entry { + pa_namereg_type_t type; + char *name; + void *data; +}; + +static int is_valid_char(char c) { + return + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '.' || + c == '_'; +} + +pa_bool_t pa_namereg_is_valid_name(const char *name) { + const char *c; + + if (*name == 0) + return FALSE; + + for (c = name; *c && (c-name < PA_NAME_MAX); c++) + if (!is_valid_char(*c)) + return FALSE; + + if (*c) + return FALSE; + + return TRUE; +} + +static char* cleanup_name(const char *name) { + const char *a; + char *b, *n; + + if (*name == 0) + return NULL; + + n = pa_xnew(char, strlen(name)+1); + + for (a = name, b = n; *a && (a-name < PA_NAME_MAX); a++, b++) + *b = is_valid_char(*a) ? *a : '_'; + + *b = 0; + + return n; +} + +void pa_namereg_free(pa_core *c) { + pa_assert(c); + + if (!c->namereg) + return; + + pa_assert(pa_hashmap_size(c->namereg) == 0); + pa_hashmap_free(c->namereg, NULL, NULL); +} + +const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t type, void *data, int fail) { + struct namereg_entry *e; + char *n = NULL; + + pa_assert(c); + pa_assert(name); + pa_assert(data); + + if (!*name) + return NULL; + + if ((type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE) && + !pa_namereg_is_valid_name(name) ) { + + if (fail) + return NULL; + + if (!(name = n = cleanup_name(name))) + return NULL; + } + + if (!c->namereg) + c->namereg = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); + + if ((e = pa_hashmap_get(c->namereg, name)) && fail) { + pa_xfree(n); + return NULL; + } + + if (e) { + unsigned i; + size_t l = strlen(name); + char *k; + + if (l+4 > PA_NAME_MAX) { + pa_xfree(n); + return NULL; + } + + k = pa_xnew(char, l+4); + + for (i = 2; i <= 99; i++) { + pa_snprintf(k, l+4, "%s.%u", name, i); + + if (!(e = pa_hashmap_get(c->namereg, k))) + break; + } + + if (e) { + pa_xfree(n); + pa_xfree(k); + return NULL; + } + + pa_xfree(n); + n = k; + } + + e = pa_xnew(struct namereg_entry, 1); + e->type = type; + e->name = n ? n : pa_xstrdup(name); + e->data = data; + + pa_assert_se(pa_hashmap_put(c->namereg, e->name, e) >= 0); + + return e->name; +} + +void pa_namereg_unregister(pa_core *c, const char *name) { + struct namereg_entry *e; + + pa_assert(c); + pa_assert(name); + + pa_assert_se(e = pa_hashmap_remove(c->namereg, name)); + + pa_xfree(e->name); + pa_xfree(e); +} + +void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, int autoload) { + struct namereg_entry *e; + uint32_t idx; + pa_assert(c); + + if (!name) { + + if (type == PA_NAMEREG_SOURCE) + name = pa_namereg_get_default_source_name(c); + else if (type == PA_NAMEREG_SINK) + name = pa_namereg_get_default_sink_name(c); + + } else if (strcmp(name, "@DEFAULT_SINK@") == 0) { + if (type == PA_NAMEREG_SINK) + name = pa_namereg_get_default_sink_name(c); + + } else if (strcmp(name, "@DEFAULT_SOURCE@") == 0) { + if (type == PA_NAMEREG_SOURCE) + name = pa_namereg_get_default_source_name(c); + + } else if (strcmp(name, "@DEFAULT_MONITOR@") == 0) { + if (type == PA_NAMEREG_SOURCE) { + pa_sink *k; + + if ((k = pa_namereg_get(c, NULL, PA_NAMEREG_SINK, autoload))) + return k->monitor_source; + } + } else if (*name == '@') + name = NULL; + + if (!name) + return NULL; + + if (c->namereg && (e = pa_hashmap_get(c->namereg, name))) + if (e->type == type) + return e->data; + + if (pa_atou(name, &idx) < 0) { + + if (autoload) { + pa_autoload_request(c, name, type); + + if (c->namereg && (e = pa_hashmap_get(c->namereg, name))) + if (e->type == type) + return e->data; + } + + return NULL; + } + + if (type == PA_NAMEREG_SINK) + return pa_idxset_get_by_index(c->sinks, idx); + else if (type == PA_NAMEREG_SOURCE) + return pa_idxset_get_by_index(c->sources, idx); + else if (type == PA_NAMEREG_SAMPLE && c->scache) + return pa_idxset_get_by_index(c->scache, idx); + + return NULL; +} + +int pa_namereg_set_default(pa_core*c, const char *name, pa_namereg_type_t type) { + char **s; + + pa_assert(c); + pa_assert(type == PA_NAMEREG_SINK || type == PA_NAMEREG_SOURCE); + + s = type == PA_NAMEREG_SINK ? &c->default_sink_name : &c->default_source_name; + + if (!name && !*s) + return 0; + + if (name && *s && !strcmp(name, *s)) + return 0; + + if (!pa_namereg_is_valid_name(name)) + return -1; + + pa_xfree(*s); + *s = pa_xstrdup(name); + pa_subscription_post(c, PA_SUBSCRIPTION_EVENT_SERVER|PA_SUBSCRIPTION_EVENT_CHANGE, PA_INVALID_INDEX); + + return 0; +} + +const char *pa_namereg_get_default_sink_name(pa_core *c) { + pa_sink *s; + + pa_assert(c); + + if (c->default_sink_name) + return c->default_sink_name; + + if ((s = pa_idxset_first(c->sinks, NULL))) + pa_namereg_set_default(c, s->name, PA_NAMEREG_SINK); + + return c->default_sink_name; +} + +const char *pa_namereg_get_default_source_name(pa_core *c) { + pa_source *s; + uint32_t idx; + + pa_assert(c); + + if (c->default_source_name) + return c->default_source_name; + + for (s = pa_idxset_first(c->sources, &idx); s; s = pa_idxset_next(c->sources, &idx)) + if (!s->monitor_of) { + pa_namereg_set_default(c, s->name, PA_NAMEREG_SOURCE); + break; + } + + if (!c->default_source_name) + if ((s = pa_idxset_first(c->sources, NULL))) + pa_namereg_set_default(c, s->name, PA_NAMEREG_SOURCE); + + return c->default_source_name; +} diff --git a/src/pulsecore/namereg.h b/src/pulsecore/namereg.h new file mode 100644 index 00000000..d0db9e81 --- /dev/null +++ b/src/pulsecore/namereg.h @@ -0,0 +1,50 @@ +#ifndef foonamereghfoo +#define foonamereghfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/core.h> +#include <pulsecore/macro.h> + +#define PA_NAME_MAX 128 + +typedef enum pa_namereg_type { + PA_NAMEREG_SINK, + PA_NAMEREG_SOURCE, + PA_NAMEREG_SAMPLE +} pa_namereg_type_t; + +void pa_namereg_free(pa_core *c); + +const char *pa_namereg_register(pa_core *c, const char *name, pa_namereg_type_t type, void *data, int fail); +void pa_namereg_unregister(pa_core *c, const char *name); +void* pa_namereg_get(pa_core *c, const char *name, pa_namereg_type_t type, int autoload); +int pa_namereg_set_default(pa_core*c, const char *name, pa_namereg_type_t type); + +const char *pa_namereg_get_default_sink_name(pa_core *c); +const char *pa_namereg_get_default_source_name(pa_core *c); + +pa_bool_t pa_namereg_is_valid_name(const char *name); + +#endif diff --git a/src/pulsecore/native-common.h b/src/pulsecore/native-common.h new file mode 100644 index 00000000..3ab2361b --- /dev/null +++ b/src/pulsecore/native-common.h @@ -0,0 +1,158 @@ +#ifndef foonativecommonhfoo +#define foonativecommonhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/cdecl.h> +#include <pulse/def.h> + +PA_C_DECL_BEGIN + +enum { + /* Generic commands */ + PA_COMMAND_ERROR, + PA_COMMAND_TIMEOUT, /* pseudo command */ + PA_COMMAND_REPLY, + + /* CLIENT->SERVER */ + PA_COMMAND_CREATE_PLAYBACK_STREAM, /* Payload changed in v9, v12 (0.9.0, 0.9.8) */ + PA_COMMAND_DELETE_PLAYBACK_STREAM, + PA_COMMAND_CREATE_RECORD_STREAM, /* Payload changed in v9, v12 (0.9.0, 0.9.8) */ + PA_COMMAND_DELETE_RECORD_STREAM, + PA_COMMAND_EXIT, + PA_COMMAND_AUTH, + PA_COMMAND_SET_CLIENT_NAME, + PA_COMMAND_LOOKUP_SINK, + PA_COMMAND_LOOKUP_SOURCE, + PA_COMMAND_DRAIN_PLAYBACK_STREAM, + PA_COMMAND_STAT, + PA_COMMAND_GET_PLAYBACK_LATENCY, + PA_COMMAND_CREATE_UPLOAD_STREAM, + PA_COMMAND_DELETE_UPLOAD_STREAM, + PA_COMMAND_FINISH_UPLOAD_STREAM, + PA_COMMAND_PLAY_SAMPLE, + PA_COMMAND_REMOVE_SAMPLE, + + PA_COMMAND_GET_SERVER_INFO, + PA_COMMAND_GET_SINK_INFO, + PA_COMMAND_GET_SINK_INFO_LIST, + PA_COMMAND_GET_SOURCE_INFO, + PA_COMMAND_GET_SOURCE_INFO_LIST, + PA_COMMAND_GET_MODULE_INFO, + PA_COMMAND_GET_MODULE_INFO_LIST, + PA_COMMAND_GET_CLIENT_INFO, + PA_COMMAND_GET_CLIENT_INFO_LIST, + PA_COMMAND_GET_SINK_INPUT_INFO, /* Payload changed in v11 (0.9.7) */ + PA_COMMAND_GET_SINK_INPUT_INFO_LIST, /* Payload changed in v11 (0.9.7) */ + PA_COMMAND_GET_SOURCE_OUTPUT_INFO, + PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST, + PA_COMMAND_GET_SAMPLE_INFO, + PA_COMMAND_GET_SAMPLE_INFO_LIST, + PA_COMMAND_SUBSCRIBE, + + PA_COMMAND_SET_SINK_VOLUME, + PA_COMMAND_SET_SINK_INPUT_VOLUME, + PA_COMMAND_SET_SOURCE_VOLUME, + + PA_COMMAND_SET_SINK_MUTE, + PA_COMMAND_SET_SOURCE_MUTE, + + PA_COMMAND_CORK_PLAYBACK_STREAM, + PA_COMMAND_FLUSH_PLAYBACK_STREAM, + PA_COMMAND_TRIGGER_PLAYBACK_STREAM, + + PA_COMMAND_SET_DEFAULT_SINK, + PA_COMMAND_SET_DEFAULT_SOURCE, + + PA_COMMAND_SET_PLAYBACK_STREAM_NAME, + PA_COMMAND_SET_RECORD_STREAM_NAME, + + PA_COMMAND_KILL_CLIENT, + PA_COMMAND_KILL_SINK_INPUT, + PA_COMMAND_KILL_SOURCE_OUTPUT, + + PA_COMMAND_LOAD_MODULE, + PA_COMMAND_UNLOAD_MODULE, + + PA_COMMAND_ADD_AUTOLOAD, + PA_COMMAND_REMOVE_AUTOLOAD, + PA_COMMAND_GET_AUTOLOAD_INFO, + PA_COMMAND_GET_AUTOLOAD_INFO_LIST, + + PA_COMMAND_GET_RECORD_LATENCY, + PA_COMMAND_CORK_RECORD_STREAM, + PA_COMMAND_FLUSH_RECORD_STREAM, + PA_COMMAND_PREBUF_PLAYBACK_STREAM, + + /* SERVER->CLIENT */ + PA_COMMAND_REQUEST, + PA_COMMAND_OVERFLOW, + PA_COMMAND_UNDERFLOW, + PA_COMMAND_PLAYBACK_STREAM_KILLED, + PA_COMMAND_RECORD_STREAM_KILLED, + PA_COMMAND_SUBSCRIBE_EVENT, + + /* A few more client->server commands */ + + /* Supported since protocol v10 (0.9.5) */ + PA_COMMAND_MOVE_SINK_INPUT, + PA_COMMAND_MOVE_SOURCE_OUTPUT, + + /* Supported since protocol v11 (0.9.7) */ + PA_COMMAND_SET_SINK_INPUT_MUTE, + + PA_COMMAND_SUSPEND_SINK, + PA_COMMAND_SUSPEND_SOURCE, + + /* Supported since protocol v13 (0.9.8) */ + PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR, + PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR, + + PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE, + PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE, + + /* SERVER->CLIENT */ + PA_COMMAND_PLAYBACK_STREAM_SUSPENDED, + PA_COMMAND_RECORD_STREAM_SUSPENDED, + PA_COMMAND_PLAYBACK_STREAM_MOVED, + PA_COMMAND_RECORD_STREAM_MOVED, + + PA_COMMAND_MAX +}; + +#define PA_NATIVE_COOKIE_LENGTH 256 +#define PA_NATIVE_COOKIE_FILE ".pulse-cookie" + +#define PA_NATIVE_DEFAULT_PORT 4713 + +#define PA_NATIVE_COOKIE_PROPERTY_NAME "protocol-native-cookie" +#define PA_NATIVE_SERVER_PROPERTY_NAME "protocol-native-server" + +#define PA_NATIVE_DEFAULT_UNIX_SOCKET "native" + + +PA_C_DECL_END + +#endif diff --git a/src/pulsecore/object.c b/src/pulsecore/object.c new file mode 100644 index 00000000..6c36242b --- /dev/null +++ b/src/pulsecore/object.c @@ -0,0 +1,72 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 "object.h" + +pa_object *pa_object_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)) { + pa_object *o; + + pa_assert(size > sizeof(pa_object)); + pa_assert(type_name); + + if (!check_type) + check_type = pa_object_check_type; + + pa_assert(check_type(type_name)); + pa_assert(check_type("pa_object")); + + o = pa_xmalloc(size); + PA_REFCNT_INIT(o); + o->type_name = type_name; + o->free = pa_object_free; + o->check_type = check_type; + + return o; +} + +pa_object *pa_object_ref(pa_object *o) { + pa_object_assert_ref(o); + + PA_REFCNT_INC(o); + return o; +} + +void pa_object_unref(pa_object *o) { + pa_object_assert_ref(o); + + if (PA_REFCNT_DEC(o) <= 0) { + pa_assert(o->free); + o->free(o); + } +} + +int pa_object_check_type(const char *type_name) { + pa_assert(type_name); + + return strcmp(type_name, "pa_object") == 0; +} diff --git a/src/pulsecore/object.h b/src/pulsecore/object.h new file mode 100644 index 00000000..562fd113 --- /dev/null +++ b/src/pulsecore/object.h @@ -0,0 +1,106 @@ +#ifndef foopulseobjecthfoo +#define foopulseobjecthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> +#include <sys/types.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/macro.h> + +typedef struct pa_object pa_object; + +struct pa_object { + PA_REFCNT_DECLARE; + const char *type_name; + void (*free)(pa_object *o); + int (*check_type)(const char *type_name); +}; + +pa_object *pa_object_new_internal(size_t size, const char *type_name, int (*check_type)(const char *type_name)); +#define pa_object_new(type) ((type*) pa_object_new_internal(sizeof(type), #type, type##_check_type) + +#define pa_object_free ((void (*) (pa_object* o)) pa_xfree) + +int pa_object_check_type(const char *type); + +static inline int pa_object_isinstance(void *o) { + pa_object *obj = (pa_object*) o; + return obj ? obj->check_type("pa_object") : 0; +} + +pa_object *pa_object_ref(pa_object *o); +void pa_object_unref(pa_object *o); + +static inline int pa_object_refcnt(pa_object *o) { + return o ? PA_REFCNT_VALUE(o) : 0; +} + +static inline pa_object* pa_object_cast(void *o) { + pa_object *obj = (pa_object*) o; + pa_assert(!obj || obj->check_type("pa_object")); + return obj; +} + +#define pa_object_assert_ref(o) pa_assert(pa_object_refcnt(o) > 0) + +#define PA_OBJECT(o) pa_object_cast(o) + +#define PA_DECLARE_CLASS(c) \ + static inline int c##_isinstance(void *o) { \ + pa_object *obj = (pa_object*) o; \ + return obj ? obj->check_type(#c) : 1; \ + } \ + static inline c* c##_cast(void *o) { \ + pa_assert(c##_isinstance(o)); \ + return (c*) o; \ + } \ + static inline c* c##_ref(c *o) { \ + return (c*) pa_object_ref(PA_OBJECT(o)); \ + } \ + static inline void c##_unref(c* o) { \ + pa_object_unref(PA_OBJECT(o)); \ + } \ + static inline int c##_refcnt(c* o) { \ + return pa_object_refcnt(PA_OBJECT(o)); \ + } \ + static inline void c##_assert_ref(c *o) { \ + pa_object_assert_ref(PA_OBJECT(o)); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#define PA_DEFINE_CHECK_TYPE(c, parent) \ + int c##_check_type(const char *type) { \ + pa_assert(type); \ + if (strcmp(type, #c) == 0) \ + return 1; \ + return parent##_check_type(type); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + + +#endif diff --git a/src/pulsecore/once.c b/src/pulsecore/once.c new file mode 100644 index 00000000..a358cf65 --- /dev/null +++ b/src/pulsecore/once.c @@ -0,0 +1,96 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/macro.h> +#include <pulsecore/mutex.h> + +#include "once.h" + +int pa_once_begin(pa_once *control) { + pa_mutex *m; + + pa_assert(control); + + if (pa_atomic_load(&control->done)) + return 0; + + pa_atomic_inc(&control->ref); + + /* Caveat: We have to make sure that the once func has completed + * before returning, even if the once func is not actually + * executed by us. Hence the awkward locking. */ + + for (;;) { + + if ((m = pa_atomic_ptr_load(&control->mutex))) { + + /* The mutex is stored in locked state, hence let's just + * wait until it is unlocked */ + pa_mutex_lock(m); + + pa_once_end(control); + return 0; + } + + pa_assert_se(m = pa_mutex_new(FALSE, FALSE)); + pa_mutex_lock(m); + + if (pa_atomic_ptr_cmpxchg(&control->mutex, NULL, m)) + return 1; + + pa_mutex_unlock(m); + pa_mutex_free(m); + } +} + +void pa_once_end(pa_once *control) { + pa_mutex *m; + + pa_assert(control); + + pa_atomic_store(&control->done, 1); + + pa_assert_se(m = pa_atomic_ptr_load(&control->mutex)); + pa_mutex_unlock(m); + + if (pa_atomic_dec(&control->ref) <= 1) { + pa_assert_se(pa_atomic_ptr_cmpxchg(&control->mutex, m, NULL)); + pa_mutex_free(m); + } +} + +/* Not reentrant -- how could it be? */ +void pa_run_once(pa_once *control, pa_once_func_t func) { + pa_assert(control); + pa_assert(func); + + if (pa_once_begin(control)) { + func(); + pa_once_end(control); + } +} + diff --git a/src/pulsecore/once.h b/src/pulsecore/once.h new file mode 100644 index 00000000..c9fe6d0a --- /dev/null +++ b/src/pulsecore/once.h @@ -0,0 +1,76 @@ +#ifndef foopulseoncehfoo +#define foopulseoncehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/mutex.h> +#include <pulsecore/atomic.h> + +typedef struct pa_once { + pa_atomic_ptr_t mutex; + pa_atomic_t ref, done; +} pa_once; + +#define PA_ONCE_INIT \ + { \ + .mutex = PA_ATOMIC_PTR_INIT(NULL), \ + .ref = PA_ATOMIC_INIT(0), \ + .done = PA_ATOMIC_INIT(0) \ + } + +/* Not to be called directly, use the macros defined below instead */ +int pa_once_begin(pa_once *o); +void pa_once_end(pa_once *o); + +#define PA_ONCE_BEGIN \ + do { \ + static pa_once _once = PA_ONCE_INIT; \ + if (pa_once_begin(&_once)) {{ + +#define PA_ONCE_END \ + } \ + pa_once_end(&_once); \ + } \ + } while(0) + +/* + + Usage of these macros is like this: + + void foo() { + + PA_ONCE_BEGIN { + + ... stuff to be called just once ... + + } PA_ONCE_END; + } + +*/ + +/* Same API but calls a function */ +typedef void (*pa_once_func_t) (void); +void pa_run_once(pa_once *o, pa_once_func_t f); + +#endif diff --git a/src/pulsecore/packet.c b/src/pulsecore/packet.c new file mode 100644 index 00000000..2706efea --- /dev/null +++ b/src/pulsecore/packet.c @@ -0,0 +1,81 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <stdlib.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "packet.h" + +pa_packet* pa_packet_new(size_t length) { + pa_packet *p; + + pa_assert(length > 0); + + p = pa_xmalloc(PA_ALIGN(sizeof(pa_packet)) + length); + PA_REFCNT_INIT(p); + p->length = length; + p->data = (uint8_t*) p + PA_ALIGN(sizeof(pa_packet)); + p->type = PA_PACKET_APPENDED; + + return p; +} + +pa_packet* pa_packet_new_dynamic(void* data, size_t length) { + pa_packet *p; + + pa_assert(data); + pa_assert(length > 0); + + p = pa_xnew(pa_packet, 1); + PA_REFCNT_INIT(p); + p->length = length; + p->data = data; + p->type = PA_PACKET_DYNAMIC; + + return p; +} + +pa_packet* pa_packet_ref(pa_packet *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + PA_REFCNT_INC(p); + return p; +} + +void pa_packet_unref(pa_packet *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) >= 1); + + if (PA_REFCNT_DEC(p) <= 0) { + if (p->type == PA_PACKET_DYNAMIC) + pa_xfree(p->data); + pa_xfree(p); + } +} diff --git a/src/pulsecore/packet.h b/src/pulsecore/packet.h new file mode 100644 index 00000000..bcac4a7f --- /dev/null +++ b/src/pulsecore/packet.h @@ -0,0 +1,45 @@ +#ifndef foopackethfoo +#define foopackethfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <sys/types.h> +#include <inttypes.h> + +#include <pulsecore/refcnt.h> + +typedef struct pa_packet { + PA_REFCNT_DECLARE; + enum { PA_PACKET_APPENDED, PA_PACKET_DYNAMIC } type; + size_t length; + uint8_t *data; +} pa_packet; + +pa_packet* pa_packet_new(size_t length); +pa_packet* pa_packet_new_dynamic(void* data, size_t length); + +pa_packet* pa_packet_ref(pa_packet *p); +void pa_packet_unref(pa_packet *p); + +#endif diff --git a/src/pulsecore/parseaddr.c b/src/pulsecore/parseaddr.c new file mode 100644 index 00000000..149c9e00 --- /dev/null +++ b/src/pulsecore/parseaddr.c @@ -0,0 +1,124 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <stdlib.h> + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "parseaddr.h" + +/* Parse addresses in one of the following forms: + * HOSTNAME + * HOSTNAME:PORT + * [HOSTNAME] + * [HOSTNAME]:PORT + * + * Return a newly allocated string of the hostname and fill in *ret_port if specified */ + +static char *parse_host(const char *s, uint16_t *ret_port) { + pa_assert(s); + pa_assert(ret_port); + + if (*s == '[') { + char *e; + if (!(e = strchr(s+1, ']'))) + return NULL; + + if (e[1] == ':') + *ret_port = atoi(e+2); + else if (e[1] != 0) + return NULL; + + return pa_xstrndup(s+1, e-s-1); + } else { + char *e; + + if (!(e = strrchr(s, ':'))) + return pa_xstrdup(s); + + *ret_port = atoi(e+1); + return pa_xstrndup(s, e-s); + } +} + +int pa_parse_address(const char *name, pa_parsed_address *ret_p) { + const char *p; + + pa_assert(name); + pa_assert(ret_p); + + memset(ret_p, 0, sizeof(pa_parsed_address)); + ret_p->type = PA_PARSED_ADDRESS_TCP_AUTO; + + if (*name == '{') { + char hn[256], *pfx; + /* The URL starts with a host specification for detecting local connections */ + + if (!pa_get_host_name(hn, sizeof(hn))) + return -1; + + pfx = pa_sprintf_malloc("{%s}", hn); + if (!pa_startswith(name, pfx)) { + pa_xfree(pfx); + /* Not local */ + return -1; + } + + p = name + strlen(pfx); + pa_xfree(pfx); + } else + p = name; + + if (*p == '/') + ret_p->type = PA_PARSED_ADDRESS_UNIX; + else if (pa_startswith(p, "unix:")) { + ret_p->type = PA_PARSED_ADDRESS_UNIX; + p += sizeof("unix:")-1; + } else if (pa_startswith(p, "tcp:")) { + ret_p->type = PA_PARSED_ADDRESS_TCP4; + p += sizeof("tcp:")-1; + } else if (pa_startswith(p, "tcp4:")) { + ret_p->type = PA_PARSED_ADDRESS_TCP4; + p += sizeof("tcp4:")-1; + } else if (pa_startswith(p, "tcp6:")) { + ret_p->type = PA_PARSED_ADDRESS_TCP6; + p += sizeof("tcp6:")-1; + } + + if (ret_p->type == PA_PARSED_ADDRESS_UNIX) + ret_p->path_or_host = pa_xstrdup(p); + else + if (!(ret_p->path_or_host = parse_host(p, &ret_p->port))) + return -1; + + return 0; +} diff --git a/src/pulsecore/parseaddr.h b/src/pulsecore/parseaddr.h new file mode 100644 index 00000000..fd7cad3b --- /dev/null +++ b/src/pulsecore/parseaddr.h @@ -0,0 +1,44 @@ +#ifndef fooparseaddrhfoo +#define fooparseaddrhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <inttypes.h> + +typedef enum pa_parsed_address_type { + PA_PARSED_ADDRESS_UNIX, + PA_PARSED_ADDRESS_TCP4, + PA_PARSED_ADDRESS_TCP6, + PA_PARSED_ADDRESS_TCP_AUTO +} pa_parsed_address_type_t; + +typedef struct pa_parsed_address { + pa_parsed_address_type_t type; + char *path_or_host; + uint16_t port; +} pa_parsed_address; + +int pa_parse_address(const char *a, pa_parsed_address *ret_p); + +#endif diff --git a/src/pulsecore/pdispatch.c b/src/pulsecore/pdispatch.c new file mode 100644 index 00000000..bdd7cde1 --- /dev/null +++ b/src/pulsecore/pdispatch.c @@ -0,0 +1,348 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdio.h> +#include <stdlib.h> + +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/native-common.h> +#include <pulsecore/llist.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/flist.h> + +#include "pdispatch.h" + +/*#define DEBUG_OPCODES */ + +#ifdef DEBUG_OPCODES + +static const char *command_names[PA_COMMAND_MAX] = { + [PA_COMMAND_ERROR] = "ERROR", + [PA_COMMAND_TIMEOUT] = "TIMEOUT", + [PA_COMMAND_REPLY] = "REPLY", + [PA_COMMAND_CREATE_PLAYBACK_STREAM] = "CREATE_PLAYBACK_STREAM", + [PA_COMMAND_DELETE_PLAYBACK_STREAM] = "DELETE_PLAYBACK_STREAM", + [PA_COMMAND_CREATE_RECORD_STREAM] = "CREATE_RECORD_STREAM", + [PA_COMMAND_DELETE_RECORD_STREAM] = "DELETE_RECORD_STREAM", + [PA_COMMAND_AUTH] = "AUTH", + [PA_COMMAND_REQUEST] = "REQUEST", + [PA_COMMAND_EXIT] = "EXIT", + [PA_COMMAND_SET_CLIENT_NAME] = "SET_CLIENT_NAME", + [PA_COMMAND_LOOKUP_SINK] = "LOOKUP_SINK", + [PA_COMMAND_LOOKUP_SOURCE] = "LOOKUP_SOURCE", + [PA_COMMAND_DRAIN_PLAYBACK_STREAM] = "DRAIN_PLAYBACK_STREAM", + [PA_COMMAND_PLAYBACK_STREAM_KILLED] = "PLAYBACK_STREAM_KILLED", + [PA_COMMAND_RECORD_STREAM_KILLED] = "RECORD_STREAM_KILLED", + [PA_COMMAND_STAT] = "STAT", + [PA_COMMAND_GET_PLAYBACK_LATENCY] = "PLAYBACK_LATENCY", + [PA_COMMAND_CREATE_UPLOAD_STREAM] = "CREATE_UPLOAD_STREAM", + [PA_COMMAND_DELETE_UPLOAD_STREAM] = "DELETE_UPLOAD_STREAM", + [PA_COMMAND_FINISH_UPLOAD_STREAM] = "FINISH_UPLOAD_STREAM", + [PA_COMMAND_PLAY_SAMPLE] = "PLAY_SAMPLE", + [PA_COMMAND_REMOVE_SAMPLE] = "REMOVE_SAMPLE", + [PA_COMMAND_GET_SERVER_INFO] = "GET_SERVER_INFO", + [PA_COMMAND_GET_SINK_INFO] = "GET_SINK_INFO", + [PA_COMMAND_GET_SINK_INFO_LIST] = "GET_SINK_INFO_LIST", + [PA_COMMAND_GET_SOURCE_INFO] = "GET_SOURCE_INFO", + [PA_COMMAND_GET_SOURCE_INFO_LIST] = "GET_SOURCE_INFO_LIST", + [PA_COMMAND_GET_MODULE_INFO] = "GET_MODULE_INFO", + [PA_COMMAND_GET_MODULE_INFO_LIST] = "GET_MODULE_INFO_LIST", + [PA_COMMAND_GET_CLIENT_INFO] = "GET_CLIENT_INFO", + [PA_COMMAND_GET_CLIENT_INFO_LIST] = "GET_CLIENT_INFO_LIST", + [PA_COMMAND_GET_SAMPLE_INFO] = "GET_SAMPLE_INFO", + [PA_COMMAND_GET_SAMPLE_INFO_LIST] = "GET_SAMPLE_INFO_LIST", + [PA_COMMAND_GET_SINK_INPUT_INFO] = "GET_SINK_INPUT_INFO", + [PA_COMMAND_GET_SINK_INPUT_INFO_LIST] = "GET_SINK_INPUT_INFO_LIST", + [PA_COMMAND_GET_SOURCE_OUTPUT_INFO] = "GET_SOURCE_OUTPUT_INFO", + [PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST] = "GET_SOURCE_OUTPUT_INFO_LIST", + [PA_COMMAND_SUBSCRIBE] = "SUBSCRIBE", + [PA_COMMAND_SUBSCRIBE_EVENT] = "SUBSCRIBE_EVENT", + [PA_COMMAND_SET_SINK_VOLUME] = "SET_SINK_VOLUME", + [PA_COMMAND_SET_SINK_INPUT_VOLUME] = "SET_SINK_INPUT_VOLUME", + [PA_COMMAND_SET_SOURCE_VOLUME] = "SET_SOURCE_VOLME", + [PA_COMMAND_TRIGGER_PLAYBACK_STREAM] = "TRIGGER_PLAYBACK_STREAM", + [PA_COMMAND_FLUSH_PLAYBACK_STREAM] = "FLUSH_PLAYBACK_STREAM", + [PA_COMMAND_CORK_PLAYBACK_STREAM] = "CORK_PLAYBACK_STREAM", + [PA_COMMAND_GET_AUTOLOAD_INFO] = "GET_AUTOLOAD_INFO", + [PA_COMMAND_GET_AUTOLOAD_INFO_LIST] = "GET_AUTOLOAD_INFO_LIST", +}; + +#endif + +PA_STATIC_FLIST_DECLARE(reply_infos, 0, pa_xfree); + +struct reply_info { + pa_pdispatch *pdispatch; + PA_LLIST_FIELDS(struct reply_info); + pa_pdispatch_cb_t callback; + void *userdata; + pa_free_cb_t free_cb; + uint32_t tag; + pa_time_event *time_event; +}; + +struct pa_pdispatch { + PA_REFCNT_DECLARE; + pa_mainloop_api *mainloop; + const pa_pdispatch_cb_t *callback_table; + unsigned n_commands; + PA_LLIST_HEAD(struct reply_info, replies); + pa_pdispatch_drain_callback drain_callback; + void *drain_userdata; + const pa_creds *creds; +}; + +static void reply_info_free(struct reply_info *r) { + pa_assert(r); + pa_assert(r->pdispatch); + pa_assert(r->pdispatch->mainloop); + + if (r->time_event) + r->pdispatch->mainloop->time_free(r->time_event); + + PA_LLIST_REMOVE(struct reply_info, r->pdispatch->replies, r); + + if (pa_flist_push(PA_STATIC_FLIST_GET(reply_infos), r) < 0) + pa_xfree(r); +} + +pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *mainloop, const pa_pdispatch_cb_t*table, unsigned entries) { + pa_pdispatch *pd; + pa_assert(mainloop); + + pa_assert((entries && table) || (!entries && !table)); + + pd = pa_xnew(pa_pdispatch, 1); + PA_REFCNT_INIT(pd); + pd->mainloop = mainloop; + pd->callback_table = table; + pd->n_commands = entries; + PA_LLIST_HEAD_INIT(struct reply_info, pd->replies); + pd->drain_callback = NULL; + pd->drain_userdata = NULL; + pd->creds = NULL; + + return pd; +} + +static void pdispatch_free(pa_pdispatch *pd) { + pa_assert(pd); + + while (pd->replies) { + if (pd->replies->free_cb) + pd->replies->free_cb(pd->replies->userdata); + + reply_info_free(pd->replies); + } + + pa_xfree(pd); +} + +static void run_action(pa_pdispatch *pd, struct reply_info *r, uint32_t command, pa_tagstruct *ts) { + pa_pdispatch_cb_t callback; + void *userdata; + uint32_t tag; + pa_assert(r); + + pa_pdispatch_ref(pd); + + callback = r->callback; + userdata = r->userdata; + tag = r->tag; + + reply_info_free(r); + + callback(pd, command, tag, ts, userdata); + + if (pd->drain_callback && !pa_pdispatch_is_pending(pd)) + pd->drain_callback(pd, pd->drain_userdata); + + pa_pdispatch_unref(pd); +} + +int pa_pdispatch_run(pa_pdispatch *pd, pa_packet*packet, const pa_creds *creds, void *userdata) { + uint32_t tag, command; + pa_tagstruct *ts = NULL; + int ret = -1; + + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + pa_assert(packet); + pa_assert(PA_REFCNT_VALUE(packet) >= 1); + pa_assert(packet->data); + + pa_pdispatch_ref(pd); + + if (packet->length <= 8) + goto finish; + + ts = pa_tagstruct_new(packet->data, packet->length); + + if (pa_tagstruct_getu32(ts, &command) < 0 || + pa_tagstruct_getu32(ts, &tag) < 0) + goto finish; + +#ifdef DEBUG_OPCODES +{ + char t[256]; + char const *p; + if (!(p = command_names[command])) + pa_snprintf((char*) (p = t), sizeof(t), "%u", command); + + pa_log("Recieved opcode <%s>", p); +} +#endif + + pd->creds = creds; + + if (command == PA_COMMAND_ERROR || command == PA_COMMAND_REPLY) { + struct reply_info *r; + + for (r = pd->replies; r; r = r->next) + if (r->tag == tag) + break; + + if (r) + run_action(pd, r, command, ts); + + } else if (pd->callback_table && (command < pd->n_commands) && pd->callback_table[command]) { + const pa_pdispatch_cb_t *c = pd->callback_table+command; + + (*c)(pd, command, tag, ts, userdata); + } else { + pa_log("Recieved unsupported command %u", command); + goto finish; + } + + ret = 0; + +finish: + pd->creds = NULL; + + if (ts) + pa_tagstruct_free(ts); + + pa_pdispatch_unref(pd); + + return ret; +} + +static void timeout_callback(pa_mainloop_api*m, pa_time_event*e, PA_GCC_UNUSED const struct timeval *tv, void *userdata) { + struct reply_info*r = userdata; + + pa_assert(r); + pa_assert(r->time_event == e); + pa_assert(r->pdispatch); + pa_assert(r->pdispatch->mainloop == m); + pa_assert(r->callback); + + run_action(r->pdispatch, r, PA_COMMAND_TIMEOUT, NULL); +} + +void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa_pdispatch_cb_t cb, void *userdata, pa_free_cb_t free_cb) { + struct reply_info *r; + struct timeval tv; + + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + pa_assert(cb); + + if (!(r = pa_flist_pop(PA_STATIC_FLIST_GET(reply_infos)))) + r = pa_xnew(struct reply_info, 1); + + r->pdispatch = pd; + r->callback = cb; + r->userdata = userdata; + r->free_cb = free_cb; + r->tag = tag; + + pa_gettimeofday(&tv); + tv.tv_sec += timeout; + + pa_assert_se(r->time_event = pd->mainloop->time_new(pd->mainloop, &tv, timeout_callback, r)); + + PA_LLIST_PREPEND(struct reply_info, pd->replies, r); +} + +int pa_pdispatch_is_pending(pa_pdispatch *pd) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + return !!pd->replies; +} + +void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, void (*cb)(pa_pdispatch *pd, void *userdata), void *userdata) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + pa_assert(!cb || pa_pdispatch_is_pending(pd)); + + pd->drain_callback = cb; + pd->drain_userdata = userdata; +} + +void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata) { + struct reply_info *r, *n; + + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + for (r = pd->replies; r; r = n) { + n = r->next; + + if (r->userdata == userdata) + reply_info_free(r); + } +} + +void pa_pdispatch_unref(pa_pdispatch *pd) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + if (PA_REFCNT_DEC(pd) <= 0) + pdispatch_free(pd); +} + +pa_pdispatch* pa_pdispatch_ref(pa_pdispatch *pd) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + PA_REFCNT_INC(pd); + return pd; +} + +const pa_creds * pa_pdispatch_creds(pa_pdispatch *pd) { + pa_assert(pd); + pa_assert(PA_REFCNT_VALUE(pd) >= 1); + + return pd->creds; +} diff --git a/src/pulsecore/pdispatch.h b/src/pulsecore/pdispatch.h new file mode 100644 index 00000000..de0aa3ec --- /dev/null +++ b/src/pulsecore/pdispatch.h @@ -0,0 +1,59 @@ +#ifndef foopdispatchhfoo +#define foopdispatchhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <inttypes.h> + +#include <pulse/mainloop-api.h> +#include <pulse/def.h> + +#include <pulsecore/tagstruct.h> +#include <pulsecore/packet.h> +#include <pulsecore/creds.h> + +typedef struct pa_pdispatch pa_pdispatch; + +typedef void (*pa_pdispatch_cb_t)(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +typedef void (*pa_pdispatch_drain_callback)(pa_pdispatch *pd, void *userdata); + +pa_pdispatch* pa_pdispatch_new(pa_mainloop_api *m, const pa_pdispatch_cb_t*table, unsigned entries); +void pa_pdispatch_unref(pa_pdispatch *pd); +pa_pdispatch* pa_pdispatch_ref(pa_pdispatch *pd); + +int pa_pdispatch_run(pa_pdispatch *pd, pa_packet*p, const pa_creds *creds, void *userdata); + +void pa_pdispatch_register_reply(pa_pdispatch *pd, uint32_t tag, int timeout, pa_pdispatch_cb_t callback, void *userdata, pa_free_cb_t free_cb); + +int pa_pdispatch_is_pending(pa_pdispatch *pd); + +void pa_pdispatch_set_drain_callback(pa_pdispatch *pd, pa_pdispatch_drain_callback callback, void *userdata); + +/* Remove all reply slots with the give userdata parameter */ +void pa_pdispatch_unregister_reply(pa_pdispatch *pd, void *userdata); + +const pa_creds * pa_pdispatch_creds(pa_pdispatch *pd); + +#endif diff --git a/src/pulsecore/pid.c b/src/pulsecore/pid.c new file mode 100644 index 00000000..f3c9faaa --- /dev/null +++ b/src/pulsecore/pid.c @@ -0,0 +1,332 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <signal.h> + +#ifdef HAVE_WINDOWS_H +#include <windows.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "pid.h" + +/* Read the PID data from the file descriptor fd, and return it. If no + * pid could be read, return 0, on failure (pid_t) -1 */ +static pid_t read_pid(const char *fn, int fd) { + ssize_t r; + char t[20], *e; + uint32_t pid; + + pa_assert(fn); + pa_assert(fd >= 0); + + if ((r = pa_loop_read(fd, t, sizeof(t)-1, NULL)) < 0) { + pa_log_warn("Failed to read PID file '%s': %s", fn, pa_cstrerror(errno)); + return (pid_t) -1; + } + + if (r == 0) + return (pid_t) 0; + + t[r] = 0; + if ((e = strchr(t, '\n'))) + *e = 0; + + if (pa_atou(t, &pid) < 0) { + pa_log_warn("Failed to parse PID file '%s'", fn); + return (pid_t) -1; + } + + return (pid_t) pid; +} + +static int open_pid_file(const char *fn, int mode) { + int fd = -1; + + pa_assert(fn); + + for (;;) { + struct stat st; + + if ((fd = open(fn, mode +#ifdef O_NOCTTY + |O_NOCTTY +#endif +#ifdef O_NOFOLLOW + |O_NOFOLLOW +#endif + , S_IRUSR|S_IWUSR + )) < 0) { + if (mode != O_RDONLY || errno != ENOENT) + pa_log_warn("Failed to open PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + /* Try to lock the file. If that fails, go without */ + if (pa_lock_fd(fd, 1) < 0) + goto fail; + + if (fstat(fd, &st) < 0) { + pa_log_warn("Failed to fstat() PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + /* Does the file still exist in the file system? When ye, w're done, otherwise restart */ + if (st.st_nlink >= 1) + break; + + if (pa_lock_fd(fd, 0) < 0) + goto fail; + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close file '%s': %s", fn, pa_cstrerror(errno)); + fd = -1; + goto fail; + } + + fd = -1; + } + + return fd; + +fail: + + if (fd >= 0) { + pa_lock_fd(fd, 0); + pa_close(fd); + } + + return -1; +} + +/* Create a new PID file for the current process. */ +int pa_pid_file_create(void) { + int fd = -1; + int ret = -1; + char fn[PATH_MAX]; + char t[20]; + pid_t pid; + size_t l; + +#ifdef OS_IS_WIN32 + HANDLE process; +#endif + + pa_runtime_path("pid", fn, sizeof(fn)); + + if ((fd = open_pid_file(fn, O_CREAT|O_RDWR)) < 0) + goto fail; + + if ((pid = read_pid(fn, fd)) == (pid_t) -1) + pa_log_warn("Corrupt PID file, overwriting."); + else if (pid > 0) { +#ifdef OS_IS_WIN32 + if ((process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid)) != NULL) { + CloseHandle(process); +#else + if (kill(pid, 0) >= 0 || errno != ESRCH) { +#endif + pa_log("Daemon already running."); + goto fail; + } + + pa_log_warn("Stale PID file, overwriting."); + } + + /* Overwrite the current PID file */ + if (lseek(fd, 0, SEEK_SET) == (off_t) -1 || ftruncate(fd, 0) < 0) { + pa_log("Failed to truncate PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + pa_snprintf(t, sizeof(t), "%lu\n", (unsigned long) getpid()); + l = strlen(t); + + if (pa_loop_write(fd, t, l, NULL) != (ssize_t) l) { + pa_log("Failed to write PID file."); + goto fail; + } + + ret = 0; + +fail: + if (fd >= 0) { + pa_lock_fd(fd, 0); + + if (pa_close(fd) < 0) { + pa_log("Failed to close PID file '%s': %s", fn, pa_cstrerror(errno)); + ret = -1; + } + } + + return ret; +} + +/* Remove the PID file, if it is ours */ +int pa_pid_file_remove(void) { + int fd = -1; + char fn[PATH_MAX]; + int ret = -1; + pid_t pid; + + pa_runtime_path("pid", fn, sizeof(fn)); + + if ((fd = open_pid_file(fn, O_RDWR)) < 0) { + pa_log_warn("Failed to open PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + if ((pid = read_pid(fn, fd)) == (pid_t) -1) + goto fail; + + if (pid != getpid()) { + pa_log("PID file '%s' not mine!", fn); + goto fail; + } + + if (ftruncate(fd, 0) < 0) { + pa_log_warn("Failed to truncate PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + +#ifdef OS_IS_WIN32 + pa_lock_fd(fd, 0); + close(fd); + fd = -1; +#endif + + if (unlink(fn) < 0) { + pa_log_warn("Failed to remove PID file '%s': %s", fn, pa_cstrerror(errno)); + goto fail; + } + + ret = 0; + +fail: + + if (fd >= 0) { + pa_lock_fd(fd, 0); + + if (pa_close(fd) < 0) { + pa_log_warn("Failed to close PID file '%s': %s", fn, pa_cstrerror(errno)); + ret = -1; + } + } + + return ret; +} + +/* Check whether the daemon is currently running, i.e. if a PID file + * exists and the PID therein too. Returns 0 on succcess, -1 + * otherwise. If pid is non-NULL and a running daemon was found, + * return its PID therein */ +int pa_pid_file_check_running(pid_t *pid, const char *binary_name) { + return pa_pid_file_kill(0, pid, binary_name); +} + +#ifndef OS_IS_WIN32 + +/* Kill a current running daemon. Return non-zero on success, -1 + * otherwise. If successful *pid contains the PID of the daemon + * process. */ +int pa_pid_file_kill(int sig, pid_t *pid, const char *binary_name) { + int fd = -1; + char fn[PATH_MAX]; + int ret = -1; + pid_t _pid; +#ifdef __linux__ + char *e = NULL; +#endif + if (!pid) + pid = &_pid; + + pa_runtime_path("pid", fn, sizeof(fn)); + + if ((fd = open_pid_file(fn, O_RDONLY)) < 0) + goto fail; + + if ((*pid = read_pid(fn, fd)) == (pid_t) -1) + goto fail; + +#ifdef __linux__ + if (binary_name) { + pa_snprintf(fn, sizeof(fn), "/proc/%lu/exe", (unsigned long) pid); + + if ((e = pa_readlink(fn))) { + char *f = pa_path_get_filename(e); + if (strcmp(f, binary_name) +#if defined(__OPTIMIZE__) + /* libtool likes to rename our binary names ... */ + && !(pa_startswith(f, "lt-") && strcmp(f+3, binary_name) == 0) +#endif + ) + goto fail; + } + } +#endif + + ret = kill(*pid, sig); + +fail: + + if (fd >= 0) { + pa_lock_fd(fd, 0); + pa_close(fd); + } + +#ifdef __linux__ + pa_xfree(e); +#endif + + return ret; + +} + +#else /* OS_IS_WIN32 */ + +int pa_pid_file_kill(int sig, pid_t *pid, const char *exe_name) { + return -1; +} + +#endif diff --git a/src/pulsecore/pid.h b/src/pulsecore/pid.h new file mode 100644 index 00000000..1d6de7b5 --- /dev/null +++ b/src/pulsecore/pid.h @@ -0,0 +1,32 @@ +#ifndef foopidhfoo +#define foopidhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + +int pa_pid_file_create(void); +int pa_pid_file_remove(void); +int pa_pid_file_check_running(pid_t *pid, const char *binary_name); +int pa_pid_file_kill(int sig, pid_t *pid, const char *binary_name); + +#endif diff --git a/src/pulsecore/pipe.c b/src/pulsecore/pipe.c new file mode 100644 index 00000000..e614c9c6 --- /dev/null +++ b/src/pulsecore/pipe.c @@ -0,0 +1,162 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <errno.h> +#include <unistd.h> +#include <fcntl.h> + +#include <sys/types.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif + +#include "winsock.h" + +#include "pipe.h" + +#ifndef HAVE_PIPE + +static int set_block(int fd, int blocking) { +#ifdef O_NONBLOCK + + int v; + + assert(fd >= 0); + + if ((v = fcntl(fd, F_GETFL)) < 0) + return -1; + + if (blocking) + v &= ~O_NONBLOCK; + else + v |= O_NONBLOCK; + + if (fcntl(fd, F_SETFL, v) < 0) + return -1; + + return 0; + +#elif defined(OS_IS_WIN32) + + u_long arg; + + arg = !blocking; + + if (ioctlsocket(fd, FIONBIO, &arg) < 0) + return -1; + + return 0; + +#else + + return -1; + +#endif +} + +int pipe(int filedes[2]) { + int listener; + struct sockaddr_in addr, peer; + socklen_t len; + + listener = -1; + filedes[0] = -1; + filedes[1] = -1; + + listener = socket(PF_INET, SOCK_STREAM, 0); + if (listener < 0) + goto error; + + filedes[0] = socket(PF_INET, SOCK_STREAM, 0); + if (filedes[0] < 0) + goto error; + + filedes[1] = socket(PF_INET, SOCK_STREAM, 0); + if (filedes[1] < 0) + goto error; + + /* Make non-blocking so that connect() won't block */ + if (set_block(filedes[0], 0) < 0) + goto error; + + addr.sin_family = AF_INET; + addr.sin_port = 0; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (bind(listener, (struct sockaddr*)&addr, sizeof(addr)) < 0) + goto error; + + if (listen(listener, 1) < 0) + goto error; + + len = sizeof(addr); + if (getsockname(listener, (struct sockaddr*)&addr, &len) < 0) + goto error; + + if (connect(filedes[0], (struct sockaddr*)&addr, sizeof(addr)) < 0) { +#ifdef OS_IS_WIN32 + if (WSAGetLastError() != EWOULDBLOCK) +#else + if (errno != EINPROGRESS) +#endif + goto error; + } + + len = sizeof(peer); + filedes[1] = accept(listener, (struct sockaddr*)&peer, &len); + if (filedes[1] < 0) + goto error; + + /* Restore blocking */ + if (set_block(filedes[0], 1) < 0) + goto error; + + len = sizeof(addr); + if (getsockname(filedes[0], (struct sockaddr*)&addr, &len) < 0) + goto error; + + /* Check that someone else didn't steal the connection */ + if ((addr.sin_port != peer.sin_port) || (addr.sin_addr.s_addr != peer.sin_addr.s_addr)) + goto error; + + pa_close(listener); + + return 0; + +error: + if (listener >= 0) + pa_close(listener); + if (filedes[0] >= 0) + pa_close(filedes[0]); + if (filedes[1] >= 0) + pa_close(filedes[0]); + + return -1; +} + +#endif /* HAVE_PIPE */ diff --git a/src/pulsecore/pipe.h b/src/pulsecore/pipe.h new file mode 100644 index 00000000..e013a2e7 --- /dev/null +++ b/src/pulsecore/pipe.h @@ -0,0 +1,33 @@ +#ifndef foopipehfoo +#define foopipehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +#ifndef HAVE_PIPE + +int pipe(int filedes[2]); + +#endif + +#endif diff --git a/src/pulsecore/play-memblockq.c b/src/pulsecore/play-memblockq.c new file mode 100644 index 00000000..5d3c2d39 --- /dev/null +++ b/src/pulsecore/play-memblockq.c @@ -0,0 +1,236 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <stdio.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/gccmacro.h> +#include <pulsecore/thread-mq.h> + +#include "play-memblockq.h" + +typedef struct memblockq_stream { + pa_msgobject parent; + pa_core *core; + pa_sink_input *sink_input; + pa_memblockq *memblockq; +} memblockq_stream; + +enum { + MEMBLOCKQ_STREAM_MESSAGE_UNLINK, +}; + +PA_DECLARE_CLASS(memblockq_stream); +#define MEMBLOCKQ_STREAM(o) (memblockq_stream_cast(o)) +static PA_DEFINE_CHECK_TYPE(memblockq_stream, pa_msgobject); + +static void memblockq_stream_unlink(memblockq_stream *u) { + pa_assert(u); + + if (!u->sink_input) + return; + + pa_sink_input_unlink(u->sink_input); + + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + memblockq_stream_unref(u); +} + +static void memblockq_stream_free(pa_object *o) { + memblockq_stream *u = MEMBLOCKQ_STREAM(o); + pa_assert(u); + + memblockq_stream_unlink(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + + pa_xfree(u); +} + +static int memblockq_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + memblockq_stream *u = MEMBLOCKQ_STREAM(o); + memblockq_stream_assert_ref(u); + + switch (code) { + case MEMBLOCKQ_STREAM_MESSAGE_UNLINK: + memblockq_stream_unlink(u); + break; + } + + return 0; +} + +static void sink_input_kill_cb(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + memblockq_stream_unlink(MEMBLOCKQ_STREAM(i->userdata)); +} + +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + memblockq_stream *u; + + pa_assert(i); + pa_assert(chunk); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (!u->memblockq) + return -1; + + if (pa_memblockq_peek(u->memblockq, chunk) < 0) { + pa_memblockq_free(u->memblockq); + u->memblockq = NULL; + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), MEMBLOCKQ_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL); + return -1; + } + + return 0; +} + +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + memblockq_stream *u; + + pa_assert(i); + pa_assert(length > 0); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (!u->memblockq) + return; + + pa_memblockq_drop(u->memblockq, length); +} + +pa_sink_input* pa_memblockq_sink_input_new( + pa_sink *sink, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *volume) { + + memblockq_stream *u = NULL; + pa_sink_input_new_data data; + + pa_assert(sink); + pa_assert(ss); + + /* We allow creating this stream with no q set, so that it can be + * filled in later */ + + if (q && pa_memblockq_get_length(q) <= 0) { + pa_memblockq_free(q); + return NULL; + } + + if (volume && pa_cvolume_is_muted(volume)) { + pa_memblockq_free(q); + return NULL; + } + + u = pa_msgobject_new(memblockq_stream); + u->parent.parent.free = memblockq_stream_free; + u->parent.process_msg = memblockq_stream_process_msg; + u->core = sink->core; + u->sink_input = NULL; + u->memblockq = q; + + pa_sink_input_new_data_init(&data); + data.sink = sink; + data.name = name; + data.driver = __FILE__; + pa_sink_input_new_data_set_sample_spec(&data, ss); + pa_sink_input_new_data_set_channel_map(&data, map); + pa_sink_input_new_data_set_volume(&data, volume); + + if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0))) + goto fail; + + u->sink_input->peek = sink_input_peek_cb; + u->sink_input->drop = sink_input_drop_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->userdata = u; + + if (q) + pa_memblockq_prebuf_disable(q); + + /* The reference to u is dangling here, because we want + * to keep this stream around until it is fully played. */ + + /* This sink input is not "put" yet, i.e. pa_sink_input_put() has + * not been called! */ + + return pa_sink_input_ref(u->sink_input); + +fail: + if (u) + memblockq_stream_unref(u); + + return NULL; +} + +int pa_play_memblockq( + pa_sink *sink, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *volume) { + + pa_sink_input *i; + + pa_assert(sink); + pa_assert(ss); + pa_assert(q); + + if (!(i = pa_memblockq_sink_input_new(sink, name, ss, map, q, volume))) + return -1; + + pa_sink_input_put(i); + pa_sink_input_unref(i); + + return 0; +} + +void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q) { + memblockq_stream *u; + + pa_sink_input_assert_ref(i); + u = MEMBLOCKQ_STREAM(i->userdata); + memblockq_stream_assert_ref(u); + + if (u->memblockq) + pa_memblockq_free(u->memblockq); + u->memblockq = q; +} diff --git a/src/pulsecore/play-memblockq.h b/src/pulsecore/play-memblockq.h new file mode 100644 index 00000000..d8790316 --- /dev/null +++ b/src/pulsecore/play-memblockq.h @@ -0,0 +1,48 @@ +#ifndef fooplaymemblockqhfoo +#define fooplaymemblockqhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/sink.h> +#include <pulsecore/memblockq.h> + +pa_sink_input* pa_memblockq_sink_input_new( + pa_sink *sink, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *volume); + +void pa_memblockq_sink_input_set_queue(pa_sink_input *i, pa_memblockq *q); + +int pa_play_memblockq( + pa_sink *sink, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + pa_memblockq *q, + pa_cvolume *cvolume); + +#endif diff --git a/src/pulsecore/play-memchunk.c b/src/pulsecore/play-memchunk.c new file mode 100644 index 00000000..6aaec567 --- /dev/null +++ b/src/pulsecore/play-memchunk.c @@ -0,0 +1,196 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <stdio.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/gccmacro.h> +#include <pulsecore/thread-mq.h> + +#include "play-memchunk.h" + +typedef struct memchunk_stream { + pa_msgobject parent; + pa_core *core; + pa_sink_input *sink_input; + pa_memchunk memchunk; +} memchunk_stream; + +enum { + MEMCHUNK_STREAM_MESSAGE_UNLINK, +}; + +PA_DECLARE_CLASS(memchunk_stream); +#define MEMCHUNK_STREAM(o) (memchunk_stream_cast(o)) +static PA_DEFINE_CHECK_TYPE(memchunk_stream, pa_msgobject); + +static void memchunk_stream_unlink(memchunk_stream *u) { + pa_assert(u); + + if (!u->sink_input) + return; + + pa_sink_input_unlink(u->sink_input); + + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + memchunk_stream_unref(u); +} + +static void memchunk_stream_free(pa_object *o) { + memchunk_stream *u = MEMCHUNK_STREAM(o); + pa_assert(u); + + memchunk_stream_unlink(u); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + pa_xfree(u); +} + +static int memchunk_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + memchunk_stream *u = MEMCHUNK_STREAM(o); + memchunk_stream_assert_ref(u); + + switch (code) { + case MEMCHUNK_STREAM_MESSAGE_UNLINK: + memchunk_stream_unlink(u); + break; + } + + return 0; +} + +static void sink_input_kill_cb(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + memchunk_stream_unlink(MEMCHUNK_STREAM(i->userdata)); +} + +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + memchunk_stream *u; + + pa_assert(i); + pa_assert(chunk); + u = MEMCHUNK_STREAM(i->userdata); + memchunk_stream_assert_ref(u); + + if (!u->memchunk.memblock) + return -1; + + if (u->memchunk.length <= 0) { + pa_memblock_unref(u->memchunk.memblock); + u->memchunk.memblock = NULL; + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), MEMCHUNK_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL); + return -1; + } + + pa_assert(u->memchunk.memblock); + *chunk = u->memchunk; + pa_memblock_ref(chunk->memblock); + + return 0; +} + +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + memchunk_stream *u; + + pa_assert(i); + pa_assert(length > 0); + u = MEMCHUNK_STREAM(i->userdata); + memchunk_stream_assert_ref(u); + + if (length < u->memchunk.length) { + u->memchunk.length -= length; + u->memchunk.index += length; + } else + u->memchunk.length = 0; +} + +int pa_play_memchunk( + pa_sink *sink, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_memchunk *chunk, + pa_cvolume *volume) { + + memchunk_stream *u = NULL; + pa_sink_input_new_data data; + + pa_assert(sink); + pa_assert(ss); + pa_assert(chunk); + + if (volume && pa_cvolume_is_muted(volume)) + return 0; + + pa_memchunk_will_need(chunk); + + u = pa_msgobject_new(memchunk_stream); + u->parent.parent.free = memchunk_stream_free; + u->parent.process_msg = memchunk_stream_process_msg; + u->core = sink->core; + u->memchunk = *chunk; + pa_memblock_ref(u->memchunk.memblock); + + pa_sink_input_new_data_init(&data); + data.sink = sink; + data.driver = __FILE__; + data.name = name; + pa_sink_input_new_data_set_sample_spec(&data, ss); + pa_sink_input_new_data_set_channel_map(&data, map); + pa_sink_input_new_data_set_volume(&data, volume); + + if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0))) + goto fail; + + u->sink_input->peek = sink_input_peek_cb; + u->sink_input->drop = sink_input_drop_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->userdata = u; + + pa_sink_input_put(u->sink_input); + + /* The reference to u is dangling here, because we want to keep + * this stream around until it is fully played. */ + + return 0; + +fail: + if (u) + memchunk_stream_unref(u); + + return -1; +} + diff --git a/src/pulsecore/play-memchunk.h b/src/pulsecore/play-memchunk.h new file mode 100644 index 00000000..5afb094c --- /dev/null +++ b/src/pulsecore/play-memchunk.h @@ -0,0 +1,38 @@ +#ifndef fooplaychunkhfoo +#define fooplaychunkhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/sink.h> +#include <pulsecore/memchunk.h> + +int pa_play_memchunk( + pa_sink *sink, + const char *name, + const pa_sample_spec *ss, + const pa_channel_map *map, + const pa_memchunk *chunk, + pa_cvolume *cvolume); + +#endif diff --git a/src/pulsecore/poll.c b/src/pulsecore/poll.c new file mode 100644 index 00000000..288f7dfb --- /dev/null +++ b/src/pulsecore/poll.c @@ -0,0 +1,196 @@ +/* $Id$ */ + + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +/*** + Based on work for the GNU C Library. + Copyright (C) 1994, 1996, 1997 Free Software Foundation, Inc. +***/ + +/* Poll the file descriptors described by the NFDS structures starting at + FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for + an event to occur; if TIMEOUT is -1, block until an event occurs. + Returns the number of file descriptors with events, zero if timed out, + or -1 for errors. */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <errno.h> + +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#include "winsock.h" + +#ifndef HAVE_POLL_H + +#include <pulsecore/core-util.h> + +#include "poll.h" + +int poll (struct pollfd *fds, unsigned long int nfds, int timeout) { + struct timeval tv; + fd_set rset, wset, xset; + struct pollfd *f; + int ready; + int maxfd = 0; + char data[64]; + + FD_ZERO (&rset); + FD_ZERO (&wset); + FD_ZERO (&xset); + + if (nfds == 0) { + if (timeout >= 0) { + pa_msleep(timeout); + return 0; + } + +#ifdef OS_IS_WIN32 + /* + * Windows does not support signals properly so waiting for them would + * mean a deadlock. + */ + pa_msleep(100); + return 0; +#else + return select(0, NULL, NULL, NULL, NULL); +#endif + } + + for (f = fds; f < &fds[nfds]; ++f) { + if (f->fd != -1) { + if (f->events & POLLIN) + FD_SET (f->fd, &rset); + if (f->events & POLLOUT) + FD_SET (f->fd, &wset); + if (f->events & POLLPRI) + FD_SET (f->fd, &xset); + if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI))) + maxfd = f->fd; + } + } + + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + + ready = select ((SELECT_TYPE_ARG1) maxfd + 1, SELECT_TYPE_ARG234 &rset, + SELECT_TYPE_ARG234 &wset, SELECT_TYPE_ARG234 &xset, + SELECT_TYPE_ARG5 (timeout == -1 ? NULL : &tv)); + if ((ready == -1) && (errno == EBADF)) { + ready = 0; + + FD_ZERO (&rset); + FD_ZERO (&wset); + FD_ZERO (&xset); + + maxfd = -1; + + for (f = fds; f < &fds[nfds]; ++f) { + if (f->fd != -1) { + fd_set sngl_rset, sngl_wset, sngl_xset; + + FD_ZERO (&sngl_rset); + FD_ZERO (&sngl_wset); + FD_ZERO (&sngl_xset); + + if (f->events & POLLIN) + FD_SET (f->fd, &sngl_rset); + if (f->events & POLLOUT) + FD_SET (f->fd, &sngl_wset); + if (f->events & POLLPRI) + FD_SET (f->fd, &sngl_xset); + if (f->events & (POLLIN|POLLOUT|POLLPRI)) { + struct timeval singl_tv; + + singl_tv.tv_sec = 0; + singl_tv.tv_usec = 0; + + if (select((SELECT_TYPE_ARG1) f->fd, SELECT_TYPE_ARG234 &rset, + SELECT_TYPE_ARG234 &wset, SELECT_TYPE_ARG234 &xset, + SELECT_TYPE_ARG5 &singl_tv) != -1) { + if (f->events & POLLIN) + FD_SET (f->fd, &rset); + if (f->events & POLLOUT) + FD_SET (f->fd, &wset); + if (f->events & POLLPRI) + FD_SET (f->fd, &xset); + if (f->fd > maxfd && (f->events & (POLLIN|POLLOUT|POLLPRI))) + maxfd = f->fd; + ++ready; + } else if (errno == EBADF) + f->revents |= POLLNVAL; + } + } + } + + if (ready) { + /* Linux alters the tv struct... but it shouldn't matter here ... + * as we're going to be a little bit out anyway as we've just eaten + * more than a couple of cpu cycles above */ + ready = select ((SELECT_TYPE_ARG1) maxfd + 1, SELECT_TYPE_ARG234 &rset, + SELECT_TYPE_ARG234 &wset, SELECT_TYPE_ARG234 &xset, + SELECT_TYPE_ARG5 (timeout == -1 ? NULL : &tv)); + } + } + +#ifdef OS_IS_WIN32 + errno = WSAGetLastError(); +#endif + + if (ready > 0) { + ready = 0; + for (f = fds; f < &fds[nfds]; ++f) { + f->revents = 0; + if (f->fd != -1) { + if (FD_ISSET (f->fd, &rset)) { + /* support for POLLHUP. An hung up descriptor does not + increase the return value! */ + if (recv (f->fd, data, 64, MSG_PEEK) == -1) { + if (errno == ESHUTDOWN || errno == ECONNRESET || + errno == ECONNABORTED || errno == ENETRESET) { + fprintf(stderr, "Hangup\n"); + f->revents |= POLLHUP; + } + } + + if (f->revents == 0) + f->revents |= POLLIN; + } + if (FD_ISSET (f->fd, &wset)) + f->revents |= POLLOUT; + if (FD_ISSET (f->fd, &xset)) + f->revents |= POLLPRI; + } + if (f->revents) + ready++; + } + } + + return ready; +} + +#endif /* HAVE_SYS_POLL_H */ diff --git a/src/pulsecore/poll.h b/src/pulsecore/poll.h new file mode 100644 index 00000000..6be6069b --- /dev/null +++ b/src/pulsecore/poll.h @@ -0,0 +1,60 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +/*** + Based on work for the GNU C Library. + Copyright (C) 1994,96,97,98,99,2000,2001,2004 Free Software Foundation, Inc. +***/ + +/* Event types that can be polled for. These bits may be set in `events' + to indicate the interesting event types; they will appear in `revents' + to indicate the status of the file descriptor. */ +#define POLLIN 0x001 /* There is data to read. */ +#define POLLPRI 0x002 /* There is urgent data to read. */ +#define POLLOUT 0x004 /* Writing now will not block. */ + +/* Event types always implicitly polled for. These bits need not be set in + `events', but they will appear in `revents' to indicate the status of + the file descriptor. */ +#define POLLERR 0x008 /* Error condition. */ +#define POLLHUP 0x010 /* Hung up. */ +#define POLLNVAL 0x020 /* Invalid polling request. */ + + +/* Type used for the number of file descriptors. */ +typedef unsigned long int nfds_t; + +/* Data structure describing a polling request. */ +struct pollfd + { + int fd; /* File descriptor to poll. */ + short int events; /* Types of events poller cares about. */ + short int revents; /* Types of events that actually occurred. */ + }; + +/* Poll the file descriptors described by the NFDS structures starting at + FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for + an event to occur; if TIMEOUT is -1, block until an event occurs. + Returns the number of file descriptors with events, zero if timed out, + or -1 for errors. */ +extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout); diff --git a/src/pulsecore/props.c b/src/pulsecore/props.c new file mode 100644 index 00000000..cbf748df --- /dev/null +++ b/src/pulsecore/props.c @@ -0,0 +1,140 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/xmalloc.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "props.h" + +typedef struct pa_property { + char *name; /* Points to memory allocated by the property subsystem */ + void *data; /* Points to memory maintained by the caller */ +} pa_property; + +/* Allocate a new property object */ +static pa_property* property_new(const char *name, void *data) { + pa_property* p; + + pa_assert(name); + pa_assert(data); + + p = pa_xnew(pa_property, 1); + p->name = pa_xstrdup(name); + p->data = data; + + return p; +} + +/* Free a property object */ +static void property_free(pa_property *p) { + pa_assert(p); + + pa_xfree(p->name); + pa_xfree(p); +} + +void* pa_property_get(pa_core *c, const char *name) { + pa_property *p; + + pa_assert(c); + pa_assert(name); + pa_assert(c->properties); + + if (!(p = pa_hashmap_get(c->properties, name))) + return NULL; + + return p->data; +} + +int pa_property_set(pa_core *c, const char *name, void *data) { + pa_property *p; + + pa_assert(c); + pa_assert(name); + pa_assert(data); + pa_assert(c->properties); + + if (pa_hashmap_get(c->properties, name)) + return -1; + + p = property_new(name, data); + pa_hashmap_put(c->properties, p->name, p); + return 0; +} + +int pa_property_remove(pa_core *c, const char *name) { + pa_property *p; + + pa_assert(c); + pa_assert(name); + pa_assert(c->properties); + + if (!(p = pa_hashmap_remove(c->properties, name))) + return -1; + + property_free(p); + return 0; +} + +void pa_property_init(pa_core *c) { + pa_assert(c); + + c->properties = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func); +} + +void pa_property_cleanup(pa_core *c) { + pa_assert(c); + + if (!c->properties) + return; + + pa_assert(!pa_hashmap_size(c->properties)); + + pa_hashmap_free(c->properties, NULL, NULL); + c->properties = NULL; + +} + +void pa_property_dump(pa_core *c, pa_strbuf *s) { + void *state = NULL; + pa_property *p; + + pa_assert(c); + pa_assert(s); + + while ((p = pa_hashmap_iterate(c->properties, &state, NULL))) + pa_strbuf_printf(s, "[%s] -> [%p]\n", p->name, p->data); +} + +int pa_property_replace(pa_core *c, const char *name, void *data) { + pa_assert(c); + pa_assert(name); + + pa_property_remove(c, name); + return pa_property_set(c, name, data); +} diff --git a/src/pulsecore/props.h b/src/pulsecore/props.h new file mode 100644 index 00000000..880325f6 --- /dev/null +++ b/src/pulsecore/props.h @@ -0,0 +1,60 @@ +#ifndef foopropshfoo +#define foopropshfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/core.h> +#include <pulsecore/strbuf.h> + +/* The property subsystem is to be used to share data between + * modules. Consider them to be kind of "global" variables for a + * core. Why not use the hashmap functions directly? The hashmap + * functions copy neither the key nor value, while this property + * system copies the key. Users of this system have to think about + * reference counting themselves. */ + +/* Return a pointer to the value of the specified property. */ +void* pa_property_get(pa_core *c, const char *name); + +/* Set the property 'name' to 'data'. This function fails in case a + * property by this name already exists. The property data is not + * copied or reference counted. This is the caller's job. */ +int pa_property_set(pa_core *c, const char *name, void *data); + +/* Remove the specified property. Return non-zero on failure */ +int pa_property_remove(pa_core *c, const char *name); + +/* A combination of pa_property_remove() and pa_property_set() */ +int pa_property_replace(pa_core *c, const char *name, void *data); + +/* Free all memory used by the property system */ +void pa_property_cleanup(pa_core *c); + +/* Initialize the properties subsystem */ +void pa_property_init(pa_core *c); + +/* Dump the current set of properties */ +void pa_property_dump(pa_core *c, pa_strbuf *s); + +#endif diff --git a/src/pulsecore/protocol-cli.c b/src/pulsecore/protocol-cli.c new file mode 100644 index 00000000..ceb6ae4d --- /dev/null +++ b/src/pulsecore/protocol-cli.c @@ -0,0 +1,105 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/xmalloc.h> + +#include <pulsecore/cli.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "protocol-cli.h" + +/* Don't allow more than this many concurrent connections */ +#define MAX_CONNECTIONS 25 + +struct pa_protocol_cli { + pa_module *module; + pa_core *core; + pa_socket_server*server; + pa_idxset *connections; +}; + +static void cli_eof_cb(pa_cli*c, void*userdata) { + pa_protocol_cli *p = userdata; + pa_assert(p); + + pa_idxset_remove_by_data(p->connections, c, NULL); + pa_cli_free(c); +} + +static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) { + pa_protocol_cli *p = userdata; + pa_cli *c; + + pa_assert(s); + pa_assert(io); + pa_assert(p); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + c = pa_cli_new(p->core, io, p->module); + pa_cli_set_eof_callback(c, cli_eof_cb, p); + + pa_idxset_put(p->connections, c, NULL); +} + +pa_protocol_cli* pa_protocol_cli_new(pa_core *core, pa_socket_server *server, pa_module *m, PA_GCC_UNUSED pa_modargs *ma) { + pa_protocol_cli* p; + + pa_core_assert_ref(core); + pa_assert(server); + + p = pa_xnew(pa_protocol_cli, 1); + p->module = m; + p->core = core; + p->server = server; + p->connections = pa_idxset_new(NULL, NULL); + + pa_socket_server_set_callback(p->server, on_connection, p); + + return p; +} + +static void free_connection(void *p, PA_GCC_UNUSED void *userdata) { + pa_assert(p); + + pa_cli_free(p); +} + +void pa_protocol_cli_free(pa_protocol_cli *p) { + pa_assert(p); + + pa_idxset_free(p->connections, free_connection, NULL); + pa_socket_server_unref(p->server); + pa_xfree(p); +} diff --git a/src/pulsecore/protocol-cli.h b/src/pulsecore/protocol-cli.h new file mode 100644 index 00000000..3870def3 --- /dev/null +++ b/src/pulsecore/protocol-cli.h @@ -0,0 +1,37 @@ +#ifndef fooprotocolclihfoo +#define fooprotocolclihfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/core.h> +#include <pulsecore/socket-server.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> + +typedef struct pa_protocol_cli pa_protocol_cli; + +pa_protocol_cli* pa_protocol_cli_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma); +void pa_protocol_cli_free(pa_protocol_cli *n); + +#endif diff --git a/src/pulsecore/protocol-esound.c b/src/pulsecore/protocol-esound.c new file mode 100644 index 00000000..004e535e --- /dev/null +++ b/src/pulsecore/protocol-esound.c @@ -0,0 +1,1471 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <errno.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> + +#include <pulse/sample.h> +#include <pulse/timeval.h> +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/esound.h> +#include <pulsecore/memblock.h> +#include <pulsecore/client.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/sink.h> +#include <pulsecore/source-output.h> +#include <pulsecore/source.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/authkey.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> +#include <pulsecore/ipacl.h> +#include <pulsecore/macro.h> +#include <pulsecore/thread-mq.h> + +#include "endianmacros.h" + +#include "protocol-esound.h" + +/* Don't accept more connection than this */ +#define MAX_CONNECTIONS 64 + +/* Kick a client if it doesn't authenticate within this time */ +#define AUTH_TIMEOUT 5 + +#define DEFAULT_COOKIE_FILE ".esd_auth" + +#define PLAYBACK_BUFFER_SECONDS (.25) +#define PLAYBACK_BUFFER_FRAGMENTS (10) +#define RECORD_BUFFER_SECONDS (5) +#define RECORD_BUFFER_FRAGMENTS (100) + +#define MAX_CACHE_SAMPLE_SIZE (1024000) + +#define SCACHE_PREFIX "esound." + +/* This is heavily based on esound's code */ + +typedef struct connection { + pa_msgobject parent; + + uint32_t index; + pa_bool_t dead; + pa_protocol_esound *protocol; + pa_iochannel *io; + pa_client *client; + pa_bool_t authorized, swap_byte_order; + void *write_data; + size_t write_data_alloc, write_data_index, write_data_length; + void *read_data; + size_t read_data_alloc, read_data_length; + esd_proto_t request; + esd_client_state_t state; + pa_sink_input *sink_input; + pa_source_output *source_output; + pa_memblockq *input_memblockq, *output_memblockq; + pa_defer_event *defer_event; + + char *original_name; + + struct { + pa_memblock *current_memblock; + size_t memblock_index, fragment_size; + pa_atomic_t missing; + } playback; + + struct { + pa_memchunk memchunk; + char *name; + pa_sample_spec sample_spec; + } scache; + + pa_time_event *auth_timeout_event; +} connection; + +PA_DECLARE_CLASS(connection); +#define CONNECTION(o) (connection_cast(o)) +static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject); + +struct pa_protocol_esound { + pa_module *module; + pa_core *core; + int public; + pa_socket_server *server; + pa_idxset *connections; + + char *sink_name, *source_name; + unsigned n_player; + uint8_t esd_key[ESD_KEY_LEN]; + pa_ip_acl *auth_ip_acl; +}; + +enum { + SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */ + SINK_INPUT_MESSAGE_DISABLE_PREBUF +}; + +enum { + CONNECTION_MESSAGE_REQUEST_DATA, + CONNECTION_MESSAGE_POST_DATA, + CONNECTION_MESSAGE_UNLINK_CONNECTION +}; + +typedef struct proto_handler { + size_t data_length; + int (*proc)(connection *c, esd_proto_t request, const void *data, size_t length); + const char *description; +} esd_proto_handler_info_t; + +static void sink_input_drop_cb(pa_sink_input *i, size_t length); +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk); +static void sink_input_kill_cb(pa_sink_input *i); +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); +static pa_usec_t source_output_get_latency_cb(pa_source_output *o); + +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk); +static void source_output_kill_cb(pa_source_output *o); + +static int esd_proto_connect(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_stream_play(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_stream_record(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_get_latency(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_server_info(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_all_info(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_stream_pan(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_sample_cache(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_sample_free_or_play(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_sample_get_id(connection *c, esd_proto_t request, const void *data, size_t length); +static int esd_proto_standby_or_resume(connection *c, esd_proto_t request, const void *data, size_t length); + +/* the big map of protocol handler info */ +static struct proto_handler proto_map[ESD_PROTO_MAX] = { + { ESD_KEY_LEN + sizeof(int), esd_proto_connect, "connect" }, + { ESD_KEY_LEN + sizeof(int), NULL, "lock" }, + { ESD_KEY_LEN + sizeof(int), NULL, "unlock" }, + + { ESD_NAME_MAX + 2 * sizeof(int), esd_proto_stream_play, "stream play" }, + { ESD_NAME_MAX + 2 * sizeof(int), esd_proto_stream_record, "stream rec" }, + { ESD_NAME_MAX + 2 * sizeof(int), esd_proto_stream_record, "stream mon" }, + + { ESD_NAME_MAX + 3 * sizeof(int), esd_proto_sample_cache, "sample cache" }, /* 6 */ + { sizeof(int), esd_proto_sample_free_or_play, "sample free" }, + { sizeof(int), esd_proto_sample_free_or_play, "sample play" }, /* 8 */ + { sizeof(int), NULL, "sample loop" }, + { sizeof(int), NULL, "sample stop" }, + { -1, NULL, "TODO: sample kill" }, + + { ESD_KEY_LEN + sizeof(int), esd_proto_standby_or_resume, "standby" }, /* NOOP! */ + { ESD_KEY_LEN + sizeof(int), esd_proto_standby_or_resume, "resume" }, /* NOOP! */ /* 13 */ + + { ESD_NAME_MAX, esd_proto_sample_get_id, "sample getid" }, /* 14 */ + { ESD_NAME_MAX + 2 * sizeof(int), NULL, "stream filter" }, + + { sizeof(int), esd_proto_server_info, "server info" }, + { sizeof(int), esd_proto_all_info, "all info" }, + { -1, NULL, "TODO: subscribe" }, + { -1, NULL, "TODO: unsubscribe" }, + + { 3 * sizeof(int), esd_proto_stream_pan, "stream pan"}, + { 3 * sizeof(int), NULL, "sample pan" }, + + { sizeof(int), NULL, "standby mode" }, + { 0, esd_proto_get_latency, "get latency" } +}; + +static void connection_unlink(connection *c) { + pa_assert(c); + + if (!c->protocol) + return; + + if (c->sink_input) { + pa_sink_input_unlink(c->sink_input); + pa_sink_input_unref(c->sink_input); + c->sink_input = NULL; + } + + if (c->source_output) { + pa_source_output_unlink(c->source_output); + pa_source_output_unref(c->source_output); + c->source_output = NULL; + } + + if (c->client) { + pa_client_free(c->client); + c->client = NULL; + } + + if (c->state == ESD_STREAMING_DATA) + c->protocol->n_player--; + + if (c->io) { + pa_iochannel_free(c->io); + c->io = NULL; + } + + if (c->defer_event) { + c->protocol->core->mainloop->defer_free(c->defer_event); + c->defer_event = NULL; + } + + if (c->auth_timeout_event) { + c->protocol->core->mainloop->time_free(c->auth_timeout_event); + c->auth_timeout_event = NULL; + } + + pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c); + c->protocol = NULL; + connection_unref(c); +} + +static void connection_free(pa_object *obj) { + connection *c = CONNECTION(obj); + pa_assert(c); + + if (c->input_memblockq) + pa_memblockq_free(c->input_memblockq); + if (c->output_memblockq) + pa_memblockq_free(c->output_memblockq); + + if (c->playback.current_memblock) + pa_memblock_unref(c->playback.current_memblock); + + pa_xfree(c->read_data); + pa_xfree(c->write_data); + + if (c->scache.memchunk.memblock) + pa_memblock_unref(c->scache.memchunk.memblock); + pa_xfree(c->scache.name); + + pa_xfree(c->original_name); + pa_xfree(c); +} + +static void connection_write_prepare(connection *c, size_t length) { + size_t t; + pa_assert(c); + + t = c->write_data_length+length; + + if (c->write_data_alloc < t) + c->write_data = pa_xrealloc(c->write_data, c->write_data_alloc = t); + + pa_assert(c->write_data); +} + +static void connection_write(connection *c, const void *data, size_t length) { + size_t i; + pa_assert(c); + + c->protocol->core->mainloop->defer_enable(c->defer_event, 1); + + connection_write_prepare(c, length); + + pa_assert(c->write_data); + + i = c->write_data_length; + c->write_data_length += length; + + memcpy((uint8_t*) c->write_data + i, data, length); +} + +static void format_esd2native(int format, pa_bool_t swap_bytes, pa_sample_spec *ss) { + pa_assert(ss); + + ss->channels = ((format & ESD_MASK_CHAN) == ESD_STEREO) ? 2 : 1; + if ((format & ESD_MASK_BITS) == ESD_BITS16) + ss->format = swap_bytes ? PA_SAMPLE_S16RE : PA_SAMPLE_S16NE; + else + ss->format = PA_SAMPLE_U8; +} + +static int format_native2esd(pa_sample_spec *ss) { + int format = 0; + + format = (ss->format == PA_SAMPLE_U8) ? ESD_BITS8 : ESD_BITS16; + format |= (ss->channels >= 2) ? ESD_STEREO : ESD_MONO; + + return format; +} + +#define CHECK_VALIDITY(expression, ...) do { \ + if (!(expression)) { \ + pa_log_warn(__FILE__ ": " __VA_ARGS__); \ + return -1; \ + } \ +} while(0); + +/*** esound commands ***/ + +static int esd_proto_connect(connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { + uint32_t ekey; + int ok; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (ESD_KEY_LEN + sizeof(uint32_t))); + + if (!c->authorized) { + if (memcmp(data, c->protocol->esd_key, ESD_KEY_LEN) != 0) { + pa_log("kicked client with invalid authorization key."); + return -1; + } + + c->authorized = TRUE; + if (c->auth_timeout_event) { + c->protocol->core->mainloop->time_free(c->auth_timeout_event); + c->auth_timeout_event = NULL; + } + } + + data = (const char*)data + ESD_KEY_LEN; + + memcpy(&ekey, data, sizeof(uint32_t)); + if (ekey == ESD_ENDIAN_KEY) + c->swap_byte_order = FALSE; + else if (ekey == ESD_SWAP_ENDIAN_KEY) + c->swap_byte_order = TRUE; + else { + pa_log_warn("Client sent invalid endian key"); + return -1; + } + + ok = 1; + connection_write(c, &ok, sizeof(int)); + return 0; +} + +static int esd_proto_stream_play(connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { + char name[ESD_NAME_MAX], *utf8_name; + int32_t format, rate; + pa_sample_spec ss; + size_t l; + pa_sink *sink = NULL; + pa_sink_input_new_data sdata; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (sizeof(int32_t)*2+ESD_NAME_MAX)); + + memcpy(&format, data, sizeof(int32_t)); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + data = (const char*) data + sizeof(int32_t); + + memcpy(&rate, data, sizeof(int32_t)); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + data = (const char*) data + sizeof(int32_t); + + ss.rate = rate; + format_esd2native(format, c->swap_byte_order, &ss); + + CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification"); + + if (c->protocol->sink_name) { + sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1); + CHECK_VALIDITY(sink, "No such sink: %s", c->protocol->sink_name); + } + + strncpy(name, data, sizeof(name)); + name[sizeof(name)-1] = 0; + + utf8_name = pa_utf8_filter(name); + pa_client_set_name(c->client, utf8_name); + pa_xfree(utf8_name); + + c->original_name = pa_xstrdup(name); + + pa_assert(!c->sink_input && !c->input_memblockq); + + pa_sink_input_new_data_init(&sdata); + sdata.sink = sink; + sdata.driver = __FILE__; + sdata.name = c->client->name; + pa_sink_input_new_data_set_sample_spec(&sdata, &ss); + sdata.module = c->protocol->module; + sdata.client = c->client; + + c->sink_input = pa_sink_input_new(c->protocol->core, &sdata, 0); + CHECK_VALIDITY(c->sink_input, "Failed to create sink input."); + + l = (size_t) (pa_bytes_per_second(&ss)*PLAYBACK_BUFFER_SECONDS); + c->input_memblockq = pa_memblockq_new( + 0, + l, + 0, + pa_frame_size(&ss), + (size_t) -1, + l/PLAYBACK_BUFFER_FRAGMENTS, + NULL); + pa_iochannel_socket_set_rcvbuf(c->io, l/PLAYBACK_BUFFER_FRAGMENTS*2); + c->playback.fragment_size = l/PLAYBACK_BUFFER_FRAGMENTS; + + c->sink_input->parent.process_msg = sink_input_process_msg; + c->sink_input->peek = sink_input_peek_cb; + c->sink_input->drop = sink_input_drop_cb; + c->sink_input->kill = sink_input_kill_cb; + c->sink_input->userdata = c; + + c->state = ESD_STREAMING_DATA; + + c->protocol->n_player++; + + pa_atomic_store(&c->playback.missing, pa_memblockq_missing(c->input_memblockq)); + + pa_sink_input_put(c->sink_input); + + return 0; +} + +static int esd_proto_stream_record(connection *c, esd_proto_t request, const void *data, size_t length) { + char name[ESD_NAME_MAX], *utf8_name; + int32_t format, rate; + pa_source *source = NULL; + pa_sample_spec ss; + size_t l; + pa_source_output_new_data sdata; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (sizeof(int32_t)*2+ESD_NAME_MAX)); + + memcpy(&format, data, sizeof(int32_t)); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + data = (const char*) data + sizeof(int32_t); + + memcpy(&rate, data, sizeof(int32_t)); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + data = (const char*) data + sizeof(int32_t); + + ss.rate = rate; + format_esd2native(format, c->swap_byte_order, &ss); + + CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification."); + + if (request == ESD_PROTO_STREAM_MON) { + pa_sink* sink; + + if (!(sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1))) { + pa_log("no such sink."); + return -1; + } + + if (!(source = sink->monitor_source)) { + pa_log("no such monitor source."); + return -1; + } + } else { + pa_assert(request == ESD_PROTO_STREAM_REC); + + if (c->protocol->source_name) { + if (!(source = pa_namereg_get(c->protocol->core, c->protocol->source_name, PA_NAMEREG_SOURCE, 1))) { + pa_log("no such source."); + return -1; + } + } + } + + strncpy(name, data, sizeof(name)); + name[sizeof(name)-1] = 0; + + utf8_name = pa_utf8_filter(name); + pa_client_set_name(c->client, utf8_name); + pa_xfree(utf8_name); + + c->original_name = pa_xstrdup(name); + + pa_assert(!c->output_memblockq && !c->source_output); + + pa_source_output_new_data_init(&sdata); + sdata.source = source; + sdata.driver = __FILE__; + sdata.name = c->client->name; + pa_source_output_new_data_set_sample_spec(&sdata, &ss); + sdata.module = c->protocol->module; + sdata.client = c->client; + + c->source_output = pa_source_output_new(c->protocol->core, &sdata, 9); + CHECK_VALIDITY(c->source_output, "Failed to create source_output."); + + l = (size_t) (pa_bytes_per_second(&ss)*RECORD_BUFFER_SECONDS); + c->output_memblockq = pa_memblockq_new( + 0, + l, + 0, + pa_frame_size(&ss), + 1, + 0, + NULL); + pa_iochannel_socket_set_sndbuf(c->io, l/RECORD_BUFFER_FRAGMENTS*2); + + c->source_output->push = source_output_push_cb; + c->source_output->kill = source_output_kill_cb; + c->source_output->get_latency = source_output_get_latency_cb; + c->source_output->userdata = c; + + c->state = ESD_STREAMING_DATA; + + c->protocol->n_player++; + + pa_source_output_put(c->source_output); + + return 0; +} + +static int esd_proto_get_latency(connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { + pa_sink *sink; + int32_t latency; + + connection_ref(c); + pa_assert(!data); + pa_assert(length == 0); + + if (!(sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1))) + latency = 0; + else { + double usec = pa_sink_get_latency(sink); + latency = (int) ((usec*44100)/1000000); + } + + latency = PA_MAYBE_INT32_SWAP(c->swap_byte_order, latency); + connection_write(c, &latency, sizeof(int32_t)); + return 0; +} + +static int esd_proto_server_info(connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { + int32_t rate = 44100, format = ESD_STEREO|ESD_BITS16; + int32_t response; + pa_sink *sink; + + connection_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)); + + if ((sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1))) { + rate = sink->sample_spec.rate; + format = format_native2esd(&sink->sample_spec); + } + + connection_write_prepare(c, sizeof(int32_t) * 3); + + response = 0; + connection_write(c, &response, sizeof(int32_t)); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + connection_write(c, &rate, sizeof(int32_t)); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + connection_write(c, &format, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_all_info(connection *c, esd_proto_t request, const void *data, size_t length) { + size_t t, k, s; + connection *conn; + uint32_t idx = PA_IDXSET_INVALID; + unsigned nsamples; + char terminator[sizeof(int32_t)*6+ESD_NAME_MAX]; + + connection_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)); + + if (esd_proto_server_info(c, request, data, length) < 0) + return -1; + + k = sizeof(int32_t)*5+ESD_NAME_MAX; + s = sizeof(int32_t)*6+ESD_NAME_MAX; + nsamples = c->protocol->core->scache ? pa_idxset_size(c->protocol->core->scache) : 0; + t = s*(nsamples+1) + k*(c->protocol->n_player+1); + + connection_write_prepare(c, t); + + memset(terminator, 0, sizeof(terminator)); + + for (conn = pa_idxset_first(c->protocol->connections, &idx); conn; conn = pa_idxset_next(c->protocol->connections, &idx)) { + int32_t id, format = ESD_BITS16 | ESD_STEREO, rate = 44100, lvolume = ESD_VOLUME_BASE, rvolume = ESD_VOLUME_BASE; + char name[ESD_NAME_MAX]; + + if (conn->state != ESD_STREAMING_DATA) + continue; + + pa_assert(t >= k*2+s); + + if (conn->sink_input) { + pa_cvolume volume = *pa_sink_input_get_volume(conn->sink_input); + rate = conn->sink_input->sample_spec.rate; + lvolume = (volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM; + rvolume = (volume.values[1]*ESD_VOLUME_BASE)/PA_VOLUME_NORM; + format = format_native2esd(&conn->sink_input->sample_spec); + } + + /* id */ + id = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int32_t) (conn->index+1)); + connection_write(c, &id, sizeof(int32_t)); + + /* name */ + memset(name, 0, ESD_NAME_MAX); /* don't leak old data */ + if (conn->original_name) + strncpy(name, conn->original_name, ESD_NAME_MAX); + else if (conn->client && conn->client->name) + strncpy(name, conn->client->name, ESD_NAME_MAX); + connection_write(c, name, ESD_NAME_MAX); + + /* rate */ + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + connection_write(c, &rate, sizeof(int32_t)); + + /* left */ + lvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, lvolume); + connection_write(c, &lvolume, sizeof(int32_t)); + + /*right*/ + rvolume = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rvolume); + connection_write(c, &rvolume, sizeof(int32_t)); + + /*format*/ + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + connection_write(c, &format, sizeof(int32_t)); + + t -= k; + } + + pa_assert(t == s*(nsamples+1)+k); + t -= k; + + connection_write(c, terminator, k); + + if (nsamples) { + pa_scache_entry *ce; + + idx = PA_IDXSET_INVALID; + for (ce = pa_idxset_first(c->protocol->core->scache, &idx); ce; ce = pa_idxset_next(c->protocol->core->scache, &idx)) { + int32_t id, rate, lvolume, rvolume, format, len; + char name[ESD_NAME_MAX]; + + pa_assert(t >= s*2); + + /* id */ + id = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int) (ce->index+1)); + connection_write(c, &id, sizeof(int32_t)); + + /* name */ + memset(name, 0, ESD_NAME_MAX); /* don't leak old data */ + if (strncmp(ce->name, SCACHE_PREFIX, sizeof(SCACHE_PREFIX)-1) == 0) + strncpy(name, ce->name+sizeof(SCACHE_PREFIX)-1, ESD_NAME_MAX); + else + pa_snprintf(name, ESD_NAME_MAX, "native.%s", ce->name); + connection_write(c, name, ESD_NAME_MAX); + + /* rate */ + rate = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, ce->sample_spec.rate); + connection_write(c, &rate, sizeof(int32_t)); + + /* left */ + lvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, (ce->volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM); + connection_write(c, &lvolume, sizeof(int32_t)); + + /*right*/ + rvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, (ce->volume.values[0]*ESD_VOLUME_BASE)/PA_VOLUME_NORM); + connection_write(c, &rvolume, sizeof(int32_t)); + + /*format*/ + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format_native2esd(&ce->sample_spec)); + connection_write(c, &format, sizeof(int32_t)); + + /*length*/ + len = PA_MAYBE_INT32_SWAP(c->swap_byte_order, (int) ce->memchunk.length); + connection_write(c, &len, sizeof(int32_t)); + + t -= s; + } + } + + pa_assert(t == s); + + connection_write(c, terminator, s); + + return 0; +} + +static int esd_proto_stream_pan(connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { + int32_t ok; + uint32_t idx, lvolume, rvolume; + connection *conn; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)*3); + + memcpy(&idx, data, sizeof(uint32_t)); + idx = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1; + data = (const char*)data + sizeof(uint32_t); + + memcpy(&lvolume, data, sizeof(uint32_t)); + lvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, lvolume); + data = (const char*)data + sizeof(uint32_t); + + memcpy(&rvolume, data, sizeof(uint32_t)); + rvolume = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, rvolume); + data = (const char*)data + sizeof(uint32_t); + + if ((conn = pa_idxset_get_by_index(c->protocol->connections, idx)) && conn->sink_input) { + pa_cvolume volume; + volume.values[0] = (lvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE; + volume.values[1] = (rvolume*PA_VOLUME_NORM)/ESD_VOLUME_BASE; + volume.channels = 2; + pa_sink_input_set_volume(conn->sink_input, &volume); + ok = 1; + } else + ok = 0; + + connection_write(c, &ok, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_sample_cache(connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { + pa_sample_spec ss; + int32_t format, rate, sc_length; + uint32_t idx; + char name[ESD_NAME_MAX+sizeof(SCACHE_PREFIX)-1]; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == (ESD_NAME_MAX+3*sizeof(int32_t))); + + memcpy(&format, data, sizeof(int32_t)); + format = PA_MAYBE_INT32_SWAP(c->swap_byte_order, format); + data = (const char*)data + sizeof(int32_t); + + memcpy(&rate, data, sizeof(int32_t)); + rate = PA_MAYBE_INT32_SWAP(c->swap_byte_order, rate); + data = (const char*)data + sizeof(int32_t); + + ss.rate = rate; + format_esd2native(format, c->swap_byte_order, &ss); + + CHECK_VALIDITY(pa_sample_spec_valid(&ss), "Invalid sample specification."); + + memcpy(&sc_length, data, sizeof(int32_t)); + sc_length = PA_MAYBE_INT32_SWAP(c->swap_byte_order, sc_length); + data = (const char*)data + sizeof(int32_t); + + CHECK_VALIDITY(sc_length <= MAX_CACHE_SAMPLE_SIZE, "Sample too large (%d bytes).", (int)sc_length); + + strcpy(name, SCACHE_PREFIX); + strncpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); + name[sizeof(name)-1] = 0; + + CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name."); + + pa_assert(!c->scache.memchunk.memblock); + c->scache.memchunk.memblock = pa_memblock_new(c->protocol->core->mempool, sc_length); + c->scache.memchunk.index = 0; + c->scache.memchunk.length = sc_length; + c->scache.sample_spec = ss; + pa_assert(!c->scache.name); + c->scache.name = pa_xstrdup(name); + + c->state = ESD_CACHING_SAMPLE; + + pa_scache_add_item(c->protocol->core, c->scache.name, NULL, NULL, NULL, &idx); + + idx += 1; + connection_write(c, &idx, sizeof(uint32_t)); + + return 0; +} + +static int esd_proto_sample_get_id(connection *c, PA_GCC_UNUSED esd_proto_t request, const void *data, size_t length) { + int32_t ok; + uint32_t idx; + char name[ESD_NAME_MAX+sizeof(SCACHE_PREFIX)-1]; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == ESD_NAME_MAX); + + strcpy(name, SCACHE_PREFIX); + strncpy(name+sizeof(SCACHE_PREFIX)-1, data, ESD_NAME_MAX); + name[sizeof(name)-1] = 0; + + CHECK_VALIDITY(pa_utf8_valid(name), "Invalid UTF8 in sample name."); + + ok = -1; + if ((idx = pa_scache_get_id_by_name(c->protocol->core, name)) != PA_IDXSET_INVALID) + ok = idx + 1; + + connection_write(c, &ok, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_sample_free_or_play(connection *c, esd_proto_t request, const void *data, size_t length) { + int32_t ok; + const char *name; + uint32_t idx; + + connection_assert_ref(c); + pa_assert(data); + pa_assert(length == sizeof(int32_t)); + + memcpy(&idx, data, sizeof(uint32_t)); + idx = PA_MAYBE_UINT32_SWAP(c->swap_byte_order, idx) - 1; + + ok = 0; + + if ((name = pa_scache_get_name_by_id(c->protocol->core, idx))) { + if (request == ESD_PROTO_SAMPLE_PLAY) { + pa_sink *sink; + + if ((sink = pa_namereg_get(c->protocol->core, c->protocol->sink_name, PA_NAMEREG_SINK, 1))) + if (pa_scache_play_item(c->protocol->core, name, sink, PA_VOLUME_NORM) >= 0) + ok = idx + 1; + } else { + pa_assert(request == ESD_PROTO_SAMPLE_FREE); + + if (pa_scache_remove_item(c->protocol->core, name) >= 0) + ok = idx + 1; + } + } + + connection_write(c, &ok, sizeof(int32_t)); + + return 0; +} + +static int esd_proto_standby_or_resume(connection *c, PA_GCC_UNUSED esd_proto_t request, PA_GCC_UNUSED const void *data, PA_GCC_UNUSED size_t length) { + int32_t ok; + + connection_assert_ref(c); + + connection_write_prepare(c, sizeof(int32_t) * 2); + + ok = 1; + connection_write(c, &ok, sizeof(int32_t)); + connection_write(c, &ok, sizeof(int32_t)); + + return 0; +} + +/*** client callbacks ***/ + +static void client_kill_cb(pa_client *c) { + pa_assert(c); + + connection_unlink(CONNECTION(c->userdata)); +} + +/*** pa_iochannel callbacks ***/ + +static int do_read(connection *c) { + connection_assert_ref(c); + +/* pa_log("READ"); */ + + if (c->state == ESD_NEXT_REQUEST) { + ssize_t r; + pa_assert(c->read_data_length < sizeof(c->request)); + + if ((r = pa_iochannel_read(c->io, ((uint8_t*) &c->request) + c->read_data_length, sizeof(c->request) - c->read_data_length)) <= 0) { + pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + return -1; + } + + if ((c->read_data_length+= r) >= sizeof(c->request)) { + struct proto_handler *handler; + + c->request = PA_MAYBE_INT32_SWAP(c->swap_byte_order, c->request); + + if (c->request < ESD_PROTO_CONNECT || c->request > ESD_PROTO_MAX) { + pa_log("recieved invalid request."); + return -1; + } + + handler = proto_map+c->request; + +/* pa_log("executing request #%u", c->request); */ + + if (!handler->proc) { + pa_log("recieved unimplemented request #%u.", c->request); + return -1; + } + + if (handler->data_length == 0) { + c->read_data_length = 0; + + if (handler->proc(c, c->request, NULL, 0) < 0) + return -1; + + } else { + if (c->read_data_alloc < handler->data_length) + c->read_data = pa_xrealloc(c->read_data, c->read_data_alloc = handler->data_length); + pa_assert(c->read_data); + + c->state = ESD_NEEDS_REQDATA; + c->read_data_length = 0; + } + } + + } else if (c->state == ESD_NEEDS_REQDATA) { + ssize_t r; + struct proto_handler *handler = proto_map+c->request; + + pa_assert(handler->proc); + + pa_assert(c->read_data && c->read_data_length < handler->data_length); + + if ((r = pa_iochannel_read(c->io, (uint8_t*) c->read_data + c->read_data_length, handler->data_length - c->read_data_length)) <= 0) { + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + return -1; + } + + if ((c->read_data_length += r) >= handler->data_length) { + size_t l = c->read_data_length; + pa_assert(handler->proc); + + c->state = ESD_NEXT_REQUEST; + c->read_data_length = 0; + + if (handler->proc(c, c->request, c->read_data, l) < 0) + return -1; + } + } else if (c->state == ESD_CACHING_SAMPLE) { + ssize_t r; + void *p; + + pa_assert(c->scache.memchunk.memblock); + pa_assert(c->scache.name); + pa_assert(c->scache.memchunk.index < c->scache.memchunk.length); + + p = pa_memblock_acquire(c->scache.memchunk.memblock); + r = pa_iochannel_read(c->io, (uint8_t*) p+c->scache.memchunk.index, c->scache.memchunk.length-c->scache.memchunk.index); + pa_memblock_release(c->scache.memchunk.memblock); + + if (r <= 0) { + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + return -1; + } + + c->scache.memchunk.index += r; + pa_assert(c->scache.memchunk.index <= c->scache.memchunk.length); + + if (c->scache.memchunk.index == c->scache.memchunk.length) { + uint32_t idx; + + c->scache.memchunk.index = 0; + pa_scache_add_item(c->protocol->core, c->scache.name, &c->scache.sample_spec, NULL, &c->scache.memchunk, &idx); + + pa_memblock_unref(c->scache.memchunk.memblock); + c->scache.memchunk.memblock = NULL; + c->scache.memchunk.index = c->scache.memchunk.length = 0; + + pa_xfree(c->scache.name); + c->scache.name = NULL; + + c->state = ESD_NEXT_REQUEST; + + idx += 1; + connection_write(c, &idx, sizeof(uint32_t)); + } + + } else if (c->state == ESD_STREAMING_DATA && c->sink_input) { + pa_memchunk chunk; + ssize_t r; + size_t l; + void *p; + + pa_assert(c->input_memblockq); + +/* pa_log("STREAMING_DATA"); */ + + if (!(l = pa_atomic_load(&c->playback.missing))) + return 0; + + if (l > c->playback.fragment_size) + l = c->playback.fragment_size; + + if (c->playback.current_memblock) + if (pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index < l) { + pa_memblock_unref(c->playback.current_memblock); + c->playback.current_memblock = NULL; + c->playback.memblock_index = 0; + } + + if (!c->playback.current_memblock) { + pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, c->playback.fragment_size*2)); + c->playback.memblock_index = 0; + } + + p = pa_memblock_acquire(c->playback.current_memblock); + r = pa_iochannel_read(c->io, (uint8_t*) p+c->playback.memblock_index, l); + pa_memblock_release(c->playback.current_memblock); + + if (r <= 0) { + + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log_debug("read(): %s", r < 0 ? pa_cstrerror(errno) : "EOF"); + return -1; + } + + chunk.memblock = c->playback.current_memblock; + chunk.index = c->playback.memblock_index; + chunk.length = r; + + c->playback.memblock_index += r; + + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, &chunk, NULL); + pa_atomic_sub(&c->playback.missing, r); + } + + return 0; +} + +static int do_write(connection *c) { + connection_assert_ref(c); + +/* pa_log("WRITE"); */ + + if (c->write_data_length) { + ssize_t r; + + pa_assert(c->write_data_index < c->write_data_length); + if ((r = pa_iochannel_write(c->io, (uint8_t*) c->write_data+c->write_data_index, c->write_data_length-c->write_data_index)) < 0) { + + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log("write(): %s", pa_cstrerror(errno)); + return -1; + } + + if ((c->write_data_index +=r) >= c->write_data_length) + c->write_data_length = c->write_data_index = 0; + + } else if (c->state == ESD_STREAMING_DATA && c->source_output) { + pa_memchunk chunk; + ssize_t r; + void *p; + + if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) + return 0; + + pa_assert(chunk.memblock); + pa_assert(chunk.length); + + p = pa_memblock_acquire(chunk.memblock); + r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length); + pa_memblock_release(chunk.memblock); + + pa_memblock_unref(chunk.memblock); + + if (r < 0) { + + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log("write(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_memblockq_drop(c->output_memblockq, r); + } + + return 0; +} + +static void do_work(connection *c) { + connection_assert_ref(c); + + c->protocol->core->mainloop->defer_enable(c->defer_event, 0); + + if (c->dead) + return; + + if (pa_iochannel_is_readable(c->io)) { + if (do_read(c) < 0) + goto fail; + } + + if (c->state == ESD_STREAMING_DATA && c->source_output && pa_iochannel_is_hungup(c->io)) + /* In case we are in capture mode we will never call read() + * on the socket, hence we need to detect the hangup manually + * here, instead of simply waiting for read() to return 0. */ + goto fail; + + if (pa_iochannel_is_writable(c->io)) + if (do_write(c) < 0) + goto fail; + + return; + +fail: + + if (c->state == ESD_STREAMING_DATA && c->sink_input) { + c->dead = TRUE; + + pa_iochannel_free(c->io); + c->io = NULL; + + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_DISABLE_PREBUF, NULL, 0, NULL, NULL); + } else + connection_unlink(c); +} + +static void io_callback(pa_iochannel*io, void *userdata) { + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + pa_assert(io); + + do_work(c); +} + +static void defer_callback(pa_mainloop_api*a, pa_defer_event *e, void *userdata) { + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + pa_assert(e); + + do_work(c); +} + +static int connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + connection *c = CONNECTION(o); + connection_assert_ref(c); + + switch (code) { + case CONNECTION_MESSAGE_REQUEST_DATA: + do_work(c); + break; + + case CONNECTION_MESSAGE_POST_DATA: +/* pa_log("got data %u", chunk->length); */ + pa_memblockq_push_align(c->output_memblockq, chunk); + do_work(c); + break; + + case CONNECTION_MESSAGE_UNLINK_CONNECTION: + connection_unlink(c); + break; + } + + return 0; +} + +/*** sink_input callbacks ***/ + +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + connection*c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + switch (code) { + + case SINK_INPUT_MESSAGE_POST_DATA: { + pa_assert(chunk); + + /* New data from the main loop */ + pa_memblockq_push_align(c->input_memblockq, chunk); + +/* pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */ + + return 0; + } + + case SINK_INPUT_MESSAGE_DISABLE_PREBUF: { + pa_memblockq_prebuf_disable(c->input_memblockq); + return 0; + } + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + *r = pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + } + + default: + return pa_sink_input_process_msg(o, code, userdata, offset, chunk); + } +} + +/* Called from thread context */ +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + connection*c; + int r; + + pa_assert(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + pa_assert(chunk); + + if ((r = pa_memblockq_peek(c->input_memblockq, chunk)) < 0 && c->dead) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL); + + return r; +} + +/* Called from thread context */ +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + connection*c; + size_t old, new; + + pa_assert(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + pa_assert(length); + + /* pa_log("DROP"); */ + + old = pa_memblockq_missing(c->input_memblockq); + pa_memblockq_drop(c->input_memblockq, length); + new = pa_memblockq_missing(c->input_memblockq); + + if (new > old) { + if (pa_atomic_add(&c->playback.missing, new - old) <= 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); + } +} + +static void sink_input_kill_cb(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + connection_unlink(CONNECTION(i->userdata)); +} + +/*** source_output callbacks ***/ + +/* Called from thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + connection *c; + + pa_assert(o); + c = CONNECTION(o->userdata); + pa_assert(c); + pa_assert(chunk); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); +} + +static void source_output_kill_cb(pa_source_output *o) { + pa_source_output_assert_ref(o); + + connection_unlink(CONNECTION(o->userdata)); +} + +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { + connection*c; + + pa_assert(o); + c = CONNECTION(o->userdata); + pa_assert(c); + + return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); +} + +/*** socket server callback ***/ + +static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) { + connection *c = CONNECTION(userdata); + + pa_assert(m); + pa_assert(tv); + connection_assert_ref(c); + pa_assert(c->auth_timeout_event == e); + + if (!c->authorized) + connection_unlink(c); +} + +static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) { + connection *c; + pa_protocol_esound *p = userdata; + char cname[256], pname[128]; + + pa_assert(s); + pa_assert(io); + pa_assert(p); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + c = pa_msgobject_new(connection); + c->parent.parent.free = connection_free; + c->parent.process_msg = connection_process_msg; + c->protocol = p; + c->io = io; + pa_iochannel_set_callback(c->io, io_callback, c); + + pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); + pa_snprintf(cname, sizeof(cname), "EsounD client (%s)", pname); + c->client = pa_client_new(p->core, __FILE__, cname); + c->client->owner = p->module; + c->client->kill = client_kill_cb; + c->client->userdata = c; + + c->authorized = !!p->public; + c->swap_byte_order = FALSE; + c->dead = FALSE; + + c->read_data_length = 0; + c->read_data = pa_xmalloc(c->read_data_alloc = proto_map[ESD_PROTO_CONNECT].data_length); + + c->write_data_length = c->write_data_index = c->write_data_alloc = 0; + c->write_data = NULL; + + c->state = ESD_NEEDS_REQDATA; + c->request = ESD_PROTO_CONNECT; + + c->sink_input = NULL; + c->input_memblockq = NULL; + + c->source_output = NULL; + c->output_memblockq = NULL; + + c->playback.current_memblock = NULL; + c->playback.memblock_index = 0; + c->playback.fragment_size = 0; + pa_atomic_store(&c->playback.missing, 0); + + c->scache.memchunk.length = c->scache.memchunk.index = 0; + c->scache.memchunk.memblock = NULL; + c->scache.name = NULL; + + c->original_name = NULL; + + if (!c->authorized && p->auth_ip_acl && pa_ip_acl_check(p->auth_ip_acl, pa_iochannel_get_recv_fd(io)) > 0) { + pa_log_info("Client authenticated by IP ACL."); + c->authorized = TRUE; + } + + if (!c->authorized) { + struct timeval tv; + pa_gettimeofday(&tv); + tv.tv_sec += AUTH_TIMEOUT; + c->auth_timeout_event = p->core->mainloop->time_new(p->core->mainloop, &tv, auth_timeout, c); + } else + c->auth_timeout_event = NULL; + + c->defer_event = p->core->mainloop->defer_new(p->core->mainloop, defer_callback, c); + p->core->mainloop->defer_enable(c->defer_event, 0); + + pa_idxset_put(p->connections, c, &c->index); +} + +/*** entry points ***/ + +pa_protocol_esound* pa_protocol_esound_new(pa_core*core, pa_socket_server *server, pa_module *m, pa_modargs *ma) { + pa_protocol_esound *p = NULL; + pa_bool_t public = FALSE; + const char *acl; + + pa_assert(core); + pa_assert(server); + pa_assert(m); + pa_assert(ma); + + if (pa_modargs_get_value_boolean(ma, "auth-anonymous", &public) < 0) { + pa_log("auth-anonymous= expects a boolean argument."); + goto fail; + } + + p = pa_xnew(pa_protocol_esound, 1); + + if (pa_authkey_load_auto(pa_modargs_get_value(ma, "cookie", DEFAULT_COOKIE_FILE), p->esd_key, sizeof(p->esd_key)) < 0) + goto fail; + + if ((acl = pa_modargs_get_value(ma, "auth-ip-acl", NULL))) { + + if (!(p->auth_ip_acl = pa_ip_acl_new(acl))) { + pa_log("Failed to parse IP ACL '%s'", acl); + goto fail; + } + } else + p->auth_ip_acl = NULL; + + p->core = core; + p->module = m; + p->public = public; + p->server = server; + pa_socket_server_set_callback(p->server, on_connection, p); + p->connections = pa_idxset_new(NULL, NULL); + + p->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + p->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL)); + p->n_player = 0; + + return p; + +fail: + pa_xfree(p); + return NULL; +} + +void pa_protocol_esound_free(pa_protocol_esound *p) { + connection *c; + pa_assert(p); + + while ((c = pa_idxset_first(p->connections, NULL))) + connection_unlink(c); + pa_idxset_free(p->connections, NULL, NULL); + + pa_socket_server_unref(p->server); + + if (p->auth_ip_acl) + pa_ip_acl_free(p->auth_ip_acl); + + pa_xfree(p->sink_name); + pa_xfree(p->source_name); + + pa_xfree(p); +} diff --git a/src/pulsecore/protocol-esound.h b/src/pulsecore/protocol-esound.h new file mode 100644 index 00000000..868ef5d2 --- /dev/null +++ b/src/pulsecore/protocol-esound.h @@ -0,0 +1,38 @@ +#ifndef fooprotocolesoundhfoo +#define fooprotocolesoundhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulsecore/core.h> +#include <pulsecore/socket-server.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> + +typedef struct pa_protocol_esound pa_protocol_esound; + +pa_protocol_esound* pa_protocol_esound_new(pa_core*core, pa_socket_server *server, pa_module *m, pa_modargs *ma); +void pa_protocol_esound_free(pa_protocol_esound *p); + +#endif diff --git a/src/pulsecore/protocol-http.c b/src/pulsecore/protocol-http.c new file mode 100644 index 00000000..d91ae142 --- /dev/null +++ b/src/pulsecore/protocol-http.c @@ -0,0 +1,277 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 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 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 <stdio.h> +#include <string.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/ioline.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> +#include <pulsecore/cli-text.h> + +#include "protocol-http.h" + +/* Don't allow more than this many concurrent connections */ +#define MAX_CONNECTIONS 10 + +#define internal_server_error(c) http_message((c), 500, "Internal Server Error", NULL) + +#define URL_ROOT "/" +#define URL_CSS "/style" +#define URL_STATUS "/status" + +struct connection { + pa_protocol_http *protocol; + pa_ioline *line; + enum { REQUEST_LINE, MIME_HEADER, DATA } state; + char *url; +}; + +struct pa_protocol_http { + pa_module *module; + pa_core *core; + pa_socket_server*server; + pa_idxset *connections; +}; + +static void http_response(struct connection *c, int code, const char *msg, const char *mime) { + char s[256]; + + pa_assert(c); + pa_assert(msg); + pa_assert(mime); + + pa_snprintf(s, sizeof(s), + "HTTP/1.0 %i %s\n" + "Connection: close\n" + "Content-Type: %s\n" + "Cache-Control: no-cache\n" + "Expires: 0\n" + "Server: "PACKAGE_NAME"/"PACKAGE_VERSION"\n" + "\n", code, msg, mime); + + pa_ioline_puts(c->line, s); +} + +static void http_message(struct connection *c, int code, const char *msg, const char *text) { + char s[256]; + pa_assert(c); + + http_response(c, code, msg, "text/html"); + + if (!text) + text = msg; + + pa_snprintf(s, sizeof(s), + "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\"><head><title>%s</title></head>\n" + "<body>%s</body></html>\n", + text, text); + + pa_ioline_puts(c->line, s); + pa_ioline_defer_close(c->line); +} + + +static void connection_free(struct connection *c, int del) { + pa_assert(c); + + if (c->url) + pa_xfree(c->url); + + if (del) + pa_idxset_remove_by_data(c->protocol->connections, c, NULL); + + pa_ioline_unref(c->line); + pa_xfree(c); +} + +static void line_callback(pa_ioline *line, const char *s, void *userdata) { + struct connection *c = userdata; + pa_assert(line); + pa_assert(c); + + if (!s) { + /* EOF */ + connection_free(c, 1); + return; + } + + switch (c->state) { + case REQUEST_LINE: { + if (memcmp(s, "GET ", 4)) + goto fail; + + s +=4; + + c->url = pa_xstrndup(s, strcspn(s, " \r\n\t?")); + c->state = MIME_HEADER; + break; + + } + + case MIME_HEADER: { + + /* Ignore MIME headers */ + if (strcspn(s, " \r\n") != 0) + break; + + /* We're done */ + c->state = DATA; + + pa_log_info("request for %s", c->url); + + if (!strcmp(c->url, URL_ROOT)) { + char txt[256]; + http_response(c, 200, "OK", "text/html"); + + pa_ioline_puts(c->line, + "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" + "<html xmlns=\"http://www.w3.org/1999/xhtml\"><title>"PACKAGE_NAME" "PACKAGE_VERSION"</title>\n" + "<link rel=\"stylesheet\" type=\"text/css\" href=\"style\"/></head><body>\n"); + + pa_ioline_puts(c->line, + "<h1>"PACKAGE_NAME" "PACKAGE_VERSION"</h1>\n" + "<table>"); + +#define PRINTF_FIELD(a,b) pa_ioline_printf(c->line, "<tr><td><b>%s</b></td><td>%s</td></tr>\n",(a),(b)) + + PRINTF_FIELD("User Name:", pa_get_user_name(txt, sizeof(txt))); + PRINTF_FIELD("Fully Qualified Domain Name:", pa_get_fqdn(txt, sizeof(txt))); + PRINTF_FIELD("Default Sample Specification:", pa_sample_spec_snprint(txt, sizeof(txt), &c->protocol->core->default_sample_spec)); + PRINTF_FIELD("Default Sink:", pa_namereg_get_default_sink_name(c->protocol->core)); + PRINTF_FIELD("Default Source:", pa_namereg_get_default_source_name(c->protocol->core)); + + pa_ioline_puts(c->line, "</table>"); + + pa_ioline_puts(c->line, "<p><a href=\"/status\">Click here</a> for an extensive server status report.</p>"); + + pa_ioline_puts(c->line, "</body></html>\n"); + + pa_ioline_defer_close(c->line); + } else if (!strcmp(c->url, URL_CSS)) { + http_response(c, 200, "OK", "text/css"); + + pa_ioline_puts(c->line, + "body { color: black; background-color: white; margin: 0.5cm; }\n" + "a:link, a:visited { color: #900000; }\n" + "p { margin-left: 0.5cm; margin-right: 0.5cm; }\n" + "h1 { color: #00009F; }\n" + "h2 { color: #00009F; }\n" + "ul { margin-left: .5cm; }\n" + "ol { margin-left: .5cm; }\n" + "pre { margin-left: .5cm; background-color: #f0f0f0; padding: 0.4cm;}\n" + ".grey { color: #afafaf; }\n" + "table { margin-left: 1cm; border:1px solid lightgrey; padding: 0.2cm; }\n" + "td { padding-left:10px; padding-right:10px; }\n"); + + pa_ioline_defer_close(c->line); + } else if (!strcmp(c->url, URL_STATUS)) { + char *r; + + http_response(c, 200, "OK", "text/plain"); + r = pa_full_status_string(c->protocol->core); + pa_ioline_puts(c->line, r); + pa_xfree(r); + + pa_ioline_defer_close(c->line); + } else + http_message(c, 404, "Not Found", NULL); + + break; + } + + default: + ; + } + + return; + +fail: + internal_server_error(c); +} + +static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) { + pa_protocol_http *p = userdata; + struct connection *c; + + pa_assert(s); + pa_assert(io); + pa_assert(p); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log_warn("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + c = pa_xnew(struct connection, 1); + c->protocol = p; + c->line = pa_ioline_new(io); + c->state = REQUEST_LINE; + c->url = NULL; + + pa_ioline_set_callback(c->line, line_callback, c); + pa_idxset_put(p->connections, c, NULL); +} + +pa_protocol_http* pa_protocol_http_new(pa_core *core, pa_socket_server *server, pa_module *m, PA_GCC_UNUSED pa_modargs *ma) { + pa_protocol_http* p; + + pa_core_assert_ref(core); + pa_assert(server); + + p = pa_xnew(pa_protocol_http, 1); + p->module = m; + p->core = core; + p->server = server; + p->connections = pa_idxset_new(NULL, NULL); + + pa_socket_server_set_callback(p->server, on_connection, p); + + return p; +} + +static void free_connection(void *p, PA_GCC_UNUSED void *userdata) { + pa_assert(p); + connection_free(p, 0); +} + +void pa_protocol_http_free(pa_protocol_http *p) { + pa_assert(p); + + pa_idxset_free(p->connections, free_connection, NULL); + pa_socket_server_unref(p->server); + pa_xfree(p); +} diff --git a/src/pulsecore/protocol-http.h b/src/pulsecore/protocol-http.h new file mode 100644 index 00000000..cf952476 --- /dev/null +++ b/src/pulsecore/protocol-http.h @@ -0,0 +1,37 @@ +#ifndef fooprotocolhttphfoo +#define fooprotocolhttphfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2005-2006 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 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 <pulsecore/core.h> +#include <pulsecore/socket-server.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> + +typedef struct pa_protocol_http pa_protocol_http; + +pa_protocol_http* pa_protocol_http_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma); +void pa_protocol_http_free(pa_protocol_http *n); + +#endif diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c new file mode 100644 index 00000000..4f582798 --- /dev/null +++ b/src/pulsecore/protocol-native.c @@ -0,0 +1,3458 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include <pulse/timeval.h> +#include <pulse/version.h> +#include <pulse/utf8.h> +#include <pulse/util.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/native-common.h> +#include <pulsecore/packet.h> +#include <pulsecore/client.h> +#include <pulsecore/source-output.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/pstream.h> +#include <pulsecore/tagstruct.h> +#include <pulsecore/pdispatch.h> +#include <pulsecore/pstream-util.h> +#include <pulsecore/authkey.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/autoload.h> +#include <pulsecore/authkey-prop.h> +#include <pulsecore/strlist.h> +#include <pulsecore/props.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/llist.h> +#include <pulsecore/creds.h> +#include <pulsecore/core-util.h> +#include <pulsecore/ipacl.h> +#include <pulsecore/thread-mq.h> + +#include "protocol-native.h" + +/* Kick a client if it doesn't authenticate within this time */ +#define AUTH_TIMEOUT 60 + +/* Don't accept more connection than this */ +#define MAX_CONNECTIONS 64 + +#define MAX_MEMBLOCKQ_LENGTH (4*1024*1024) /* 4MB */ + +typedef struct connection connection; +struct pa_protocol_native; + +typedef struct record_stream { + pa_msgobject parent; + + connection *connection; + uint32_t index; + + pa_source_output *source_output; + pa_memblockq *memblockq; + size_t fragment_size; +} record_stream; + +typedef struct output_stream { + pa_msgobject parent; +} output_stream; + +typedef struct playback_stream { + output_stream parent; + + connection *connection; + uint32_t index; + + pa_sink_input *sink_input; + pa_memblockq *memblockq; + int drain_request; + uint32_t drain_tag; + uint32_t syncid; + int underrun; + + pa_atomic_t missing; + size_t minreq; + + /* Only updated after SINK_INPUT_MESSAGE_UPDATE_LATENCY */ + int64_t read_index, write_index; + size_t resampled_chunk_length; +} playback_stream; + +typedef struct upload_stream { + output_stream parent; + + connection *connection; + uint32_t index; + + pa_memchunk memchunk; + size_t length; + char *name; + pa_sample_spec sample_spec; + pa_channel_map channel_map; +} upload_stream; + +struct connection { + pa_msgobject parent; + + int authorized; + uint32_t version; + pa_protocol_native *protocol; + pa_client *client; + pa_pstream *pstream; + pa_pdispatch *pdispatch; + pa_idxset *record_streams, *output_streams; + uint32_t rrobin_index; + pa_subscription *subscription; + pa_time_event *auth_timeout_event; +}; + +PA_DECLARE_CLASS(record_stream); +#define RECORD_STREAM(o) (record_stream_cast(o)) +static PA_DEFINE_CHECK_TYPE(record_stream, pa_msgobject); + +PA_DECLARE_CLASS(output_stream); +#define OUTPUT_STREAM(o) (output_stream_cast(o)) +static PA_DEFINE_CHECK_TYPE(output_stream, pa_msgobject); + +PA_DECLARE_CLASS(playback_stream); +#define PLAYBACK_STREAM(o) (playback_stream_cast(o)) +static PA_DEFINE_CHECK_TYPE(playback_stream, output_stream); + +PA_DECLARE_CLASS(upload_stream); +#define UPLOAD_STREAM(o) (upload_stream_cast(o)) +static PA_DEFINE_CHECK_TYPE(upload_stream, output_stream); + +PA_DECLARE_CLASS(connection); +#define CONNECTION(o) (connection_cast(o)) +static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject); + +struct pa_protocol_native { + pa_module *module; + pa_core *core; + int public; + pa_socket_server *server; + pa_idxset *connections; + uint8_t auth_cookie[PA_NATIVE_COOKIE_LENGTH]; + int auth_cookie_in_property; +#ifdef HAVE_CREDS + char *auth_group; +#endif + pa_ip_acl *auth_ip_acl; +}; + +enum { + SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */ + SINK_INPUT_MESSAGE_DRAIN, /* disabled prebuf, get playback started. */ + SINK_INPUT_MESSAGE_FLUSH, + SINK_INPUT_MESSAGE_TRIGGER, + SINK_INPUT_MESSAGE_SEEK, + SINK_INPUT_MESSAGE_PREBUF_FORCE, + SINK_INPUT_MESSAGE_UPDATE_LATENCY +}; + +enum { + PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, /* data requested from sink input from the main loop */ + PLAYBACK_STREAM_MESSAGE_UNDERFLOW, + PLAYBACK_STREAM_MESSAGE_OVERFLOW, + PLAYBACK_STREAM_MESSAGE_DRAIN_ACK +}; + +enum { + RECORD_STREAM_MESSAGE_POST_DATA /* data from source output to main loop */ +}; + +enum { + CONNECTION_MESSAGE_RELEASE, + CONNECTION_MESSAGE_REVOKE +}; + +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk); +static void sink_input_drop_cb(pa_sink_input *i, size_t length); +static void sink_input_kill_cb(pa_sink_input *i); +static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend); +static void sink_input_moved_cb(pa_sink_input *i); + +static void send_memblock(connection *c); +static void request_bytes(struct playback_stream*s); + +static void source_output_kill_cb(pa_source_output *o); +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk); +static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend); +static void source_output_moved_cb(pa_source_output *o); +static pa_usec_t source_output_get_latency_cb(pa_source_output *o); + +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +static void command_exit(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_create_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_drain_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_create_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_delete_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_auth(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_set_client_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_lookup(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_stat(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_get_playback_latency(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_get_record_latency(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_create_upload_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_finish_upload_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_play_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_remove_sample(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_get_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_get_info_list(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_get_server_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_subscribe(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_set_volume(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_set_mute(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_cork_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_trigger_or_flush_or_prebuf_playback_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_set_default_sink_or_source(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_set_stream_name(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_kill(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_load_module(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_unload_module(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_add_autoload(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_remove_autoload(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_get_autoload_info(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_get_autoload_info_list(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_cork_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_flush_record_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); +static void command_update_stream_sample_rate(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata); + +static const pa_pdispatch_cb_t command_table[PA_COMMAND_MAX] = { + [PA_COMMAND_ERROR] = NULL, + [PA_COMMAND_TIMEOUT] = NULL, + [PA_COMMAND_REPLY] = NULL, + [PA_COMMAND_CREATE_PLAYBACK_STREAM] = command_create_playback_stream, + [PA_COMMAND_DELETE_PLAYBACK_STREAM] = command_delete_stream, + [PA_COMMAND_DRAIN_PLAYBACK_STREAM] = command_drain_playback_stream, + [PA_COMMAND_CREATE_RECORD_STREAM] = command_create_record_stream, + [PA_COMMAND_DELETE_RECORD_STREAM] = command_delete_stream, + [PA_COMMAND_AUTH] = command_auth, + [PA_COMMAND_REQUEST] = NULL, + [PA_COMMAND_EXIT] = command_exit, + [PA_COMMAND_SET_CLIENT_NAME] = command_set_client_name, + [PA_COMMAND_LOOKUP_SINK] = command_lookup, + [PA_COMMAND_LOOKUP_SOURCE] = command_lookup, + [PA_COMMAND_STAT] = command_stat, + [PA_COMMAND_GET_PLAYBACK_LATENCY] = command_get_playback_latency, + [PA_COMMAND_GET_RECORD_LATENCY] = command_get_record_latency, + [PA_COMMAND_CREATE_UPLOAD_STREAM] = command_create_upload_stream, + [PA_COMMAND_DELETE_UPLOAD_STREAM] = command_delete_stream, + [PA_COMMAND_FINISH_UPLOAD_STREAM] = command_finish_upload_stream, + [PA_COMMAND_PLAY_SAMPLE] = command_play_sample, + [PA_COMMAND_REMOVE_SAMPLE] = command_remove_sample, + [PA_COMMAND_GET_SINK_INFO] = command_get_info, + [PA_COMMAND_GET_SOURCE_INFO] = command_get_info, + [PA_COMMAND_GET_CLIENT_INFO] = command_get_info, + [PA_COMMAND_GET_MODULE_INFO] = command_get_info, + [PA_COMMAND_GET_SINK_INPUT_INFO] = command_get_info, + [PA_COMMAND_GET_SOURCE_OUTPUT_INFO] = command_get_info, + [PA_COMMAND_GET_SAMPLE_INFO] = command_get_info, + [PA_COMMAND_GET_SINK_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SOURCE_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_MODULE_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_CLIENT_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SINK_INPUT_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SAMPLE_INFO_LIST] = command_get_info_list, + [PA_COMMAND_GET_SERVER_INFO] = command_get_server_info, + [PA_COMMAND_SUBSCRIBE] = command_subscribe, + + [PA_COMMAND_SET_SINK_VOLUME] = command_set_volume, + [PA_COMMAND_SET_SINK_INPUT_VOLUME] = command_set_volume, + [PA_COMMAND_SET_SOURCE_VOLUME] = command_set_volume, + + [PA_COMMAND_SET_SINK_MUTE] = command_set_mute, + [PA_COMMAND_SET_SINK_INPUT_MUTE] = command_set_mute, + [PA_COMMAND_SET_SOURCE_MUTE] = command_set_mute, + + [PA_COMMAND_SUSPEND_SINK] = command_suspend, + [PA_COMMAND_SUSPEND_SOURCE] = command_suspend, + + [PA_COMMAND_CORK_PLAYBACK_STREAM] = command_cork_playback_stream, + [PA_COMMAND_FLUSH_PLAYBACK_STREAM] = command_trigger_or_flush_or_prebuf_playback_stream, + [PA_COMMAND_TRIGGER_PLAYBACK_STREAM] = command_trigger_or_flush_or_prebuf_playback_stream, + [PA_COMMAND_PREBUF_PLAYBACK_STREAM] = command_trigger_or_flush_or_prebuf_playback_stream, + + [PA_COMMAND_CORK_RECORD_STREAM] = command_cork_record_stream, + [PA_COMMAND_FLUSH_RECORD_STREAM] = command_flush_record_stream, + + [PA_COMMAND_SET_DEFAULT_SINK] = command_set_default_sink_or_source, + [PA_COMMAND_SET_DEFAULT_SOURCE] = command_set_default_sink_or_source, + [PA_COMMAND_SET_PLAYBACK_STREAM_NAME] = command_set_stream_name, + [PA_COMMAND_SET_RECORD_STREAM_NAME] = command_set_stream_name, + [PA_COMMAND_KILL_CLIENT] = command_kill, + [PA_COMMAND_KILL_SINK_INPUT] = command_kill, + [PA_COMMAND_KILL_SOURCE_OUTPUT] = command_kill, + [PA_COMMAND_LOAD_MODULE] = command_load_module, + [PA_COMMAND_UNLOAD_MODULE] = command_unload_module, + [PA_COMMAND_GET_AUTOLOAD_INFO] = command_get_autoload_info, + [PA_COMMAND_GET_AUTOLOAD_INFO_LIST] = command_get_autoload_info_list, + [PA_COMMAND_ADD_AUTOLOAD] = command_add_autoload, + [PA_COMMAND_REMOVE_AUTOLOAD] = command_remove_autoload, + + [PA_COMMAND_MOVE_SINK_INPUT] = command_move_stream, + [PA_COMMAND_MOVE_SOURCE_OUTPUT] = command_move_stream, + + [PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr, + [PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR] = command_set_stream_buffer_attr, + + [PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate, + [PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE] = command_update_stream_sample_rate +}; + +/* structure management */ + +static void upload_stream_unlink(upload_stream *s) { + pa_assert(s); + + if (!s->connection) + return; + + pa_assert_se(pa_idxset_remove_by_data(s->connection->output_streams, s, NULL) == s); + s->connection = NULL; + upload_stream_unref(s); +} + +static void upload_stream_free(pa_object *o) { + upload_stream *s = UPLOAD_STREAM(o); + pa_assert(s); + + upload_stream_unlink(s); + + pa_xfree(s->name); + + if (s->memchunk.memblock) + pa_memblock_unref(s->memchunk.memblock); + + pa_xfree(s); +} + +static upload_stream* upload_stream_new( + connection *c, + const pa_sample_spec *ss, + const pa_channel_map *map, + const char *name, size_t length) { + + upload_stream *s; + + pa_assert(c); + pa_assert(ss); + pa_assert(name); + pa_assert(length > 0); + + s = pa_msgobject_new(upload_stream); + s->parent.parent.parent.free = upload_stream_free; + s->connection = c; + s->sample_spec = *ss; + s->channel_map = *map; + s->name = pa_xstrdup(name); + pa_memchunk_reset(&s->memchunk); + s->length = length; + + pa_idxset_put(c->output_streams, s, &s->index); + + return s; +} + +static void record_stream_unlink(record_stream *s) { + pa_assert(s); + + if (!s->connection) + return; + + if (s->source_output) { + pa_source_output_unlink(s->source_output); + pa_source_output_unref(s->source_output); + s->source_output = NULL; + } + + pa_assert_se(pa_idxset_remove_by_data(s->connection->record_streams, s, NULL) == s); + s->connection = NULL; + record_stream_unref(s); +} + +static void record_stream_free(pa_object *o) { + record_stream *s = RECORD_STREAM(o); + pa_assert(s); + + record_stream_unlink(s); + + pa_memblockq_free(s->memblockq); + pa_xfree(s); +} + +static int record_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + record_stream *s = RECORD_STREAM(o); + record_stream_assert_ref(s); + + if (!s->connection) + return -1; + + switch (code) { + + case RECORD_STREAM_MESSAGE_POST_DATA: + + if (pa_memblockq_push_align(s->memblockq, chunk) < 0) { +/* pa_log_warn("Failed to push data into output queue."); */ + return -1; + } + + if (!pa_pstream_is_pending(s->connection->pstream)) + send_memblock(s->connection); + + break; + } + + return 0; +} + +static record_stream* record_stream_new( + connection *c, + pa_source *source, + pa_sample_spec *ss, + pa_channel_map *map, + const char *name, + uint32_t *maxlength, + uint32_t fragment_size, + pa_source_output_flags_t flags) { + + record_stream *s; + pa_source_output *source_output; + size_t base; + pa_source_output_new_data data; + + pa_assert(c); + pa_assert(ss); + pa_assert(name); + pa_assert(maxlength); + pa_assert(*maxlength > 0); + + pa_source_output_new_data_init(&data); + data.module = c->protocol->module; + data.client = c->client; + data.source = source; + data.driver = __FILE__; + data.name = name; + pa_source_output_new_data_set_sample_spec(&data, ss); + pa_source_output_new_data_set_channel_map(&data, map); + + if (!(source_output = pa_source_output_new(c->protocol->core, &data, flags))) + return NULL; + + s = pa_msgobject_new(record_stream); + s->parent.parent.free = record_stream_free; + s->parent.process_msg = record_stream_process_msg; + s->connection = c; + s->source_output = source_output; + s->source_output->push = source_output_push_cb; + s->source_output->kill = source_output_kill_cb; + s->source_output->get_latency = source_output_get_latency_cb; + s->source_output->moved = source_output_moved_cb; + s->source_output->suspend = source_output_suspend_cb; + s->source_output->userdata = s; + + s->memblockq = pa_memblockq_new( + 0, + *maxlength, + 0, + base = pa_frame_size(&s->source_output->sample_spec), + 1, + 0, + NULL); + + *maxlength = pa_memblockq_get_maxlength(s->memblockq); + + s->fragment_size = (fragment_size/base)*base; + if (s->fragment_size <= 0) + s->fragment_size = base; + + if (s->fragment_size > *maxlength) + s->fragment_size = *maxlength; + + *ss = s->source_output->sample_spec; + *map = s->source_output->channel_map; + + pa_idxset_put(c->record_streams, s, &s->index); + + pa_source_output_put(s->source_output); + return s; +} + +static void playback_stream_unlink(playback_stream *s) { + pa_assert(s); + + if (!s->connection) + return; + + if (s->sink_input) { + pa_sink_input_unlink(s->sink_input); + pa_sink_input_unref(s->sink_input); + s->sink_input = NULL; + } + + if (s->drain_request) + pa_pstream_send_error(s->connection->pstream, s->drain_tag, PA_ERR_NOENTITY); + + pa_assert_se(pa_idxset_remove_by_data(s->connection->output_streams, s, NULL) == s); + s->connection = NULL; + playback_stream_unref(s); +} + +static void playback_stream_free(pa_object* o) { + playback_stream *s = PLAYBACK_STREAM(o); + pa_assert(s); + + playback_stream_unlink(s); + + pa_memblockq_free(s->memblockq); + pa_xfree(s); +} + +static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + playback_stream *s = PLAYBACK_STREAM(o); + playback_stream_assert_ref(s); + + if (!s->connection) + return -1; + + switch (code) { + case PLAYBACK_STREAM_MESSAGE_REQUEST_DATA: { + pa_tagstruct *t; + uint32_t l = 0; + + for (;;) { + int32_t k; + + if ((k = pa_atomic_load(&s->missing)) <= 0) + break; + + l += k; + + if (l < s->minreq) + break; + + if (pa_atomic_sub(&s->missing, k) <= k) + break; + } + + if (l < s->minreq) + break; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_REQUEST); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, l); + pa_pstream_send_tagstruct(s->connection->pstream, t); + +/* pa_log("Requesting %u bytes", l); */ + break; + } + + case PLAYBACK_STREAM_MESSAGE_UNDERFLOW: { + pa_tagstruct *t; + + /* Report that we're empty */ + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_UNDERFLOW); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_pstream_send_tagstruct(s->connection->pstream, t); + break; + } + + case PLAYBACK_STREAM_MESSAGE_OVERFLOW: { + pa_tagstruct *t; + + /* Notify the user we're overflowed*/ + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_OVERFLOW); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_pstream_send_tagstruct(s->connection->pstream, t); + break; + } + + case PLAYBACK_STREAM_MESSAGE_DRAIN_ACK: + pa_pstream_send_simple_ack(s->connection->pstream, PA_PTR_TO_UINT(userdata)); + break; + + } + + return 0; +} + +static playback_stream* playback_stream_new( + connection *c, + pa_sink *sink, + pa_sample_spec *ss, + pa_channel_map *map, + const char *name, + uint32_t *maxlength, + uint32_t *tlength, + uint32_t *prebuf, + uint32_t *minreq, + pa_cvolume *volume, + uint32_t syncid, + uint32_t *missing, + pa_sink_input_flags_t flags) { + + playback_stream *s, *ssync; + pa_sink_input *sink_input; + pa_memblock *silence; + uint32_t idx; + int64_t start_index; + pa_sink_input_new_data data; + + pa_assert(c); + pa_assert(ss); + pa_assert(name); + pa_assert(maxlength); + + /* Find syncid group */ + for (ssync = pa_idxset_first(c->output_streams, &idx); ssync; ssync = pa_idxset_next(c->output_streams, &idx)) { + + if (!playback_stream_isinstance(ssync)) + continue; + + if (ssync->syncid == syncid) + break; + } + + /* Synced streams must connect to the same sink */ + if (ssync) { + + if (!sink) + sink = ssync->sink_input->sink; + else if (sink != ssync->sink_input->sink) + return NULL; + } + + pa_sink_input_new_data_init(&data); + data.sink = sink; + data.driver = __FILE__; + data.name = name; + pa_sink_input_new_data_set_sample_spec(&data, ss); + pa_sink_input_new_data_set_channel_map(&data, map); + pa_sink_input_new_data_set_volume(&data, volume); + data.module = c->protocol->module; + data.client = c->client; + data.sync_base = ssync ? ssync->sink_input : NULL; + + if (!(sink_input = pa_sink_input_new(c->protocol->core, &data, flags))) + return NULL; + + s = pa_msgobject_new(playback_stream); + s->parent.parent.parent.free = playback_stream_free; + s->parent.parent.process_msg = playback_stream_process_msg; + s->connection = c; + s->syncid = syncid; + s->sink_input = sink_input; + s->underrun = 1; + + s->sink_input->parent.process_msg = sink_input_process_msg; + s->sink_input->peek = sink_input_peek_cb; + s->sink_input->drop = sink_input_drop_cb; + s->sink_input->kill = sink_input_kill_cb; + s->sink_input->moved = sink_input_moved_cb; + s->sink_input->suspend = sink_input_suspend_cb; + s->sink_input->userdata = s; + + start_index = ssync ? pa_memblockq_get_read_index(ssync->memblockq) : 0; + + silence = pa_silence_memblock_new(c->protocol->core->mempool, &s->sink_input->sample_spec, 0); + + s->memblockq = pa_memblockq_new( + start_index, + *maxlength, + *tlength, + pa_frame_size(&s->sink_input->sample_spec), + *prebuf, + *minreq, + silence); + + pa_memblock_unref(silence); + + *maxlength = (uint32_t) pa_memblockq_get_maxlength(s->memblockq); + *tlength = (uint32_t) pa_memblockq_get_tlength(s->memblockq); + *prebuf = (uint32_t) pa_memblockq_get_prebuf(s->memblockq); + *minreq = (uint32_t) pa_memblockq_get_minreq(s->memblockq); + *missing = (uint32_t) pa_memblockq_pop_missing(s->memblockq); + + *ss = s->sink_input->sample_spec; + *map = s->sink_input->channel_map; + + s->minreq = pa_memblockq_get_minreq(s->memblockq); + pa_atomic_store(&s->missing, 0); + s->drain_request = 0; + + pa_idxset_put(c->output_streams, s, &s->index); + + pa_sink_input_put(s->sink_input); + + return s; +} + +static int connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + connection *c = CONNECTION(o); + connection_assert_ref(c); + + if (!c->protocol) + return -1; + + switch (code) { + + case CONNECTION_MESSAGE_REVOKE: + pa_pstream_send_revoke(c->pstream, PA_PTR_TO_UINT(userdata)); + break; + + case CONNECTION_MESSAGE_RELEASE: + pa_pstream_send_release(c->pstream, PA_PTR_TO_UINT(userdata)); + break; + } + + return 0; +} + +static void connection_unlink(connection *c) { + record_stream *r; + output_stream *o; + + pa_assert(c); + + if (!c->protocol) + return; + + while ((r = pa_idxset_first(c->record_streams, NULL))) + record_stream_unlink(r); + + while ((o = pa_idxset_first(c->output_streams, NULL))) + if (playback_stream_isinstance(o)) + playback_stream_unlink(PLAYBACK_STREAM(o)); + else + upload_stream_unlink(UPLOAD_STREAM(o)); + + if (c->subscription) + pa_subscription_free(c->subscription); + + if (c->pstream) + pa_pstream_unlink(c->pstream); + + if (c->auth_timeout_event) { + c->protocol->core->mainloop->time_free(c->auth_timeout_event); + c->auth_timeout_event = NULL; + } + + pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c); + c->protocol = NULL; + connection_unref(c); +} + +static void connection_free(pa_object *o) { + connection *c = CONNECTION(o); + + pa_assert(c); + + connection_unlink(c); + + pa_idxset_free(c->record_streams, NULL, NULL); + pa_idxset_free(c->output_streams, NULL, NULL); + + pa_pdispatch_unref(c->pdispatch); + pa_pstream_unref(c->pstream); + pa_client_free(c->client); + + pa_xfree(c); +} + +/* Called from thread context */ +static void request_bytes(playback_stream *s) { + size_t m, previous_missing; + + playback_stream_assert_ref(s); + + m = pa_memblockq_pop_missing(s->memblockq); + + if (m <= 0) + return; + +/* pa_log("request_bytes(%u)", m); */ + + previous_missing = pa_atomic_add(&s->missing, m); + if (previous_missing < s->minreq && previous_missing+m >= s->minreq) { + pa_assert(pa_thread_mq_get()); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); + } +} + +static void send_memblock(connection *c) { + uint32_t start; + record_stream *r; + + start = PA_IDXSET_INVALID; + for (;;) { + pa_memchunk chunk; + + if (!(r = RECORD_STREAM(pa_idxset_rrobin(c->record_streams, &c->rrobin_index)))) + return; + + if (start == PA_IDXSET_INVALID) + start = c->rrobin_index; + else if (start == c->rrobin_index) + return; + + if (pa_memblockq_peek(r->memblockq, &chunk) >= 0) { + pa_memchunk schunk = chunk; + + if (schunk.length > r->fragment_size) + schunk.length = r->fragment_size; + + pa_pstream_send_memblock(c->pstream, r->index, 0, PA_SEEK_RELATIVE, &schunk); + + pa_memblockq_drop(r->memblockq, schunk.length); + pa_memblock_unref(schunk.memblock); + + return; + } + } +} + +static void send_playback_stream_killed(playback_stream *p) { + pa_tagstruct *t; + playback_stream_assert_ref(p); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_KILLED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, p->index); + pa_pstream_send_tagstruct(p->connection->pstream, t); +} + +static void send_record_stream_killed(record_stream *r) { + pa_tagstruct *t; + record_stream_assert_ref(r); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_KILLED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, r->index); + pa_pstream_send_tagstruct(r->connection->pstream, t); +} + +/*** sink input callbacks ***/ + +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + switch (code) { + + case SINK_INPUT_MESSAGE_SEEK: + pa_memblockq_seek(s->memblockq, offset, PA_PTR_TO_UINT(userdata)); + request_bytes(s); + return 0; + + case SINK_INPUT_MESSAGE_POST_DATA: { + pa_assert(chunk); + +/* pa_log("sink input post: %u", chunk->length); */ + + if (pa_memblockq_push_align(s->memblockq, chunk) < 0) { + + pa_log_warn("Failed to push data into queue"); + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_OVERFLOW, NULL, 0, NULL, NULL); + pa_memblockq_seek(s->memblockq, chunk->length, PA_SEEK_RELATIVE); + } + + request_bytes(s); + + s->underrun = 0; + return 0; + } + + case SINK_INPUT_MESSAGE_DRAIN: { + + pa_memblockq_prebuf_disable(s->memblockq); + + if (!pa_memblockq_is_readable(s->memblockq)) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, userdata, 0, NULL, NULL); + else { + s->drain_tag = PA_PTR_TO_UINT(userdata); + s->drain_request = 1; + } + request_bytes(s); + + return 0; + } + + case SINK_INPUT_MESSAGE_FLUSH: + case SINK_INPUT_MESSAGE_PREBUF_FORCE: + case SINK_INPUT_MESSAGE_TRIGGER: { + + pa_sink_input *isync; + void (*func)(pa_memblockq *bq); + + switch (code) { + case SINK_INPUT_MESSAGE_FLUSH: + func = pa_memblockq_flush; + break; + + case SINK_INPUT_MESSAGE_PREBUF_FORCE: + func = pa_memblockq_prebuf_force; + break; + + case SINK_INPUT_MESSAGE_TRIGGER: + func = pa_memblockq_prebuf_disable; + break; + + default: + pa_assert_not_reached(); + } + + func(s->memblockq); + s->underrun = 0; + request_bytes(s); + + /* Do the same for all other members in the sync group */ + for (isync = i->sync_prev; isync; isync = isync->sync_prev) { + playback_stream *ssync = PLAYBACK_STREAM(isync->userdata); + func(ssync->memblockq); + ssync->underrun = 0; + request_bytes(ssync); + } + + for (isync = i->sync_next; isync; isync = isync->sync_next) { + playback_stream *ssync = PLAYBACK_STREAM(isync->userdata); + func(ssync->memblockq); + ssync->underrun = 0; + request_bytes(ssync); + } + + return 0; + } + + case SINK_INPUT_MESSAGE_UPDATE_LATENCY: + + s->read_index = pa_memblockq_get_read_index(s->memblockq); + s->write_index = pa_memblockq_get_write_index(s->memblockq); + s->resampled_chunk_length = s->sink_input->thread_info.resampled_chunk.memblock ? s->sink_input->thread_info.resampled_chunk.length : 0; + return 0; + + case PA_SINK_INPUT_MESSAGE_SET_STATE: + + pa_memblockq_prebuf_force(s->memblockq); + request_bytes(s); + break; + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + *r = pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &i->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + break; + } + } + + return pa_sink_input_process_msg(o, code, userdata, offset, chunk); +} + +/* Called from thread context */ +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + pa_assert(chunk); + + if (pa_memblockq_get_length(s->memblockq) <= 0 && !s->underrun) { + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_UNDERFLOW, NULL, 0, NULL, NULL); + s->underrun = 1; + } + + if (pa_memblockq_peek(s->memblockq, chunk) < 0) { +/* pa_log("peek: failure"); */ + return -1; + } + +/* pa_log("peek: %u", chunk->length); */ + + request_bytes(s); + + return 0; +} + +/* Called from thread context */ +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + pa_assert(length > 0); + + pa_memblockq_drop(s->memblockq, length); + + if (s->drain_request && !pa_memblockq_is_readable(s->memblockq)) { + s->drain_request = 0; + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), PLAYBACK_STREAM_MESSAGE_DRAIN_ACK, PA_UINT_TO_PTR(s->drain_tag), 0, NULL, NULL); + } + + request_bytes(s); + +/* pa_log("after_drop: %u %u", pa_memblockq_get_length(s->memblockq), pa_memblockq_is_readable(s->memblockq)); */ +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + playback_stream *s; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + send_playback_stream_killed(s); + playback_stream_unlink(s); +} + +/* Called from main context */ +static void sink_input_suspend_cb(pa_sink_input *i, pa_bool_t suspend) { + playback_stream *s; + pa_tagstruct *t; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + if (s->connection->version < 12) + return; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_SUSPENDED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/* Called from main context */ +static void sink_input_moved_cb(pa_sink_input *i) { + playback_stream *s; + pa_tagstruct *t; + + pa_sink_input_assert_ref(i); + s = PLAYBACK_STREAM(i->userdata); + playback_stream_assert_ref(s); + + if (s->connection->version < 12) + return; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_PLAYBACK_STREAM_MOVED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, i->sink->index); + pa_tagstruct_puts(t, i->sink->name); + pa_tagstruct_put_boolean(t, pa_sink_get_state(i->sink) == PA_SINK_SUSPENDED); + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/*** source_output callbacks ***/ + +/* Called from thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + record_stream *s; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + pa_assert(chunk); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(s), RECORD_STREAM_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); +} + +static void source_output_kill_cb(pa_source_output *o) { + record_stream *s; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + send_record_stream_killed(s); + record_stream_unlink(s); +} + +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { + record_stream *s; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + /*pa_log("get_latency: %u", pa_memblockq_get_length(s->memblockq));*/ + + return pa_bytes_to_usec(pa_memblockq_get_length(s->memblockq), &o->sample_spec); +} + +/* Called from main context */ +static void source_output_suspend_cb(pa_source_output *o, pa_bool_t suspend) { + record_stream *s; + pa_tagstruct *t; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + if (s->connection->version < 12) + return; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_SUSPENDED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_put_boolean(t, suspend); + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/* Called from main context */ +static void source_output_moved_cb(pa_source_output *o) { + record_stream *s; + pa_tagstruct *t; + + pa_source_output_assert_ref(o); + s = RECORD_STREAM(o->userdata); + record_stream_assert_ref(s); + + if (s->connection->version < 12) + return; + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_RECORD_STREAM_MOVED); + pa_tagstruct_putu32(t, (uint32_t) -1); /* tag */ + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_putu32(t, o->source->index); + pa_tagstruct_puts(t, o->source->name); + pa_pstream_send_tagstruct(s->connection->pstream, t); +} + +/*** pdispatch callbacks ***/ + +static void protocol_error(connection *c) { + pa_log("protocol error, kicking client"); + connection_unlink(c); +} + +#define CHECK_VALIDITY(pstream, expression, tag, error) do { \ +if (!(expression)) { \ + pa_pstream_send_error((pstream), (tag), (error)); \ + return; \ +} \ +} while(0); + +static pa_tagstruct *reply_new(uint32_t tag) { + pa_tagstruct *reply; + + reply = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(reply, PA_COMMAND_REPLY); + pa_tagstruct_putu32(reply, tag); + return reply; +} + +static void command_create_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + playback_stream *s; + uint32_t maxlength, tlength, prebuf, minreq, sink_index, syncid, missing; + const char *name, *sink_name; + pa_sample_spec ss; + pa_channel_map map; + pa_tagstruct *reply; + pa_sink *sink = NULL; + pa_cvolume volume; + int corked; + int no_remap = 0, no_remix = 0, fix_format = 0, fix_rate = 0, fix_channels = 0, no_move = 0, variable_rate = 0; + pa_sink_input_flags_t flags = 0; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_get( + t, + PA_TAG_STRING, &name, + PA_TAG_SAMPLE_SPEC, &ss, + PA_TAG_CHANNEL_MAP, &map, + PA_TAG_U32, &sink_index, + PA_TAG_STRING, &sink_name, + PA_TAG_U32, &maxlength, + PA_TAG_BOOLEAN, &corked, + PA_TAG_U32, &tlength, + PA_TAG_U32, &prebuf, + PA_TAG_U32, &minreq, + PA_TAG_U32, &syncid, + PA_TAG_CVOLUME, &volume, + PA_TAG_INVALID) < 0 || !name) { + protocol_error(c); + return; + } + + if (c->version >= 12) { + /* Since 0.9.8 the user can ask for a couple of additional flags */ + + if (pa_tagstruct_get_boolean(t, &no_remap) < 0 || + pa_tagstruct_get_boolean(t, &no_remix) < 0 || + pa_tagstruct_get_boolean(t, &fix_format) < 0 || + pa_tagstruct_get_boolean(t, &fix_rate) < 0 || + pa_tagstruct_get_boolean(t, &fix_channels) < 0 || + pa_tagstruct_get_boolean(t, &no_move) < 0 || + pa_tagstruct_get_boolean(t, &variable_rate) < 0) { + protocol_error(c); + return; + } + } + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, sink_index != PA_INVALID_INDEX || !sink_name || (*sink_name && pa_utf8_valid(name)), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, map.channels == ss.channels && volume.channels == ss.channels, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID); + + if (sink_index != PA_INVALID_INDEX) { + sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index); + CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + } else if (sink_name) { + sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK, 1); + CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + } + + flags = + (corked ? PA_SINK_INPUT_START_CORKED : 0) | + (no_remap ? PA_SINK_INPUT_NO_REMAP : 0) | + (no_remix ? PA_SINK_INPUT_NO_REMIX : 0) | + (fix_format ? PA_SINK_INPUT_FIX_FORMAT : 0) | + (fix_rate ? PA_SINK_INPUT_FIX_RATE : 0) | + (fix_channels ? PA_SINK_INPUT_FIX_CHANNELS : 0) | + (no_move ? PA_SINK_INPUT_DONT_MOVE : 0) | + (variable_rate ? PA_SINK_INPUT_VARIABLE_RATE : 0); + + s = playback_stream_new(c, sink, &ss, &map, name, &maxlength, &tlength, &prebuf, &minreq, &volume, syncid, &missing, flags); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, s->index); + pa_assert(s->sink_input); + pa_tagstruct_putu32(reply, s->sink_input->index); + pa_tagstruct_putu32(reply, missing); + +/* pa_log("initial request is %u", missing); */ + + if (c->version >= 9) { + /* Since 0.9.0 we support sending the buffer metrics back to the client */ + + pa_tagstruct_putu32(reply, (uint32_t) maxlength); + pa_tagstruct_putu32(reply, (uint32_t) tlength); + pa_tagstruct_putu32(reply, (uint32_t) prebuf); + pa_tagstruct_putu32(reply, (uint32_t) minreq); + } + + if (c->version >= 12) { + /* Since 0.9.8 we support sending the chosen sample + * spec/channel map/device/suspend status back to the + * client */ + + pa_tagstruct_put_sample_spec(reply, &ss); + pa_tagstruct_put_channel_map(reply, &map); + + pa_tagstruct_putu32(reply, s->sink_input->sink->index); + pa_tagstruct_puts(reply, s->sink_input->sink->name); + + pa_tagstruct_put_boolean(reply, pa_sink_get_state(s->sink_input->sink) == PA_SINK_SUSPENDED); + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_delete_stream(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t channel; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + switch (command) { + + case PA_COMMAND_DELETE_PLAYBACK_STREAM: { + playback_stream *s; + if (!(s = pa_idxset_get_by_index(c->output_streams, channel)) || !playback_stream_isinstance(s)) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_EXIST); + return; + } + + playback_stream_unlink(s); + break; + } + + case PA_COMMAND_DELETE_RECORD_STREAM: { + record_stream *s; + if (!(s = pa_idxset_get_by_index(c->record_streams, channel))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_EXIST); + return; + } + + record_stream_unlink(s); + break; + } + + case PA_COMMAND_DELETE_UPLOAD_STREAM: { + upload_stream *s; + + if (!(s = pa_idxset_get_by_index(c->output_streams, channel)) || !upload_stream_isinstance(s)) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_EXIST); + return; + } + + upload_stream_unlink(s); + break; + } + + default: + pa_assert_not_reached(); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_create_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + record_stream *s; + uint32_t maxlength, fragment_size; + uint32_t source_index; + const char *name, *source_name; + pa_sample_spec ss; + pa_channel_map map; + pa_tagstruct *reply; + pa_source *source = NULL; + int corked; + int no_remap = 0, no_remix = 0, fix_format = 0, fix_rate = 0, fix_channels = 0, no_move = 0, variable_rate = 0; + pa_source_output_flags_t flags = 0; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &map) < 0 || + pa_tagstruct_getu32(t, &source_index) < 0 || + pa_tagstruct_gets(t, &source_name) < 0 || + pa_tagstruct_getu32(t, &maxlength) < 0 || + pa_tagstruct_get_boolean(t, &corked) < 0 || + pa_tagstruct_getu32(t, &fragment_size) < 0) { + protocol_error(c); + return; + } + + if (c->version >= 12) { + /* Since 0.9.8 the user can ask for a couple of additional flags */ + + if (pa_tagstruct_get_boolean(t, &no_remap) < 0 || + pa_tagstruct_get_boolean(t, &no_remix) < 0 || + pa_tagstruct_get_boolean(t, &fix_format) < 0 || + pa_tagstruct_get_boolean(t, &fix_rate) < 0 || + pa_tagstruct_get_boolean(t, &fix_channels) < 0 || + pa_tagstruct_get_boolean(t, &no_move) < 0 || + pa_tagstruct_get_boolean(t, &variable_rate) < 0) { + protocol_error(c); + return; + } + } + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + flags = + (corked ? PA_SOURCE_OUTPUT_START_CORKED : 0) | + (no_remap ? PA_SOURCE_OUTPUT_NO_REMAP : 0) | + (no_remix ? PA_SOURCE_OUTPUT_NO_REMIX : 0) | + (fix_format ? PA_SOURCE_OUTPUT_FIX_FORMAT : 0) | + (fix_rate ? PA_SOURCE_OUTPUT_FIX_RATE : 0) | + (fix_channels ? PA_SOURCE_OUTPUT_FIX_CHANNELS : 0) | + (no_move ? PA_SOURCE_OUTPUT_DONT_MOVE : 0) | + (variable_rate ? PA_SOURCE_OUTPUT_VARIABLE_RATE : 0); + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, source_index != PA_INVALID_INDEX || !source_name || (*source_name && pa_utf8_valid(source_name)), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID); + + if (source_index != PA_INVALID_INDEX) { + source = pa_idxset_get_by_index(c->protocol->core->sources, source_index); + CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); + } else if (source_name) { + source = pa_namereg_get(c->protocol->core, source_name, PA_NAMEREG_SOURCE, 1); + CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); + } + + s = record_stream_new(c, source, &ss, &map, name, &maxlength, fragment_size, flags); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, s->index); + pa_assert(s->source_output); + pa_tagstruct_putu32(reply, s->source_output->index); + + if (c->version >= 9) { + /* Since 0.9 we support sending the buffer metrics back to the client */ + + pa_tagstruct_putu32(reply, (uint32_t) maxlength); + pa_tagstruct_putu32(reply, (uint32_t) s->fragment_size); + } + + if (c->version >= 12) { + /* Since 0.9.8 we support sending the chosen sample + * spec/channel map/device/suspend status back to the + * client */ + + pa_tagstruct_put_sample_spec(reply, &ss); + pa_tagstruct_put_channel_map(reply, &map); + + pa_tagstruct_putu32(reply, s->source_output->source->index); + pa_tagstruct_puts(reply, s->source_output->source->name); + + pa_tagstruct_put_boolean(reply, pa_source_get_state(s->source_output->source) == PA_SOURCE_SUSPENDED); + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_exit(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + pa_assert(t); + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + c->protocol->core->mainloop->quit(c->protocol->core->mainloop, 0); + pa_pstream_send_simple_ack(c->pstream, tag); /* nonsense */ +} + +static void command_auth(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + const void*cookie; + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &c->version) < 0 || + pa_tagstruct_get_arbitrary(t, &cookie, PA_NATIVE_COOKIE_LENGTH) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + /* Minimum supported version */ + if (c->version < 8) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_VERSION); + return; + } + + if (!c->authorized) { + int success = 0; + +#ifdef HAVE_CREDS + const pa_creds *creds; + + if ((creds = pa_pdispatch_creds(pd))) { + if (creds->uid == getuid()) + success = 1; + else if (c->protocol->auth_group) { + int r; + gid_t gid; + + if ((gid = pa_get_gid_of_group(c->protocol->auth_group)) == (gid_t) -1) + pa_log_warn("failed to get GID of group '%s'", c->protocol->auth_group); + else if (gid == creds->gid) + success = 1; + + if (!success) { + if ((r = pa_uid_in_group(creds->uid, c->protocol->auth_group)) < 0) + pa_log_warn("failed to check group membership."); + else if (r > 0) + success = 1; + } + } + + pa_log_info("Got credentials: uid=%lu gid=%lu success=%i", + (unsigned long) creds->uid, + (unsigned long) creds->gid, + success); + + if (c->version >= 10 && + pa_mempool_is_shared(c->protocol->core->mempool) && + creds->uid == getuid()) { + + pa_pstream_use_shm(c->pstream, 1); + pa_log_info("Enabled SHM for new connection"); + } + + } +#endif + + if (!success && memcmp(c->protocol->auth_cookie, cookie, PA_NATIVE_COOKIE_LENGTH) == 0) + success = 1; + + if (!success) { + pa_log_warn("Denied access to client with invalid authorization data."); + pa_pstream_send_error(c->pstream, tag, PA_ERR_ACCESS); + return; + } + + c->authorized = 1; + if (c->auth_timeout_event) { + c->protocol->core->mainloop->time_free(c->auth_timeout_event); + c->auth_timeout_event = NULL; + } + } + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, PA_PROTOCOL_VERSION); + +#ifdef HAVE_CREDS +{ + /* SHM support is only enabled after both sides made sure they are the same user. */ + + pa_creds ucred; + + ucred.uid = getuid(); + ucred.gid = getgid(); + + pa_pstream_send_tagstruct_with_creds(c->pstream, reply, &ucred); +} +#else + pa_pstream_send_tagstruct(c->pstream, reply); +#endif +} + +static void command_set_client_name(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + const char *name; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + + pa_client_set_name(c->client, name); + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_lookup(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + const char *name; + uint32_t idx = PA_IDXSET_INVALID; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_LOOKUP_SINK) { + pa_sink *sink; + if ((sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK, 1))) + idx = sink->index; + } else { + pa_source *source; + pa_assert(command == PA_COMMAND_LOOKUP_SOURCE); + if ((source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE, 1))) + idx = source->index; + } + + if (idx == PA_IDXSET_INVALID) + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + else { + pa_tagstruct *reply; + reply = reply_new(tag); + pa_tagstruct_putu32(reply, idx); + pa_pstream_send_tagstruct(c->pstream, reply); + } +} + +static void command_drain_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + playback_stream *s; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + pa_asyncmsgq_post(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_DRAIN, PA_UINT_TO_PTR(tag), 0, NULL, NULL); +} + +static void command_stat(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + pa_tagstruct *reply; + const pa_mempool_stat *stat; + + connection_assert_ref(c); + pa_assert(t); + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + stat = pa_mempool_get_stat(c->protocol->core->mempool); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->n_allocated)); + pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->allocated_size)); + pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->n_accumulated)); + pa_tagstruct_putu32(reply, (uint32_t) pa_atomic_load(&stat->accumulated_size)); + pa_tagstruct_putu32(reply, pa_scache_total_size(c->protocol->core)); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_get_playback_latency(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + pa_tagstruct *reply; + playback_stream *s; + struct timeval tv, now; + uint32_t idx; + pa_usec_t latency; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_get_timeval(t, &tv) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_UPDATE_LATENCY, s, 0, NULL) == 0, tag, PA_ERR_NOENTITY) + + reply = reply_new(tag); + + latency = pa_sink_get_latency(s->sink_input->sink); + latency += pa_bytes_to_usec(s->resampled_chunk_length, &s->sink_input->sample_spec); + + pa_tagstruct_put_usec(reply, latency); + + pa_tagstruct_put_usec(reply, 0); + pa_tagstruct_put_boolean(reply, pa_sink_input_get_state(s->sink_input) == PA_SINK_INPUT_RUNNING); + pa_tagstruct_put_timeval(reply, &tv); + pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now)); + pa_tagstruct_puts64(reply, s->write_index); + pa_tagstruct_puts64(reply, s->read_index); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_get_record_latency(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + pa_tagstruct *reply; + record_stream *s; + struct timeval tv, now; + uint32_t idx; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_get_timeval(t, &tv) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + reply = reply_new(tag); + pa_tagstruct_put_usec(reply, s->source_output->source->monitor_of ? pa_sink_get_latency(s->source_output->source->monitor_of) : 0); + pa_tagstruct_put_usec(reply, pa_source_get_latency(s->source_output->source)); + pa_tagstruct_put_boolean(reply, 0); + pa_tagstruct_put_timeval(reply, &tv); + pa_tagstruct_put_timeval(reply, pa_gettimeofday(&now)); + pa_tagstruct_puts64(reply, pa_memblockq_get_write_index(s->memblockq)); + pa_tagstruct_puts64(reply, pa_memblockq_get_read_index(s->memblockq)); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_create_upload_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + upload_stream *s; + uint32_t length; + const char *name; + pa_sample_spec ss; + pa_channel_map map; + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_get_sample_spec(t, &ss) < 0 || + pa_tagstruct_get_channel_map(t, &map) < 0 || + pa_tagstruct_getu32(t, &length) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, pa_sample_spec_valid(&ss), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_channel_map_valid(&map), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, map.channels == ss.channels, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, (length % pa_frame_size(&ss)) == 0 && length > 0, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, length <= PA_SCACHE_ENTRY_SIZE_MAX, tag, PA_ERR_TOOLARGE); + CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + + s = upload_stream_new(c, &ss, &map, name, length); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_INVALID); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, s->index); + pa_tagstruct_putu32(reply, length); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_finish_upload_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t channel; + upload_stream *s; + uint32_t idx; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &channel) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + s = pa_idxset_get_by_index(c->output_streams, channel); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, upload_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + if (pa_scache_add_item(c->protocol->core, s->name, &s->sample_spec, &s->channel_map, &s->memchunk, &idx) < 0) + pa_pstream_send_error(c->pstream, tag, PA_ERR_INTERNAL); + else + pa_pstream_send_simple_ack(c->pstream, tag); + + upload_stream_unlink(s); +} + +static void command_play_sample(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t sink_index; + pa_volume_t volume; + pa_sink *sink; + const char *name, *sink_name; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &sink_index) < 0 || + pa_tagstruct_gets(t, &sink_name) < 0 || + pa_tagstruct_getu32(t, &volume) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, sink_index != PA_INVALID_INDEX || !sink_name || (*sink_name && pa_utf8_valid(name)), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + + if (sink_index != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, sink_index); + else + sink = pa_namereg_get(c->protocol->core, sink_name, PA_NAMEREG_SINK, 1); + + CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + + if (pa_scache_play_item(c->protocol->core, name, sink, volume) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + return; + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_remove_sample(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + const char *name; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + + if (pa_scache_remove_item(c->protocol->core, name) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + return; + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void fixup_sample_spec(connection *c, pa_sample_spec *fixed, const pa_sample_spec *original) { + pa_assert(c); + pa_assert(fixed); + pa_assert(original); + + *fixed = *original; + + if (c->version < 12) { + /* Before protocol version 12 we didn't support S32 samples, + * so we need to lie about this to the client */ + + if (fixed->format == PA_SAMPLE_S32LE) + fixed->format = PA_SAMPLE_FLOAT32LE; + if (fixed->format == PA_SAMPLE_S32BE) + fixed->format = PA_SAMPLE_FLOAT32BE; + } +} + +static void sink_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sink *sink) { + pa_sample_spec fixed_ss; + + pa_assert(t); + pa_sink_assert_ref(sink); + + fixup_sample_spec(c, &fixed_ss, &sink->sample_spec); + + pa_tagstruct_put( + t, + PA_TAG_U32, sink->index, + PA_TAG_STRING, sink->name, + PA_TAG_STRING, sink->description, + PA_TAG_SAMPLE_SPEC, &fixed_ss, + PA_TAG_CHANNEL_MAP, &sink->channel_map, + PA_TAG_U32, sink->module ? sink->module->index : PA_INVALID_INDEX, + PA_TAG_CVOLUME, pa_sink_get_volume(sink), + PA_TAG_BOOLEAN, pa_sink_get_mute(sink), + PA_TAG_U32, sink->monitor_source ? sink->monitor_source->index : PA_INVALID_INDEX, + PA_TAG_STRING, sink->monitor_source ? sink->monitor_source->name : NULL, + PA_TAG_USEC, pa_sink_get_latency(sink), + PA_TAG_STRING, sink->driver, + PA_TAG_U32, sink->flags, + PA_TAG_INVALID); +} + +static void source_fill_tagstruct(connection *c, pa_tagstruct *t, pa_source *source) { + pa_sample_spec fixed_ss; + + pa_assert(t); + pa_source_assert_ref(source); + + fixup_sample_spec(c, &fixed_ss, &source->sample_spec); + + pa_tagstruct_put( + t, + PA_TAG_U32, source->index, + PA_TAG_STRING, source->name, + PA_TAG_STRING, source->description, + PA_TAG_SAMPLE_SPEC, &fixed_ss, + PA_TAG_CHANNEL_MAP, &source->channel_map, + PA_TAG_U32, source->module ? source->module->index : PA_INVALID_INDEX, + PA_TAG_CVOLUME, pa_source_get_volume(source), + PA_TAG_BOOLEAN, pa_source_get_mute(source), + PA_TAG_U32, source->monitor_of ? source->monitor_of->index : PA_INVALID_INDEX, + PA_TAG_STRING, source->monitor_of ? source->monitor_of->name : NULL, + PA_TAG_USEC, pa_source_get_latency(source), + PA_TAG_STRING, source->driver, + PA_TAG_U32, source->flags, + PA_TAG_INVALID); +} + +static void client_fill_tagstruct(pa_tagstruct *t, pa_client *client) { + pa_assert(t); + pa_assert(client); + + pa_tagstruct_putu32(t, client->index); + pa_tagstruct_puts(t, client->name); + pa_tagstruct_putu32(t, client->owner ? client->owner->index : PA_INVALID_INDEX); + pa_tagstruct_puts(t, client->driver); +} + +static void module_fill_tagstruct(pa_tagstruct *t, pa_module *module) { + pa_assert(t); + pa_assert(module); + + pa_tagstruct_putu32(t, module->index); + pa_tagstruct_puts(t, module->name); + pa_tagstruct_puts(t, module->argument); + pa_tagstruct_putu32(t, module->n_used); + pa_tagstruct_put_boolean(t, module->auto_unload); +} + +static void sink_input_fill_tagstruct(connection *c, pa_tagstruct *t, pa_sink_input *s) { + pa_sample_spec fixed_ss; + + pa_assert(t); + pa_sink_input_assert_ref(s); + + fixup_sample_spec(c, &fixed_ss, &s->sample_spec); + + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_puts(t, s->name); + pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX); + pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX); + pa_tagstruct_putu32(t, s->sink->index); + pa_tagstruct_put_sample_spec(t, &fixed_ss); + pa_tagstruct_put_channel_map(t, &s->channel_map); + pa_tagstruct_put_cvolume(t, &s->volume); + pa_tagstruct_put_usec(t, pa_sink_input_get_latency(s)); + pa_tagstruct_put_usec(t, pa_sink_get_latency(s->sink)); + pa_tagstruct_puts(t, pa_resample_method_to_string(pa_sink_input_get_resample_method(s))); + pa_tagstruct_puts(t, s->driver); + if (c->version >= 11) + pa_tagstruct_put_boolean(t, pa_sink_input_get_mute(s)); +} + +static void source_output_fill_tagstruct(connection *c, pa_tagstruct *t, pa_source_output *s) { + pa_sample_spec fixed_ss; + + pa_assert(t); + pa_source_output_assert_ref(s); + + fixup_sample_spec(c, &fixed_ss, &s->sample_spec); + + pa_tagstruct_putu32(t, s->index); + pa_tagstruct_puts(t, s->name); + pa_tagstruct_putu32(t, s->module ? s->module->index : PA_INVALID_INDEX); + pa_tagstruct_putu32(t, s->client ? s->client->index : PA_INVALID_INDEX); + pa_tagstruct_putu32(t, s->source->index); + pa_tagstruct_put_sample_spec(t, &fixed_ss); + pa_tagstruct_put_channel_map(t, &s->channel_map); + pa_tagstruct_put_usec(t, pa_source_output_get_latency(s)); + pa_tagstruct_put_usec(t, pa_source_get_latency(s->source)); + pa_tagstruct_puts(t, pa_resample_method_to_string(pa_source_output_get_resample_method(s))); + pa_tagstruct_puts(t, s->driver); +} + +static void scache_fill_tagstruct(connection *c, pa_tagstruct *t, pa_scache_entry *e) { + pa_sample_spec fixed_ss; + + pa_assert(t); + pa_assert(e); + + fixup_sample_spec(c, &fixed_ss, &e->sample_spec); + + pa_tagstruct_putu32(t, e->index); + pa_tagstruct_puts(t, e->name); + pa_tagstruct_put_cvolume(t, &e->volume); + pa_tagstruct_put_usec(t, pa_bytes_to_usec(e->memchunk.length, &e->sample_spec)); + pa_tagstruct_put_sample_spec(t, &fixed_ss); + pa_tagstruct_put_channel_map(t, &e->channel_map); + pa_tagstruct_putu32(t, e->memchunk.length); + pa_tagstruct_put_boolean(t, e->lazy); + pa_tagstruct_puts(t, e->filename); +} + +static void command_get_info(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + pa_sink *sink = NULL; + pa_source *source = NULL; + pa_client *client = NULL; + pa_module *module = NULL; + pa_sink_input *si = NULL; + pa_source_output *so = NULL; + pa_scache_entry *sce = NULL; + const char *name; + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + (command != PA_COMMAND_GET_CLIENT_INFO && + command != PA_COMMAND_GET_MODULE_INFO && + command != PA_COMMAND_GET_SINK_INPUT_INFO && + command != PA_COMMAND_GET_SOURCE_OUTPUT_INFO && + pa_tagstruct_gets(t, &name) < 0) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || !name || (*name && pa_utf8_valid(name)), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_GET_SINK_INFO) { + if (idx != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK, 1); + } else if (command == PA_COMMAND_GET_SOURCE_INFO) { + if (idx != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE, 1); + } else if (command == PA_COMMAND_GET_CLIENT_INFO) + client = pa_idxset_get_by_index(c->protocol->core->clients, idx); + else if (command == PA_COMMAND_GET_MODULE_INFO) + module = pa_idxset_get_by_index(c->protocol->core->modules, idx); + else if (command == PA_COMMAND_GET_SINK_INPUT_INFO) + si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO) + so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx); + else { + pa_assert(command == PA_COMMAND_GET_SAMPLE_INFO); + if (idx != PA_INVALID_INDEX) + sce = pa_idxset_get_by_index(c->protocol->core->scache, idx); + else + sce = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SAMPLE, 0); + } + + if (!sink && !source && !client && !module && !si && !so && !sce) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_NOENTITY); + return; + } + + reply = reply_new(tag); + if (sink) + sink_fill_tagstruct(c, reply, sink); + else if (source) + source_fill_tagstruct(c, reply, source); + else if (client) + client_fill_tagstruct(reply, client); + else if (module) + module_fill_tagstruct(reply, module); + else if (si) + sink_input_fill_tagstruct(c, reply, si); + else if (so) + source_output_fill_tagstruct(c, reply, so); + else + scache_fill_tagstruct(c, reply, sce); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_get_info_list(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + pa_idxset *i; + uint32_t idx; + void *p; + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + reply = reply_new(tag); + + if (command == PA_COMMAND_GET_SINK_INFO_LIST) + i = c->protocol->core->sinks; + else if (command == PA_COMMAND_GET_SOURCE_INFO_LIST) + i = c->protocol->core->sources; + else if (command == PA_COMMAND_GET_CLIENT_INFO_LIST) + i = c->protocol->core->clients; + else if (command == PA_COMMAND_GET_MODULE_INFO_LIST) + i = c->protocol->core->modules; + else if (command == PA_COMMAND_GET_SINK_INPUT_INFO_LIST) + i = c->protocol->core->sink_inputs; + else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST) + i = c->protocol->core->source_outputs; + else { + pa_assert(command == PA_COMMAND_GET_SAMPLE_INFO_LIST); + i = c->protocol->core->scache; + } + + if (i) { + for (p = pa_idxset_first(i, &idx); p; p = pa_idxset_next(i, &idx)) { + if (command == PA_COMMAND_GET_SINK_INFO_LIST) + sink_fill_tagstruct(c, reply, p); + else if (command == PA_COMMAND_GET_SOURCE_INFO_LIST) + source_fill_tagstruct(c, reply, p); + else if (command == PA_COMMAND_GET_CLIENT_INFO_LIST) + client_fill_tagstruct(reply, p); + else if (command == PA_COMMAND_GET_MODULE_INFO_LIST) + module_fill_tagstruct(reply, p); + else if (command == PA_COMMAND_GET_SINK_INPUT_INFO_LIST) + sink_input_fill_tagstruct(c, reply, p); + else if (command == PA_COMMAND_GET_SOURCE_OUTPUT_INFO_LIST) + source_output_fill_tagstruct(c, reply, p); + else { + pa_assert(command == PA_COMMAND_GET_SAMPLE_INFO_LIST); + scache_fill_tagstruct(c, reply, p); + } + } + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_get_server_info(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + pa_tagstruct *reply; + char txt[256]; + const char *n; + pa_sample_spec fixed_ss; + + connection_assert_ref(c); + pa_assert(t); + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + reply = reply_new(tag); + pa_tagstruct_puts(reply, PACKAGE_NAME); + pa_tagstruct_puts(reply, PACKAGE_VERSION); + pa_tagstruct_puts(reply, pa_get_user_name(txt, sizeof(txt))); + pa_tagstruct_puts(reply, pa_get_fqdn(txt, sizeof(txt))); + + fixup_sample_spec(c, &fixed_ss, &c->protocol->core->default_sample_spec); + pa_tagstruct_put_sample_spec(reply, &fixed_ss); + + n = pa_namereg_get_default_sink_name(c->protocol->core); + pa_tagstruct_puts(reply, n); + n = pa_namereg_get_default_source_name(c->protocol->core); + pa_tagstruct_puts(reply, n); + + pa_tagstruct_putu32(reply, c->protocol->core->cookie); + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void subscription_cb(pa_core *core, pa_subscription_event_type_t e, uint32_t idx, void *userdata) { + pa_tagstruct *t; + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + + t = pa_tagstruct_new(NULL, 0); + pa_tagstruct_putu32(t, PA_COMMAND_SUBSCRIBE_EVENT); + pa_tagstruct_putu32(t, (uint32_t) -1); + pa_tagstruct_putu32(t, e); + pa_tagstruct_putu32(t, idx); + pa_pstream_send_tagstruct(c->pstream, t); +} + +static void command_subscribe(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + pa_subscription_mask_t m; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &m) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, (m & ~PA_SUBSCRIPTION_MASK_ALL) == 0, tag, PA_ERR_INVALID); + + if (c->subscription) + pa_subscription_free(c->subscription); + + if (m != 0) { + c->subscription = pa_subscription_new(c->protocol->core, m, subscription_cb, c); + pa_assert(c->subscription); + } else + c->subscription = NULL; + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_volume( + PA_GCC_UNUSED pa_pdispatch *pd, + uint32_t command, + uint32_t tag, + pa_tagstruct *t, + void *userdata) { + + connection *c = CONNECTION(userdata); + uint32_t idx; + pa_cvolume volume; + pa_sink *sink = NULL; + pa_source *source = NULL; + pa_sink_input *si = NULL; + const char *name = NULL; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + (command == PA_COMMAND_SET_SINK_VOLUME && pa_tagstruct_gets(t, &name) < 0) || + (command == PA_COMMAND_SET_SOURCE_VOLUME && pa_tagstruct_gets(t, &name) < 0) || + pa_tagstruct_get_cvolume(t, &volume) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || !name || (*name && pa_utf8_valid(name)), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, pa_cvolume_valid(&volume), tag, PA_ERR_INVALID); + + switch (command) { + + case PA_COMMAND_SET_SINK_VOLUME: + if (idx != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK, 1); + break; + + case PA_COMMAND_SET_SOURCE_VOLUME: + if (idx != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE, 1); + break; + + case PA_COMMAND_SET_SINK_INPUT_VOLUME: + si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + break; + + default: + pa_assert_not_reached(); + } + + CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY); + + if (sink) + pa_sink_set_volume(sink, &volume); + else if (source) + pa_source_set_volume(source, &volume); + else if (si) + pa_sink_input_set_volume(si, &volume); + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_mute( + PA_GCC_UNUSED pa_pdispatch *pd, + uint32_t command, + uint32_t tag, + pa_tagstruct *t, + void *userdata) { + + connection *c = CONNECTION(userdata); + uint32_t idx; + int mute; + pa_sink *sink = NULL; + pa_source *source = NULL; + pa_sink_input *si = NULL; + const char *name = NULL; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + (command == PA_COMMAND_SET_SINK_MUTE && pa_tagstruct_gets(t, &name) < 0) || + (command == PA_COMMAND_SET_SOURCE_MUTE && pa_tagstruct_gets(t, &name) < 0) || + pa_tagstruct_get_boolean(t, &mute) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || !name || (*name && pa_utf8_valid(name)), tag, PA_ERR_INVALID); + + switch (command) { + + case PA_COMMAND_SET_SINK_MUTE: + + if (idx != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK, 1); + + break; + + case PA_COMMAND_SET_SOURCE_MUTE: + if (idx != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE, 1); + + break; + + case PA_COMMAND_SET_SINK_INPUT_MUTE: + si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + break; + + default: + pa_assert_not_reached(); + } + + CHECK_VALIDITY(c->pstream, si || sink || source, tag, PA_ERR_NOENTITY); + + if (sink) + pa_sink_set_mute(sink, mute); + else if (source) + pa_source_set_mute(source, mute); + else if (si) + pa_sink_input_set_mute(si, mute); + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_cork_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + int b; + playback_stream *s; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_get_boolean(t, &b) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID); + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + pa_sink_input_cork(s->sink_input, b); + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_trigger_or_flush_or_prebuf_playback_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + playback_stream *s; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID); + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + switch (command) { + case PA_COMMAND_FLUSH_PLAYBACK_STREAM: + pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_FLUSH, NULL, 0, NULL); + break; + + case PA_COMMAND_PREBUF_PLAYBACK_STREAM: + pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_PREBUF_FORCE, NULL, 0, NULL); + break; + + case PA_COMMAND_TRIGGER_PLAYBACK_STREAM: + pa_asyncmsgq_send(s->sink_input->sink->asyncmsgq, PA_MSGOBJECT(s->sink_input), SINK_INPUT_MESSAGE_TRIGGER, NULL, 0, NULL); + break; + + default: + pa_assert_not_reached(); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_cork_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + record_stream *s; + int b; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_get_boolean(t, &b) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_source_output_cork(s->source_output, b); + pa_memblockq_prebuf_force(s->memblockq); + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_flush_record_stream(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + record_stream *s; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_memblockq_flush(s->memblockq); + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_stream_buffer_attr(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + uint32_t maxlength, tlength, prebuf, minreq, fragsize; + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + if (command == PA_COMMAND_SET_PLAYBACK_STREAM_BUFFER_ATTR) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + if (pa_tagstruct_get( + t, + PA_TAG_U32, &maxlength, + PA_TAG_U32, &tlength, + PA_TAG_U32, &prebuf, + PA_TAG_U32, &minreq, + PA_TAG_INVALID) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID); + + pa_memblockq_set_maxlength(s->memblockq, maxlength); + pa_memblockq_set_tlength(s->memblockq, tlength); + pa_memblockq_set_prebuf(s->memblockq, prebuf); + pa_memblockq_set_minreq(s->memblockq, minreq); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_maxlength(s->memblockq)); + pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_tlength(s->memblockq)); + pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_prebuf(s->memblockq)); + pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_minreq(s->memblockq)); + + } else { + record_stream *s; + size_t base; + pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_BUFFER_ATTR); + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + if (pa_tagstruct_get( + t, + PA_TAG_U32, &maxlength, + PA_TAG_U32, &fragsize, + PA_TAG_INVALID) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, maxlength > 0, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, maxlength <= MAX_MEMBLOCKQ_LENGTH, tag, PA_ERR_INVALID); + + pa_memblockq_set_maxlength(s->memblockq, maxlength); + + base = pa_frame_size(&s->source_output->sample_spec); + s->fragment_size = (fragsize/base)*base; + if (s->fragment_size <= 0) + s->fragment_size = base; + + if (s->fragment_size > pa_memblockq_get_maxlength(s->memblockq)) + s->fragment_size = pa_memblockq_get_maxlength(s->memblockq); + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, (uint32_t) pa_memblockq_get_maxlength(s->memblockq)); + pa_tagstruct_putu32(reply, s->fragment_size); + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_update_stream_sample_rate(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + uint32_t rate; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_getu32(t, &rate) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, rate > 0 && rate <= PA_RATE_MAX, tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_UPDATE_PLAYBACK_STREAM_SAMPLE_RATE) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + pa_sink_input_set_rate(s->sink_input, rate); + + } else { + record_stream *s; + pa_assert(command == PA_COMMAND_UPDATE_RECORD_STREAM_SAMPLE_RATE); + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_source_output_set_rate(s->source_output, rate); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_default_sink_or_source(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + const char *s; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &s) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, !s || (*s && pa_utf8_valid(s)), tag, PA_ERR_INVALID); + + pa_namereg_set_default(c->protocol->core, s, command == PA_COMMAND_SET_DEFAULT_SOURCE ? PA_NAMEREG_SOURCE : PA_NAMEREG_SINK); + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_set_stream_name(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + const char *name; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_SET_PLAYBACK_STREAM_NAME) { + playback_stream *s; + + s = pa_idxset_get_by_index(c->output_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + CHECK_VALIDITY(c->pstream, playback_stream_isinstance(s), tag, PA_ERR_NOENTITY); + + pa_sink_input_set_name(s->sink_input, name); + + } else { + record_stream *s; + pa_assert(command == PA_COMMAND_SET_RECORD_STREAM_NAME); + + s = pa_idxset_get_by_index(c->record_streams, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + pa_source_output_set_name(s->source_output, name); + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_kill(PA_GCC_UNUSED pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + if (command == PA_COMMAND_KILL_CLIENT) { + pa_client *client; + + client = pa_idxset_get_by_index(c->protocol->core->clients, idx); + CHECK_VALIDITY(c->pstream, client, tag, PA_ERR_NOENTITY); + + connection_ref(c); + pa_client_kill(client); + + } else if (command == PA_COMMAND_KILL_SINK_INPUT) { + pa_sink_input *s; + + s = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + connection_ref(c); + pa_sink_input_kill(s); + } else { + pa_source_output *s; + + pa_assert(command == PA_COMMAND_KILL_SOURCE_OUTPUT); + + s = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx); + CHECK_VALIDITY(c->pstream, s, tag, PA_ERR_NOENTITY); + + connection_ref(c); + pa_source_output_kill(s); + } + + pa_pstream_send_simple_ack(c->pstream, tag); + connection_unref(c); +} + +static void command_load_module(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + pa_module *m; + const char *name, *argument; + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_gets(t, &argument) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name) && !strchr(name, '/'), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !argument || pa_utf8_valid(argument), tag, PA_ERR_INVALID); + + if (!(m = pa_module_load(c->protocol->core, name, argument))) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_MODINITFAILED); + return; + } + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, m->index); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_unload_module(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx; + pa_module *m; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + m = pa_idxset_get_by_index(c->protocol->core->modules, idx); + CHECK_VALIDITY(c->pstream, m, tag, PA_ERR_NOENTITY); + + pa_module_unload_request(m); + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_add_autoload(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + const char *name, *module, *argument; + uint32_t type; + uint32_t idx; + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_getu32(t, &type) < 0 || + pa_tagstruct_gets(t, &module) < 0 || + pa_tagstruct_gets(t, &argument) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name && *name && pa_utf8_valid(name), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, type == 0 || type == 1, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, module && *module && pa_utf8_valid(module), tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !argument || pa_utf8_valid(argument), tag, PA_ERR_INVALID); + + if (pa_autoload_add(c->protocol->core, name, type == 0 ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE, module, argument, &idx) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_EXIST); + return; + } + + reply = reply_new(tag); + pa_tagstruct_putu32(reply, idx); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_remove_autoload(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + const char *name = NULL; + uint32_t type, idx = PA_IDXSET_INVALID; + int r; + + connection_assert_ref(c); + pa_assert(t); + + if ((pa_tagstruct_getu32(t, &idx) < 0 && + (pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_getu32(t, &type) < 0)) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name || idx != PA_IDXSET_INVALID, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !name || (*name && pa_utf8_valid(name) && (type == 0 || type == 1)), tag, PA_ERR_INVALID); + + if (name) + r = pa_autoload_remove_by_name(c->protocol->core, name, type == 0 ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE); + else + r = pa_autoload_remove_by_index(c->protocol->core, idx); + + CHECK_VALIDITY(c->pstream, r >= 0, tag, PA_ERR_NOENTITY); + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void autoload_fill_tagstruct(pa_tagstruct *t, const pa_autoload_entry *e) { + pa_assert(t && e); + + pa_tagstruct_putu32(t, e->index); + pa_tagstruct_puts(t, e->name); + pa_tagstruct_putu32(t, e->type == PA_NAMEREG_SINK ? 0 : 1); + pa_tagstruct_puts(t, e->module); + pa_tagstruct_puts(t, e->argument); +} + +static void command_get_autoload_info(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + const pa_autoload_entry *a = NULL; + uint32_t type, idx; + const char *name; + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + if ((pa_tagstruct_getu32(t, &idx) < 0 && + (pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_getu32(t, &type) < 0)) || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, name || idx != PA_IDXSET_INVALID, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, !name || (*name && (type == 0 || type == 1) && pa_utf8_valid(name)), tag, PA_ERR_INVALID); + + if (name) + a = pa_autoload_get_by_name(c->protocol->core, name, type == 0 ? PA_NAMEREG_SINK : PA_NAMEREG_SOURCE); + else + a = pa_autoload_get_by_index(c->protocol->core, idx); + + CHECK_VALIDITY(c->pstream, a, tag, PA_ERR_NOENTITY); + + reply = reply_new(tag); + autoload_fill_tagstruct(reply, a); + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_get_autoload_info_list(PA_GCC_UNUSED pa_pdispatch *pd, PA_GCC_UNUSED uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + pa_tagstruct *reply; + + connection_assert_ref(c); + pa_assert(t); + + if (!pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + + reply = reply_new(tag); + + if (c->protocol->core->autoload_hashmap) { + pa_autoload_entry *a; + void *state = NULL; + + while ((a = pa_hashmap_iterate(c->protocol->core->autoload_hashmap, &state, NULL))) + autoload_fill_tagstruct(reply, a); + } + + pa_pstream_send_tagstruct(c->pstream, reply); +} + +static void command_move_stream(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx = PA_INVALID_INDEX, idx_device = PA_INVALID_INDEX; + const char *name = NULL; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_getu32(t, &idx_device) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX, tag, PA_ERR_INVALID); + CHECK_VALIDITY(c->pstream, idx_device != PA_INVALID_INDEX || !name || (*name && pa_utf8_valid(name)), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_MOVE_SINK_INPUT) { + pa_sink_input *si = NULL; + pa_sink *sink = NULL; + + si = pa_idxset_get_by_index(c->protocol->core->sink_inputs, idx); + + if (idx_device != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx_device); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK, 1); + + CHECK_VALIDITY(c->pstream, si && sink, tag, PA_ERR_NOENTITY); + + if (pa_sink_input_move_to(si, sink, 0) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } else { + pa_source_output *so = NULL; + pa_source *source; + + pa_assert(command == PA_COMMAND_MOVE_SOURCE_OUTPUT); + + so = pa_idxset_get_by_index(c->protocol->core->source_outputs, idx); + + if (idx_device != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx_device); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE, 1); + + CHECK_VALIDITY(c->pstream, so && source, tag, PA_ERR_NOENTITY); + + if (pa_source_output_move_to(so, source) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +static void command_suspend(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { + connection *c = CONNECTION(userdata); + uint32_t idx = PA_INVALID_INDEX; + const char *name = NULL; + int b; + + connection_assert_ref(c); + pa_assert(t); + + if (pa_tagstruct_getu32(t, &idx) < 0 || + pa_tagstruct_gets(t, &name) < 0 || + pa_tagstruct_get_boolean(t, &b) < 0 || + !pa_tagstruct_eof(t)) { + protocol_error(c); + return; + } + + CHECK_VALIDITY(c->pstream, c->authorized, tag, PA_ERR_ACCESS); + CHECK_VALIDITY(c->pstream, idx != PA_INVALID_INDEX || !name || !*name || pa_utf8_valid(name), tag, PA_ERR_INVALID); + + if (command == PA_COMMAND_SUSPEND_SINK) { + + if (idx == PA_INVALID_INDEX && name && !*name) { + + if (pa_sink_suspend_all(c->protocol->core, b) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } else { + pa_sink *sink = NULL; + + if (idx != PA_INVALID_INDEX) + sink = pa_idxset_get_by_index(c->protocol->core->sinks, idx); + else + sink = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SINK, 1); + + CHECK_VALIDITY(c->pstream, sink, tag, PA_ERR_NOENTITY); + + if (pa_sink_suspend(sink, b) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } + } else { + + pa_assert(command == PA_COMMAND_SUSPEND_SOURCE); + + if (idx == PA_INVALID_INDEX && name && !*name) { + + if (pa_source_suspend_all(c->protocol->core, b) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + + } else { + pa_source *source; + + if (idx != PA_INVALID_INDEX) + source = pa_idxset_get_by_index(c->protocol->core->sources, idx); + else + source = pa_namereg_get(c->protocol->core, name, PA_NAMEREG_SOURCE, 1); + + CHECK_VALIDITY(c->pstream, source, tag, PA_ERR_NOENTITY); + + if (pa_source_suspend(source, b) < 0) { + pa_pstream_send_error(c->pstream, tag, PA_ERR_INVALID); + return; + } + } + } + + pa_pstream_send_simple_ack(c->pstream, tag); +} + +/*** pstream callbacks ***/ + +static void pstream_packet_callback(pa_pstream *p, pa_packet *packet, const pa_creds *creds, void *userdata) { + connection *c = CONNECTION(userdata); + + pa_assert(p); + pa_assert(packet); + connection_assert_ref(c); + + if (pa_pdispatch_run(c->pdispatch, packet, creds, c) < 0) { + pa_log("invalid packet."); + connection_unlink(c); + } +} + +static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata) { + connection *c = CONNECTION(userdata); + output_stream *stream; + + pa_assert(p); + pa_assert(chunk); + connection_assert_ref(c); + + if (!(stream = OUTPUT_STREAM(pa_idxset_get_by_index(c->output_streams, channel)))) { + pa_log("client sent block for invalid stream."); + /* Ignoring */ + return; + } + + if (playback_stream_isinstance(stream)) { + playback_stream *ps = PLAYBACK_STREAM(stream); + + if (seek != PA_SEEK_RELATIVE || offset != 0) + pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_SEEK, PA_UINT_TO_PTR(seek), offset, NULL, NULL); + + pa_asyncmsgq_post(ps->sink_input->sink->asyncmsgq, PA_MSGOBJECT(ps->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); + + } else { + upload_stream *u = UPLOAD_STREAM(stream); + size_t l; + + if (!u->memchunk.memblock) { + if (u->length == chunk->length) { + u->memchunk = *chunk; + pa_memblock_ref(u->memchunk.memblock); + u->length = 0; + } else { + u->memchunk.memblock = pa_memblock_new(c->protocol->core->mempool, u->length); + u->memchunk.index = u->memchunk.length = 0; + } + } + + pa_assert(u->memchunk.memblock); + + l = u->length; + if (l > chunk->length) + l = chunk->length; + + + if (l > 0) { + void *src, *dst; + dst = pa_memblock_acquire(u->memchunk.memblock); + src = pa_memblock_acquire(chunk->memblock); + + memcpy((uint8_t*) dst + u->memchunk.index + u->memchunk.length, + (uint8_t*) src+chunk->index, l); + + pa_memblock_release(u->memchunk.memblock); + pa_memblock_release(chunk->memblock); + + u->memchunk.length += l; + u->length -= l; + } + } +} + +static void pstream_die_callback(pa_pstream *p, void *userdata) { + connection *c = CONNECTION(userdata); + + pa_assert(p); + connection_assert_ref(c); + + connection_unlink(c); + pa_log_info("connection died."); +} + +static void pstream_drain_callback(pa_pstream *p, void *userdata) { + connection *c = CONNECTION(userdata); + + pa_assert(p); + connection_assert_ref(c); + + send_memblock(c); +} + +static void pstream_revoke_callback(pa_pstream *p, uint32_t block_id, void *userdata) { + pa_thread_mq *q; + + if (!(q = pa_thread_mq_get())) + pa_pstream_send_revoke(p, block_id); + else + pa_asyncmsgq_post(q->outq, PA_MSGOBJECT(userdata), CONNECTION_MESSAGE_REVOKE, PA_UINT_TO_PTR(block_id), 0, NULL, NULL); +} + +static void pstream_release_callback(pa_pstream *p, uint32_t block_id, void *userdata) { + pa_thread_mq *q; + + if (!(q = pa_thread_mq_get())) + pa_pstream_send_release(p, block_id); + else + pa_asyncmsgq_post(q->outq, PA_MSGOBJECT(userdata), CONNECTION_MESSAGE_RELEASE, PA_UINT_TO_PTR(block_id), 0, NULL, NULL); +} + +/*** client callbacks ***/ + +static void client_kill_cb(pa_client *c) { + pa_assert(c); + + connection_unlink(CONNECTION(c->userdata)); +} + +/*** socket server callbacks ***/ + +static void auth_timeout(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) { + connection *c = CONNECTION(userdata); + + pa_assert(m); + pa_assert(tv); + connection_assert_ref(c); + pa_assert(c->auth_timeout_event == e); + + if (!c->authorized) + connection_unlink(c); +} + +static void on_connection(PA_GCC_UNUSED pa_socket_server*s, pa_iochannel *io, void *userdata) { + pa_protocol_native *p = userdata; + connection *c; + char cname[256], pname[128]; + + pa_assert(s); + pa_assert(io); + pa_assert(p); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log_warn("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + c = pa_msgobject_new(connection); + c->parent.parent.free = connection_free; + c->parent.process_msg = connection_process_msg; + + c->authorized = !!p->public; + + if (!c->authorized && p->auth_ip_acl && pa_ip_acl_check(p->auth_ip_acl, pa_iochannel_get_recv_fd(io)) > 0) { + pa_log_info("Client authenticated by IP ACL."); + c->authorized = 1; + } + + if (!c->authorized) { + struct timeval tv; + pa_gettimeofday(&tv); + tv.tv_sec += AUTH_TIMEOUT; + c->auth_timeout_event = p->core->mainloop->time_new(p->core->mainloop, &tv, auth_timeout, c); + } else + c->auth_timeout_event = NULL; + + c->version = 8; + c->protocol = p; + pa_iochannel_socket_peer_to_string(io, pname, sizeof(pname)); + pa_snprintf(cname, sizeof(cname), "Native client (%s)", pname); + c->client = pa_client_new(p->core, __FILE__, cname); + c->client->kill = client_kill_cb; + c->client->userdata = c; + c->client->owner = p->module; + + c->pstream = pa_pstream_new(p->core->mainloop, io, p->core->mempool); + + pa_pstream_set_recieve_packet_callback(c->pstream, pstream_packet_callback, c); + pa_pstream_set_recieve_memblock_callback(c->pstream, pstream_memblock_callback, c); + pa_pstream_set_die_callback(c->pstream, pstream_die_callback, c); + pa_pstream_set_drain_callback(c->pstream, pstream_drain_callback, c); + pa_pstream_set_revoke_callback(c->pstream, pstream_revoke_callback, c); + pa_pstream_set_release_callback(c->pstream, pstream_release_callback, c); + + c->pdispatch = pa_pdispatch_new(p->core->mainloop, command_table, PA_COMMAND_MAX); + + c->record_streams = pa_idxset_new(NULL, NULL); + c->output_streams = pa_idxset_new(NULL, NULL); + + c->rrobin_index = PA_IDXSET_INVALID; + c->subscription = NULL; + + pa_idxset_put(p->connections, c, NULL); + +#ifdef HAVE_CREDS + if (pa_iochannel_creds_supported(io)) + pa_iochannel_creds_enable(io); + +#endif +} + +/*** module entry points ***/ + +static int load_key(pa_protocol_native*p, const char*fn) { + pa_assert(p); + + p->auth_cookie_in_property = 0; + + if (!fn && pa_authkey_prop_get(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME, p->auth_cookie, sizeof(p->auth_cookie)) >= 0) { + pa_log_info("using already loaded auth cookie."); + pa_authkey_prop_ref(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME); + p->auth_cookie_in_property = 1; + return 0; + } + + if (!fn) + fn = PA_NATIVE_COOKIE_FILE; + + if (pa_authkey_load_auto(fn, p->auth_cookie, sizeof(p->auth_cookie)) < 0) + return -1; + + pa_log_info("loading cookie from disk."); + + if (pa_authkey_prop_put(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME, p->auth_cookie, sizeof(p->auth_cookie)) >= 0) + p->auth_cookie_in_property = 1; + + return 0; +} + +static pa_protocol_native* protocol_new_internal(pa_core *c, pa_module *m, pa_modargs *ma) { + pa_protocol_native *p; + pa_bool_t public = FALSE; + const char *acl; + + pa_assert(c); + pa_assert(ma); + + if (pa_modargs_get_value_boolean(ma, "auth-anonymous", &public) < 0) { + pa_log("auth-anonymous= expects a boolean argument."); + return NULL; + } + + p = pa_xnew(pa_protocol_native, 1); + p->core = c; + p->module = m; + p->public = public; + p->server = NULL; + p->auth_ip_acl = NULL; + +#ifdef HAVE_CREDS + { + pa_bool_t a = 1; + if (pa_modargs_get_value_boolean(ma, "auth-group-enabled", &a) < 0) { + pa_log("auth-group-enabled= expects a boolean argument."); + return NULL; + } + p->auth_group = a ? pa_xstrdup(pa_modargs_get_value(ma, "auth-group", c->is_system_instance ? PA_ACCESS_GROUP : NULL)) : NULL; + + if (p->auth_group) + pa_log_info("Allowing access to group '%s'.", p->auth_group); + } +#endif + + + if ((acl = pa_modargs_get_value(ma, "auth-ip-acl", NULL))) { + + if (!(p->auth_ip_acl = pa_ip_acl_new(acl))) { + pa_log("Failed to parse IP ACL '%s'", acl); + goto fail; + } + } + + if (load_key(p, pa_modargs_get_value(ma, "cookie", NULL)) < 0) + goto fail; + + p->connections = pa_idxset_new(NULL, NULL); + + return p; + +fail: +#ifdef HAVE_CREDS + pa_xfree(p->auth_group); +#endif + if (p->auth_ip_acl) + pa_ip_acl_free(p->auth_ip_acl); + pa_xfree(p); + return NULL; +} + +pa_protocol_native* pa_protocol_native_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma) { + char t[256]; + pa_protocol_native *p; + + if (!(p = protocol_new_internal(core, m, ma))) + return NULL; + + p->server = server; + pa_socket_server_set_callback(p->server, on_connection, p); + + if (pa_socket_server_get_address(p->server, t, sizeof(t))) { + pa_strlist *l; + l = pa_property_get(core, PA_NATIVE_SERVER_PROPERTY_NAME); + l = pa_strlist_prepend(l, t); + pa_property_replace(core, PA_NATIVE_SERVER_PROPERTY_NAME, l); + } + + return p; +} + +void pa_protocol_native_free(pa_protocol_native *p) { + connection *c; + pa_assert(p); + + while ((c = pa_idxset_first(p->connections, NULL))) + connection_unlink(c); + pa_idxset_free(p->connections, NULL, NULL); + + if (p->server) { + char t[256]; + + if (pa_socket_server_get_address(p->server, t, sizeof(t))) { + pa_strlist *l; + l = pa_property_get(p->core, PA_NATIVE_SERVER_PROPERTY_NAME); + l = pa_strlist_remove(l, t); + + if (l) + pa_property_replace(p->core, PA_NATIVE_SERVER_PROPERTY_NAME, l); + else + pa_property_remove(p->core, PA_NATIVE_SERVER_PROPERTY_NAME); + } + + pa_socket_server_unref(p->server); + } + + if (p->auth_cookie_in_property) + pa_authkey_prop_unref(p->core, PA_NATIVE_COOKIE_PROPERTY_NAME); + + if (p->auth_ip_acl) + pa_ip_acl_free(p->auth_ip_acl); + +#ifdef HAVE_CREDS + pa_xfree(p->auth_group); +#endif + pa_xfree(p); +} + +pa_protocol_native* pa_protocol_native_new_iochannel( + pa_core*core, + pa_iochannel *io, + pa_module *m, + pa_modargs *ma) { + + pa_protocol_native *p; + + if (!(p = protocol_new_internal(core, m, ma))) + return NULL; + + on_connection(NULL, io, p); + + return p; +} diff --git a/src/pulsecore/protocol-native.h b/src/pulsecore/protocol-native.h new file mode 100644 index 00000000..bf05f937 --- /dev/null +++ b/src/pulsecore/protocol-native.h @@ -0,0 +1,40 @@ +#ifndef fooprotocolnativehfoo +#define fooprotocolnativehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulsecore/core.h> +#include <pulsecore/socket-server.h> +#include <pulsecore/module.h> +#include <pulsecore/modargs.h> + +typedef struct pa_protocol_native pa_protocol_native; + +pa_protocol_native* pa_protocol_native_new(pa_core*core, pa_socket_server *server, pa_module *m, pa_modargs *ma); +void pa_protocol_native_free(pa_protocol_native *n); + +pa_protocol_native* pa_protocol_native_new_iochannel(pa_core*core, pa_iochannel *io, pa_module *m, pa_modargs *ma); + +#endif diff --git a/src/pulsecore/protocol-simple.c b/src/pulsecore/protocol-simple.c new file mode 100644 index 00000000..777def30 --- /dev/null +++ b/src/pulsecore/protocol-simple.c @@ -0,0 +1,636 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <limits.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/source-output.h> +#include <pulsecore/client.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/namereg.h> +#include <pulsecore/log.h> +#include <pulsecore/core-error.h> +#include <pulsecore/atomic.h> +#include <pulsecore/thread-mq.h> + +#include "protocol-simple.h" + +/* Don't allow more than this many concurrent connections */ +#define MAX_CONNECTIONS 10 + +typedef struct connection { + pa_msgobject parent; + pa_protocol_simple *protocol; + pa_iochannel *io; + pa_sink_input *sink_input; + pa_source_output *source_output; + pa_client *client; + pa_memblockq *input_memblockq, *output_memblockq; + + int dead; + + struct { + pa_memblock *current_memblock; + size_t memblock_index, fragment_size; + pa_atomic_t missing; + } playback; +} connection; + +PA_DECLARE_CLASS(connection); +#define CONNECTION(o) (connection_cast(o)) +static PA_DEFINE_CHECK_TYPE(connection, pa_msgobject); + +struct pa_protocol_simple { + pa_module *module; + pa_core *core; + pa_socket_server*server; + pa_idxset *connections; + + enum { + RECORD = 1, + PLAYBACK = 2, + DUPLEX = 3 + } mode; + + pa_sample_spec sample_spec; + char *source_name, *sink_name; +}; + +enum { + SINK_INPUT_MESSAGE_POST_DATA = PA_SINK_INPUT_MESSAGE_MAX, /* data from main loop to sink input */ + SINK_INPUT_MESSAGE_DISABLE_PREBUF /* disabled prebuf, get playback started. */ +}; + +enum { + CONNECTION_MESSAGE_REQUEST_DATA, /* data requested from sink input from the main loop */ + CONNECTION_MESSAGE_POST_DATA, /* data from source output to main loop */ + CONNECTION_MESSAGE_UNLINK_CONNECTION /* Please drop a aconnection now */ +}; + + +#define PLAYBACK_BUFFER_SECONDS (.5) +#define PLAYBACK_BUFFER_FRAGMENTS (10) +#define RECORD_BUFFER_SECONDS (5) +#define RECORD_BUFFER_FRAGMENTS (100) + +static void connection_unlink(connection *c) { + pa_assert(c); + + if (!c->protocol) + return; + + if (c->sink_input) { + pa_sink_input_unlink(c->sink_input); + pa_sink_input_unref(c->sink_input); + c->sink_input = NULL; + } + + if (c->source_output) { + pa_source_output_unlink(c->source_output); + pa_source_output_unref(c->source_output); + c->source_output = NULL; + } + + if (c->client) { + pa_client_free(c->client); + c->client = NULL; + } + + if (c->io) { + pa_iochannel_free(c->io); + c->io = NULL; + } + + pa_assert_se(pa_idxset_remove_by_data(c->protocol->connections, c, NULL) == c); + c->protocol = NULL; + connection_unref(c); +} + +static void connection_free(pa_object *o) { + connection *c = CONNECTION(o); + pa_assert(c); + + connection_unref(c); + + if (c->playback.current_memblock) + pa_memblock_unref(c->playback.current_memblock); + + if (c->input_memblockq) + pa_memblockq_free(c->input_memblockq); + if (c->output_memblockq) + pa_memblockq_free(c->output_memblockq); + + pa_xfree(c); +} + +static int do_read(connection *c) { + pa_memchunk chunk; + ssize_t r; + size_t l; + void *p; + + connection_assert_ref(c); + + if (!c->sink_input || (l = pa_atomic_load(&c->playback.missing)) <= 0) + return 0; + + if (l > c->playback.fragment_size) + l = c->playback.fragment_size; + + if (c->playback.current_memblock) + if (pa_memblock_get_length(c->playback.current_memblock) - c->playback.memblock_index < l) { + pa_memblock_unref(c->playback.current_memblock); + c->playback.current_memblock = NULL; + c->playback.memblock_index = 0; + } + + if (!c->playback.current_memblock) { + pa_assert_se(c->playback.current_memblock = pa_memblock_new(c->protocol->core->mempool, l)); + c->playback.memblock_index = 0; + } + + p = pa_memblock_acquire(c->playback.current_memblock); + r = pa_iochannel_read(c->io, (uint8_t*) p + c->playback.memblock_index, l); + pa_memblock_release(c->playback.current_memblock); + + if (r <= 0) { + + if (r < 0 && (errno == EINTR || errno == EAGAIN)) + return 0; + + pa_log_debug("read(): %s", r == 0 ? "EOF" : pa_cstrerror(errno)); + return -1; + } + + chunk.memblock = c->playback.current_memblock; + chunk.index = c->playback.memblock_index; + chunk.length = r; + + c->playback.memblock_index += r; + + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_POST_DATA, NULL, 0, &chunk, NULL); + pa_atomic_sub(&c->playback.missing, r); + + return 0; +} + +static int do_write(connection *c) { + pa_memchunk chunk; + ssize_t r; + void *p; + + connection_assert_ref(c); + + if (!c->source_output) + return 0; + + if (pa_memblockq_peek(c->output_memblockq, &chunk) < 0) { +/* pa_log("peek failed"); */ + return 0; + } + + pa_assert(chunk.memblock); + pa_assert(chunk.length); + + p = pa_memblock_acquire(chunk.memblock); + r = pa_iochannel_write(c->io, (uint8_t*) p+chunk.index, chunk.length); + pa_memblock_release(chunk.memblock); + + pa_memblock_unref(chunk.memblock); + + if (r < 0) { + + if (errno == EINTR || errno == EAGAIN) + return 0; + + pa_log("write(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_memblockq_drop(c->output_memblockq, r); + + return 0; +} + +static void do_work(connection *c) { + connection_assert_ref(c); + + if (c->dead) + return; + + if (pa_iochannel_is_readable(c->io)) { + if (do_read(c) < 0) + goto fail; + } else if (pa_iochannel_is_hungup(c->io)) + goto fail; + + if (pa_iochannel_is_writable(c->io)) { + if (do_write(c) < 0) + goto fail; + } + + return; + +fail: + + if (c->sink_input) { + + /* If there is a sink input, we first drain what we already have read before shutting down the connection */ + c->dead = 1; + + pa_iochannel_free(c->io); + c->io = NULL; + + pa_asyncmsgq_post(c->sink_input->sink->asyncmsgq, PA_MSGOBJECT(c->sink_input), SINK_INPUT_MESSAGE_DISABLE_PREBUF, NULL, 0, NULL, NULL); + } else + connection_unlink(c); +} + +static int connection_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + connection *c = CONNECTION(o); + connection_assert_ref(c); + + switch (code) { + case CONNECTION_MESSAGE_REQUEST_DATA: + do_work(c); + break; + + case CONNECTION_MESSAGE_POST_DATA: +/* pa_log("got data %u", chunk->length); */ + pa_memblockq_push_align(c->output_memblockq, chunk); + do_work(c); + break; + + case CONNECTION_MESSAGE_UNLINK_CONNECTION: + connection_unlink(c); + break; + } + + return 0; +} + +/*** sink_input callbacks ***/ + +/* Called from thread context */ +static int sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + connection*c; + + pa_sink_input_assert_ref(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + + switch (code) { + + case SINK_INPUT_MESSAGE_POST_DATA: { + pa_assert(chunk); + + /* New data from the main loop */ + pa_memblockq_push_align(c->input_memblockq, chunk); + +/* pa_log("got data, %u", pa_memblockq_get_length(c->input_memblockq)); */ + + return 0; + } + + case SINK_INPUT_MESSAGE_DISABLE_PREBUF: { + pa_memblockq_prebuf_disable(c->input_memblockq); + return 0; + } + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + *r = pa_bytes_to_usec(pa_memblockq_get_length(c->input_memblockq), &c->sink_input->sample_spec); + + /* Fall through, the default handler will add in the extra + * latency added by the resampler */ + } + + default: + return pa_sink_input_process_msg(o, code, userdata, offset, chunk); + } +} + +/* Called from thread context */ +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + connection *c; + int r; + + pa_assert(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + pa_assert(chunk); + + r = pa_memblockq_peek(c->input_memblockq, chunk); + +/* pa_log("peeked %u %i", r >= 0 ? chunk->length: 0, r); */ + + if (c->dead && r < 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_UNLINK_CONNECTION, NULL, 0, NULL, NULL); + + return r; +} + +/* Called from thread context */ +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + connection *c; + size_t old, new; + + pa_assert(i); + c = CONNECTION(i->userdata); + connection_assert_ref(c); + pa_assert(length); + + old = pa_memblockq_missing(c->input_memblockq); + pa_memblockq_drop(c->input_memblockq, length); + new = pa_memblockq_missing(c->input_memblockq); + + if (new > old) { + if (pa_atomic_add(&c->playback.missing, new - old) <= 0) + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_REQUEST_DATA, NULL, 0, NULL, NULL); + } +} + +/* Called from main context */ +static void sink_input_kill_cb(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + connection_unlink(CONNECTION(i->userdata)); +} + +/*** source_output callbacks ***/ + +/* Called from thread context */ +static void source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) { + connection *c; + + pa_assert(o); + c = CONNECTION(o->userdata); + pa_assert(c); + pa_assert(chunk); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(c), CONNECTION_MESSAGE_POST_DATA, NULL, 0, chunk, NULL); +} + +/* Called from main context */ +static void source_output_kill_cb(pa_source_output *o) { + pa_source_output_assert_ref(o); + + connection_unlink(CONNECTION(o->userdata)); +} + +/* Called from main context */ +static pa_usec_t source_output_get_latency_cb(pa_source_output *o) { + connection*c; + + pa_assert(o); + c = CONNECTION(o->userdata); + pa_assert(c); + + return pa_bytes_to_usec(pa_memblockq_get_length(c->output_memblockq), &c->source_output->sample_spec); +} + +/*** client callbacks ***/ + +static void client_kill_cb(pa_client *client) { + connection*c; + + pa_assert(client); + c = CONNECTION(client->userdata); + pa_assert(c); + + connection_unlink(c); +} + +/*** pa_iochannel callbacks ***/ + +static void io_callback(pa_iochannel*io, void *userdata) { + connection *c = CONNECTION(userdata); + + connection_assert_ref(c); + pa_assert(io); + + do_work(c); +} + +/*** socket_server callbacks ***/ + +static void on_connection(pa_socket_server*s, pa_iochannel *io, void *userdata) { + pa_protocol_simple *p = userdata; + connection *c = NULL; + char cname[256]; + + pa_assert(s); + pa_assert(io); + pa_assert(p); + + if (pa_idxset_size(p->connections)+1 > MAX_CONNECTIONS) { + pa_log("Warning! Too many connections (%u), dropping incoming connection.", MAX_CONNECTIONS); + pa_iochannel_free(io); + return; + } + + c = pa_msgobject_new(connection); + c->parent.parent.free = connection_free; + c->parent.process_msg = connection_process_msg; + c->io = io; + c->sink_input = NULL; + c->source_output = NULL; + c->input_memblockq = c->output_memblockq = NULL; + c->protocol = p; + c->playback.current_memblock = NULL; + c->playback.memblock_index = 0; + c->playback.fragment_size = 0; + c->dead = 0; + pa_atomic_store(&c->playback.missing, 0); + + pa_iochannel_socket_peer_to_string(io, cname, sizeof(cname)); + pa_assert_se(c->client = pa_client_new(p->core, __FILE__, cname)); + c->client->owner = p->module; + c->client->kill = client_kill_cb; + c->client->userdata = c; + + if (p->mode & PLAYBACK) { + pa_sink_input_new_data data; + size_t l; + + pa_sink_input_new_data_init(&data); + data.driver = __FILE__; + data.name = c->client->name; + pa_sink_input_new_data_set_sample_spec(&data, &p->sample_spec); + data.module = p->module; + data.client = c->client; + + if (!(c->sink_input = pa_sink_input_new(p->core, &data, 0))) { + pa_log("Failed to create sink input."); + goto fail; + } + + c->sink_input->parent.process_msg = sink_input_process_msg; + c->sink_input->peek = sink_input_peek_cb; + c->sink_input->drop = sink_input_drop_cb; + c->sink_input->kill = sink_input_kill_cb; + c->sink_input->userdata = c; + + l = (size_t) (pa_bytes_per_second(&p->sample_spec)*PLAYBACK_BUFFER_SECONDS); + c->input_memblockq = pa_memblockq_new( + 0, + l, + 0, + pa_frame_size(&p->sample_spec), + (size_t) -1, + l/PLAYBACK_BUFFER_FRAGMENTS, + NULL); + pa_iochannel_socket_set_rcvbuf(io, l/PLAYBACK_BUFFER_FRAGMENTS*5); + c->playback.fragment_size = l/PLAYBACK_BUFFER_FRAGMENTS; + + pa_atomic_store(&c->playback.missing, pa_memblockq_missing(c->input_memblockq)); + + pa_sink_input_put(c->sink_input); + } + + if (p->mode & RECORD) { + pa_source_output_new_data data; + size_t l; + + pa_source_output_new_data_init(&data); + data.driver = __FILE__; + data.name = c->client->name; + pa_source_output_new_data_set_sample_spec(&data, &p->sample_spec); + data.module = p->module; + data.client = c->client; + + if (!(c->source_output = pa_source_output_new(p->core, &data, 0))) { + pa_log("Failed to create source output."); + goto fail; + } + c->source_output->push = source_output_push_cb; + c->source_output->kill = source_output_kill_cb; + c->source_output->get_latency = source_output_get_latency_cb; + c->source_output->userdata = c; + + l = (size_t) (pa_bytes_per_second(&p->sample_spec)*RECORD_BUFFER_SECONDS); + c->output_memblockq = pa_memblockq_new( + 0, + l, + 0, + pa_frame_size(&p->sample_spec), + 1, + 0, + NULL); + pa_iochannel_socket_set_sndbuf(io, l/RECORD_BUFFER_FRAGMENTS*2); + + pa_source_output_put(c->source_output); + } + + pa_iochannel_set_callback(c->io, io_callback, c); + pa_idxset_put(p->connections, c, NULL); + + return; + +fail: + if (c) + connection_unlink(c); +} + +pa_protocol_simple* pa_protocol_simple_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma) { + pa_protocol_simple* p = NULL; + pa_bool_t enable; + + pa_assert(core); + pa_assert(server); + pa_assert(ma); + + p = pa_xnew0(pa_protocol_simple, 1); + p->module = m; + p->core = core; + p->server = server; + p->connections = pa_idxset_new(NULL, NULL); + + p->sample_spec = core->default_sample_spec; + if (pa_modargs_get_sample_spec(ma, &p->sample_spec) < 0) { + pa_log("Failed to parse sample type specification."); + goto fail; + } + + p->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL)); + p->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); + + enable = FALSE; + if (pa_modargs_get_value_boolean(ma, "record", &enable) < 0) { + pa_log("record= expects a numeric argument."); + goto fail; + } + p->mode = enable ? RECORD : 0; + + enable = 1; + if (pa_modargs_get_value_boolean(ma, "playback", &enable) < 0) { + pa_log("playback= expects a numeric argument."); + goto fail; + } + p->mode |= enable ? PLAYBACK : 0; + + if ((p->mode & (RECORD|PLAYBACK)) == 0) { + pa_log("neither playback nor recording enabled for protocol."); + goto fail; + } + + pa_socket_server_set_callback(p->server, on_connection, p); + + return p; + +fail: + if (p) + pa_protocol_simple_free(p); + + return NULL; +} + + +void pa_protocol_simple_free(pa_protocol_simple *p) { + connection *c; + pa_assert(p); + + if (p->connections) { + while((c = pa_idxset_first(p->connections, NULL))) + connection_unlink(c); + + pa_idxset_free(p->connections, NULL, NULL); + } + + if (p->server) + pa_socket_server_unref(p->server); + + pa_xfree(p); +} diff --git a/src/pulsecore/protocol-simple.h b/src/pulsecore/protocol-simple.h new file mode 100644 index 00000000..3b02c88e --- /dev/null +++ b/src/pulsecore/protocol-simple.h @@ -0,0 +1,37 @@ +#ifndef fooprotocolsimplehfoo +#define fooprotocolsimplehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/socket-server.h> +#include <pulsecore/module.h> +#include <pulsecore/core.h> +#include <pulsecore/modargs.h> + +typedef struct pa_protocol_simple pa_protocol_simple; + +pa_protocol_simple* pa_protocol_simple_new(pa_core *core, pa_socket_server *server, pa_module *m, pa_modargs *ma); +void pa_protocol_simple_free(pa_protocol_simple *n); + +#endif diff --git a/src/pulsecore/pstream-util.c b/src/pulsecore/pstream-util.c new file mode 100644 index 00000000..a6932158 --- /dev/null +++ b/src/pulsecore/pstream-util.c @@ -0,0 +1,64 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <pulsecore/native-common.h> +#include <pulsecore/macro.h> + +#include "pstream-util.h" + +void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds) { + size_t length; + uint8_t *data; + pa_packet *packet; + + pa_assert(p); + pa_assert(t); + + pa_assert_se(data = pa_tagstruct_free_data(t, &length)); + pa_assert_se(packet = pa_packet_new_dynamic(data, length)); + pa_pstream_send_packet(p, packet, creds); + pa_packet_unref(packet); +} + +void pa_pstream_send_error(pa_pstream *p, uint32_t tag, uint32_t error) { + pa_tagstruct *t; + + pa_assert_se(t = pa_tagstruct_new(NULL, 0)); + pa_tagstruct_putu32(t, PA_COMMAND_ERROR); + pa_tagstruct_putu32(t, tag); + pa_tagstruct_putu32(t, error); + pa_pstream_send_tagstruct(p, t); +} + +void pa_pstream_send_simple_ack(pa_pstream *p, uint32_t tag) { + pa_tagstruct *t; + + pa_assert_se(t = pa_tagstruct_new(NULL, 0)); + pa_tagstruct_putu32(t, PA_COMMAND_REPLY); + pa_tagstruct_putu32(t, tag); + pa_pstream_send_tagstruct(p, t); +} diff --git a/src/pulsecore/pstream-util.h b/src/pulsecore/pstream-util.h new file mode 100644 index 00000000..67759f2a --- /dev/null +++ b/src/pulsecore/pstream-util.h @@ -0,0 +1,40 @@ +#ifndef foopstreamutilhfoo +#define foopstreamutilhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <inttypes.h> +#include <pulsecore/pstream.h> +#include <pulsecore/tagstruct.h> +#include <pulsecore/creds.h> + +/* The tagstruct is freed!*/ +void pa_pstream_send_tagstruct_with_creds(pa_pstream *p, pa_tagstruct *t, const pa_creds *creds); + +#define pa_pstream_send_tagstruct(p, t) pa_pstream_send_tagstruct_with_creds((p), (t), NULL) + +void pa_pstream_send_error(pa_pstream *p, uint32_t tag, uint32_t error); +void pa_pstream_send_simple_ack(pa_pstream *p, uint32_t tag); + +#endif diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c new file mode 100644 index 00000000..9d32a363 --- /dev/null +++ b/src/pulsecore/pstream.c @@ -0,0 +1,1020 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + + +#include <pulse/xmalloc.h> + +#include <pulsecore/winsock.h> +#include <pulsecore/queue.h> +#include <pulsecore/log.h> +#include <pulsecore/core-scache.h> +#include <pulsecore/creds.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/flist.h> +#include <pulsecore/macro.h> + +#include "pstream.h" + +/* We piggyback information if audio data blocks are stored in SHM on the seek mode */ +#define PA_FLAG_SHMDATA 0x80000000LU +#define PA_FLAG_SHMRELEASE 0x40000000LU +#define PA_FLAG_SHMREVOKE 0xC0000000LU +#define PA_FLAG_SHMMASK 0xFF000000LU +#define PA_FLAG_SEEKMASK 0x000000FFLU + +/* The sequence descriptor header consists of 5 32bit integers: */ +enum { + PA_PSTREAM_DESCRIPTOR_LENGTH, + PA_PSTREAM_DESCRIPTOR_CHANNEL, + PA_PSTREAM_DESCRIPTOR_OFFSET_HI, + PA_PSTREAM_DESCRIPTOR_OFFSET_LO, + PA_PSTREAM_DESCRIPTOR_FLAGS, + PA_PSTREAM_DESCRIPTOR_MAX +}; + +/* If we have an SHM block, this info follows the descriptor */ +enum { + PA_PSTREAM_SHM_BLOCKID, + PA_PSTREAM_SHM_SHMID, + PA_PSTREAM_SHM_INDEX, + PA_PSTREAM_SHM_LENGTH, + PA_PSTREAM_SHM_MAX +}; + +typedef uint32_t pa_pstream_descriptor[PA_PSTREAM_DESCRIPTOR_MAX]; + +#define PA_PSTREAM_DESCRIPTOR_SIZE (PA_PSTREAM_DESCRIPTOR_MAX*sizeof(uint32_t)) +#define FRAME_SIZE_MAX_ALLOW PA_SCACHE_ENTRY_SIZE_MAX /* allow uploading a single sample in one frame at max */ + +PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree); + +struct item_info { + enum { + PA_PSTREAM_ITEM_PACKET, + PA_PSTREAM_ITEM_MEMBLOCK, + PA_PSTREAM_ITEM_SHMRELEASE, + PA_PSTREAM_ITEM_SHMREVOKE + } type; + + /* packet info */ + pa_packet *packet; +#ifdef HAVE_CREDS + int with_creds; + pa_creds creds; +#endif + + /* memblock info */ + pa_memchunk chunk; + uint32_t channel; + int64_t offset; + pa_seek_mode_t seek_mode; + + /* release/revoke info */ + uint32_t block_id; +}; + +struct pa_pstream { + PA_REFCNT_DECLARE; + + pa_mainloop_api *mainloop; + pa_defer_event *defer_event; + pa_iochannel *io; + + pa_queue *send_queue; + + int dead; + + struct { + pa_pstream_descriptor descriptor; + struct item_info* current; + uint32_t shm_info[PA_PSTREAM_SHM_MAX]; + void *data; + size_t index; + pa_memchunk memchunk; + } write; + + struct { + pa_pstream_descriptor descriptor; + pa_memblock *memblock; + pa_packet *packet; + uint32_t shm_info[PA_PSTREAM_SHM_MAX]; + void *data; + size_t index; + } read; + + int use_shm; + pa_memimport *import; + pa_memexport *export; + + pa_pstream_packet_cb_t recieve_packet_callback; + void *recieve_packet_callback_userdata; + + pa_pstream_memblock_cb_t recieve_memblock_callback; + void *recieve_memblock_callback_userdata; + + pa_pstream_notify_cb_t drain_callback; + void *drain_callback_userdata; + + pa_pstream_notify_cb_t die_callback; + void *die_callback_userdata; + + pa_pstream_block_id_cb_t revoke_callback; + void *revoke_callback_userdata; + + pa_pstream_block_id_cb_t release_callback; + void *release_callback_userdata; + + pa_mempool *mempool; + +#ifdef HAVE_CREDS + pa_creds read_creds, write_creds; + int read_creds_valid, send_creds_now; +#endif +}; + +static int do_write(pa_pstream *p); +static int do_read(pa_pstream *p); + +static void do_something(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + pa_pstream_ref(p); + + p->mainloop->defer_enable(p->defer_event, 0); + + if (!p->dead && pa_iochannel_is_readable(p->io)) { + if (do_read(p) < 0) + goto fail; + } else if (!p->dead && pa_iochannel_is_hungup(p->io)) + goto fail; + + if (!p->dead && pa_iochannel_is_writable(p->io)) { + if (do_write(p) < 0) + goto fail; + } + + pa_pstream_unref(p); + return; + +fail: + + if (p->die_callback) + p->die_callback(p, p->die_callback_userdata); + + pa_pstream_unlink(p); + pa_pstream_unref(p); +} + +static void io_callback(pa_iochannel*io, void *userdata) { + pa_pstream *p = userdata; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p->io == io); + + do_something(p); +} + +static void defer_callback(pa_mainloop_api *m, pa_defer_event *e, void*userdata) { + pa_pstream *p = userdata; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(p->defer_event == e); + pa_assert(p->mainloop == m); + + do_something(p); +} + +static void memimport_release_cb(pa_memimport *i, uint32_t block_id, void *userdata); + +pa_pstream *pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *pool) { + pa_pstream *p; + + pa_assert(m); + pa_assert(io); + pa_assert(pool); + + p = pa_xnew(pa_pstream, 1); + PA_REFCNT_INIT(p); + p->io = io; + pa_iochannel_set_callback(io, io_callback, p); + p->dead = 0; + + p->mainloop = m; + p->defer_event = m->defer_new(m, defer_callback, p); + m->defer_enable(p->defer_event, 0); + + p->send_queue = pa_queue_new(); + + p->write.current = NULL; + p->write.index = 0; + pa_memchunk_reset(&p->write.memchunk); + p->read.memblock = NULL; + p->read.packet = NULL; + p->read.index = 0; + + p->recieve_packet_callback = NULL; + p->recieve_packet_callback_userdata = NULL; + p->recieve_memblock_callback = NULL; + p->recieve_memblock_callback_userdata = NULL; + p->drain_callback = NULL; + p->drain_callback_userdata = NULL; + p->die_callback = NULL; + p->die_callback_userdata = NULL; + p->revoke_callback = NULL; + p->revoke_callback_userdata = NULL; + p->release_callback = NULL; + p->release_callback_userdata = NULL; + + p->mempool = pool; + + p->use_shm = 0; + p->export = NULL; + + /* We do importing unconditionally */ + p->import = pa_memimport_new(p->mempool, memimport_release_cb, p); + + pa_iochannel_socket_set_rcvbuf(io, 1024*8); + pa_iochannel_socket_set_sndbuf(io, 1024*8); + +#ifdef HAVE_CREDS + p->send_creds_now = 0; + p->read_creds_valid = 0; +#endif + return p; +} + +static void item_free(void *item, PA_GCC_UNUSED void *q) { + struct item_info *i = item; + pa_assert(i); + + if (i->type == PA_PSTREAM_ITEM_MEMBLOCK) { + pa_assert(i->chunk.memblock); + pa_memblock_unref(i->chunk.memblock); + } else if (i->type == PA_PSTREAM_ITEM_PACKET) { + pa_assert(i->packet); + pa_packet_unref(i->packet); + } + + if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0) + pa_xfree(i); +} + +static void pstream_free(pa_pstream *p) { + pa_assert(p); + + pa_pstream_unlink(p); + + pa_queue_free(p->send_queue, item_free, NULL); + + if (p->write.current) + item_free(p->write.current, NULL); + + if (p->write.memchunk.memblock) + pa_memblock_unref(p->write.memchunk.memblock); + + if (p->read.memblock) + pa_memblock_unref(p->read.memblock); + + if (p->read.packet) + pa_packet_unref(p->read.packet); + + pa_xfree(p); +} + +void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, const pa_creds *creds) { + struct item_info *i; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(packet); + + if (p->dead) + return; + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + i = pa_xnew(struct item_info, 1); + + i->type = PA_PSTREAM_ITEM_PACKET; + i->packet = pa_packet_ref(packet); + +#ifdef HAVE_CREDS + if ((i->with_creds = !!creds)) + i->creds = *creds; +#endif + + pa_queue_push(p->send_queue, i); + + p->mainloop->defer_enable(p->defer_event, 1); +} + +void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa_seek_mode_t seek_mode, const pa_memchunk *chunk) { + size_t length, idx; + size_t bsm; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + pa_assert(channel != (uint32_t) -1); + pa_assert(chunk); + + if (p->dead) + return; + + idx = 0; + length = chunk->length; + + bsm = pa_mempool_block_size_max(p->mempool); + + while (length > 0) { + struct item_info *i; + size_t n; + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + i = pa_xnew(struct item_info, 1); + i->type = PA_PSTREAM_ITEM_MEMBLOCK; + + n = MIN(length, bsm); + i->chunk.index = chunk->index + idx; + i->chunk.length = n; + i->chunk.memblock = pa_memblock_ref(chunk->memblock); + + i->channel = channel; + i->offset = offset; + i->seek_mode = seek_mode; +#ifdef HAVE_CREDS + i->with_creds = 0; +#endif + + pa_queue_push(p->send_queue, i); + + idx += n; + length -= n; + } + + p->mainloop->defer_enable(p->defer_event, 1); +} + +void pa_pstream_send_release(pa_pstream *p, uint32_t block_id) { + struct item_info *item; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->dead) + return; + +/* pa_log("Releasing block %u", block_id); */ + + if (!(item = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + item = pa_xnew(struct item_info, 1); + item->type = PA_PSTREAM_ITEM_SHMRELEASE; + item->block_id = block_id; +#ifdef HAVE_CREDS + item->with_creds = 0; +#endif + + pa_queue_push(p->send_queue, item); + p->mainloop->defer_enable(p->defer_event, 1); +} + +/* might be called from thread context */ +static void memimport_release_cb(pa_memimport *i, uint32_t block_id, void *userdata) { + pa_pstream *p = userdata; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->dead) + return; + + if (p->release_callback) + p->release_callback(p, block_id, p->release_callback_userdata); + else + pa_pstream_send_release(p, block_id); +} + +void pa_pstream_send_revoke(pa_pstream *p, uint32_t block_id) { + struct item_info *item; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->dead) + return; +/* pa_log("Revoking block %u", block_id); */ + + if (!(item = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + item = pa_xnew(struct item_info, 1); + item->type = PA_PSTREAM_ITEM_SHMREVOKE; + item->block_id = block_id; +#ifdef HAVE_CREDS + item->with_creds = 0; +#endif + + pa_queue_push(p->send_queue, item); + p->mainloop->defer_enable(p->defer_event, 1); +} + +/* might be called from thread context */ +static void memexport_revoke_cb(pa_memexport *e, uint32_t block_id, void *userdata) { + pa_pstream *p = userdata; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->revoke_callback) + p->revoke_callback(p, block_id, p->revoke_callback_userdata); + else + pa_pstream_send_revoke(p, block_id); +} + +static void prepare_next_write_item(pa_pstream *p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->write.current = pa_queue_pop(p->send_queue); + + if (!p->write.current) + return; + + p->write.index = 0; + p->write.data = NULL; + pa_memchunk_reset(&p->write.memchunk); + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = 0; + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL] = htonl((uint32_t) -1); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = 0; + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO] = 0; + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = 0; + + if (p->write.current->type == PA_PSTREAM_ITEM_PACKET) { + + pa_assert(p->write.current->packet); + p->write.data = p->write.current->packet->data; + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(p->write.current->packet->length); + + } else if (p->write.current->type == PA_PSTREAM_ITEM_SHMRELEASE) { + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(PA_FLAG_SHMRELEASE); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl(p->write.current->block_id); + + } else if (p->write.current->type == PA_PSTREAM_ITEM_SHMREVOKE) { + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(PA_FLAG_SHMREVOKE); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl(p->write.current->block_id); + + } else { + uint32_t flags; + int send_payload = 1; + + pa_assert(p->write.current->type == PA_PSTREAM_ITEM_MEMBLOCK); + pa_assert(p->write.current->chunk.memblock); + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL] = htonl(p->write.current->channel); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = htonl((uint32_t) (((uint64_t) p->write.current->offset) >> 32)); + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO] = htonl((uint32_t) ((uint64_t) p->write.current->offset)); + + flags = p->write.current->seek_mode & PA_FLAG_SEEKMASK; + + if (p->use_shm) { + uint32_t block_id, shm_id; + size_t offset, length; + + pa_assert(p->export); + + if (pa_memexport_put(p->export, + p->write.current->chunk.memblock, + &block_id, + &shm_id, + &offset, + &length) >= 0) { + + flags |= PA_FLAG_SHMDATA; + send_payload = 0; + + p->write.shm_info[PA_PSTREAM_SHM_BLOCKID] = htonl(block_id); + p->write.shm_info[PA_PSTREAM_SHM_SHMID] = htonl(shm_id); + p->write.shm_info[PA_PSTREAM_SHM_INDEX] = htonl((uint32_t) (offset + p->write.current->chunk.index)); + p->write.shm_info[PA_PSTREAM_SHM_LENGTH] = htonl((uint32_t) p->write.current->chunk.length); + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(sizeof(p->write.shm_info)); + p->write.data = p->write.shm_info; + } +/* else */ +/* pa_log_warn("Failed to export memory block."); */ + } + + if (send_payload) { + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH] = htonl(p->write.current->chunk.length); + p->write.memchunk = p->write.current->chunk; + pa_memblock_ref(p->write.memchunk.memblock); + p->write.data = NULL; + } + + p->write.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = htonl(flags); + } + +#ifdef HAVE_CREDS + if ((p->send_creds_now = p->write.current->with_creds)) + p->write_creds = p->write.current->creds; +#endif +} + +static int do_write(pa_pstream *p) { + void *d; + size_t l; + ssize_t r; + pa_memblock *release_memblock = NULL; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (!p->write.current) + prepare_next_write_item(p); + + if (!p->write.current) + return 0; + + if (p->write.index < PA_PSTREAM_DESCRIPTOR_SIZE) { + d = (uint8_t*) p->write.descriptor + p->write.index; + l = PA_PSTREAM_DESCRIPTOR_SIZE - p->write.index; + } else { + pa_assert(p->write.data || p->write.memchunk.memblock); + + if (p->write.data) + d = p->write.data; + else { + d = (uint8_t*) pa_memblock_acquire(p->write.memchunk.memblock) + p->write.memchunk.index; + release_memblock = p->write.memchunk.memblock; + } + + d = (uint8_t*) d + p->write.index - PA_PSTREAM_DESCRIPTOR_SIZE; + l = ntohl(p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) - (p->write.index - PA_PSTREAM_DESCRIPTOR_SIZE); + } + + pa_assert(l > 0); + +#ifdef HAVE_CREDS + if (p->send_creds_now) { + + if ((r = pa_iochannel_write_with_creds(p->io, d, l, &p->write_creds)) < 0) + goto fail; + + p->send_creds_now = 0; + } else +#endif + + if ((r = pa_iochannel_write(p->io, d, l)) < 0) + goto fail; + + if (release_memblock) + pa_memblock_release(release_memblock); + + p->write.index += r; + + if (p->write.index >= PA_PSTREAM_DESCRIPTOR_SIZE + ntohl(p->write.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH])) { + pa_assert(p->write.current); + item_free(p->write.current, NULL); + p->write.current = NULL; + + if (p->write.memchunk.memblock) + pa_memblock_unref(p->write.memchunk.memblock); + + pa_memchunk_reset(&p->write.memchunk); + + if (p->drain_callback && !pa_pstream_is_pending(p)) + p->drain_callback(p, p->drain_callback_userdata); + } + + return 0; + +fail: + + if (release_memblock) + pa_memblock_release(release_memblock); + + return -1; +} + +static int do_read(pa_pstream *p) { + void *d; + size_t l; + ssize_t r; + pa_memblock *release_memblock = NULL; + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->read.index < PA_PSTREAM_DESCRIPTOR_SIZE) { + d = (uint8_t*) p->read.descriptor + p->read.index; + l = PA_PSTREAM_DESCRIPTOR_SIZE - p->read.index; + } else { + pa_assert(p->read.data || p->read.memblock); + + if (p->read.data) + d = p->read.data; + else { + d = pa_memblock_acquire(p->read.memblock); + release_memblock = p->read.memblock; + } + + d = (uint8_t*) d + p->read.index - PA_PSTREAM_DESCRIPTOR_SIZE; + l = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) - (p->read.index - PA_PSTREAM_DESCRIPTOR_SIZE); + } + +#ifdef HAVE_CREDS + { + pa_bool_t b = 0; + + if ((r = pa_iochannel_read_with_creds(p->io, d, l, &p->read_creds, &b)) <= 0) + goto fail; + + p->read_creds_valid = p->read_creds_valid || b; + } +#else + if ((r = pa_iochannel_read(p->io, d, l)) <= 0) + goto fail; +#endif + + if (release_memblock) + pa_memblock_release(release_memblock); + + p->read.index += r; + + if (p->read.index == PA_PSTREAM_DESCRIPTOR_SIZE) { + uint32_t flags, length, channel; + /* Reading of frame descriptor complete */ + + flags = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]); + + if (!p->use_shm && (flags & PA_FLAG_SHMMASK) != 0) { + pa_log_warn("Recieved SHM frame on a socket where SHM is disabled."); + return -1; + } + + if (flags == PA_FLAG_SHMRELEASE) { + + /* This is a SHM memblock release frame with no payload */ + +/* pa_log("Got release frame for %u", ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); */ + + pa_assert(p->export); + pa_memexport_process_release(p->export, ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); + + goto frame_done; + + } else if (flags == PA_FLAG_SHMREVOKE) { + + /* This is a SHM memblock revoke frame with no payload */ + +/* pa_log("Got revoke frame for %u", ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); */ + + pa_assert(p->import); + pa_memimport_process_revoke(p->import, ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])); + + goto frame_done; + } + + length = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]); + + if (length > FRAME_SIZE_MAX_ALLOW || length <= 0) { + pa_log_warn("Recieved invalid frame size: %lu", (unsigned long) length); + return -1; + } + + pa_assert(!p->read.packet && !p->read.memblock); + + channel = ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]); + + if (channel == (uint32_t) -1) { + + if (flags != 0) { + pa_log_warn("Received packet frame with invalid flags value."); + return -1; + } + + /* Frame is a packet frame */ + p->read.packet = pa_packet_new(length); + p->read.data = p->read.packet->data; + + } else { + + if ((flags & PA_FLAG_SEEKMASK) > PA_SEEK_RELATIVE_END) { + pa_log_warn("Received memblock frame with invalid seek mode."); + return -1; + } + + if ((flags & PA_FLAG_SHMMASK) == PA_FLAG_SHMDATA) { + + if (length != sizeof(p->read.shm_info)) { + pa_log_warn("Recieved SHM memblock frame with Invalid frame length."); + return -1; + } + + /* Frame is a memblock frame referencing an SHM memblock */ + p->read.data = p->read.shm_info; + + } else if ((flags & PA_FLAG_SHMMASK) == 0) { + + /* Frame is a memblock frame */ + + p->read.memblock = pa_memblock_new(p->mempool, length); + p->read.data = NULL; + } else { + + pa_log_warn("Recieved memblock frame with invalid flags value."); + return -1; + } + } + + } else if (p->read.index > PA_PSTREAM_DESCRIPTOR_SIZE) { + /* Frame payload available */ + + if (p->read.memblock && p->recieve_memblock_callback) { + + /* Is this memblock data? Than pass it to the user */ + l = (p->read.index - r) < PA_PSTREAM_DESCRIPTOR_SIZE ? p->read.index - PA_PSTREAM_DESCRIPTOR_SIZE : (size_t) r; + + if (l > 0) { + pa_memchunk chunk; + + chunk.memblock = p->read.memblock; + chunk.index = p->read.index - PA_PSTREAM_DESCRIPTOR_SIZE - l; + chunk.length = l; + + if (p->recieve_memblock_callback) { + int64_t offset; + + offset = (int64_t) ( + (((uint64_t) ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])) << 32) | + (((uint64_t) ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO])))); + + p->recieve_memblock_callback( + p, + ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]), + offset, + ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SEEKMASK, + &chunk, + p->recieve_memblock_callback_userdata); + } + + /* Drop seek info for following callbacks */ + p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS] = + p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI] = + p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO] = 0; + } + } + + /* Frame complete */ + if (p->read.index >= ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_LENGTH]) + PA_PSTREAM_DESCRIPTOR_SIZE) { + + if (p->read.memblock) { + + /* This was a memblock frame. We can unref the memblock now */ + pa_memblock_unref(p->read.memblock); + + } else if (p->read.packet) { + + if (p->recieve_packet_callback) +#ifdef HAVE_CREDS + p->recieve_packet_callback(p, p->read.packet, p->read_creds_valid ? &p->read_creds : NULL, p->recieve_packet_callback_userdata); +#else + p->recieve_packet_callback(p, p->read.packet, NULL, p->recieve_packet_callback_userdata); +#endif + + pa_packet_unref(p->read.packet); + } else { + pa_memblock *b; + + pa_assert((ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SHMMASK) == PA_FLAG_SHMDATA); + + pa_assert(p->import); + + if (!(b = pa_memimport_get(p->import, + ntohl(p->read.shm_info[PA_PSTREAM_SHM_BLOCKID]), + ntohl(p->read.shm_info[PA_PSTREAM_SHM_SHMID]), + ntohl(p->read.shm_info[PA_PSTREAM_SHM_INDEX]), + ntohl(p->read.shm_info[PA_PSTREAM_SHM_LENGTH])))) { + + pa_log_warn("Failed to import memory block."); + return -1; + } + + if (p->recieve_memblock_callback) { + int64_t offset; + pa_memchunk chunk; + + chunk.memblock = b; + chunk.index = 0; + chunk.length = pa_memblock_get_length(b); + + offset = (int64_t) ( + (((uint64_t) ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_HI])) << 32) | + (((uint64_t) ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_OFFSET_LO])))); + + p->recieve_memblock_callback( + p, + ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_CHANNEL]), + offset, + ntohl(p->read.descriptor[PA_PSTREAM_DESCRIPTOR_FLAGS]) & PA_FLAG_SEEKMASK, + &chunk, + p->recieve_memblock_callback_userdata); + } + + pa_memblock_unref(b); + } + + goto frame_done; + } + } + + return 0; + +frame_done: + p->read.memblock = NULL; + p->read.packet = NULL; + p->read.index = 0; + p->read.data = NULL; + +#ifdef HAVE_CREDS + p->read_creds_valid = 0; +#endif + + return 0; + +fail: + if (release_memblock) + pa_memblock_release(release_memblock); + + return -1; +} + +void pa_pstream_set_die_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->die_callback = cb; + p->die_callback_userdata = userdata; +} + +void pa_pstream_set_drain_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->drain_callback = cb; + p->drain_callback_userdata = userdata; +} + +void pa_pstream_set_recieve_packet_callback(pa_pstream *p, pa_pstream_packet_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->recieve_packet_callback = cb; + p->recieve_packet_callback_userdata = userdata; +} + +void pa_pstream_set_recieve_memblock_callback(pa_pstream *p, pa_pstream_memblock_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->recieve_memblock_callback = cb; + p->recieve_memblock_callback_userdata = userdata; +} + +void pa_pstream_set_release_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->release_callback = cb; + p->release_callback_userdata = userdata; +} + +void pa_pstream_set_revoke_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->release_callback = cb; + p->release_callback_userdata = userdata; +} + +int pa_pstream_is_pending(pa_pstream *p) { + int b; + + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (p->dead) + b = 0; + else + b = p->write.current || !pa_queue_is_empty(p->send_queue); + + return b; +} + +void pa_pstream_unref(pa_pstream*p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + if (PA_REFCNT_DEC(p) <= 0) + pstream_free(p); +} + +pa_pstream* pa_pstream_ref(pa_pstream*p) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + PA_REFCNT_INC(p); + return p; +} + +void pa_pstream_unlink(pa_pstream *p) { + pa_assert(p); + + if (p->dead) + return; + + p->dead = 1; + + if (p->import) { + pa_memimport_free(p->import); + p->import = NULL; + } + + if (p->export) { + pa_memexport_free(p->export); + p->export = NULL; + } + + if (p->io) { + pa_iochannel_free(p->io); + p->io = NULL; + } + + if (p->defer_event) { + p->mainloop->defer_free(p->defer_event); + p->defer_event = NULL; + } + + p->die_callback = NULL; + p->drain_callback = NULL; + p->recieve_packet_callback = NULL; + p->recieve_memblock_callback = NULL; +} + +void pa_pstream_use_shm(pa_pstream *p, int enable) { + pa_assert(p); + pa_assert(PA_REFCNT_VALUE(p) > 0); + + p->use_shm = enable; + + if (enable) { + + if (!p->export) + p->export = pa_memexport_new(p->mempool, memexport_revoke_cb, p); + + } else { + + if (p->export) { + pa_memexport_free(p->export); + p->export = NULL; + } + } +} diff --git a/src/pulsecore/pstream.h b/src/pulsecore/pstream.h new file mode 100644 index 00000000..72babea9 --- /dev/null +++ b/src/pulsecore/pstream.h @@ -0,0 +1,68 @@ +#ifndef foopstreamhfoo +#define foopstreamhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <inttypes.h> + +#include <pulse/mainloop-api.h> +#include <pulse/def.h> + +#include <pulsecore/packet.h> +#include <pulsecore/memblock.h> +#include <pulsecore/iochannel.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/creds.h> + +typedef struct pa_pstream pa_pstream; + +typedef void (*pa_pstream_packet_cb_t)(pa_pstream *p, pa_packet *packet, const pa_creds *creds, void *userdata); +typedef void (*pa_pstream_memblock_cb_t)(pa_pstream *p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk, void *userdata); +typedef void (*pa_pstream_notify_cb_t)(pa_pstream *p, void *userdata); +typedef void (*pa_pstream_block_id_cb_t)(pa_pstream *p, uint32_t block_id, void *userdata); + +pa_pstream* pa_pstream_new(pa_mainloop_api *m, pa_iochannel *io, pa_mempool *p); +void pa_pstream_unref(pa_pstream*p); +pa_pstream* pa_pstream_ref(pa_pstream*p); + +void pa_pstream_send_packet(pa_pstream*p, pa_packet *packet, const pa_creds *creds); +void pa_pstream_send_memblock(pa_pstream*p, uint32_t channel, int64_t offset, pa_seek_mode_t seek, const pa_memchunk *chunk); +void pa_pstream_send_release(pa_pstream *p, uint32_t block_id); +void pa_pstream_send_revoke(pa_pstream *p, uint32_t block_id); + +void pa_pstream_set_recieve_packet_callback(pa_pstream *p, pa_pstream_packet_cb_t cb, void *userdata); +void pa_pstream_set_recieve_memblock_callback(pa_pstream *p, pa_pstream_memblock_cb_t cb, void *userdata); +void pa_pstream_set_drain_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata); +void pa_pstream_set_die_callback(pa_pstream *p, pa_pstream_notify_cb_t cb, void *userdata); +void pa_pstream_set_release_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata); +void pa_pstream_set_revoke_callback(pa_pstream *p, pa_pstream_block_id_cb_t cb, void *userdata); + +int pa_pstream_is_pending(pa_pstream *p); + +void pa_pstream_use_shm(pa_pstream *p, int enable); + +void pa_pstream_unlink(pa_pstream *p); + +#endif diff --git a/src/pulsecore/queue.c b/src/pulsecore/queue.c new file mode 100644 index 00000000..9b6a37f0 --- /dev/null +++ b/src/pulsecore/queue.c @@ -0,0 +1,125 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <stdlib.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> + +#include "queue.h" + +PA_STATIC_FLIST_DECLARE(entries, 0, pa_xfree); + +struct queue_entry { + struct queue_entry *next; + void *data; +}; + +struct pa_queue { + struct queue_entry *front, *back; + unsigned length; +}; + +pa_queue* pa_queue_new(void) { + pa_queue *q = pa_xnew(pa_queue, 1); + + q->front = q->back = NULL; + q->length = 0; + + return q; +} + +void pa_queue_free(pa_queue* q, void (*destroy)(void *p, void *userdata), void *userdata) { + void *data; + pa_assert(q); + + while ((data = pa_queue_pop(q))) + if (destroy) + destroy(data, userdata); + + pa_assert(!q->front); + pa_assert(!q->back); + pa_assert(q->length == 0); + + pa_xfree(q); +} + +void pa_queue_push(pa_queue *q, void *p) { + struct queue_entry *e; + + pa_assert(q); + pa_assert(p); + + if (!(e = pa_flist_pop(PA_STATIC_FLIST_GET(entries)))) + e = pa_xnew(struct queue_entry, 1); + + e->data = p; + e->next = NULL; + + if (q->back) { + pa_assert(q->front); + q->back->next = e; + } else { + pa_assert(!q->front); + q->front = e; + } + + q->back = e; + q->length++; +} + +void* pa_queue_pop(pa_queue *q) { + void *p; + struct queue_entry *e; + pa_assert(q); + + if (!(e = q->front)) + return NULL; + + q->front = e->next; + + if (q->back == e) { + pa_assert(!e->next); + q->back = NULL; + } + + p = e->data; + + if (pa_flist_push(PA_STATIC_FLIST_GET(entries), e) < 0) + pa_xfree(e); + + q->length--; + + return p; +} + +int pa_queue_is_empty(pa_queue *q) { + pa_assert(q); + + return q->length == 0; +} diff --git a/src/pulsecore/queue.h b/src/pulsecore/queue.h new file mode 100644 index 00000000..cd767364 --- /dev/null +++ b/src/pulsecore/queue.h @@ -0,0 +1,42 @@ +#ifndef fooqueuehfoo +#define fooqueuehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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. +***/ + +typedef struct pa_queue pa_queue; + +/* A simple implementation of the abstract data type queue. Stores + * pointers as members. The memory has to be managed by the caller. */ + +pa_queue* pa_queue_new(void); + +/* Free the queue and run the specified callback function for every remaining entry. The callback function may be NULL. */ +void pa_queue_free(pa_queue* q, void (*destroy)(void *p, void *userdata), void *userdata); + +void pa_queue_push(pa_queue *q, void *p); +void* pa_queue_pop(pa_queue *q); + +int pa_queue_is_empty(pa_queue *q); + +#endif diff --git a/src/pulsecore/random.c b/src/pulsecore/random.c new file mode 100644 index 00000000..87afebfa --- /dev/null +++ b/src/pulsecore/random.c @@ -0,0 +1,114 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <fcntl.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "random.h" + +static int has_whined = 0; + +static const char * const devices[] = { "/dev/urandom", "/dev/random", NULL }; + +static int random_proper(void *ret_data, size_t length) { +#ifdef OS_IS_WIN32 + pa_assert(ret_data); + pa_assert(length > 0); + + return -1; + +#else /* OS_IS_WIN32 */ + + int fd, ret = -1; + ssize_t r = 0; + const char *const * device; + + pa_assert(ret_data); + pa_assert(length > 0); + + device = devices; + + while (*device) { + ret = 0; + + if ((fd = open(*device, O_RDONLY)) >= 0) { + + if ((r = pa_loop_read(fd, ret_data, length, NULL)) < 0 || (size_t) r != length) + ret = -1; + + pa_close(fd); + } else + ret = -1; + + if (ret == 0) + break; + } + + return ret; +#endif /* OS_IS_WIN32 */ +} + +void pa_random_seed(void) { + unsigned int seed; + + if (random_proper(&seed, sizeof(unsigned int)) < 0) { + if (!has_whined) + pa_log_warn("Failed to get proper entropy. Falling back to seeding with current time."); + has_whined = 1; + + seed = (unsigned int) time(NULL); + } + + srand(seed); +} + +void pa_random(void *ret_data, size_t length) { + uint8_t *p; + size_t l; + + pa_assert(ret_data); + pa_assert(length > 0); + + if (random_proper(ret_data, length) >= 0) + return; + + if (!has_whined) + pa_log_warn("Failed to get proper entropy. Falling back to unsecure pseudo RNG."); + has_whined = 1; + + for (p = ret_data, l = length; l > 0; p++, l--) + *p = (uint8_t) rand(); +} diff --git a/src/pulsecore/random.h b/src/pulsecore/random.h new file mode 100644 index 00000000..01b7d746 --- /dev/null +++ b/src/pulsecore/random.h @@ -0,0 +1,33 @@ +#ifndef foorandomhfoo +#define foorandomhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> + +void pa_random_seed(void); +void pa_random(void *ret_data, size_t length); + +#endif diff --git a/src/pulsecore/refcnt.h b/src/pulsecore/refcnt.h new file mode 100644 index 00000000..64271ab2 --- /dev/null +++ b/src/pulsecore/refcnt.h @@ -0,0 +1,44 @@ +#ifndef foopulserefcnthfoo +#define foopulserefcnthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <pulsecore/atomic.h> + +#define PA_REFCNT_DECLARE \ + pa_atomic_t _ref + +#define PA_REFCNT_INIT(p) \ + pa_atomic_store(&(p)->_ref, 1) + +#define PA_REFCNT_INC(p) \ + pa_atomic_inc(&(p)->_ref) + +#define PA_REFCNT_DEC(p) \ + (pa_atomic_dec(&(p)->_ref)-1) + +#define PA_REFCNT_VALUE(p) \ + pa_atomic_load(&(p)->_ref) + +#endif diff --git a/src/pulsecore/resampler.c b/src/pulsecore/resampler.c new file mode 100644 index 00000000..fe7f1ad2 --- /dev/null +++ b/src/pulsecore/resampler.c @@ -0,0 +1,1527 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> + +#if HAVE_LIBSAMPLERATE +#include <samplerate.h> +#endif + +#include <liboil/liboilfuncs.h> +#include <liboil/liboil.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/sconv.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/strbuf.h> + +#include "speexwrap.h" + +#include "ffmpeg/avcodec.h" + +#include "resampler.h" + +/* Number of samples of extra space we allow the resamplers to return */ +#define EXTRA_SAMPLES 128 + +struct pa_resampler { + pa_resample_method_t method; + pa_resample_flags_t flags; + + pa_sample_spec i_ss, o_ss; + pa_channel_map i_cm, o_cm; + size_t i_fz, o_fz, w_sz; + pa_mempool *mempool; + + pa_memchunk buf1, buf2, buf3, buf4; + unsigned buf1_samples, buf2_samples, buf3_samples, buf4_samples; + + pa_sample_format_t work_format; + + pa_convert_func_t to_work_format_func; + pa_convert_func_t from_work_format_func; + + float map_table[PA_CHANNELS_MAX][PA_CHANNELS_MAX]; + pa_bool_t map_required; + + void (*impl_free)(pa_resampler *r); + void (*impl_update_rates)(pa_resampler *r); + void (*impl_resample)(pa_resampler *r, const pa_memchunk *in, unsigned in_samples, pa_memchunk *out, unsigned *out_samples); + void (*impl_reset)(pa_resampler *r); + + struct { /* data specific to the trivial resampler */ + unsigned o_counter; + unsigned i_counter; + } trivial; + +#ifdef HAVE_LIBSAMPLERATE + struct { /* data specific to libsamplerate */ + SRC_STATE *state; + } src; +#endif + + struct { /* data specific to speex */ + SpeexResamplerState* state; + } speex; + + struct { /* data specific to ffmpeg */ + struct AVResampleContext *state; + pa_memchunk buf[PA_CHANNELS_MAX]; + } ffmpeg; +}; + +static int copy_init(pa_resampler *r); +static int trivial_init(pa_resampler*r); +static int speex_init(pa_resampler*r); +static int ffmpeg_init(pa_resampler*r); +#ifdef HAVE_LIBSAMPLERATE +static int libsamplerate_init(pa_resampler*r); +#endif + +static void calc_map_table(pa_resampler *r); + +static int (* const init_table[])(pa_resampler*r) = { +#ifdef HAVE_LIBSAMPLERATE + [PA_RESAMPLER_SRC_SINC_BEST_QUALITY] = libsamplerate_init, + [PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY] = libsamplerate_init, + [PA_RESAMPLER_SRC_SINC_FASTEST] = libsamplerate_init, + [PA_RESAMPLER_SRC_ZERO_ORDER_HOLD] = libsamplerate_init, + [PA_RESAMPLER_SRC_LINEAR] = libsamplerate_init, +#else + [PA_RESAMPLER_SRC_SINC_BEST_QUALITY] = NULL, + [PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY] = NULL, + [PA_RESAMPLER_SRC_SINC_FASTEST] = NULL, + [PA_RESAMPLER_SRC_ZERO_ORDER_HOLD] = NULL, + [PA_RESAMPLER_SRC_LINEAR] = NULL, +#endif + [PA_RESAMPLER_TRIVIAL] = trivial_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+0] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+1] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+2] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+3] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+4] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+5] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+6] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+7] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+8] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+9] = speex_init, + [PA_RESAMPLER_SPEEX_FLOAT_BASE+10] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+0] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+1] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+2] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+3] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+4] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+5] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+6] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+7] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+8] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+9] = speex_init, + [PA_RESAMPLER_SPEEX_FIXED_BASE+10] = speex_init, + [PA_RESAMPLER_FFMPEG] = ffmpeg_init, + [PA_RESAMPLER_AUTO] = NULL, + [PA_RESAMPLER_COPY] = copy_init +}; + +static inline size_t sample_size(pa_sample_format_t f) { + pa_sample_spec ss = { + .format = f, + .rate = 0, + .channels = 1 + }; + + return pa_sample_size(&ss); +} + +pa_resampler* pa_resampler_new( + pa_mempool *pool, + const pa_sample_spec *a, + const pa_channel_map *am, + const pa_sample_spec *b, + const pa_channel_map *bm, + pa_resample_method_t method, + pa_resample_flags_t flags) { + + pa_resampler *r = NULL; + + pa_assert(pool); + pa_assert(a); + pa_assert(b); + pa_assert(pa_sample_spec_valid(a)); + pa_assert(pa_sample_spec_valid(b)); + pa_assert(method >= 0); + pa_assert(method < PA_RESAMPLER_MAX); + + /* Fix method */ + + if (!(flags & PA_RESAMPLER_VARIABLE_RATE) && a->rate == b->rate) { + pa_log_info("Forcing resampler 'copy', because of fixed, identical sample rates."); + method = PA_RESAMPLER_COPY; + } + + if (!pa_resample_method_supported(method)) { + pa_log_warn("Support for resampler '%s' not compiled in, reverting to 'auto'.", pa_resample_method_to_string(method)); + method = PA_RESAMPLER_AUTO; + } + + if (method == PA_RESAMPLER_FFMPEG && (flags & PA_RESAMPLER_VARIABLE_RATE)) { + pa_log_info("Resampler 'ffmpeg' cannot do variable rate, reverting to resampler 'auto'."); + method = PA_RESAMPLER_AUTO; + } + + if (method == PA_RESAMPLER_COPY && ((flags & PA_RESAMPLER_VARIABLE_RATE) || a->rate != b->rate)) { + pa_log_info("Resampler 'copy' cannot change sampling rate, reverting to resampler 'auto'."); + method = PA_RESAMPLER_AUTO; + } + + if (method == PA_RESAMPLER_AUTO) + method = PA_RESAMPLER_SPEEX_FLOAT_BASE + 3; + + r = pa_xnew(pa_resampler, 1); + r->mempool = pool; + r->method = method; + r->flags = flags; + + r->impl_free = NULL; + r->impl_update_rates = NULL; + r->impl_resample = NULL; + r->impl_reset = NULL; + + /* Fill sample specs */ + r->i_ss = *a; + r->o_ss = *b; + + if (am) + r->i_cm = *am; + else if (!pa_channel_map_init_auto(&r->i_cm, r->i_ss.channels, PA_CHANNEL_MAP_DEFAULT)) + goto fail; + + if (bm) + r->o_cm = *bm; + else if (!pa_channel_map_init_auto(&r->o_cm, r->o_ss.channels, PA_CHANNEL_MAP_DEFAULT)) + goto fail; + + r->i_fz = pa_frame_size(a); + r->o_fz = pa_frame_size(b); + + pa_memchunk_reset(&r->buf1); + pa_memchunk_reset(&r->buf2); + pa_memchunk_reset(&r->buf3); + pa_memchunk_reset(&r->buf4); + + r->buf1_samples = r->buf2_samples = r->buf3_samples = r->buf4_samples = 0; + + calc_map_table(r); + + pa_log_info("Using resampler '%s'", pa_resample_method_to_string(method)); + + if ((method >= PA_RESAMPLER_SPEEX_FIXED_BASE && method <= PA_RESAMPLER_SPEEX_FIXED_MAX) || + (method == PA_RESAMPLER_FFMPEG)) + r->work_format = PA_SAMPLE_S16NE; + else if (method == PA_RESAMPLER_TRIVIAL || method == PA_RESAMPLER_COPY) { + + if (r->map_required || a->format != b->format) { + + if (a->format == PA_SAMPLE_S32NE || a->format == PA_SAMPLE_S32RE || + a->format == PA_SAMPLE_FLOAT32NE || a->format == PA_SAMPLE_FLOAT32RE || + b->format == PA_SAMPLE_S32NE || b->format == PA_SAMPLE_S32RE || + b->format == PA_SAMPLE_FLOAT32NE || b->format == PA_SAMPLE_FLOAT32RE) + r->work_format = PA_SAMPLE_FLOAT32NE; + else + r->work_format = PA_SAMPLE_S16NE; + + } else + r->work_format = a->format; + + } else + r->work_format = PA_SAMPLE_FLOAT32NE; + + pa_log_info("Using %s as working format.", pa_sample_format_to_string(r->work_format)); + + r->w_sz = sample_size(r->work_format); + + if (r->i_ss.format == r->work_format) + r->to_work_format_func = NULL; + else if (r->work_format == PA_SAMPLE_FLOAT32NE) { + if (!(r->to_work_format_func = pa_get_convert_to_float32ne_function(r->i_ss.format))) + goto fail; + } else { + pa_assert(r->work_format == PA_SAMPLE_S16NE); + if (!(r->to_work_format_func = pa_get_convert_to_s16ne_function(r->i_ss.format))) + goto fail; + } + + if (r->o_ss.format == r->work_format) + r->from_work_format_func = NULL; + else if (r->work_format == PA_SAMPLE_FLOAT32NE) { + if (!(r->from_work_format_func = pa_get_convert_from_float32ne_function(r->o_ss.format))) + goto fail; + } else { + pa_assert(r->work_format == PA_SAMPLE_S16NE); + if (!(r->from_work_format_func = pa_get_convert_from_s16ne_function(r->o_ss.format))) + goto fail; + } + + /* initialize implementation */ + if (init_table[method](r) < 0) + goto fail; + + return r; + +fail: + if (r) + pa_xfree(r); + + return NULL; +} + +void pa_resampler_free(pa_resampler *r) { + pa_assert(r); + + if (r->impl_free) + r->impl_free(r); + + if (r->buf1.memblock) + pa_memblock_unref(r->buf1.memblock); + if (r->buf2.memblock) + pa_memblock_unref(r->buf2.memblock); + if (r->buf3.memblock) + pa_memblock_unref(r->buf3.memblock); + if (r->buf4.memblock) + pa_memblock_unref(r->buf4.memblock); + + pa_xfree(r); +} + +void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate) { + pa_assert(r); + pa_assert(rate > 0); + + if (r->i_ss.rate == rate) + return; + + r->i_ss.rate = rate; + + r->impl_update_rates(r); +} + +void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate) { + pa_assert(r); + pa_assert(rate > 0); + + if (r->o_ss.rate == rate) + return; + + r->o_ss.rate = rate; + + r->impl_update_rates(r); +} + +size_t pa_resampler_request(pa_resampler *r, size_t out_length) { + pa_assert(r); + + return (((out_length / r->o_fz)*r->i_ss.rate)/r->o_ss.rate) * r->i_fz; +} + +size_t pa_resampler_max_block_size(pa_resampler *r) { + size_t block_size_max; + pa_sample_spec ss; + size_t fs; + + pa_assert(r); + + block_size_max = pa_mempool_block_size_max(r->mempool); + + /* We deduce the "largest" sample spec we're using during the + * conversion */ + ss = r->i_ss; + if (r->o_ss.channels > ss.channels) + ss.channels = r->o_ss.channels; + + /* We silently assume that the format enum is ordered by size */ + if (r->o_ss.format > ss.format) + ss.format = r->o_ss.format; + if (r->work_format > ss.format) + ss.format = r->work_format; + + if (r->o_ss.rate > ss.rate) + ss.rate = r->o_ss.rate; + + fs = pa_frame_size(&ss); + + return (((block_size_max/fs + EXTRA_SAMPLES)*r->i_ss.rate)/ss.rate)*r->i_fz; +} + +void pa_resampler_reset(pa_resampler *r) { + pa_assert(r); + + if (r->impl_reset) + r->impl_reset(r); +} + +pa_resample_method_t pa_resampler_get_method(pa_resampler *r) { + pa_assert(r); + + return r->method; +} + +static const char * const resample_methods[] = { + "src-sinc-best-quality", + "src-sinc-medium-quality", + "src-sinc-fastest", + "src-zero-order-hold", + "src-linear", + "trivial", + "speex-float-0", + "speex-float-1", + "speex-float-2", + "speex-float-3", + "speex-float-4", + "speex-float-5", + "speex-float-6", + "speex-float-7", + "speex-float-8", + "speex-float-9", + "speex-float-10", + "speex-fixed-0", + "speex-fixed-1", + "speex-fixed-2", + "speex-fixed-3", + "speex-fixed-4", + "speex-fixed-5", + "speex-fixed-6", + "speex-fixed-7", + "speex-fixed-8", + "speex-fixed-9", + "speex-fixed-10", + "ffmpeg", + "auto", + "copy" +}; + +const char *pa_resample_method_to_string(pa_resample_method_t m) { + + if (m < 0 || m >= PA_RESAMPLER_MAX) + return NULL; + + return resample_methods[m]; +} + +int pa_resample_method_supported(pa_resample_method_t m) { + + if (m < 0 || m >= PA_RESAMPLER_MAX) + return 0; + +#ifndef HAVE_LIBSAMPLERATE + if (m <= PA_RESAMPLER_SRC_LINEAR) + return 0; +#endif + + return 1; +} + +pa_resample_method_t pa_parse_resample_method(const char *string) { + pa_resample_method_t m; + + pa_assert(string); + + for (m = 0; m < PA_RESAMPLER_MAX; m++) + if (!strcmp(string, resample_methods[m])) + return m; + + if (!strcmp(string, "speex-fixed")) + return PA_RESAMPLER_SPEEX_FIXED_BASE + 3; + + if (!strcmp(string, "speex-float")) + return PA_RESAMPLER_SPEEX_FLOAT_BASE + 3; + + return PA_RESAMPLER_INVALID; +} + +static pa_bool_t on_left(pa_channel_position_t p) { + + return + p == PA_CHANNEL_POSITION_FRONT_LEFT || + p == PA_CHANNEL_POSITION_REAR_LEFT || + p == PA_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER || + p == PA_CHANNEL_POSITION_SIDE_LEFT || + p == PA_CHANNEL_POSITION_TOP_FRONT_LEFT || + p == PA_CHANNEL_POSITION_TOP_REAR_LEFT; +} + +static pa_bool_t on_right(pa_channel_position_t p) { + + return + p == PA_CHANNEL_POSITION_FRONT_RIGHT || + p == PA_CHANNEL_POSITION_REAR_RIGHT || + p == PA_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER || + p == PA_CHANNEL_POSITION_SIDE_RIGHT || + p == PA_CHANNEL_POSITION_TOP_FRONT_RIGHT || + p == PA_CHANNEL_POSITION_TOP_REAR_RIGHT; +} + +static pa_bool_t on_center(pa_channel_position_t p) { + + return + p == PA_CHANNEL_POSITION_FRONT_CENTER || + p == PA_CHANNEL_POSITION_REAR_CENTER || + p == PA_CHANNEL_POSITION_TOP_CENTER || + p == PA_CHANNEL_POSITION_TOP_FRONT_CENTER || + p == PA_CHANNEL_POSITION_TOP_REAR_CENTER; +} + +static pa_bool_t on_lfe(pa_channel_position_t p) { + return + p == PA_CHANNEL_POSITION_LFE; +} + +static void calc_map_table(pa_resampler *r) { + unsigned oc, ic; + pa_bool_t ic_connected[PA_CHANNELS_MAX]; + pa_bool_t remix; + pa_strbuf *s; + char *t; + + pa_assert(r); + + if (!(r->map_required = (r->i_ss.channels != r->o_ss.channels || (!(r->flags & PA_RESAMPLER_NO_REMAP) && !pa_channel_map_equal(&r->i_cm, &r->o_cm))))) + return; + + memset(r->map_table, 0, sizeof(r->map_table)); + memset(ic_connected, 0, sizeof(ic_connected)); + remix = (r->flags & (PA_RESAMPLER_NO_REMAP|PA_RESAMPLER_NO_REMIX)) == 0; + + for (oc = 0; oc < r->o_ss.channels; oc++) { + pa_bool_t oc_connected = FALSE; + pa_channel_position_t b = r->o_cm.map[oc]; + + for (ic = 0; ic < r->i_ss.channels; ic++) { + pa_channel_position_t a = r->i_cm.map[ic]; + + if (r->flags & PA_RESAMPLER_NO_REMAP) { + /* We shall not do any remapping. Hence, just check by index */ + + if (ic == oc) + r->map_table[oc][ic] = 1.0; + + continue; + } + + if (r->flags & PA_RESAMPLER_NO_REMIX) { + /* We shall not do any remixing. Hence, just check by name */ + + if (a == b) + r->map_table[oc][ic] = 1.0; + + continue; + } + + pa_assert(remix); + + /* OK, we shall do the full monty: upmixing and + * downmixing. Our algorithm is relatively simple, does + * not do spacialization, delay elements or apply lowpass + * filters for LFE. Patches are always welcome, + * though. Oh, and it doesn't do any matrix + * decoding. (Which probably wouldn't make any sense + * anyway.) + * + * This code is not idempotent: downmixing an upmixed + * stereo stream is not identical to the original. The + * volume will not match, and the two channels will be a + * linear combination of both. + * + * This is losely based on random suggestions found on the + * Internet, such as this: + * http://www.halfgaar.net/surround-sound-in-linux and the + * alsa upmix plugin. + * + * The algorithm works basically like this: + * + * 1) Connect all channels with matching names. + * + * 2) Mono Handling: + * S:Mono: Copy into all D:channels + * D:Mono: Copy in all S:channels + * + * 3) Mix D:Left, D:Right: + * D:Left: If not connected, avg all S:Left + * D:Right: If not connected, avg all S:Right + * + * 4) Mix D:Center + * If not connected, avg all S:Center + * If still not connected, avg all S:Left, S:Right + * + * 5) Mix D:LFE + * If not connected, avg all S:* + * + * 6) Make sure S:Left/S:Right is used: S:Left/S:Right: If + * not connected, mix into all D:left and all D:right + * channels. Gain is 0.1, the current left and right + * should be multiplied by 0.9. + * + * 7) Make sure S:Center, S:LFE is used: + * + * S:Center, S:LFE: If not connected, mix into all + * D:left, all D:right, all D:center channels, gain is + * 0.375. The current (as result of 1..6) factors + * should be multiplied by 0.75. (Alt. suggestion: 0.25 + * vs. 0.5) + * + * S: and D: shall relate to the source resp. destination channels. + * + * Rationale: 1, 2 are probably obvious. For 3: this + * copies front to rear if needed. For 4: we try to find + * some suitable C source for C, if we don't find any, we + * avg L and R. For 5: LFE is mixed from all channels. For + * 6: the rear channels should not be dropped entirely, + * however have only minimal impact. For 7: movies usually + * encode speech on the center channel. Thus we have to + * make sure this channel is distributed to L and R if not + * available in the output. Also, LFE is used to achieve a + * greater dynamic range, and thus we should try to do our + * best to pass it to L+R. + */ + + if (a == b || a == PA_CHANNEL_POSITION_MONO || b == PA_CHANNEL_POSITION_MONO) { + r->map_table[oc][ic] = 1.0; + + oc_connected = TRUE; + ic_connected[ic] = TRUE; + } + } + + if (!oc_connected && remix) { + /* OK, we shall remix */ + + if (on_left(b)) { + unsigned n = 0; + + /* We are not connected and on the left side, let's + * average all left side input channels. */ + + for (ic = 0; ic < r->i_ss.channels; ic++) + if (on_left(r->i_cm.map[ic])) + n++; + + if (n > 0) + for (ic = 0; ic < r->i_ss.channels; ic++) + if (on_left(r->i_cm.map[ic])) { + r->map_table[oc][ic] = 1.0 / n; + ic_connected[ic] = TRUE; + } + + /* We ignore the case where there is no left input + * channel. Something is really wrong in this case + * anyway. */ + + } else if (on_right(b)) { + unsigned n = 0; + + /* We are not connected and on the right side, let's + * average all right side input channels. */ + + for (ic = 0; ic < r->i_ss.channels; ic++) + if (on_right(r->i_cm.map[ic])) + n++; + + if (n > 0) + for (ic = 0; ic < r->i_ss.channels; ic++) + if (on_right(r->i_cm.map[ic])) { + r->map_table[oc][ic] = 1.0 / n; + ic_connected[ic] = TRUE; + } + + /* We ignore the case where there is no right input + * channel. Something is really wrong in this case + * anyway. */ + + } else if (on_center(b)) { + unsigned n = 0; + + /* We are not connected and at the center. Let's + * average all center input channels. */ + + for (ic = 0; ic < r->i_ss.channels; ic++) + if (on_center(r->i_cm.map[ic])) + n++; + + if (n > 0) { + for (ic = 0; ic < r->i_ss.channels; ic++) + if (on_center(r->i_cm.map[ic])) { + r->map_table[oc][ic] = 1.0 / n; + ic_connected[ic] = TRUE; + } + } else { + + /* Hmm, no center channel around, let's synthesize + * it by mixing L and R.*/ + + n = 0; + + for (ic = 0; ic < r->i_ss.channels; ic++) + if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) + n++; + + if (n > 0) + for (ic = 0; ic < r->i_ss.channels; ic++) + if (on_left(r->i_cm.map[ic]) || on_right(r->i_cm.map[ic])) { + r->map_table[oc][ic] = 1.0 / n; + ic_connected[ic] = TRUE; + } + + /* We ignore the case where there is not even a + * left or right input channel. Something is + * really wrong in this case anyway. */ + } + + } else if (on_lfe(b)) { + + /* We are not connected and an LFE. Let's average all + * channels for LFE. */ + + for (ic = 0; ic < r->i_ss.channels; ic++) { + r->map_table[oc][ic] = 1.0 / r->i_ss.channels; + + /* Please note that a channel connected to LFE + * doesn't really count as connected. */ + } + } + } + } + + if (remix) { + unsigned + ic_unconnected_left = 0, + ic_unconnected_right = 0, + ic_unconnected_center = 0, + ic_unconnected_lfe = 0; + + for (ic = 0; ic < r->i_ss.channels; ic++) { + pa_channel_position_t a = r->i_cm.map[ic]; + + if (ic_connected[ic]) + continue; + + if (on_left(a)) + ic_unconnected_left++; + else if (on_right(a)) + ic_unconnected_right++; + else if (on_center(a)) + ic_unconnected_center++; + else if (on_lfe(a)) + ic_unconnected_lfe++; + } + + if (ic_unconnected_left > 0) { + + /* OK, so there are unconnected input channels on the + * left. Let's multiply all already connected channels on + * the left side by .9 and add in our averaged unconnected + * channels multplied by .1 */ + + for (oc = 0; oc < r->o_ss.channels; oc++) { + + if (!on_left(r->o_cm.map[oc])) + continue; + + for (ic = 0; ic < r->i_ss.channels; ic++) { + + if (ic_connected[ic]) { + r->map_table[oc][ic] *= .9; + continue; + } + + if (on_left(r->i_cm.map[ic])) + r->map_table[oc][ic] = .1 / ic_unconnected_left; + } + } + } + + if (ic_unconnected_right > 0) { + + /* OK, so there are unconnected input channels on the + * right. Let's multiply all already connected channels on + * the right side by .9 and add in our averaged unconnected + * channels multplied by .1 */ + + for (oc = 0; oc < r->o_ss.channels; oc++) { + + if (!on_right(r->o_cm.map[oc])) + continue; + + for (ic = 0; ic < r->i_ss.channels; ic++) { + + if (ic_connected[ic]) { + r->map_table[oc][ic] *= .9; + continue; + } + + if (on_right(r->i_cm.map[ic])) + r->map_table[oc][ic] = .1 / ic_unconnected_right; + } + } + } + + if (ic_unconnected_center > 0) { + pa_bool_t mixed_in = FALSE; + + /* OK, so there are unconnected input channels on the + * center. Let's multiply all already connected channels on + * the center side by .9 and add in our averaged unconnected + * channels multplied by .1 */ + + for (oc = 0; oc < r->o_ss.channels; oc++) { + + if (!on_center(r->o_cm.map[oc])) + continue; + + for (ic = 0; ic < r->i_ss.channels; ic++) { + + if (ic_connected[ic]) { + r->map_table[oc][ic] *= .9; + continue; + } + + if (on_center(r->i_cm.map[ic])) { + r->map_table[oc][ic] = .1 / ic_unconnected_center; + mixed_in = TRUE; + } + } + } + + if (!mixed_in) { + + /* Hmm, as it appears there was no center channel we + could mix our center channel in. In this case, mix + it into left and right. Using .375 and 0.75 as + factors. */ + + for (oc = 0; oc < r->o_ss.channels; oc++) { + + if (!on_left(r->o_cm.map[oc]) && !on_right(r->o_cm.map[oc])) + continue; + + for (ic = 0; ic < r->i_ss.channels; ic++) { + + if (ic_connected[ic]) { + r->map_table[oc][ic] *= .75; + continue; + } + + if (on_center(r->i_cm.map[ic])) + r->map_table[oc][ic] = .375 / ic_unconnected_center; + } + } + } + } + + if (ic_unconnected_lfe > 0) { + + /* OK, so there is an unconnected LFE channel. Let's mix + * it into all channels, with factor 0.375 */ + + for (ic = 0; ic < r->i_ss.channels; ic++) { + + if (!on_lfe(r->i_cm.map[ic])) + continue; + + for (oc = 0; oc < r->o_ss.channels; oc++) + r->map_table[oc][ic] = 0.375 / ic_unconnected_lfe; + } + } + } + + + s = pa_strbuf_new(); + + pa_strbuf_printf(s, " "); + for (ic = 0; ic < r->i_ss.channels; ic++) + pa_strbuf_printf(s, " I%02u ", ic); + pa_strbuf_puts(s, "\n +"); + + for (ic = 0; ic < r->i_ss.channels; ic++) + pa_strbuf_printf(s, "------"); + pa_strbuf_puts(s, "\n"); + + for (oc = 0; oc < r->o_ss.channels; oc++) { + pa_strbuf_printf(s, "O%02u |", oc); + + for (ic = 0; ic < r->i_ss.channels; ic++) + pa_strbuf_printf(s, " %1.3f", r->map_table[oc][ic]); + + pa_strbuf_puts(s, "\n"); + } + + pa_log_debug("Channel matrix:\n%s", t = pa_strbuf_tostring_free(s)); + pa_xfree(t); +} + +static pa_memchunk* convert_to_work_format(pa_resampler *r, pa_memchunk *input) { + unsigned n_samples; + void *src, *dst; + + pa_assert(r); + pa_assert(input); + pa_assert(input->memblock); + + /* Convert the incoming sample into the work sample format and place them in buf1 */ + + if (!r->to_work_format_func || !input->length) + return input; + + n_samples = (input->length / r->i_fz) * r->i_ss.channels; + + r->buf1.index = 0; + r->buf1.length = r->w_sz * n_samples; + + if (!r->buf1.memblock || r->buf1_samples < n_samples) { + if (r->buf1.memblock) + pa_memblock_unref(r->buf1.memblock); + + r->buf1_samples = n_samples; + r->buf1.memblock = pa_memblock_new(r->mempool, r->buf1.length); + } + + src = (uint8_t*) pa_memblock_acquire(input->memblock) + input->index; + dst = (uint8_t*) pa_memblock_acquire(r->buf1.memblock); + + r->to_work_format_func(n_samples, src, dst); + + pa_memblock_release(input->memblock); + pa_memblock_release(r->buf1.memblock); + + return &r->buf1; +} + +static void vectoradd_s16_with_fraction( + int16_t *d, int dstr, + const int16_t *s1, int sstr1, + const int16_t *s2, int sstr2, + int n, + float s3, float s4) { + + int32_t i3, i4; + + i3 = (int32_t) (s3 * 0x10000); + i4 = (int32_t) (s4 * 0x10000); + + for (; n > 0; n--) { + int32_t a, b; + + a = *s1; + b = *s2; + + a = (a * i3) / 0x10000; + b = (b * i4) / 0x10000; + + *d = (int16_t) (a + b); + + s1 = (const int16_t*) ((const uint8_t*) s1 + sstr1); + s2 = (const int16_t*) ((const uint8_t*) s2 + sstr2); + d = (int16_t*) ((uint8_t*) d + dstr); + + } +} + +static pa_memchunk *remap_channels(pa_resampler *r, pa_memchunk *input) { + unsigned in_n_samples, out_n_samples, n_frames; + int i_skip, o_skip; + unsigned oc; + void *src, *dst; + + pa_assert(r); + pa_assert(input); + pa_assert(input->memblock); + + /* Remap channels and place the result int buf2 */ + + if (!r->map_required || !input->length) + return input; + + in_n_samples = input->length / r->w_sz; + n_frames = in_n_samples / r->i_ss.channels; + out_n_samples = n_frames * r->o_ss.channels; + + r->buf2.index = 0; + r->buf2.length = r->w_sz * out_n_samples; + + if (!r->buf2.memblock || r->buf2_samples < out_n_samples) { + if (r->buf2.memblock) + pa_memblock_unref(r->buf2.memblock); + + r->buf2_samples = out_n_samples; + r->buf2.memblock = pa_memblock_new(r->mempool, r->buf2.length); + } + + src = ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index); + dst = pa_memblock_acquire(r->buf2.memblock); + + memset(dst, 0, r->buf2.length); + + o_skip = r->w_sz * r->o_ss.channels; + i_skip = r->w_sz * r->i_ss.channels; + + switch (r->work_format) { + case PA_SAMPLE_FLOAT32NE: + + for (oc = 0; oc < r->o_ss.channels; oc++) { + unsigned ic; + static const float one = 1.0; + + for (ic = 0; ic < r->i_ss.channels; ic++) { + + if (r->map_table[oc][ic] <= 0.0) + continue; + + oil_vectoradd_f32( + (float*) dst + oc, o_skip, + (float*) dst + oc, o_skip, + (float*) src + ic, i_skip, + n_frames, + &one, &r->map_table[oc][ic]); + } + } + + break; + + case PA_SAMPLE_S16NE: + + for (oc = 0; oc < r->o_ss.channels; oc++) { + unsigned ic; + + for (ic = 0; ic < r->i_ss.channels; ic++) { + + if (r->map_table[oc][ic] <= 0.0) + continue; + + if (r->map_table[oc][ic] >= 1.0) { + static const int16_t one = 1; + + oil_vectoradd_s16( + (int16_t*) dst + oc, o_skip, + (int16_t*) dst + oc, o_skip, + (int16_t*) src + ic, i_skip, + n_frames, + &one, &one); + + } else + + vectoradd_s16_with_fraction( + (int16_t*) dst + oc, o_skip, + (int16_t*) dst + oc, o_skip, + (int16_t*) src + ic, i_skip, + n_frames, + 1.0, r->map_table[oc][ic]); + } + } + + break; + + default: + pa_assert_not_reached(); + } + + pa_memblock_release(input->memblock); + pa_memblock_release(r->buf2.memblock); + + r->buf2.length = out_n_samples * r->w_sz; + + return &r->buf2; +} + +static pa_memchunk *resample(pa_resampler *r, pa_memchunk *input) { + unsigned in_n_frames, in_n_samples; + unsigned out_n_frames, out_n_samples; + + pa_assert(r); + pa_assert(input); + + /* Resample the data and place the result in buf3 */ + + if (!r->impl_resample || !input->length) + return input; + + in_n_samples = input->length / r->w_sz; + in_n_frames = in_n_samples / r->o_ss.channels; + + out_n_frames = ((in_n_frames*r->o_ss.rate)/r->i_ss.rate)+EXTRA_SAMPLES; + out_n_samples = out_n_frames * r->o_ss.channels; + + r->buf3.index = 0; + r->buf3.length = r->w_sz * out_n_samples; + + if (!r->buf3.memblock || r->buf3_samples < out_n_samples) { + if (r->buf3.memblock) + pa_memblock_unref(r->buf3.memblock); + + r->buf3_samples = out_n_samples; + r->buf3.memblock = pa_memblock_new(r->mempool, r->buf3.length); + } + + r->impl_resample(r, input, in_n_frames, &r->buf3, &out_n_frames); + r->buf3.length = out_n_frames * r->w_sz * r->o_ss.channels; + + return &r->buf3; +} + +static pa_memchunk *convert_from_work_format(pa_resampler *r, pa_memchunk *input) { + unsigned n_samples, n_frames; + void *src, *dst; + + pa_assert(r); + pa_assert(input); + + /* Convert the data into the correct sample type and place the result in buf4 */ + + if (!r->from_work_format_func || !input->length) + return input; + + n_samples = input->length / r->w_sz; + n_frames = n_samples / r->o_ss.channels; + + r->buf4.index = 0; + r->buf4.length = r->o_fz * n_frames; + + if (!r->buf4.memblock || r->buf4_samples < n_samples) { + if (r->buf4.memblock) + pa_memblock_unref(r->buf4.memblock); + + r->buf4_samples = n_samples; + r->buf4.memblock = pa_memblock_new(r->mempool, r->buf4.length); + } + + src = (uint8_t*) pa_memblock_acquire(input->memblock) + input->index; + dst = pa_memblock_acquire(r->buf4.memblock); + r->from_work_format_func(n_samples, src, dst); + pa_memblock_release(input->memblock); + pa_memblock_release(r->buf4.memblock); + + r->buf4.length = r->o_fz * n_frames; + + return &r->buf4; +} + +void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out) { + pa_memchunk *buf; + + pa_assert(r); + pa_assert(in); + pa_assert(out); + pa_assert(in->length); + pa_assert(in->memblock); + pa_assert(in->length % r->i_fz == 0); + + buf = (pa_memchunk*) in; + buf = convert_to_work_format(r, buf); + buf = remap_channels(r, buf); + buf = resample(r, buf); + + if (buf->length) { + buf = convert_from_work_format(r, buf); + *out = *buf; + + if (buf == in) + pa_memblock_ref(buf->memblock); + else + pa_memchunk_reset(buf); + } else + pa_memchunk_reset(out); +} + +/*** libsamplerate based implementation ***/ + +#ifdef HAVE_LIBSAMPLERATE +static void libsamplerate_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + SRC_DATA data; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + memset(&data, 0, sizeof(data)); + + data.data_in = (float*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index); + data.input_frames = in_n_frames; + + data.data_out = (float*) ((uint8_t*) pa_memblock_acquire(output->memblock) + output->index); + data.output_frames = *out_n_frames; + + data.src_ratio = (double) r->o_ss.rate / r->i_ss.rate; + data.end_of_input = 0; + + pa_assert_se(src_process(r->src.state, &data) == 0); + pa_assert((unsigned) data.input_frames_used == in_n_frames); + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + *out_n_frames = data.output_frames_gen; +} + +static void libsamplerate_update_rates(pa_resampler *r) { + pa_assert(r); + + pa_assert_se(src_set_ratio(r->src.state, (double) r->o_ss.rate / r->i_ss.rate) == 0); +} + +static void libsamplerate_reset(pa_resampler *r) { + pa_assert(r); + + pa_assert_se(src_reset(r->src.state) == 0); +} + +static void libsamplerate_free(pa_resampler *r) { + pa_assert(r); + + if (r->src.state) + src_delete(r->src.state); +} + +static int libsamplerate_init(pa_resampler *r) { + int err; + + pa_assert(r); + + if (!(r->src.state = src_new(r->method, r->o_ss.channels, &err))) + return -1; + + r->impl_free = libsamplerate_free; + r->impl_update_rates = libsamplerate_update_rates; + r->impl_resample = libsamplerate_resample; + r->impl_reset = libsamplerate_reset; + + return 0; +} +#endif + +/*** speex based implementation ***/ + +static void speex_resample_float(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + float *in, *out; + uint32_t inf = in_n_frames, outf = *out_n_frames; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + in = (float*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index); + out = (float*) ((uint8_t*) pa_memblock_acquire(output->memblock) + output->index); + + pa_assert_se(paspfl_resampler_process_interleaved_float(r->speex.state, in, &inf, out, &outf) == 0); + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + pa_assert(inf == in_n_frames); + *out_n_frames = outf; +} + +static void speex_resample_int(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + int16_t *in, *out; + uint32_t inf = in_n_frames, outf = *out_n_frames; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + in = (int16_t*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index); + out = (int16_t*) ((uint8_t*) pa_memblock_acquire(output->memblock) + output->index); + + pa_assert_se(paspfx_resampler_process_interleaved_int(r->speex.state, in, &inf, out, &outf) == 0); + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + pa_assert(inf == in_n_frames); + *out_n_frames = outf; +} + +static void speex_update_rates(pa_resampler *r) { + pa_assert(r); + + if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX) + pa_assert_se(paspfx_resampler_set_rate(r->speex.state, r->i_ss.rate, r->o_ss.rate) == 0); + else { + pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX); + pa_assert_se(paspfl_resampler_set_rate(r->speex.state, r->i_ss.rate, r->o_ss.rate) == 0); + } +} + +static void speex_reset(pa_resampler *r) { + pa_assert(r); + + if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX) + pa_assert_se(paspfx_resampler_reset_mem(r->speex.state) == 0); + else { + pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX); + pa_assert_se(paspfl_resampler_reset_mem(r->speex.state) == 0); + } +} + +static void speex_free(pa_resampler *r) { + pa_assert(r); + + if (!r->speex.state) + return; + + if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX) + paspfx_resampler_destroy(r->speex.state); + else { + pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX); + paspfl_resampler_destroy(r->speex.state); + } +} + +static int speex_init(pa_resampler *r) { + int q, err; + + pa_assert(r); + + r->impl_free = speex_free; + r->impl_update_rates = speex_update_rates; + r->impl_reset = speex_reset; + + if (r->method >= PA_RESAMPLER_SPEEX_FIXED_BASE && r->method <= PA_RESAMPLER_SPEEX_FIXED_MAX) { + q = r->method - PA_RESAMPLER_SPEEX_FIXED_BASE; + + pa_log_info("Choosing speex quality setting %i.", q); + + if (!(r->speex.state = paspfx_resampler_init(r->o_ss.channels, r->i_ss.rate, r->o_ss.rate, q, &err))) + return -1; + + r->impl_resample = speex_resample_int; + } else { + pa_assert(r->method >= PA_RESAMPLER_SPEEX_FLOAT_BASE && r->method <= PA_RESAMPLER_SPEEX_FLOAT_MAX); + q = r->method - PA_RESAMPLER_SPEEX_FLOAT_BASE; + + pa_log_info("Choosing speex quality setting %i.", q); + + if (!(r->speex.state = paspfl_resampler_init(r->o_ss.channels, r->i_ss.rate, r->o_ss.rate, q, &err))) + return -1; + + r->impl_resample = speex_resample_float; + } + + return 0; +} + +/* Trivial implementation */ + +static void trivial_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + size_t fz; + unsigned o_index; + void *src, *dst; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + fz = r->w_sz * r->o_ss.channels; + + src = (uint8_t*) pa_memblock_acquire(input->memblock) + input->index; + dst = (uint8_t*) pa_memblock_acquire(output->memblock) + output->index; + + for (o_index = 0;; o_index++, r->trivial.o_counter++) { + unsigned j; + + j = ((r->trivial.o_counter * r->i_ss.rate) / r->o_ss.rate); + j = j > r->trivial.i_counter ? j - r->trivial.i_counter : 0; + + if (j >= in_n_frames) + break; + + pa_assert(o_index * fz < pa_memblock_get_length(output->memblock)); + + oil_memcpy((uint8_t*) dst + fz * o_index, + (uint8_t*) src + fz * j, fz); + } + + pa_memblock_release(input->memblock); + pa_memblock_release(output->memblock); + + *out_n_frames = o_index; + + r->trivial.i_counter += in_n_frames; + + /* Normalize counters */ + while (r->trivial.i_counter >= r->i_ss.rate) { + pa_assert(r->trivial.o_counter >= r->o_ss.rate); + + r->trivial.i_counter -= r->i_ss.rate; + r->trivial.o_counter -= r->o_ss.rate; + } +} + +static void trivial_update_rates_or_reset(pa_resampler *r) { + pa_assert(r); + + r->trivial.i_counter = 0; + r->trivial.o_counter = 0; +} + +static int trivial_init(pa_resampler*r) { + pa_assert(r); + + r->trivial.o_counter = r->trivial.i_counter = 0; + + r->impl_resample = trivial_resample; + r->impl_update_rates = trivial_update_rates_or_reset; + r->impl_reset = trivial_update_rates_or_reset; + + return 0; +} + +/*** ffmpeg based implementation ***/ + +static void ffmpeg_resample(pa_resampler *r, const pa_memchunk *input, unsigned in_n_frames, pa_memchunk *output, unsigned *out_n_frames) { + unsigned used_frames = 0, c; + + pa_assert(r); + pa_assert(input); + pa_assert(output); + pa_assert(out_n_frames); + + for (c = 0; c < r->o_ss.channels; c++) { + unsigned u; + pa_memblock *b, *w; + int16_t *p, *t, *k, *q, *s; + int consumed_frames; + unsigned in, l; + + /* Allocate a new block */ + b = pa_memblock_new(r->mempool, r->ffmpeg.buf[c].length + in_n_frames * sizeof(int16_t)); + p = pa_memblock_acquire(b); + + /* Copy the remaining data into it */ + l = r->ffmpeg.buf[c].length; + if (r->ffmpeg.buf[c].memblock) { + t = (int16_t*) ((uint8_t*) pa_memblock_acquire(r->ffmpeg.buf[c].memblock) + r->ffmpeg.buf[c].index); + memcpy(p, t, l); + pa_memblock_release(r->ffmpeg.buf[c].memblock); + pa_memblock_unref(r->ffmpeg.buf[c].memblock); + pa_memchunk_reset(&r->ffmpeg.buf[c]); + } + + /* Now append the new data, splitting up channels */ + t = ((int16_t*) ((uint8_t*) pa_memblock_acquire(input->memblock) + input->index)) + c; + k = (int16_t*) ((uint8_t*) p + l); + for (u = 0; u < in_n_frames; u++) { + *k = *t; + t += r->o_ss.channels; + k ++; + } + pa_memblock_release(input->memblock); + + /* Calculate the resulting number of frames */ + in = in_n_frames + l / sizeof(int16_t); + + /* Allocate buffer for the result */ + w = pa_memblock_new(r->mempool, *out_n_frames * sizeof(int16_t)); + q = pa_memblock_acquire(w); + + /* Now, resample */ + used_frames = av_resample(r->ffmpeg.state, + q, p, + &consumed_frames, + in, *out_n_frames, + c >= (unsigned) r->o_ss.channels-1); + + pa_memblock_release(b); + + /* Now store the remaining samples away */ + pa_assert(consumed_frames <= (int) in); + if (consumed_frames < (int) in) { + r->ffmpeg.buf[c].memblock = b; + r->ffmpeg.buf[c].index = consumed_frames * sizeof(int16_t); + r->ffmpeg.buf[c].length = (in - consumed_frames) * sizeof(int16_t); + } else + pa_memblock_unref(b); + + /* And place the results in the output buffer */ + s = (short*) ((uint8_t*) pa_memblock_acquire(output->memblock) + output->index) + c; + for (u = 0; u < used_frames; u++) { + *s = *q; + q++; + s += r->o_ss.channels; + } + pa_memblock_release(output->memblock); + pa_memblock_release(w); + pa_memblock_unref(w); + } + + *out_n_frames = used_frames; +} + +static void ffmpeg_free(pa_resampler *r) { + unsigned c; + + pa_assert(r); + + if (r->ffmpeg.state) + av_resample_close(r->ffmpeg.state); + + for (c = 0; c < PA_ELEMENTSOF(r->ffmpeg.buf); c++) + if (r->ffmpeg.buf[c].memblock) + pa_memblock_unref(r->ffmpeg.buf[c].memblock); +} + +static int ffmpeg_init(pa_resampler *r) { + unsigned c; + + pa_assert(r); + + /* We could probably implement different quality levels by + * adjusting the filter parameters here. However, ffmpeg + * internally only uses these hardcoded values, so let's use them + * here for now as well until ffmpeg makes this configurable. */ + + if (!(r->ffmpeg.state = av_resample_init(r->o_ss.rate, r->i_ss.rate, 16, 10, 0, 0.8))) + return -1; + + r->impl_free = ffmpeg_free; + r->impl_resample = ffmpeg_resample; + + for (c = 0; c < PA_ELEMENTSOF(r->ffmpeg.buf); c++) + pa_memchunk_reset(&r->ffmpeg.buf[c]); + + return 0; +} + +/*** copy (noop) implementation ***/ + +static int copy_init(pa_resampler *r) { + pa_assert(r); + + pa_assert(r->o_ss.rate == r->i_ss.rate); + + return 0; +} diff --git a/src/pulsecore/resampler.h b/src/pulsecore/resampler.h new file mode 100644 index 00000000..82d01082 --- /dev/null +++ b/src/pulsecore/resampler.h @@ -0,0 +1,100 @@ +#ifndef fooresamplerhfoo +#define fooresamplerhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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/sample.h> +#include <pulse/channelmap.h> +#include <pulsecore/memblock.h> +#include <pulsecore/memchunk.h> + +typedef struct pa_resampler pa_resampler; + +typedef enum pa_resample_method { + PA_RESAMPLER_INVALID = -1, + PA_RESAMPLER_SRC_SINC_BEST_QUALITY = 0, /* = SRC_SINC_BEST_QUALITY */ + PA_RESAMPLER_SRC_SINC_MEDIUM_QUALITY = 1, /* = SRC_SINC_MEDIUM_QUALITY */ + PA_RESAMPLER_SRC_SINC_FASTEST = 2, /* = SRC_SINC_FASTEST */ + PA_RESAMPLER_SRC_ZERO_ORDER_HOLD = 3, /* = SRC_ZERO_ORDER_HOLD */ + PA_RESAMPLER_SRC_LINEAR = 4, /* = SRC_LINEAR */ + PA_RESAMPLER_TRIVIAL, + PA_RESAMPLER_SPEEX_FLOAT_BASE, + PA_RESAMPLER_SPEEX_FLOAT_MAX = PA_RESAMPLER_SPEEX_FLOAT_BASE + 10, + PA_RESAMPLER_SPEEX_FIXED_BASE, + PA_RESAMPLER_SPEEX_FIXED_MAX = PA_RESAMPLER_SPEEX_FIXED_BASE + 10, + PA_RESAMPLER_FFMPEG, + PA_RESAMPLER_AUTO, /* automatic select based on sample format */ + PA_RESAMPLER_COPY, + PA_RESAMPLER_MAX +} pa_resample_method_t; + +typedef enum pa_resample_flags { + PA_RESAMPLER_VARIABLE_RATE = 1, + PA_RESAMPLER_NO_REMAP = 2, /* implies NO_REMIX */ + PA_RESAMPLER_NO_REMIX = 4 +} pa_resample_flags_t; + +pa_resampler* pa_resampler_new( + pa_mempool *pool, + const pa_sample_spec *a, + const pa_channel_map *am, + const pa_sample_spec *b, + const pa_channel_map *bm, + pa_resample_method_t resample_method, + pa_resample_flags_t flags); + +void pa_resampler_free(pa_resampler *r); + +/* Returns the size of an input memory block which is required to return the specified amount of output data */ +size_t pa_resampler_request(pa_resampler *r, size_t out_length); + +/* Returns the maximum size of input blocks we can process without needing bounce buffers larger than the mempool tile size. */ +size_t pa_resampler_max_block_size(pa_resampler *r); + +/* Pass the specified memory chunk to the resampler and return the newly resampled data */ +void pa_resampler_run(pa_resampler *r, const pa_memchunk *in, pa_memchunk *out); + +/* Change the input rate of the resampler object */ +void pa_resampler_set_input_rate(pa_resampler *r, uint32_t rate); + +/* Change the output rate of the resampler object */ +void pa_resampler_set_output_rate(pa_resampler *r, uint32_t rate); + +/* Reinitialize state of the resampler, possibly due to seeking or other discontinuities */ +void pa_resampler_reset(pa_resampler *r); + +/* Return the resampling method of the resampler object */ +pa_resample_method_t pa_resampler_get_method(pa_resampler *r); + +/* Try to parse the resampler method */ +pa_resample_method_t pa_parse_resample_method(const char *string); + +/* return a human readable string for the specified resampling method. Inverse of pa_parse_resample_method() */ +const char *pa_resample_method_to_string(pa_resample_method_t m); + +/* Return 1 when the specified resampling method is supported */ +int pa_resample_method_supported(pa_resample_method_t m); + + +#endif diff --git a/src/pulsecore/rtclock.c b/src/pulsecore/rtclock.c new file mode 100644 index 00000000..07d776e4 --- /dev/null +++ b/src/pulsecore/rtclock.c @@ -0,0 +1,98 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stddef.h> +#include <time.h> +#include <sys/time.h> + +#include <pulse/timeval.h> +#include <pulsecore/macro.h> + +#include "rtclock.h" + +pa_usec_t pa_rtclock_age(const struct timeval *tv) { + struct timeval now; + pa_assert(tv); + + return pa_timeval_diff(pa_rtclock_get(&now), tv); +} + +struct timeval *pa_rtclock_get(struct timeval *tv) { +#ifdef HAVE_CLOCK_GETTIME + struct timespec ts; + +#ifdef CLOCK_MONOTONIC + /* No locking or atomic ops for no_monotonic here */ + static pa_bool_t no_monotonic = FALSE; + + if (!no_monotonic) + if (clock_gettime(CLOCK_MONOTONIC, &ts) < 0) + no_monotonic = TRUE; + + if (no_monotonic) +#endif + pa_assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + + pa_assert(tv); + + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; + + return tv; + +#else /* HAVE_CLOCK_GETTIME */ + + return pa_gettimeofday(tv); + +#endif +} + +pa_bool_t pa_rtclock_hrtimer(void) { +#ifdef HAVE_CLOCK_GETTIME + struct timespec ts; + +#ifdef CLOCK_MONOTONIC + if (clock_getres(CLOCK_MONOTONIC, &ts) >= 0) + return ts.tv_sec == 0 && ts.tv_nsec <= PA_HRTIMER_THRESHOLD_USEC*1000; +#endif + + pa_assert_se(clock_getres(CLOCK_REALTIME, &ts) == 0); + return ts.tv_sec == 0 && ts.tv_nsec <= PA_HRTIMER_THRESHOLD_USEC*1000; + +#else /* HAVE_CLOCK_GETTIME */ + + return FALSE; + +#endif +} + +pa_usec_t pa_rtclock_usec(void) { + struct timeval tv; + + return pa_timeval_load(pa_rtclock_get(&tv)); +} diff --git a/src/pulsecore/rtclock.h b/src/pulsecore/rtclock.h new file mode 100644 index 00000000..f0360af3 --- /dev/null +++ b/src/pulsecore/rtclock.h @@ -0,0 +1,43 @@ +#ifndef foopulsertclockhfoo +#define foopulsertclockhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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> + +struct timeval; + +/* Something like pulse/timeval.h but based on CLOCK_MONOTONIC */ + +struct timeval *pa_rtclock_get(struct timeval *ts); + +pa_usec_t pa_rtclock_usec(void); + +pa_usec_t pa_rtclock_age(const struct timeval *tv); +pa_bool_t pa_rtclock_hrtimer(void); + +/* timer with a resolution better than this are considered high-resolution */ +#define PA_HRTIMER_THRESHOLD_USEC 10 + +#endif diff --git a/src/pulsecore/rtpoll.c b/src/pulsecore/rtpoll.c new file mode 100644 index 00000000..83008266 --- /dev/null +++ b/src/pulsecore/rtpoll.c @@ -0,0 +1,753 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> +#include <stdio.h> +#include <signal.h> +#include <string.h> +#include <errno.h> + +#ifdef __linux__ +#include <sys/utsname.h> +#endif + +#ifdef HAVE_POLL_H +#include <poll.h> +#else +#include <pulsecore/poll.h> +#endif + +#include <pulse/xmalloc.h> +#include <pulse/timeval.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/rtclock.h> +#include <pulsecore/macro.h> +#include <pulsecore/llist.h> +#include <pulsecore/rtsig.h> +#include <pulsecore/flist.h> +#include <pulsecore/core-util.h> + +#include <pulsecore/winsock.h> + +#include "rtpoll.h" + +struct pa_rtpoll { + struct pollfd *pollfd, *pollfd2; + unsigned n_pollfd_alloc, n_pollfd_used; + + pa_bool_t timer_enabled; + struct timeval next_elapse; + pa_usec_t period; + + pa_bool_t scan_for_dead; + pa_bool_t running, installed, rebuild_needed, quit; + +#ifdef HAVE_PPOLL + int rtsig; + sigset_t sigset_unblocked; + timer_t timer; +#ifdef __linux__ + pa_bool_t dont_use_ppoll; +#endif +#endif + + PA_LLIST_HEAD(pa_rtpoll_item, items); +}; + +struct pa_rtpoll_item { + pa_rtpoll *rtpoll; + pa_bool_t dead; + + pa_rtpoll_priority_t priority; + + struct pollfd *pollfd; + unsigned n_pollfd; + + int (*work_cb)(pa_rtpoll_item *i); + int (*before_cb)(pa_rtpoll_item *i); + void (*after_cb)(pa_rtpoll_item *i); + void *userdata; + + PA_LLIST_FIELDS(pa_rtpoll_item); +}; + +PA_STATIC_FLIST_DECLARE(items, 0, pa_xfree); + +static void signal_handler_noop(int s) { } + +pa_rtpoll *pa_rtpoll_new(void) { + pa_rtpoll *p; + + p = pa_xnew(pa_rtpoll, 1); + +#ifdef HAVE_PPOLL + +#ifdef __linux__ + /* ppoll is broken on Linux < 2.6.16 */ + p->dont_use_ppoll = FALSE; + + { + struct utsname u; + unsigned major, minor, micro; + + pa_assert_se(uname(&u) == 0); + + if (sscanf(u.release, "%u.%u.%u", &major, &minor, µ) != 3 || + (major < 2) || + (major == 2 && minor < 6) || + (major == 2 && minor == 6 && micro < 16)) + + p->dont_use_ppoll = TRUE; + } + +#endif + + p->rtsig = -1; + sigemptyset(&p->sigset_unblocked); + p->timer = (timer_t) -1; + +#endif + + p->n_pollfd_alloc = 32; + p->pollfd = pa_xnew(struct pollfd, p->n_pollfd_alloc); + p->pollfd2 = pa_xnew(struct pollfd, p->n_pollfd_alloc); + p->n_pollfd_used = 0; + + p->period = 0; + memset(&p->next_elapse, 0, sizeof(p->next_elapse)); + p->timer_enabled = FALSE; + + p->running = FALSE; + p->installed = FALSE; + p->scan_for_dead = FALSE; + p->rebuild_needed = FALSE; + p->quit = FALSE; + + PA_LLIST_HEAD_INIT(pa_rtpoll_item, p->items); + + return p; +} + +void pa_rtpoll_install(pa_rtpoll *p) { + pa_assert(p); + pa_assert(!p->installed); + + p->installed = 1; + +#ifdef HAVE_PPOLL +# ifdef __linux__ + if (p->dont_use_ppoll) + return; +# endif + + if ((p->rtsig = pa_rtsig_get_for_thread()) < 0) { + pa_log_warn("Failed to reserve POSIX realtime signal."); + return; + } + + pa_log_debug("Acquired POSIX realtime signal %s", pa_sig2str(p->rtsig)); + + { + sigset_t ss; + struct sigaction sa; + + pa_assert_se(sigemptyset(&ss) == 0); + pa_assert_se(sigaddset(&ss, p->rtsig) == 0); + pa_assert_se(pthread_sigmask(SIG_BLOCK, &ss, &p->sigset_unblocked) == 0); + pa_assert_se(sigdelset(&p->sigset_unblocked, p->rtsig) == 0); + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = signal_handler_noop; + pa_assert_se(sigemptyset(&sa.sa_mask) == 0); + + pa_assert_se(sigaction(p->rtsig, &sa, NULL) == 0); + + /* We never reset the signal handler. Why should we? */ + } + +#endif +} + +static void rtpoll_rebuild(pa_rtpoll *p) { + + struct pollfd *e, *t; + pa_rtpoll_item *i; + int ra = 0; + + pa_assert(p); + + p->rebuild_needed = FALSE; + + if (p->n_pollfd_used > p->n_pollfd_alloc) { + /* Hmm, we have to allocate some more space */ + p->n_pollfd_alloc = p->n_pollfd_used * 2; + p->pollfd2 = pa_xrealloc(p->pollfd2, p->n_pollfd_alloc * sizeof(struct pollfd)); + ra = 1; + } + + e = p->pollfd2; + + for (i = p->items; i; i = i->next) { + + if (i->n_pollfd > 0) { + size_t l = i->n_pollfd * sizeof(struct pollfd); + + if (i->pollfd) + memcpy(e, i->pollfd, l); + else + memset(e, 0, l); + + i->pollfd = e; + } else + i->pollfd = NULL; + + e += i->n_pollfd; + } + + pa_assert((unsigned) (e - p->pollfd2) == p->n_pollfd_used); + t = p->pollfd; + p->pollfd = p->pollfd2; + p->pollfd2 = t; + + if (ra) + p->pollfd2 = pa_xrealloc(p->pollfd2, p->n_pollfd_alloc * sizeof(struct pollfd)); + +} + +static void rtpoll_item_destroy(pa_rtpoll_item *i) { + pa_rtpoll *p; + + pa_assert(i); + + p = i->rtpoll; + + PA_LLIST_REMOVE(pa_rtpoll_item, p->items, i); + + p->n_pollfd_used -= i->n_pollfd; + + if (pa_flist_push(PA_STATIC_FLIST_GET(items), i) < 0) + pa_xfree(i); + + p->rebuild_needed = TRUE; +} + +void pa_rtpoll_free(pa_rtpoll *p) { + pa_assert(p); + + while (p->items) + rtpoll_item_destroy(p->items); + + pa_xfree(p->pollfd); + pa_xfree(p->pollfd2); + +#ifdef HAVE_PPOLL + if (p->timer != (timer_t) -1) + timer_delete(p->timer); +#endif + + pa_xfree(p); +} + +static void reset_revents(pa_rtpoll_item *i) { + struct pollfd *f; + unsigned n; + + pa_assert(i); + + if (!(f = pa_rtpoll_item_get_pollfd(i, &n))) + return; + + for (; n > 0; n--) + f[n-1].revents = 0; +} + +static void reset_all_revents(pa_rtpoll *p) { + pa_rtpoll_item *i; + + pa_assert(p); + + for (i = p->items; i; i = i->next) { + + if (i->dead) + continue; + + reset_revents(i); + } +} + +int pa_rtpoll_run(pa_rtpoll *p, pa_bool_t wait) { + pa_rtpoll_item *i; + int r = 0; + struct timeval timeout; + + pa_assert(p); + pa_assert(!p->running); + pa_assert(p->installed); + + p->running = TRUE; + + /* First, let's do some work */ + for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { + int k; + + if (i->dead) + continue; + + if (!i->work_cb) + continue; + + if (p->quit) + goto finish; + + if ((k = i->work_cb(i)) != 0) { + if (k < 0) + r = k; + + goto finish; + } + } + + /* Now let's prepare for entering the sleep */ + for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { + int k = 0; + + if (i->dead) + continue; + + if (!i->before_cb) + continue; + + if (p->quit || (k = i->before_cb(i)) != 0) { + + /* Hmm, this one doesn't let us enter the poll, so rewind everything */ + + for (i = i->prev; i; i = i->prev) { + + if (i->dead) + continue; + + if (!i->after_cb) + continue; + + i->after_cb(i); + } + + if (k < 0) + r = k; + + goto finish; + } + } + + if (p->rebuild_needed) + rtpoll_rebuild(p); + + /* Calculate timeout */ + if (!wait || p->quit) { + timeout.tv_sec = 0; + timeout.tv_usec = 0; + } else if (p->timer_enabled) { + struct timeval now; + pa_rtclock_get(&now); + + memset(&timeout, 0, sizeof(timeout)); + if (pa_timeval_cmp(&p->next_elapse, &now) > 0) + pa_timeval_add(&timeout, pa_timeval_diff(&p->next_elapse, &now)); + } + + /* OK, now let's sleep */ +#ifdef HAVE_PPOLL + +#ifdef __linux__ + if (!p->dont_use_ppoll) +#endif + { + struct timespec ts; + ts.tv_sec = timeout.tv_sec; + ts.tv_nsec = timeout.tv_usec * 1000; + r = ppoll(p->pollfd, p->n_pollfd_used, p->timer_enabled ? &ts : NULL, p->rtsig < 0 ? NULL : &p->sigset_unblocked); + } +#ifdef __linux__ + else +#endif + +#endif + r = poll(p->pollfd, p->n_pollfd_used, p->timer_enabled ? (timeout.tv_sec*1000) + (timeout.tv_usec / 1000) : -1); + + if (r < 0) { + if (errno == EAGAIN || errno == EINTR) + r = 0; + else + pa_log_error("poll(): %s", pa_cstrerror(errno)); + + reset_all_revents(p); + } + + if (p->timer_enabled) { + if (p->period > 0) { + struct timeval now; + pa_rtclock_get(&now); + + pa_timeval_add(&p->next_elapse, p->period); + + /* Guarantee that the next timeout will happen in the future */ + if (pa_timeval_cmp(&p->next_elapse, &now) < 0) + pa_timeval_add(&p->next_elapse, (pa_timeval_diff(&now, &p->next_elapse) / p->period + 1) * p->period); + + } else + p->timer_enabled = FALSE; + } + + /* Let's tell everyone that we left the sleep */ + for (i = p->items; i && i->priority < PA_RTPOLL_NEVER; i = i->next) { + + if (i->dead) + continue; + + if (!i->after_cb) + continue; + + i->after_cb(i); + } + +finish: + + p->running = FALSE; + + if (p->scan_for_dead) { + pa_rtpoll_item *n; + + p->scan_for_dead = FALSE; + + for (i = p->items; i; i = n) { + n = i->next; + + if (i->dead) + rtpoll_item_destroy(i); + } + } + + return r < 0 ? r : !p->quit; +} + +static void update_timer(pa_rtpoll *p) { + pa_assert(p); + +#ifdef HAVE_PPOLL + +#ifdef __linux__ + if (!p->dont_use_ppoll) { +#endif + + if (p->timer == (timer_t) -1) { + struct sigevent se; + + memset(&se, 0, sizeof(se)); + se.sigev_notify = SIGEV_SIGNAL; + se.sigev_signo = p->rtsig; + + if (timer_create(CLOCK_MONOTONIC, &se, &p->timer) < 0) + if (timer_create(CLOCK_REALTIME, &se, &p->timer) < 0) { + pa_log_warn("Failed to allocate POSIX timer: %s", pa_cstrerror(errno)); + p->timer = (timer_t) -1; + } + } + + if (p->timer != (timer_t) -1) { + struct itimerspec its; + memset(&its, 0, sizeof(its)); + + if (p->timer_enabled) { + its.it_value.tv_sec = p->next_elapse.tv_sec; + its.it_value.tv_nsec = p->next_elapse.tv_usec*1000; + + /* Make sure that 0,0 is not understood as + * "disarming" */ + if (its.it_value.tv_sec == 0) + its.it_value.tv_nsec = 1; + + if (p->period > 0) { + struct timeval tv; + pa_timeval_store(&tv, p->period); + its.it_interval.tv_sec = tv.tv_sec; + its.it_interval.tv_nsec = tv.tv_usec*1000; + } + } + + pa_assert_se(timer_settime(p->timer, TIMER_ABSTIME, &its, NULL) == 0); + } + +#ifdef __linux__ + } +#endif + +#endif +} + +void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, const struct timeval *ts) { + pa_assert(p); + pa_assert(ts); + + p->next_elapse = *ts; + p->period = 0; + p->timer_enabled = TRUE; + + update_timer(p); +} + +void pa_rtpoll_set_timer_periodic(pa_rtpoll *p, pa_usec_t usec) { + pa_assert(p); + + p->period = usec; + pa_rtclock_get(&p->next_elapse); + pa_timeval_add(&p->next_elapse, usec); + p->timer_enabled = TRUE; + + update_timer(p); +} + +void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec) { + pa_assert(p); + + p->period = 0; + pa_rtclock_get(&p->next_elapse); + pa_timeval_add(&p->next_elapse, usec); + p->timer_enabled = TRUE; + + update_timer(p); +} + +void pa_rtpoll_set_timer_disabled(pa_rtpoll *p) { + pa_assert(p); + + p->period = 0; + memset(&p->next_elapse, 0, sizeof(p->next_elapse)); + p->timer_enabled = FALSE; + + update_timer(p); +} + +pa_rtpoll_item *pa_rtpoll_item_new(pa_rtpoll *p, pa_rtpoll_priority_t prio, unsigned n_fds) { + pa_rtpoll_item *i, *j, *l = NULL; + + pa_assert(p); + + if (!(i = pa_flist_pop(PA_STATIC_FLIST_GET(items)))) + i = pa_xnew(pa_rtpoll_item, 1); + + i->rtpoll = p; + i->dead = FALSE; + i->n_pollfd = n_fds; + i->pollfd = NULL; + i->priority = prio; + + i->userdata = NULL; + i->before_cb = NULL; + i->after_cb = NULL; + i->work_cb = NULL; + + for (j = p->items; j; j = j->next) { + if (prio <= j->priority) + break; + + l = j; + } + + PA_LLIST_INSERT_AFTER(pa_rtpoll_item, p->items, j ? j->prev : l, i); + + if (n_fds > 0) { + p->rebuild_needed = 1; + p->n_pollfd_used += n_fds; + } + + return i; +} + +void pa_rtpoll_item_free(pa_rtpoll_item *i) { + pa_assert(i); + + if (i->rtpoll->running) { + i->dead = TRUE; + i->rtpoll->scan_for_dead = TRUE; + return; + } + + rtpoll_item_destroy(i); +} + +struct pollfd *pa_rtpoll_item_get_pollfd(pa_rtpoll_item *i, unsigned *n_fds) { + pa_assert(i); + + if (i->n_pollfd > 0) + if (i->rtpoll->rebuild_needed) + rtpoll_rebuild(i->rtpoll); + + if (n_fds) + *n_fds = i->n_pollfd; + + return i->pollfd; +} + +void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i)) { + pa_assert(i); + pa_assert(i->priority < PA_RTPOLL_NEVER); + + i->before_cb = before_cb; +} + +void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i)) { + pa_assert(i); + pa_assert(i->priority < PA_RTPOLL_NEVER); + + i->after_cb = after_cb; +} + +void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i)) { + pa_assert(i); + pa_assert(i->priority < PA_RTPOLL_NEVER); + + i->work_cb = work_cb; +} + +void pa_rtpoll_item_set_userdata(pa_rtpoll_item *i, void *userdata) { + pa_assert(i); + + i->userdata = userdata; +} + +void* pa_rtpoll_item_get_userdata(pa_rtpoll_item *i) { + pa_assert(i); + + return i->userdata; +} + +static int fdsem_before(pa_rtpoll_item *i) { + + if (pa_fdsem_before_poll(i->userdata) < 0) + return 1; /* 1 means immediate restart of the loop */ + + return 0; +} + +static void fdsem_after(pa_rtpoll_item *i) { + pa_assert(i); + + pa_assert((i->pollfd[0].revents & ~POLLIN) == 0); + pa_fdsem_after_poll(i->userdata); +} + +pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_fdsem *f) { + pa_rtpoll_item *i; + struct pollfd *pollfd; + + pa_assert(p); + pa_assert(f); + + i = pa_rtpoll_item_new(p, prio, 1); + + pollfd = pa_rtpoll_item_get_pollfd(i, NULL); + + pollfd->fd = pa_fdsem_get(f); + pollfd->events = POLLIN; + + i->before_cb = fdsem_before; + i->after_cb = fdsem_after; + i->userdata = f; + + return i; +} + +static int asyncmsgq_before(pa_rtpoll_item *i) { + pa_assert(i); + + if (pa_asyncmsgq_before_poll(i->userdata) < 0) + return 1; /* 1 means immediate restart of the loop */ + + return 0; +} + +static void asyncmsgq_after(pa_rtpoll_item *i) { + pa_assert(i); + + pa_assert((i->pollfd[0].revents & ~POLLIN) == 0); + pa_asyncmsgq_after_poll(i->userdata); +} + +static int asyncmsgq_work(pa_rtpoll_item *i) { + pa_msgobject *object; + int code; + void *data; + pa_memchunk chunk; + int64_t offset; + + pa_assert(i); + + if (pa_asyncmsgq_get(i->userdata, &object, &code, &data, &offset, &chunk, 0) == 0) { + int ret; + + if (!object && code == PA_MESSAGE_SHUTDOWN) { + pa_asyncmsgq_done(i->userdata, 0); + pa_rtpoll_quit(i->rtpoll); + return 1; + } + + ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk); + pa_asyncmsgq_done(i->userdata, ret); + return 1; + } + + return 0; +} + +pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q) { + pa_rtpoll_item *i; + struct pollfd *pollfd; + + pa_assert(p); + pa_assert(q); + + i = pa_rtpoll_item_new(p, prio, 1); + + pollfd = pa_rtpoll_item_get_pollfd(i, NULL); + pollfd->fd = pa_asyncmsgq_get_fd(q); + pollfd->events = POLLIN; + + i->before_cb = asyncmsgq_before; + i->after_cb = asyncmsgq_after; + i->work_cb = asyncmsgq_work; + i->userdata = q; + + return i; +} + +void pa_rtpoll_quit(pa_rtpoll *p) { + pa_assert(p); + + p->quit = TRUE; +} diff --git a/src/pulsecore/rtpoll.h b/src/pulsecore/rtpoll.h new file mode 100644 index 00000000..02f5c7c2 --- /dev/null +++ b/src/pulsecore/rtpoll.h @@ -0,0 +1,116 @@ +#ifndef foopulsertpollhfoo +#define foopulsertpollhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <sys/types.h> +#include <limits.h> + +#include <pulse/sample.h> +#include <pulsecore/asyncmsgq.h> +#include <pulsecore/fdsem.h> +#include <pulsecore/macro.h> + +/* An implementation of a "real-time" poll loop. Basically, this is + * yet another wrapper around poll(). However it has certain + * advantages over pa_mainloop and suchlike: + * + * 1) It uses timer_create() and POSIX real time signals to guarantee + * optimal high-resolution timing. Starting with Linux 2.6.21 hrtimers + * are available, and since right now only nanosleep() and the POSIX + * clock and timer interfaces have been ported to hrtimers (and not + * ppoll/pselect!) we have to combine ppoll() with timer_create(). The + * fact that POSIX timers and POSIX rt signals are used internally is + * completely hidden. + * + * 2) It allows raw access to the pollfd data to users + * + * 3) It allows arbitrary functions to be run before entering the + * actual poll() and after it. + * + * Only a single interval timer is supported..*/ + +typedef struct pa_rtpoll pa_rtpoll; +typedef struct pa_rtpoll_item pa_rtpoll_item; + +typedef enum pa_rtpoll_priority { + PA_RTPOLL_EARLY = -100, /* For veeery important stuff, like handling control messages */ + PA_RTPOLL_NORMAL = 0, /* For normal stuff */ + PA_RTPOLL_LATE = +100, /* For housekeeping */ + PA_RTPOLL_NEVER = INT_MAX, /* For stuff that doesn't register any callbacks, but only fds to listen on */ +} pa_rtpoll_priority_t; + +pa_rtpoll *pa_rtpoll_new(void); +void pa_rtpoll_free(pa_rtpoll *p); + +/* Install the rtpoll in the current thread */ +void pa_rtpoll_install(pa_rtpoll *p); + +/* Sleep on the rtpoll until the time event, or any of the fd events + * is triggered. If "wait" is 0 we don't sleep but only update the + * struct pollfd. Returns negative on error, positive if the loop + * should continue to run, 0 when the loop should be terminated + * cleanly. */ +int pa_rtpoll_run(pa_rtpoll *f, pa_bool_t wait); + +void pa_rtpoll_set_timer_absolute(pa_rtpoll *p, const struct timeval *ts); +void pa_rtpoll_set_timer_periodic(pa_rtpoll *p, pa_usec_t usec); +void pa_rtpoll_set_timer_relative(pa_rtpoll *p, pa_usec_t usec); +void pa_rtpoll_set_timer_disabled(pa_rtpoll *p); + +/* A new fd wakeup item for pa_rtpoll */ +pa_rtpoll_item *pa_rtpoll_item_new(pa_rtpoll *p, pa_rtpoll_priority_t prio, unsigned n_fds); +void pa_rtpoll_item_free(pa_rtpoll_item *i); + +/* Please note that this pointer might change on every call and when + * pa_rtpoll_run() is called. Hence: call this immediately before + * using the pointer and don't save the result anywhere */ +struct pollfd *pa_rtpoll_item_get_pollfd(pa_rtpoll_item *i, unsigned *n_fds); + +/* Set the callback that shall be called when there's time to do some work: If the + * callback returns a value > 0, the poll is skipped and the next + * iteraton of the loop will start immediately. */ +void pa_rtpoll_item_set_work_callback(pa_rtpoll_item *i, int (*work_cb)(pa_rtpoll_item *i)); + +/* Set the callback that shall be called immediately before entering + * the sleeping poll: If the callback returns a value > 0, the poll is + * skipped and the next iteraton of the loop will start + * immediately.. */ +void pa_rtpoll_item_set_before_callback(pa_rtpoll_item *i, int (*before_cb)(pa_rtpoll_item *i)); + +/* Set the callback that shall be called immediately after having + * entered the sleeping poll */ +void pa_rtpoll_item_set_after_callback(pa_rtpoll_item *i, void (*after_cb)(pa_rtpoll_item *i)); + +void pa_rtpoll_item_set_userdata(pa_rtpoll_item *i, void *userdata); +void* pa_rtpoll_item_get_userdata(pa_rtpoll_item *i); + +pa_rtpoll_item *pa_rtpoll_item_new_fdsem(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_fdsem *s); +pa_rtpoll_item *pa_rtpoll_item_new_asyncmsgq(pa_rtpoll *p, pa_rtpoll_priority_t prio, pa_asyncmsgq *q); + +/* Requests the loop to exit. Will cause the next iteration of + * pa_rtpoll_run() to return 0 */ +void pa_rtpoll_quit(pa_rtpoll *p); + +#endif diff --git a/src/pulsecore/rtsig.c b/src/pulsecore/rtsig.c new file mode 100644 index 00000000..bfc49c88 --- /dev/null +++ b/src/pulsecore/rtsig.c @@ -0,0 +1,133 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <signal.h> + +#include <pulsecore/macro.h> +#include <pulsecore/flist.h> +#include <pulsecore/once.h> +#include <pulsecore/thread.h> +#include <pulsecore/core-util.h> + +#include "rtsig.h" + +#ifdef SIGRTMIN + +static void _free_rtsig(void *p) { + pa_rtsig_put(PA_PTR_TO_INT(p)); +} + +PA_STATIC_FLIST_DECLARE(rtsig_flist, pa_make_power_of_two(SIGRTMAX-SIGRTMIN+1), NULL); +PA_STATIC_TLS_DECLARE(rtsig_tls, _free_rtsig); + +static pa_atomic_t rtsig_current = PA_ATOMIC_INIT(-1); + +static int rtsig_start = -1, rtsig_end = -1; + +int pa_rtsig_get(void) { + void *p; + int sig; + + if ((p = pa_flist_pop(PA_STATIC_FLIST_GET(rtsig_flist)))) + return PA_PTR_TO_INT(p); + + sig = pa_atomic_dec(&rtsig_current); + + pa_assert(sig <= SIGRTMAX); + pa_assert(sig <= rtsig_end); + + if (sig < rtsig_start) { + pa_atomic_inc(&rtsig_current); + return -1; + } + + return sig; +} + +int pa_rtsig_get_for_thread(void) { + int sig; + void *p; + + if ((p = PA_STATIC_TLS_GET(rtsig_tls))) + return PA_PTR_TO_INT(p); + + if ((sig = pa_rtsig_get()) < 0) + return -1; + + PA_STATIC_TLS_SET(rtsig_tls, PA_INT_TO_PTR(sig)); + return sig; +} + +void pa_rtsig_put(int sig) { + pa_assert(sig >= rtsig_start); + pa_assert(sig <= rtsig_end); + + pa_assert_se(pa_flist_push(PA_STATIC_FLIST_GET(rtsig_flist), PA_INT_TO_PTR(sig)) >= 0); +} + +void pa_rtsig_configure(int start, int end) { + int s; + sigset_t ss; + + pa_assert(pa_atomic_load(&rtsig_current) == -1); + + pa_assert(SIGRTMIN <= start); + pa_assert(start <= end); + pa_assert(end <= SIGRTMAX); + + rtsig_start = start; + rtsig_end = end; + + sigemptyset(&ss); + + for (s = rtsig_start; s <= rtsig_end; s++) + pa_assert_se(sigaddset(&ss, s) == 0); + + pa_assert(pthread_sigmask(SIG_BLOCK, &ss, NULL) == 0); + + /* We allocate starting from the end */ + pa_atomic_store(&rtsig_current, rtsig_end); +} + +#else /* SIGRTMIN */ + +int pa_rtsig_get(void) { + return -1; +} + +int pa_rtsig_get_for_thread(void) { + return -1; +} + +void pa_rtsig_put(int sig) { +} + +void pa_rtsig_configure(int start, int end) { +} + +#endif /* SIGRTMIN */ diff --git a/src/pulsecore/rtsig.h b/src/pulsecore/rtsig.h new file mode 100644 index 00000000..7830d272 --- /dev/null +++ b/src/pulsecore/rtsig.h @@ -0,0 +1,41 @@ +#ifndef foopulsertsighfoo +#define foopulsertsighfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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. +***/ + +/* Return the next unused POSIX Realtime signals */ +int pa_rtsig_get(void); + +/* If not called before in the current thread, return the next unused + * rtsig, and install it in a TLS region and give it up automatically + * when the thread shuts down */ +int pa_rtsig_get_for_thread(void); + +/* Give an rtsig back. */ +void pa_rtsig_put(int sig); + +/* Block all RT signals */ +void pa_rtsig_configure(int start, int end); + +#endif diff --git a/src/pulsecore/sample-util.c b/src/pulsecore/sample-util.c new file mode 100644 index 00000000..4ea5d446 --- /dev/null +++ b/src/pulsecore/sample-util.c @@ -0,0 +1,933 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> +#include <stdlib.h> + +#include <liboil/liboilfuncs.h> +#include <liboil/liboil.h> + +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/g711.h> + +#include "sample-util.h" +#include "endianmacros.h" + +#define PA_SILENCE_MAX (PA_PAGE_SIZE*16) + +pa_memblock *pa_silence_memblock_new(pa_mempool *pool, const pa_sample_spec *spec, size_t length) { + size_t fs; + pa_assert(pool); + pa_assert(spec); + + if (length <= 0) + length = pa_bytes_per_second(spec)/20; /* 50 ms */ + + if (length > PA_SILENCE_MAX) + length = PA_SILENCE_MAX; + + fs = pa_frame_size(spec); + + length = (length+fs-1)/fs; + + if (length <= 0) + length = 1; + + length *= fs; + + return pa_silence_memblock(pa_memblock_new(pool, length), spec); +} + +pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec) { + void *data; + + pa_assert(b); + pa_assert(spec); + + data = pa_memblock_acquire(b); + pa_silence_memory(data, pa_memblock_get_length(b), spec); + pa_memblock_release(b); + return b; +} + +void pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec) { + void *data; + + pa_assert(c); + pa_assert(c->memblock); + pa_assert(spec); + + data = pa_memblock_acquire(c->memblock); + pa_silence_memory((uint8_t*) data+c->index, c->length, spec); + pa_memblock_release(c->memblock); +} + +void pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec) { + uint8_t c = 0; + pa_assert(p); + pa_assert(length > 0); + pa_assert(spec); + + switch (spec->format) { + case PA_SAMPLE_U8: + c = 0x80; + break; + case PA_SAMPLE_S16LE: + case PA_SAMPLE_S16BE: + case PA_SAMPLE_S32LE: + case PA_SAMPLE_S32BE: + case PA_SAMPLE_FLOAT32: + case PA_SAMPLE_FLOAT32RE: + c = 0; + break; + case PA_SAMPLE_ALAW: + c = 0xd5; + break; + case PA_SAMPLE_ULAW: + c = 0xff; + break; + default: + pa_assert_not_reached(); + } + + memset(p, c, length); +} + +static void calc_linear_integer_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_sample_spec *spec) { + unsigned k; + + pa_assert(streams); + pa_assert(spec); + + for (k = 0; k < nstreams; k++) { + unsigned channel; + + for (channel = 0; channel < spec->channels; channel++) { + pa_mix_info *m = streams + k; + m->linear[channel].i = (int32_t) (pa_sw_volume_to_linear(m->volume.values[channel]) * 0x10000); + } + } +} + +static void calc_linear_integer_volume(int32_t linear[], const pa_cvolume *volume) { + unsigned channel; + + pa_assert(linear); + pa_assert(volume); + + for (channel = 0; channel < volume->channels; channel++) + linear[channel] = (int32_t) (pa_sw_volume_to_linear(volume->values[channel]) * 0x10000); +} + +static void calc_linear_float_stream_volumes(pa_mix_info streams[], unsigned nstreams, const pa_sample_spec *spec) { + unsigned k; + + pa_assert(streams); + pa_assert(spec); + + for (k = 0; k < nstreams; k++) { + unsigned channel; + + for (channel = 0; channel < spec->channels; channel++) { + pa_mix_info *m = streams + k; + m->linear[channel].f = pa_sw_volume_to_linear(m->volume.values[channel]); + } + } +} + +static void calc_linear_float_volume(float linear[], const pa_cvolume *volume) { + unsigned channel; + + pa_assert(linear); + pa_assert(volume); + + for (channel = 0; channel < volume->channels; channel++) + linear[channel] = pa_sw_volume_to_linear(volume->values[channel]); +} + +size_t pa_mix( + pa_mix_info streams[], + unsigned nstreams, + void *data, + size_t length, + const pa_sample_spec *spec, + const pa_cvolume *volume, + pa_bool_t mute) { + + pa_cvolume full_volume; + unsigned k; + size_t d = 0; + + pa_assert(streams); + pa_assert(data); + pa_assert(length); + pa_assert(spec); + + if (!volume) + volume = pa_cvolume_reset(&full_volume, spec->channels); + + for (k = 0; k < nstreams; k++) + streams[k].ptr = (uint8_t*) pa_memblock_acquire(streams[k].chunk.memblock) + streams[k].chunk.index; + + switch (spec->format) { + + case PA_SAMPLE_S16NE:{ + unsigned channel = 0; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_stream_volumes(streams, nstreams, spec); + calc_linear_integer_volume(linear, volume); + + for (d = 0;; d += sizeof(int16_t)) { + int32_t sum = 0; + unsigned i; + + if (PA_UNLIKELY(d >= length)) + goto finish; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t v, cv = m->linear[channel].i; + + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; + + if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0)) + v = 0; + else { + v = *((int16_t*) m->ptr); + v = (v * cv) / 0x10000; + } + + sum += v; + m->ptr = (uint8_t*) m->ptr + sizeof(int16_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + sum = (sum * linear[channel]) / 0x10000; + *((int16_t*) data) = (int16_t) sum; + + data = (uint8_t*) data + sizeof(int16_t); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_S16RE:{ + unsigned channel = 0; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_stream_volumes(streams, nstreams, spec); + calc_linear_integer_volume(linear, volume); + + for (d = 0;; d += sizeof(int16_t)) { + int32_t sum = 0; + unsigned i; + + if (PA_UNLIKELY(d >= length)) + goto finish; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t v, cv = m->linear[channel].i; + + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; + + if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0)) + v = 0; + else { + v = PA_INT16_SWAP(*((int16_t*) m->ptr)); + v = (v * cv) / 0x10000; + } + + sum += v; + m->ptr = (uint8_t*) m->ptr + sizeof(int16_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + sum = (sum * linear[channel]) / 0x10000; + *((int16_t*) data) = PA_INT16_SWAP((int16_t) sum); + + data = (uint8_t*) data + sizeof(int16_t); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_S32NE:{ + unsigned channel = 0; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_stream_volumes(streams, nstreams, spec); + calc_linear_integer_volume(linear, volume); + + for (d = 0;; d += sizeof(int32_t)) { + int64_t sum = 0; + unsigned i; + + if (PA_UNLIKELY(d >= length)) + goto finish; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int64_t v; + int32_t cv = m->linear[channel].i; + + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; + + if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0)) + v = 0; + else { + v = *((int32_t*) m->ptr); + v = (v * cv) / 0x10000; + } + + sum += v; + m->ptr = (uint8_t*) m->ptr + sizeof(int32_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL); + sum = (sum * linear[channel]) / 0x10000; + *((int32_t*) data) = (int32_t) sum; + + data = (uint8_t*) data + sizeof(int32_t); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_S32RE:{ + unsigned channel = 0; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_stream_volumes(streams, nstreams, spec); + calc_linear_integer_volume(linear, volume); + + for (d = 0;; d += sizeof(int32_t)) { + int64_t sum = 0; + unsigned i; + + if (PA_UNLIKELY(d >= length)) + goto finish; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int64_t v; + int32_t cv = m->linear[channel].i; + + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; + + if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0)) + v = 0; + else { + v = PA_INT32_SWAP(*((int32_t*) m->ptr)); + v = (v * cv) / 0x10000; + } + + sum += v; + m->ptr = (uint8_t*) m->ptr + sizeof(int32_t); + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x80000000LL, 0x7FFFFFFFLL); + sum = (sum * linear[channel]) / 0x10000; + *((int32_t*) data) = PA_INT32_SWAP((int32_t) sum); + + data = (uint8_t*) data + sizeof(int32_t); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_U8: { + unsigned channel = 0; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_stream_volumes(streams, nstreams, spec); + calc_linear_integer_volume(linear, volume); + + for (d = 0;; d ++) { + int32_t sum = 0; + unsigned i; + + if (PA_UNLIKELY(d >= length)) + goto finish; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t v, cv = m->linear[channel].i; + + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; + + if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0)) + v = 0; + else { + v = (int32_t) *((uint8_t*) m->ptr) - 0x80; + v = (v * cv) / 0x10000; + } + + sum += v; + m->ptr = (uint8_t*) m->ptr + 1; + } + + sum = (sum * linear[channel]) / 0x10000; + sum = PA_CLAMP_UNLIKELY(sum, -0x80, 0x7F); + *((uint8_t*) data) = (uint8_t) (sum + 0x80); + + data = (uint8_t*) data + 1; + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_ULAW: { + unsigned channel = 0; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_stream_volumes(streams, nstreams, spec); + calc_linear_integer_volume(linear, volume); + + for (d = 0;; d ++) { + int32_t sum = 0; + unsigned i; + + if (PA_UNLIKELY(d >= length)) + goto finish; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t v, cv = m->linear[channel].i; + + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; + + if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0)) + v = 0; + else { + v = (int32_t) st_ulaw2linear16(*((uint8_t*) m->ptr)); + v = (v * cv) / 0x10000; + } + + sum += v; + m->ptr = (uint8_t*) m->ptr + 1; + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + sum = (sum * linear[channel]) / 0x10000; + *((uint8_t*) data) = (uint8_t) st_14linear2ulaw(sum >> 2); + + data = (uint8_t*) data + 1; + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_ALAW: { + unsigned channel = 0; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_stream_volumes(streams, nstreams, spec); + calc_linear_integer_volume(linear, volume); + + for (d = 0;; d ++) { + int32_t sum = 0; + unsigned i; + + if (PA_UNLIKELY(d >= length)) + goto finish; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + int32_t v, cv = m->linear[channel].i; + + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; + + if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0)) + v = 0; + else { + v = (int32_t) st_alaw2linear16(*((uint8_t*) m->ptr)); + v = (v * cv) / 0x10000; + } + + sum += v; + m->ptr = (uint8_t*) m->ptr + 1; + } + + sum = PA_CLAMP_UNLIKELY(sum, -0x8000, 0x7FFF); + sum = (sum * linear[channel]) / 0x10000; + *((uint8_t*) data) = (uint8_t) st_13linear2alaw(sum >> 3); + + data = (uint8_t*) data + 1; + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_FLOAT32NE: { + unsigned channel = 0; + float linear[PA_CHANNELS_MAX]; + + calc_linear_float_stream_volumes(streams, nstreams, spec); + calc_linear_float_volume(linear, volume); + + for (d = 0;; d += sizeof(float)) { + float sum = 0; + unsigned i; + + if (PA_UNLIKELY(d >= length)) + goto finish; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + float v, cv = m->linear[channel].f; + + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; + + if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0)) + v = 0; + else { + v = *((float*) m->ptr); + v *= cv; + } + + sum += v; + m->ptr = (uint8_t*) m->ptr + sizeof(float); + } + + sum *= linear[channel]; + *((float*) data) = sum; + + data = (uint8_t*) data + sizeof(float); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_FLOAT32RE: { + unsigned channel = 0; + float linear[PA_CHANNELS_MAX]; + + calc_linear_float_stream_volumes(streams, nstreams, spec); + calc_linear_float_volume(linear, volume); + + for (d = 0;; d += sizeof(float)) { + float sum = 0; + unsigned i; + + if (PA_UNLIKELY(d >= length)) + goto finish; + + for (i = 0; i < nstreams; i++) { + pa_mix_info *m = streams + i; + float v, cv = m->linear[channel].f; + + if (PA_UNLIKELY(d >= m->chunk.length)) + goto finish; + + if (PA_UNLIKELY(cv <= 0) || PA_UNLIKELY(!!mute) || PA_UNLIKELY(linear[channel] <= 0)) + v = 0; + else { + uint32_t z = *(uint32_t*) m->ptr; + z = PA_UINT32_SWAP(z); + v = *((float*) &z); + v *= cv; + } + + sum += v; + m->ptr = (uint8_t*) m->ptr + sizeof(float); + } + + sum *= linear[channel]; + *((uint32_t*) data) = PA_UINT32_SWAP(*(uint32_t*) &sum); + + data = (uint8_t*) data + sizeof(float); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + default: + pa_log_error("ERROR: Unable to mix audio data of format %s.", pa_sample_format_to_string(spec->format)); + pa_assert_not_reached(); + } + +finish: + + for (k = 0; k < nstreams; k++) + pa_memblock_release(streams[k].chunk.memblock); + + return d; +} + + +void pa_volume_memchunk( + pa_memchunk*c, + const pa_sample_spec *spec, + const pa_cvolume *volume) { + + void *ptr; + + pa_assert(c); + pa_assert(spec); + pa_assert(c->length % pa_frame_size(spec) == 0); + pa_assert(volume); + + if (pa_cvolume_channels_equal_to(volume, PA_VOLUME_NORM)) + return; + + if (pa_cvolume_channels_equal_to(volume, PA_VOLUME_MUTED)) { + pa_silence_memchunk(c, spec); + return; + } + + ptr = (uint8_t*) pa_memblock_acquire(c->memblock) + c->index; + + switch (spec->format) { + + case PA_SAMPLE_S16NE: { + int16_t *d; + size_t n; + unsigned channel; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_volume(linear, volume); + + for (channel = 0, d = ptr, n = c->length/sizeof(int16_t); n > 0; d++, n--) { + int32_t t; + + t = (int32_t)(*d); + t = (t * linear[channel]) / 0x10000; + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *d = (int16_t) t; + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + break; + } + + case PA_SAMPLE_S16RE: { + int16_t *d; + size_t n; + unsigned channel; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_volume(linear, volume); + + for (channel = 0, d = ptr, n = c->length/sizeof(int16_t); n > 0; d++, n--) { + int32_t t; + + t = (int32_t)(PA_INT16_SWAP(*d)); + t = (t * linear[channel]) / 0x10000; + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *d = PA_INT16_SWAP((int16_t) t); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_S32NE: { + int32_t *d; + size_t n; + unsigned channel; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_volume(linear, volume); + + for (channel = 0, d = ptr, n = c->length/sizeof(int32_t); n > 0; d++, n--) { + int64_t t; + + t = (int64_t)(*d); + t = (t * linear[channel]) / 0x10000; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *d = (int32_t) t; + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + break; + } + + case PA_SAMPLE_S32RE: { + int32_t *d; + size_t n; + unsigned channel; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_volume(linear, volume); + + for (channel = 0, d = ptr, n = c->length/sizeof(int32_t); n > 0; d++, n--) { + int64_t t; + + t = (int64_t)(PA_INT32_SWAP(*d)); + t = (t * linear[channel]) / 0x10000; + t = PA_CLAMP_UNLIKELY(t, -0x80000000LL, 0x7FFFFFFFLL); + *d = PA_INT32_SWAP((int32_t) t); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + case PA_SAMPLE_U8: { + uint8_t *d; + size_t n; + unsigned channel; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_volume(linear, volume); + + for (channel = 0, d = ptr, n = c->length; n > 0; d++, n--) { + int32_t t; + + t = (int32_t) *d - 0x80; + t = (t * linear[channel]) / 0x10000; + t = PA_CLAMP_UNLIKELY(t, -0x80, 0x7F); + *d = (uint8_t) (t + 0x80); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + break; + } + + case PA_SAMPLE_ULAW: { + uint8_t *d; + size_t n; + unsigned channel; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_volume(linear, volume); + + for (channel = 0, d = ptr, n = c->length; n > 0; d++, n--) { + int32_t t; + + t = (int32_t) st_ulaw2linear16(*d); + t = (t * linear[channel]) / 0x10000; + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *d = (uint8_t) st_14linear2ulaw(t >> 2); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + break; + } + + case PA_SAMPLE_ALAW: { + uint8_t *d; + size_t n; + unsigned channel; + int32_t linear[PA_CHANNELS_MAX]; + + calc_linear_integer_volume(linear, volume); + + for (channel = 0, d = ptr, n = c->length; n > 0; d++, n--) { + int32_t t; + + t = (int32_t) st_alaw2linear16(*d); + t = (t * linear[channel]) / 0x10000; + t = PA_CLAMP_UNLIKELY(t, -0x8000, 0x7FFF); + *d = (uint8_t) st_13linear2alaw(t >> 3); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + break; + } + + case PA_SAMPLE_FLOAT32NE: { + float *d; + int skip; + unsigned n; + unsigned channel; + + d = ptr; + skip = spec->channels * sizeof(float); + n = c->length/sizeof(float)/spec->channels; + + for (channel = 0; channel < spec->channels; channel ++) { + float v, *t; + + if (PA_UNLIKELY(volume->values[channel] == PA_VOLUME_NORM)) + continue; + + v = (float) pa_sw_volume_to_linear(volume->values[channel]); + t = d + channel; + oil_scalarmult_f32(t, skip, t, skip, &v, n); + } + break; + } + + case PA_SAMPLE_FLOAT32RE: { + uint32_t *d; + size_t n; + unsigned channel; + float linear[PA_CHANNELS_MAX]; + + calc_linear_float_volume(linear, volume); + + for (channel = 0, d = ptr, n = c->length/sizeof(float); n > 0; d++, n--) { + float t; + uint32_t z; + + z = PA_UINT32_SWAP(*d); + t = *(float*) &z; + t *= linear[channel]; + z = *(uint32_t*) &t; + *d = PA_UINT32_SWAP(z); + + if (PA_UNLIKELY(++channel >= spec->channels)) + channel = 0; + } + + break; + } + + + default: + pa_log_warn(" Unable to change volume of format %s.", pa_sample_format_to_string(spec->format)); + /* If we cannot change the volume, we just don't do it */ + } + + pa_memblock_release(c->memblock); +} + +size_t pa_frame_align(size_t l, const pa_sample_spec *ss) { + size_t fs; + + pa_assert(ss); + + fs = pa_frame_size(ss); + + return (l/fs) * fs; +} + +int pa_frame_aligned(size_t l, const pa_sample_spec *ss) { + size_t fs; + + pa_assert(ss); + + fs = pa_frame_size(ss); + + return l % fs == 0; +} + +void pa_interleave(const void *src[], unsigned channels, void *dst, size_t ss, unsigned n) { + unsigned c; + size_t fs; + + pa_assert(src); + pa_assert(channels > 0); + pa_assert(dst); + pa_assert(ss > 0); + pa_assert(n > 0); + + fs = ss * channels; + + for (c = 0; c < channels; c++) { + unsigned j; + void *d; + const void *s; + + s = src[c]; + d = (uint8_t*) dst + c * ss; + + for (j = 0; j < n; j ++) { + oil_memcpy(d, s, ss); + s = (uint8_t*) s + ss; + d = (uint8_t*) d + fs; + } + } +} + +void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss, unsigned n) { + size_t fs; + unsigned c; + + pa_assert(src); + pa_assert(dst); + pa_assert(channels > 0); + pa_assert(ss > 0); + pa_assert(n > 0); + + fs = ss * channels; + + for (c = 0; c < channels; c++) { + unsigned j; + const void *s; + void *d; + + s = (uint8_t*) src + c * ss; + d = dst[c]; + + for (j = 0; j < n; j ++) { + oil_memcpy(d, s, ss); + s = (uint8_t*) s + fs; + d = (uint8_t*) d + ss; + } + } +} diff --git a/src/pulsecore/sample-util.h b/src/pulsecore/sample-util.h new file mode 100644 index 00000000..2ef8f924 --- /dev/null +++ b/src/pulsecore/sample-util.h @@ -0,0 +1,73 @@ +#ifndef foosampleutilhfoo +#define foosampleutilhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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/sample.h> +#include <pulse/volume.h> +#include <pulsecore/memblock.h> +#include <pulsecore/memchunk.h> + +pa_memblock *pa_silence_memblock(pa_memblock* b, const pa_sample_spec *spec); +pa_memblock *pa_silence_memblock_new(pa_mempool *pool, const pa_sample_spec *spec, size_t length); +void pa_silence_memchunk(pa_memchunk *c, const pa_sample_spec *spec); +void pa_silence_memory(void *p, size_t length, const pa_sample_spec *spec); + +typedef struct pa_mix_info { + pa_memchunk chunk; + pa_cvolume volume; + void *userdata; + + /* The following fields are used internally by pa_mix(), should + * not be initialised by the caller of pa_mix(). */ + void *ptr; + union { + int32_t i; + float f; + } linear[PA_CHANNELS_MAX]; +} pa_mix_info; + +size_t pa_mix( + pa_mix_info channels[], + unsigned nchannels, + void *data, + size_t length, + const pa_sample_spec *spec, + const pa_cvolume *volume, + pa_bool_t mute); + +void pa_volume_memchunk( + pa_memchunk*c, + const pa_sample_spec *spec, + const pa_cvolume *volume); + +size_t pa_frame_align(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; + +int pa_frame_aligned(size_t l, const pa_sample_spec *ss) PA_GCC_PURE; + +void pa_interleave(const void *src[], unsigned channels, void *dst, size_t ss, unsigned n); +void pa_deinterleave(const void *src, void *dst[], unsigned channels, size_t ss, unsigned n); + +#endif diff --git a/src/pulsecore/sconv-s16be.c b/src/pulsecore/sconv-s16be.c new file mode 100644 index 00000000..638beb2e --- /dev/null +++ b/src/pulsecore/sconv-s16be.c @@ -0,0 +1,61 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 "endianmacros.h" + +#define INT16_FROM PA_INT16_FROM_BE +#define INT16_TO PA_INT16_TO_BE + +#define INT32_FROM PA_INT32_FROM_BE +#define INT32_TO PA_INT32_TO_BE + +#define pa_sconv_s16le_to_float32ne pa_sconv_s16be_to_float32ne +#define pa_sconv_s16le_from_float32ne pa_sconv_s16be_from_float32ne + +#define pa_sconv_s16le_to_float32re pa_sconv_s16be_to_float32re +#define pa_sconv_s16le_from_float32re pa_sconv_s16be_from_float32re + +#define pa_sconv_s32le_to_float32ne pa_sconv_s32be_to_float32ne +#define pa_sconv_s32le_from_float32ne pa_sconv_s32be_from_float32ne + +#define pa_sconv_s32le_to_float32re pa_sconv_s32be_to_float32re +#define pa_sconv_s32le_from_float32re pa_sconv_s32be_from_float32re + +#define pa_sconv_s32le_to_s16ne pa_sconv_s32be_to_s16ne +#define pa_sconv_s32le_from_s16ne pa_sconv_s32be_from_s16ne + +#define pa_sconv_s32le_to_s16re pa_sconv_s32be_to_s16re +#define pa_sconv_s32le_from_s16re pa_sconv_s32be_from_s16re + +#ifdef WORDS_BIGENDIAN +#define SWAP_WORDS 0 +#else +#define SWAP_WORDS 1 +#endif + +#include "sconv-s16le.h" +#include "sconv-s16le.c" diff --git a/src/pulsecore/sconv-s16be.h b/src/pulsecore/sconv-s16be.h new file mode 100644 index 00000000..454c9508 --- /dev/null +++ b/src/pulsecore/sconv-s16be.h @@ -0,0 +1,61 @@ +#ifndef foosconv_s16befoo +#define foosconv_s16befoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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> + +void pa_sconv_s16be_to_float32ne(unsigned n, const int16_t *a, float *b); +void pa_sconv_s16be_from_float32ne(unsigned n, const float *a, int16_t *b); +void pa_sconv_s16be_to_float32re(unsigned n, const int16_t *a, float *b); +void pa_sconv_s16be_from_float32re(unsigned n, const float *a, int16_t *b); + +void pa_sconv_s32be_to_float32ne(unsigned n, const int32_t *a, float *b); +void pa_sconv_s32be_from_float32ne(unsigned n, const float *a, int32_t *b); +void pa_sconv_s32be_to_float32re(unsigned n, const int32_t *a, float *b); +void pa_sconv_s32be_from_float32re(unsigned n, const float *a, int32_t *b); + +void pa_sconv_s32be_to_s16ne(unsigned n, const int32_t *a, int16_t *b); +void pa_sconv_s32be_from_s16ne(unsigned n, const int16_t *a, int32_t *b); +void pa_sconv_s32be_to_s16re(unsigned n, const int32_t *a, int16_t *b); +void pa_sconv_s32be_from_s16re(unsigned n, const int16_t *a, int32_t *b); + +#ifdef WORDS_BIGENDIAN +#define pa_sconv_float32be_to_s16ne pa_sconv_s16be_from_float32ne +#define pa_sconv_float32be_from_s16ne pa_sconv_s16be_to_float32ne +#define pa_sconv_float32le_to_s16ne pa_sconv_s16be_from_float32re +#define pa_sconv_float32le_from_s16ne pa_sconv_s16be_to_float32re + +#define pa_sconv_float32be_to_s32ne pa_sconv_s32be_from_float32ne +#define pa_sconv_float32be_from_s32ne pa_sconv_s32be_to_float32ne +#define pa_sconv_float32le_to_s32ne pa_sconv_s32be_from_float32re +#define pa_sconv_float32le_from_s32ne pa_sconv_s32be_to_float32re + +#define pa_sconv_s16be_to_s32ne pa_sconv_s32be_from_s16ne +#define pa_sconv_s16be_from_s32ne pa_sconv_s32be_to_s16ne +#define pa_sconv_s16le_to_s32ne pa_sconv_s32be_from_s16re +#define pa_sconv_s16le_from_s32ne pa_sconv_s32be_to_s16re +#endif + +#endif diff --git a/src/pulsecore/sconv-s16le.c b/src/pulsecore/sconv-s16le.c new file mode 100644 index 00000000..90e9b6d2 --- /dev/null +++ b/src/pulsecore/sconv-s16le.c @@ -0,0 +1,251 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 + +/* Despite the name of this file we implement S32 handling here, too. */ + +#include <inttypes.h> +#include <stdio.h> + +#include <liboil/liboilfuncs.h> + +#include <pulsecore/sconv.h> +#include <pulsecore/macro.h> +#include <pulsecore/log.h> + +#include "endianmacros.h" + +#include "sconv-s16le.h" + +#ifndef INT16_FROM +#define INT16_FROM PA_INT16_FROM_LE +#endif + +#ifndef INT16_TO +#define INT16_TO PA_INT16_TO_LE +#endif + +#ifndef INT32_FROM +#define INT32_FROM PA_INT32_FROM_LE +#endif + +#ifndef INT32_TO +#define INT32_TO PA_INT32_TO_LE +#endif + +#ifndef SWAP_WORDS +#ifdef WORDS_BIGENDIAN +#define SWAP_WORDS 1 +#else +#define SWAP_WORDS 0 +#endif +#endif + +void pa_sconv_s16le_to_float32ne(unsigned n, const int16_t *a, float *b) { + pa_assert(a); + pa_assert(b); + +#if SWAP_WORDS == 1 + + for (; n > 0; n--) { + int16_t s = *(a++); + *(b++) = ((float) INT16_FROM(s))/0x7FFF; + } + +#else +{ + static const double add = 0, factor = 1.0/0x7FFF; + oil_scaleconv_f32_s16(b, a, n, &add, &factor); +} +#endif +} + +void pa_sconv_s32le_to_float32ne(unsigned n, const int32_t *a, float *b) { + pa_assert(a); + pa_assert(b); + +#if SWAP_WORDS == 1 + + for (; n > 0; n--) { + int32_t s = *(a++); + *(b++) = (float) (((double) INT32_FROM(s))/0x7FFFFFFF); + } + +#else +{ + static const double add = 0, factor = 1.0/0x7FFFFFFF; + oil_scaleconv_f32_s32(b, a, n, &add, &factor); +} +#endif +} + +void pa_sconv_s16le_from_float32ne(unsigned n, const float *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + +#if SWAP_WORDS == 1 + + for (; n > 0; n--) { + int16_t s; + float v = *(a++); + + v = PA_CLAMP_UNLIKELY(v, -1, 1); + s = (int16_t) (v * 0x7FFF); + *(b++) = INT16_TO(s); + } + +#else +{ + static const double add = 0, factor = 0x7FFF; + oil_scaleconv_s16_f32(b, a, n, &add, &factor); +} +#endif +} + +void pa_sconv_s32le_from_float32ne(unsigned n, const float *a, int32_t *b) { + pa_assert(a); + pa_assert(b); + +#if SWAP_WORDS == 1 + + for (; n > 0; n--) { + int32_t s; + float v = *(a++); + + v = PA_CLAMP_UNLIKELY(v, -1, 1); + s = (int32_t) ((double) v * (double) 0x7FFFFFFF); + *(b++) = INT32_TO(s); + } + +#else +{ + static const double add = 0, factor = 0x7FFFFFFF; + oil_scaleconv_s32_f32(b, a, n, &add, &factor); +} +#endif +} + +void pa_sconv_s16le_to_float32re(unsigned n, const int16_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int16_t s = *(a++); + float k = ((float) INT16_FROM(s))/0x7FFF; + uint32_t *j = (uint32_t*) &k; + *j = PA_UINT32_SWAP(*j); + *(b++) = k; + } +} + +void pa_sconv_s32le_to_float32re(unsigned n, const int32_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s = *(a++); + float k = (float) (((double) INT32_FROM(s))/0x7FFFFFFF); + uint32_t *j = (uint32_t*) &k; + *j = PA_UINT32_SWAP(*j); + *(b++) = k; + } +} + +void pa_sconv_s16le_from_float32re(unsigned n, const float *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int16_t s; + float v = *(a++); + uint32_t *j = (uint32_t*) &v; + *j = PA_UINT32_SWAP(*j); + v = PA_CLAMP_UNLIKELY(v, -1, 1); + s = (int16_t) (v * 0x7FFF); + *(b++) = INT16_TO(s); + } +} + +void pa_sconv_s32le_from_float32re(unsigned n, const float *a, int32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s; + float v = *(a++); + uint32_t *j = (uint32_t*) &v; + *j = PA_UINT32_SWAP(*j); + v = PA_CLAMP_UNLIKELY(v, -1, 1); + s = (int32_t) ((double) v * 0x7FFFFFFF); + *(b++) = INT32_TO(s); + } +} + +void pa_sconv_s32le_to_s16ne(unsigned n, const int32_t*a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + *b = (int16_t) (INT32_FROM(*a) >> 16); + a++; + b++; + } +} + +void pa_sconv_s32le_to_s16re(unsigned n, const int32_t*a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int16_t s = (int16_t) (INT32_FROM(*a) >> 16); + *b = PA_UINT32_SWAP(s); + a++; + b++; + } +} + +void pa_sconv_s32le_from_s16ne(unsigned n, const int16_t *a, int32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + *b = INT32_TO(((int32_t) *a) << 16); + a++; + b++; + } +} + +void pa_sconv_s32le_from_s16re(unsigned n, const int16_t *a, int32_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + int32_t s = ((int32_t) PA_UINT16_SWAP(*a)) << 16; + *b = INT32_TO(s); + a++; + b++; + } +} diff --git a/src/pulsecore/sconv-s16le.h b/src/pulsecore/sconv-s16le.h new file mode 100644 index 00000000..4165f8a2 --- /dev/null +++ b/src/pulsecore/sconv-s16le.h @@ -0,0 +1,61 @@ +#ifndef foosconv_s16lefoo +#define foosconv_s16lefoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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> + +void pa_sconv_s16le_to_float32ne(unsigned n, const int16_t *a, float *b); +void pa_sconv_s16le_from_float32ne(unsigned n, const float *a, int16_t *b); +void pa_sconv_s16le_to_float32re(unsigned n, const int16_t *a, float *b); +void pa_sconv_s16le_from_float32re(unsigned n, const float *a, int16_t *b); + +void pa_sconv_s32le_to_float32ne(unsigned n, const int32_t *a, float *b); +void pa_sconv_s32le_from_float32ne(unsigned n, const float *a, int32_t *b); +void pa_sconv_s32le_to_float32re(unsigned n, const int32_t *a, float *b); +void pa_sconv_s32le_from_float32re(unsigned n, const float *a, int32_t *b); + +void pa_sconv_s32le_to_s16ne(unsigned n, const int32_t *a, int16_t *b); +void pa_sconv_s32le_from_s16ne(unsigned n, const int16_t *a, int32_t *b); +void pa_sconv_s32le_to_s16re(unsigned n, const int32_t *a, int16_t *b); +void pa_sconv_s32le_from_s16re(unsigned n, const int16_t *a, int32_t *b); + +#ifndef WORDS_BIGENDIAN +#define pa_sconv_float32be_to_s16ne pa_sconv_s16le_from_float32re +#define pa_sconv_float32be_from_s16ne pa_sconv_s16le_to_float32re +#define pa_sconv_float32le_to_s16ne pa_sconv_s16le_from_float32ne +#define pa_sconv_float32le_from_s16ne pa_sconv_s16le_to_float32ne + +#define pa_sconv_float32be_to_s32ne pa_sconv_s32le_from_float32re +#define pa_sconv_float32be_from_s32ne pa_sconv_s32le_to_float32re +#define pa_sconv_float32le_to_s32ne pa_sconv_s32le_from_float32ne +#define pa_sconv_float32le_from_s32ne pa_sconv_s32le_to_float32ne + +#define pa_sconv_s16be_to_s32ne pa_sconv_s32le_from_s16re +#define pa_sconv_s16be_from_s32ne pa_sconv_s32le_to_s16re +#define pa_sconv_s16le_to_s32ne pa_sconv_s32le_from_s16ne +#define pa_sconv_s16le_from_s32ne pa_sconv_s32le_to_s16ne +#endif + +#endif diff --git a/src/pulsecore/sconv.c b/src/pulsecore/sconv.c new file mode 100644 index 00000000..ebd74586 --- /dev/null +++ b/src/pulsecore/sconv.c @@ -0,0 +1,271 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <liboil/liboilfuncs.h> +#include <liboil/liboil.h> + +#include <pulsecore/g711.h> +#include <pulsecore/macro.h> + +#include "endianmacros.h" +#include "sconv-s16le.h" +#include "sconv-s16be.h" + +#include "sconv.h" + +/* u8 */ +static void u8_to_float32ne(unsigned n, const uint8_t *a, float *b) { + static const double add = -1, factor = 1.0/128.0; + + pa_assert(a); + pa_assert(b); + + oil_scaleconv_f32_u8(b, a, n, &add, &factor); +} + +static void u8_from_float32ne(unsigned n, const float *a, uint8_t *b) { + static const double add = 128, factor = 127.0; + + pa_assert(a); + pa_assert(b); + + oil_scaleconv_u8_f32(b, a, n, &add, &factor); +} + +static void u8_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) { + static const int16_t add = -0x80, factor = 0x100; + + pa_assert(a); + pa_assert(b); + + oil_conv_s16_u8(b, 2, a, 1, n); + oil_scalaradd_s16(b, 2, b, 2, &add, n); + oil_scalarmult_s16(b, 2, b, 2, &factor, n); +} + +static void u8_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { + + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = (uint8_t) (*a / 0x100 + 0x80); +} + +/* float32 */ + +static void float32ne_to_float32ne(unsigned n, const float *a, float *b) { + pa_assert(a); + pa_assert(b); + + oil_memcpy(b, a, sizeof(float) * n); +} + +static void float32re_to_float32ne(unsigned n, const float *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *((uint32_t *) b) = PA_UINT32_SWAP(*((uint32_t *) a)); +} + +/* s16 */ + +static void s16ne_to_s16ne(unsigned n, const int16_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + oil_memcpy(b, a, sizeof(int16_t) * n); +} + +static void s16re_to_s16ne(unsigned n, const int16_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = PA_UINT16_SWAP(*a); +} + +/* ulaw */ + +static void ulaw_to_float32ne(unsigned n, const uint8_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) + *(b++) = (float) st_ulaw2linear16(*(a++)) / 0x8000; +} + +static void ulaw_from_float32ne(unsigned n, const float *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--) { + float v = *(a++); + v = PA_CLAMP_UNLIKELY(v, -1, 1); + v *= 0x1FFF; + *(b++) = st_14linear2ulaw((int16_t) v); + } +} + +static void ulaw_to_s16ne(unsigned n, const uint8_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = st_ulaw2linear16(*a); +} + +static void ulaw_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = st_14linear2ulaw(*a >> 2); +} + +/* alaw */ + +static void alaw_to_float32ne(unsigned n, const uint8_t *a, float *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = (float) st_alaw2linear16(*a) / 0x8000; +} + +static void alaw_from_float32ne(unsigned n, const float *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) { + float v = *a; + v = PA_CLAMP_UNLIKELY(v, -1, 1); + v *= 0xFFF; + *b = st_13linear2alaw((int16_t) v); + } +} + +static void alaw_to_s16ne(unsigned n, const int8_t *a, int16_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = st_alaw2linear16(*a); +} + +static void alaw_from_s16ne(unsigned n, const int16_t *a, uint8_t *b) { + pa_assert(a); + pa_assert(b); + + for (; n > 0; n--, a++, b++) + *b = st_13linear2alaw(*a >> 3); +} + +pa_convert_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f) { + + static const pa_convert_func_t table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_float32ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_float32ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_float32ne, + [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_to_float32ne, + [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_to_float32ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_float32ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_float32ne, + [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne, + [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne, + }; + + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + return table[f]; +} + +pa_convert_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) { + + static const pa_convert_func_t table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_float32ne, + [PA_SAMPLE_S16LE] = (pa_convert_func_t) pa_sconv_s16le_from_float32ne, + [PA_SAMPLE_S16BE] = (pa_convert_func_t) pa_sconv_s16be_from_float32ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_float32ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_float32ne, + [PA_SAMPLE_FLOAT32NE] = (pa_convert_func_t) float32ne_to_float32ne, + [PA_SAMPLE_FLOAT32RE] = (pa_convert_func_t) float32re_to_float32ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_float32ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_float32ne + }; + + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + return table[f]; +} + +pa_convert_func_t pa_get_convert_to_s16ne_function(pa_sample_format_t f) { + + static const pa_convert_func_t table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_to_s16ne, + [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne, + [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne, + [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_to_s16ne, + [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_to_s16ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_to_s16ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_to_s16ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_to_s16ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_to_s16ne + }; + + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + return table[f]; +} + +pa_convert_func_t pa_get_convert_from_s16ne_function(pa_sample_format_t f) { + + static const pa_convert_func_t table[] = { + [PA_SAMPLE_U8] = (pa_convert_func_t) u8_from_s16ne, + [PA_SAMPLE_S16NE] = (pa_convert_func_t) s16ne_to_s16ne, + [PA_SAMPLE_S16RE] = (pa_convert_func_t) s16re_to_s16ne, + [PA_SAMPLE_FLOAT32BE] = (pa_convert_func_t) pa_sconv_float32be_from_s16ne, + [PA_SAMPLE_FLOAT32LE] = (pa_convert_func_t) pa_sconv_float32le_from_s16ne, + [PA_SAMPLE_S32BE] = (pa_convert_func_t) pa_sconv_s32be_from_s16ne, + [PA_SAMPLE_S32LE] = (pa_convert_func_t) pa_sconv_s32le_from_s16ne, + [PA_SAMPLE_ALAW] = (pa_convert_func_t) alaw_from_s16ne, + [PA_SAMPLE_ULAW] = (pa_convert_func_t) ulaw_from_s16ne, + }; + + pa_assert(f >= 0); + pa_assert(f < PA_SAMPLE_MAX); + + return table[f]; +} diff --git a/src/pulsecore/sconv.h b/src/pulsecore/sconv.h new file mode 100644 index 00000000..901f50a3 --- /dev/null +++ b/src/pulsecore/sconv.h @@ -0,0 +1,38 @@ +#ifndef foosconvhfoo +#define foosconvhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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/sample.h> + +typedef void (*pa_convert_func_t)(unsigned n, const void *a, void *b); + +pa_convert_func_t pa_get_convert_to_float32ne_function(pa_sample_format_t f) PA_GCC_PURE; +pa_convert_func_t pa_get_convert_from_float32ne_function(pa_sample_format_t f) PA_GCC_PURE; + +pa_convert_func_t pa_get_convert_to_s16ne_function(pa_sample_format_t f) PA_GCC_PURE; +pa_convert_func_t pa_get_convert_from_s16ne_function(pa_sample_format_t f) PA_GCC_PURE; + +#endif diff --git a/src/pulsecore/semaphore-posix.c b/src/pulsecore/semaphore-posix.c new file mode 100644 index 00000000..750c2afc --- /dev/null +++ b/src/pulsecore/semaphore-posix.c @@ -0,0 +1,69 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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 <errno.h> +#include <pthread.h> +#include <semaphore.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "semaphore.h" + +struct pa_semaphore { + sem_t sem; +}; + +pa_semaphore* pa_semaphore_new(unsigned value) { + pa_semaphore *s; + + s = pa_xnew(pa_semaphore, 1); + pa_assert_se(sem_init(&s->sem, 0, value) == 0); + return s; +} + +void pa_semaphore_free(pa_semaphore *s) { + pa_assert(s); + pa_assert_se(sem_destroy(&s->sem) == 0); + pa_xfree(s); +} + +void pa_semaphore_post(pa_semaphore *s) { + pa_assert(s); + pa_assert_se(sem_post(&s->sem) == 0); +} + +void pa_semaphore_wait(pa_semaphore *s) { + int ret; + pa_assert(s); + + do { + ret = sem_wait(&s->sem); + } while (ret < 0 && errno == EINTR); + + pa_assert(ret == 0); +} diff --git a/src/pulsecore/semaphore-win32.c b/src/pulsecore/semaphore-win32.c new file mode 100644 index 00000000..f6576348 --- /dev/null +++ b/src/pulsecore/semaphore-win32.c @@ -0,0 +1,65 @@ +/* $Id: mutex-win32.c 1426 2007-02-13 15:35:19Z ossman $ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <windows.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "semaphore.h" + +struct pa_semaphore +{ + HANDLE sema; +}; + +pa_semaphore* pa_semaphore_new(unsigned value) { + pa_semaphore *s; + + s = pa_xnew(pa_semaphore, 1); + + s->sema = CreateSemaphore(NULL, value, 32767, NULL); + pa_assert(s->sema != NULL); + + return s; +} + +void pa_semaphore_free(pa_semaphore *s) { + pa_assert(s); + CloseHandle(s->sema); + pa_xfree(s); +} + +void pa_semaphore_post(pa_semaphore *s) { + pa_assert(s); + ReleaseSemaphore(s->sema, 1, NULL); +} + +void pa_semaphore_wait(pa_semaphore *s) { + pa_assert(s); + WaitForSingleObject(s->sema, INFINITE); +} diff --git a/src/pulsecore/semaphore.h b/src/pulsecore/semaphore.h new file mode 100644 index 00000000..c394e0f2 --- /dev/null +++ b/src/pulsecore/semaphore.h @@ -0,0 +1,35 @@ +#ifndef foopulsesemaphorehfoo +#define foopulsesemaphorehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 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. +***/ + +typedef struct pa_semaphore pa_semaphore; + +pa_semaphore* pa_semaphore_new(unsigned value); +void pa_semaphore_free(pa_semaphore *m); + +void pa_semaphore_post(pa_semaphore *m); +void pa_semaphore_wait(pa_semaphore *m); + +#endif diff --git a/src/pulsecore/shm.c b/src/pulsecore/shm.c new file mode 100644 index 00000000..7c764e3a --- /dev/null +++ b/src/pulsecore/shm.c @@ -0,0 +1,383 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <dirent.h> +#include <signal.h> + +#ifdef HAVE_SYS_MMAN_H +#include <sys/mman.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/log.h> +#include <pulsecore/random.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> +#include <pulsecore/atomic.h> + +#include "shm.h" + +#if defined(__linux__) && !defined(MADV_REMOVE) +#define MADV_REMOVE 9 +#endif + +#define MAX_SHM_SIZE (PA_ALIGN(1024*1024*20)) + +#ifdef __linux__ +/* On Linux we know that the shared memory blocks are files in + * /dev/shm. We can use that information to list all blocks and + * cleanup unused ones */ +#define SHM_PATH "/dev/shm/" +#else +#undef SHM_PATH +#endif + +#define SHM_MARKER ((int) 0xbeefcafe) + +/* We now put this SHM marker at the end of each segment. It's optional to not require a reboot when upgrading, though */ +struct shm_marker { + pa_atomic_t marker; /* 0xbeefcafe */ + pa_atomic_t pid; + void *_reserverd1; + void *_reserverd2; + void *_reserverd3; + void *_reserverd4; +}; + +static char *segment_name(char *fn, size_t l, unsigned id) { + pa_snprintf(fn, l, "/pulse-shm-%u", id); + return fn; +} + +int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode) { + char fn[32]; + int fd = -1; + + pa_assert(m); + pa_assert(size > 0); + pa_assert(size < MAX_SHM_SIZE); + pa_assert(mode >= 0600); + + /* Each time we create a new SHM area, let's first drop all stale + * ones */ + pa_shm_cleanup(); + + /* Round up to make it aligned */ + size = PA_ALIGN(size); + + if (!shared) { + m->id = 0; + m->size = size; + +#ifdef MAP_ANONYMOUS + if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0)) == MAP_FAILED) { + pa_log("mmap() failed: %s", pa_cstrerror(errno)); + goto fail; + } +#elif defined(HAVE_POSIX_MEMALIGN) + { + int r; + + if ((r = posix_memalign(&m->ptr, PA_PAGE_SIZE, size)) < 0) { + pa_log("posix_memalign() failed: %s", pa_cstrerror(r)); + goto fail; + } + } +#else + m->ptr = pa_xmalloc(m->size); +#endif + + m->do_unlink = 0; + + } else { +#ifdef HAVE_SHM_OPEN + struct shm_marker *marker; + + pa_random(&m->id, sizeof(m->id)); + segment_name(fn, sizeof(fn), m->id); + + if ((fd = shm_open(fn, O_RDWR|O_CREAT|O_EXCL, mode & 0444)) < 0) { + pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + m->size = size + PA_ALIGN(sizeof(struct shm_marker)); + + if (ftruncate(fd, m->size) < 0) { + pa_log("ftruncate() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if ((m->ptr = mmap(NULL, m->size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) { + pa_log("mmap() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + /* We store our PID at the end of the shm block, so that we + * can check for dead shm segments later */ + marker = (struct shm_marker*) ((uint8_t*) m->ptr + m->size - PA_ALIGN(sizeof(struct shm_marker))); + pa_atomic_store(&marker->pid, (int) getpid()); + pa_atomic_store(&marker->marker, SHM_MARKER); + + pa_assert_se(close(fd) == 0); + m->do_unlink = 1; +#else + return -1; +#endif + } + + m->shared = shared; + + return 0; + +fail: + +#ifdef HAVE_SHM_OPEN + if (fd >= 0) { + shm_unlink(fn); + pa_close(fd); + } +#endif + + return -1; +} + +void pa_shm_free(pa_shm *m) { + pa_assert(m); + pa_assert(m->ptr); + pa_assert(m->size > 0); + +#ifdef MAP_FAILED + pa_assert(m->ptr != MAP_FAILED); +#endif + + if (!m->shared) { +#ifdef MAP_ANONYMOUS + if (munmap(m->ptr, m->size) < 0) + pa_log("munmap() failed: %s", pa_cstrerror(errno)); +#elif defined(HAVE_POSIX_MEMALIGN) + free(m->ptr); +#else + pa_xfree(m->ptr); +#endif + } else { +#ifdef HAVE_SHM_OPEN + if (munmap(m->ptr, m->size) < 0) + pa_log("munmap() failed: %s", pa_cstrerror(errno)); + + if (m->do_unlink) { + char fn[32]; + + segment_name(fn, sizeof(fn), m->id); + + if (shm_unlink(fn) < 0) + pa_log(" shm_unlink(%s) failed: %s", fn, pa_cstrerror(errno)); + } +#else + /* We shouldn't be here without shm support */ + pa_assert_not_reached(); +#endif + } + + memset(m, 0, sizeof(*m)); +} + +void pa_shm_punch(pa_shm *m, size_t offset, size_t size) { + void *ptr; + size_t o, ps; + + pa_assert(m); + pa_assert(m->ptr); + pa_assert(m->size > 0); + pa_assert(offset+size <= m->size); + +#ifdef MAP_FAILED + pa_assert(m->ptr != MAP_FAILED); +#endif + + /* You're welcome to implement this as NOOP on systems that don't + * support it */ + + /* Align this to multiples of the page size */ + ptr = (uint8_t*) m->ptr + offset; + o = (uint8_t*) ptr - (uint8_t*) PA_PAGE_ALIGN_PTR(ptr); + + if (o > 0) { + ps = PA_PAGE_SIZE; + ptr = (uint8_t*) ptr + (ps - o); + size -= ps - o; + } + +#ifdef MADV_REMOVE + if (madvise(ptr, size, MADV_REMOVE) >= 0) + return; +#endif + +#ifdef MADV_FREE + if (madvise(ptr, size, MADV_FREE) >= 0) + return; +#endif + +#ifdef MADV_DONTNEED + pa_assert_se(madvise(ptr, size, MADV_DONTNEED) == 0); +#elif defined(POSIX_MADV_DONTNEED) + pa_assert_se(posix_madvise(ptr, size, POSIX_MADV_DONTNEED) == 0); +#endif +} + +#ifdef HAVE_SHM_OPEN + +int pa_shm_attach_ro(pa_shm *m, unsigned id) { + char fn[32]; + int fd = -1; + struct stat st; + + pa_assert(m); + + segment_name(fn, sizeof(fn), m->id = id); + + if ((fd = shm_open(fn, O_RDONLY, 0)) < 0) { + if (errno != EACCES) + pa_log("shm_open() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (fstat(fd, &st) < 0) { + pa_log("fstat() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if (st.st_size <= 0 || st.st_size > MAX_SHM_SIZE+PA_ALIGN(sizeof(struct shm_marker)) || PA_ALIGN(st.st_size) != st.st_size) { + pa_log("Invalid shared memory segment size"); + goto fail; + } + + m->size = st.st_size; + + if ((m->ptr = mmap(NULL, m->size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) { + pa_log("mmap() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + m->do_unlink = 0; + m->shared = 1; + + pa_assert_se(pa_close(fd) == 0); + + return 0; + +fail: + if (fd >= 0) + pa_close(fd); + + return -1; +} + +#else /* HAVE_SHM_OPEN */ + +int pa_shm_attach_ro(pa_shm *m, unsigned id) { + return -1; +} + +#endif /* HAVE_SHM_OPEN */ + +int pa_shm_cleanup(void) { + +#ifdef HAVE_SHM_OPEN +#ifdef SHM_PATH + DIR *d; + struct dirent *de; + + if (!(d = opendir(SHM_PATH))) { + pa_log_warn("Failed to read "SHM_PATH": %s", pa_cstrerror(errno)); + return -1; + } + + while ((de = readdir(d))) { + pa_shm seg; + unsigned id; + pid_t pid; + char fn[128]; + struct shm_marker *m; + + if (strncmp(de->d_name, "pulse-shm-", 10)) + continue; + + if (pa_atou(de->d_name + 10, &id) < 0) + continue; + + if (pa_shm_attach_ro(&seg, id) < 0) + continue; + + if (seg.size < PA_ALIGN(sizeof(struct shm_marker))) { + pa_shm_free(&seg); + continue; + } + + m = (struct shm_marker*) ((uint8_t*) seg.ptr + seg.size - PA_ALIGN(sizeof(struct shm_marker))); + + if (pa_atomic_load(&m->marker) != SHM_MARKER) { + pa_shm_free(&seg); + continue; + } + + if (!(pid = (pid_t) pa_atomic_load(&m->pid))) { + pa_shm_free(&seg); + continue; + } + + if (kill(pid, 0) == 0 || errno != ESRCH) { + pa_shm_free(&seg); + continue; + } + + pa_shm_free(&seg); + + /* Ok, the owner of this shms segment is dead, so, let's remove the segment */ + segment_name(fn, sizeof(fn), id); + + if (shm_unlink(fn) < 0 && errno != EACCES) + pa_log_warn("Failed to remove SHM segment %s: %s\n", fn, pa_cstrerror(errno)); + } + + closedir(d); +#endif /* SHM_PATH */ +#endif /* HAVE_SHM_OPEN */ + + return 0; +} diff --git a/src/pulsecore/shm.h b/src/pulsecore/shm.h new file mode 100644 index 00000000..270591de --- /dev/null +++ b/src/pulsecore/shm.h @@ -0,0 +1,46 @@ +#ifndef foopulseshmhfoo +#define foopulseshmhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 <sys/types.h> + +typedef struct pa_shm { + unsigned id; + void *ptr; + size_t size; + int do_unlink; + int shared; +} pa_shm; + +int pa_shm_create_rw(pa_shm *m, size_t size, int shared, mode_t mode); +int pa_shm_attach_ro(pa_shm *m, unsigned id); + +void pa_shm_punch(pa_shm *m, size_t offset, size_t size); + +void pa_shm_free(pa_shm *m); + +int pa_shm_cleanup(void); + +#endif diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c new file mode 100644 index 00000000..07ddb83a --- /dev/null +++ b/src/pulsecore/sink-input.c @@ -0,0 +1,989 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/sample-util.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/play-memblockq.h> +#include <pulsecore/namereg.h> + +#include "sink-input.h" + +#define CONVERT_BUFFER_LENGTH (PA_PAGE_SIZE) +#define SILENCE_BUFFER_LENGTH (PA_PAGE_SIZE*12) +#define MOVE_BUFFER_LENGTH (PA_PAGE_SIZE*256) + +static PA_DEFINE_CHECK_TYPE(pa_sink_input, pa_msgobject); + +static void sink_input_free(pa_object *o); + +pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data) { + pa_assert(data); + + memset(data, 0, sizeof(*data)); + data->resample_method = PA_RESAMPLER_INVALID; + + return data; +} + +void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map) { + pa_assert(data); + + if ((data->channel_map_is_set = !!map)) + data->channel_map = *map; +} + +void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume) { + pa_assert(data); + + if ((data->volume_is_set = !!volume)) + data->volume = *volume; +} + +void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + +void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute) { + pa_assert(data); + + data->muted_is_set = TRUE; + data->muted = !!mute; +} + +pa_sink_input* pa_sink_input_new( + pa_core *core, + pa_sink_input_new_data *data, + pa_sink_input_flags_t flags) { + + pa_sink_input *i; + pa_resampler *resampler = NULL; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_assert(core); + pa_assert(data); + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], data) < 0) + return NULL; + + pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver)); + pa_return_null_if_fail(!data->name || pa_utf8_valid(data->name)); + + if (!data->sink) + data->sink = pa_namereg_get(core, NULL, PA_NAMEREG_SINK, 1); + + pa_return_null_if_fail(data->sink); + pa_return_null_if_fail(pa_sink_get_state(data->sink) != PA_SINK_UNLINKED); + pa_return_null_if_fail(!data->sync_base || (data->sync_base->sink == data->sink && pa_sink_input_get_state(data->sync_base) == PA_SINK_INPUT_CORKED)); + + if (!data->sample_spec_is_set) + data->sample_spec = data->sink->sample_spec; + + pa_return_null_if_fail(pa_sample_spec_valid(&data->sample_spec)); + + if (!data->channel_map_is_set) { + if (data->sink->channel_map.channels == data->sample_spec.channels) + data->channel_map = data->sink->channel_map; + else + pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT)); + } + + pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map)); + pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels); + + if (!data->volume_is_set) + pa_cvolume_reset(&data->volume, data->sample_spec.channels); + + pa_return_null_if_fail(pa_cvolume_valid(&data->volume)); + pa_return_null_if_fail(data->volume.channels == data->sample_spec.channels); + + if (flags & PA_SINK_INPUT_FIX_FORMAT) + data->sample_spec.format = data->sink->sample_spec.format; + + if (flags & PA_SINK_INPUT_FIX_RATE) + data->sample_spec.rate = data->sink->sample_spec.rate; + + if (flags & PA_SINK_INPUT_FIX_CHANNELS) { + data->sample_spec.channels = data->sink->sample_spec.channels; + data->channel_map = data->sink->channel_map; + } + + pa_assert(pa_sample_spec_valid(&data->sample_spec)); + pa_assert(pa_channel_map_valid(&data->channel_map)); + + /* Due to the fixing of the sample spec the volume might not match anymore */ + if (data->volume.channels != data->sample_spec.channels) + pa_cvolume_set(&data->volume, data->sample_spec.channels, pa_cvolume_avg(&data->volume)); + + if (!data->muted_is_set) + data->muted = 0; + + if (data->resample_method == PA_RESAMPLER_INVALID) + data->resample_method = core->resample_method; + + pa_return_null_if_fail(data->resample_method < PA_RESAMPLER_MAX); + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SINK_INPUT_FIXATE], data) < 0) + return NULL; + + if (pa_idxset_size(data->sink->inputs) >= PA_MAX_INPUTS_PER_SINK) { + pa_log_warn("Failed to create sink input: too many inputs per sink."); + return NULL; + } + + if ((flags & PA_SINK_INPUT_VARIABLE_RATE) || + !pa_sample_spec_equal(&data->sample_spec, &data->sink->sample_spec) || + !pa_channel_map_equal(&data->channel_map, &data->sink->channel_map)) { + + if (!(resampler = pa_resampler_new( + core->mempool, + &data->sample_spec, &data->channel_map, + &data->sink->sample_spec, &data->sink->channel_map, + data->resample_method, + ((flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (core->disable_remixing || (flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) { + pa_log_warn("Unsupported resampling operation."); + return NULL; + } + + data->resample_method = pa_resampler_get_method(resampler); + } + + i = pa_msgobject_new(pa_sink_input); + i->parent.parent.free = sink_input_free; + i->parent.process_msg = pa_sink_input_process_msg; + + i->core = core; + i->state = PA_SINK_INPUT_INIT; + i->flags = flags; + i->name = pa_xstrdup(data->name); + i->driver = pa_xstrdup(data->driver); + i->module = data->module; + i->sink = data->sink; + i->client = data->client; + + i->resample_method = data->resample_method; + i->sample_spec = data->sample_spec; + i->channel_map = data->channel_map; + + i->volume = data->volume; + i->muted = data->muted; + + if (data->sync_base) { + i->sync_next = data->sync_base->sync_next; + i->sync_prev = data->sync_base; + + if (data->sync_base->sync_next) + data->sync_base->sync_next->sync_prev = i; + data->sync_base->sync_next = i; + } else + i->sync_next = i->sync_prev = NULL; + + i->peek = NULL; + i->drop = NULL; + i->kill = NULL; + i->get_latency = NULL; + i->attach = NULL; + i->detach = NULL; + i->suspend = NULL; + i->moved = NULL; + i->userdata = NULL; + + i->thread_info.state = i->state; + pa_atomic_store(&i->thread_info.drained, 1); + i->thread_info.sample_spec = i->sample_spec; + i->thread_info.silence_memblock = NULL; + i->thread_info.move_silence = 0; + pa_memchunk_reset(&i->thread_info.resampled_chunk); + i->thread_info.resampler = resampler; + i->thread_info.volume = i->volume; + i->thread_info.muted = i->muted; + i->thread_info.attached = FALSE; + + pa_assert_se(pa_idxset_put(core->sink_inputs, pa_sink_input_ref(i), &i->index) == 0); + pa_assert_se(pa_idxset_put(i->sink->inputs, i, NULL) == 0); + + pa_log_info("Created input %u \"%s\" on %s with sample spec %s and channel map %s", + i->index, + i->name, + i->sink->name, + pa_sample_spec_snprint(st, sizeof(st), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map)); + + /* Don't forget to call pa_sink_input_put! */ + + return i; +} + +static void update_n_corked(pa_sink_input *i, pa_sink_input_state_t state) { + pa_assert(i); + + if (i->state == PA_SINK_INPUT_CORKED && state != PA_SINK_INPUT_CORKED) + pa_assert_se(i->sink->n_corked -- >= 1); + else if (i->state != PA_SINK_INPUT_CORKED && state == PA_SINK_INPUT_CORKED) + i->sink->n_corked++; + + pa_sink_update_status(i->sink); +} + +static int sink_input_set_state(pa_sink_input *i, pa_sink_input_state_t state) { + pa_sink_input *ssync; + pa_assert(i); + + if (state == PA_SINK_INPUT_DRAINED) + state = PA_SINK_INPUT_RUNNING; + + if (i->state == state) + return 0; + + if (pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0) + return -1; + + update_n_corked(i, state); + i->state = state; + + for (ssync = i->sync_prev; ssync; ssync = ssync->sync_prev) { + update_n_corked(ssync, state); + ssync->state = state; + } + for (ssync = i->sync_next; ssync; ssync = ssync->sync_next) { + update_n_corked(ssync, state); + ssync->state = state; + } + + if (state != PA_SINK_INPUT_UNLINKED) + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED], i); + + return 0; +} + +void pa_sink_input_unlink(pa_sink_input *i) { + pa_bool_t linked; + pa_assert(i); + + /* See pa_sink_unlink() for a couple of comments how this function + * works */ + + pa_sink_input_ref(i); + + linked = PA_SINK_INPUT_LINKED(i->state); + + if (linked) + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK], i); + + if (i->sync_prev) + i->sync_prev->sync_next = i->sync_next; + if (i->sync_next) + i->sync_next->sync_prev = i->sync_prev; + + i->sync_prev = i->sync_next = NULL; + + pa_idxset_remove_by_data(i->sink->core->sink_inputs, i, NULL); + if (pa_idxset_remove_by_data(i->sink->inputs, i, NULL)) + pa_sink_input_unref(i); + + if (linked) { + pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT, i, 0, NULL); + sink_input_set_state(i, PA_SINK_INPUT_UNLINKED); + pa_sink_update_status(i->sink); + } else + i->state = PA_SINK_INPUT_UNLINKED; + + i->peek = NULL; + i->drop = NULL; + i->kill = NULL; + i->get_latency = NULL; + i->attach = NULL; + i->detach = NULL; + i->suspend = NULL; + i->moved = NULL; + + if (linked) { + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_REMOVE, i->index); + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], i); + } + + i->sink = NULL; + pa_sink_input_unref(i); +} + +static void sink_input_free(pa_object *o) { + pa_sink_input* i = PA_SINK_INPUT(o); + + pa_assert(i); + pa_assert(pa_sink_input_refcnt(i) == 0); + + if (PA_SINK_INPUT_LINKED(i->state)) + pa_sink_input_unlink(i); + + pa_log_info("Freeing output %u \"%s\"", i->index, i->name); + + pa_assert(!i->thread_info.attached); + + if (i->thread_info.resampled_chunk.memblock) + pa_memblock_unref(i->thread_info.resampled_chunk.memblock); + + if (i->thread_info.resampler) + pa_resampler_free(i->thread_info.resampler); + + if (i->thread_info.silence_memblock) + pa_memblock_unref(i->thread_info.silence_memblock); + + pa_xfree(i->name); + pa_xfree(i->driver); + pa_xfree(i); +} + +void pa_sink_input_put(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + pa_assert(i->state == PA_SINK_INPUT_INIT); + pa_assert(i->peek); + pa_assert(i->drop); + + i->thread_info.state = i->state = i->flags & PA_SINK_INPUT_START_CORKED ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING; + i->thread_info.volume = i->volume; + i->thread_info.muted = i->muted; + + if (i->state == PA_SINK_INPUT_CORKED) + i->sink->n_corked++; + + pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL); + pa_sink_update_status(i->sink); + + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, i->index); + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], i); + + /* Please note that if you change something here, you have to + change something in pa_sink_input_move() with the ghost stream + registration too. */ +} + +void pa_sink_input_kill(pa_sink_input*i) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->state)); + + if (i->kill) + i->kill(i); +} + +pa_usec_t pa_sink_input_get_latency(pa_sink_input *i) { + pa_usec_t r = 0; + + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->state)); + + if (pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_GET_LATENCY, &r, 0, NULL) < 0) + r = 0; + + if (i->get_latency) + r += i->get_latency(i); + + return r; +} + +/* Called from thread context */ +int pa_sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk, pa_cvolume *volume) { + int ret = -1; + int do_volume_adj_here; + int volume_is_norm; + size_t block_size_max; + + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(length, &i->sink->sample_spec)); + pa_assert(chunk); + pa_assert(volume); + + if (!i->peek || !i->drop || i->thread_info.state == PA_SINK_INPUT_CORKED) + goto finish; + + pa_assert(i->thread_info.state == PA_SINK_INPUT_RUNNING || i->thread_info.state == PA_SINK_INPUT_DRAINED); + + /* Default buffer size */ + if (length <= 0) + length = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sink->sample_spec); + + /* Make sure the buffer fits in the mempool tile */ + block_size_max = pa_mempool_block_size_max(i->sink->core->mempool); + if (length > block_size_max) + length = pa_frame_align(block_size_max, &i->sink->sample_spec); + + if (i->thread_info.move_silence > 0) { + size_t l; + + /* We have just been moved and shall play some silence for a + * while until the old sink has drained its playback buffer */ + + if (!i->thread_info.silence_memblock) + i->thread_info.silence_memblock = pa_silence_memblock_new( + i->sink->core->mempool, + &i->sink->sample_spec, + pa_frame_align(SILENCE_BUFFER_LENGTH, &i->sink->sample_spec)); + + chunk->memblock = pa_memblock_ref(i->thread_info.silence_memblock); + chunk->index = 0; + l = pa_memblock_get_length(chunk->memblock); + chunk->length = i->thread_info.move_silence < l ? i->thread_info.move_silence : l; + + ret = 0; + do_volume_adj_here = 1; + goto finish; + } + + if (!i->thread_info.resampler) { + do_volume_adj_here = 0; /* FIXME??? */ + ret = i->peek(i, length, chunk); + goto finish; + } + + do_volume_adj_here = !pa_channel_map_equal(&i->channel_map, &i->sink->channel_map); + volume_is_norm = pa_cvolume_is_norm(&i->thread_info.volume) && !i->thread_info.muted; + + while (!i->thread_info.resampled_chunk.memblock) { + pa_memchunk tchunk; + size_t l, rmbs; + + l = pa_resampler_request(i->thread_info.resampler, length); + + if (l <= 0) + l = pa_frame_align(CONVERT_BUFFER_LENGTH, &i->sample_spec); + + rmbs = pa_resampler_max_block_size(i->thread_info.resampler); + if (l > rmbs) + l = rmbs; + + if ((ret = i->peek(i, l, &tchunk)) < 0) + goto finish; + + pa_assert(tchunk.length > 0); + + if (tchunk.length > l) + tchunk.length = l; + + i->drop(i, tchunk.length); + + /* It might be necessary to adjust the volume here */ + if (do_volume_adj_here && !volume_is_norm) { + pa_memchunk_make_writable(&tchunk, 0); + + if (i->thread_info.muted) + pa_silence_memchunk(&tchunk, &i->thread_info.sample_spec); + else + pa_volume_memchunk(&tchunk, &i->thread_info.sample_spec, &i->thread_info.volume); + } + + pa_resampler_run(i->thread_info.resampler, &tchunk, &i->thread_info.resampled_chunk); + pa_memblock_unref(tchunk.memblock); + } + + pa_assert(i->thread_info.resampled_chunk.memblock); + pa_assert(i->thread_info.resampled_chunk.length > 0); + + *chunk = i->thread_info.resampled_chunk; + pa_memblock_ref(i->thread_info.resampled_chunk.memblock); + + ret = 0; + +finish: + + if (ret >= 0) + pa_atomic_store(&i->thread_info.drained, 0); + else if (ret < 0) + pa_atomic_store(&i->thread_info.drained, 1); + + if (ret >= 0) { + /* Let's see if we had to apply the volume adjustment + * ourselves, or if this can be done by the sink for us */ + + if (do_volume_adj_here) + /* We had different channel maps, so we already did the adjustment */ + pa_cvolume_reset(volume, i->sink->sample_spec.channels); + else if (i->thread_info.muted) + /* We've both the same channel map, so let's have the sink do the adjustment for us*/ + pa_cvolume_mute(volume, i->sink->sample_spec.channels); + else + *volume = i->thread_info.volume; + } + + return ret; +} + +/* Called from thread context */ +void pa_sink_input_drop(pa_sink_input *i, size_t length) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->thread_info.state)); + pa_assert(pa_frame_aligned(length, &i->sink->sample_spec)); + pa_assert(length > 0); + + if (!i->peek || !i->drop || i->thread_info.state == PA_SINK_INPUT_CORKED) + return; + + if (i->thread_info.move_silence > 0) { + + if (i->thread_info.move_silence >= length) { + i->thread_info.move_silence -= length; + length = 0; + } else { + length -= i->thread_info.move_silence; + i->thread_info.move_silence = 0; + } + + if (i->thread_info.move_silence <= 0) { + if (i->thread_info.silence_memblock) { + pa_memblock_unref(i->thread_info.silence_memblock); + i->thread_info.silence_memblock = NULL; + } + } + + if (length <= 0) + return; + } + + if (i->thread_info.resampled_chunk.memblock) { + size_t l = length; + + if (l > i->thread_info.resampled_chunk.length) + l = i->thread_info.resampled_chunk.length; + + i->thread_info.resampled_chunk.index += l; + i->thread_info.resampled_chunk.length -= l; + + if (i->thread_info.resampled_chunk.length <= 0) { + pa_memblock_unref(i->thread_info.resampled_chunk.memblock); + pa_memchunk_reset(&i->thread_info.resampled_chunk); + } + + length -= l; + } + + if (length > 0) { + + if (i->thread_info.resampler) { + /* So, we have a resampler. To avoid discontinuities we + * have to actually read all data that could be read and + * pass it through the resampler. */ + + while (length > 0) { + pa_memchunk chunk; + pa_cvolume volume; + + if (pa_sink_input_peek(i, length, &chunk, &volume) >= 0) { + size_t l; + + pa_memblock_unref(chunk.memblock); + + l = chunk.length; + if (l > length) + l = length; + + pa_sink_input_drop(i, l); + length -= l; + + } else { + size_t l; + + l = pa_resampler_request(i->thread_info.resampler, length); + + /* Hmmm, peeking failed, so let's at least drop + * the right amount of data */ + + if (l > 0) + if (i->drop) + i->drop(i, l); + + break; + } + } + + } else { + + /* We have no resampler, hence let's just drop the data */ + + if (i->drop) + i->drop(i, length); + } + } +} + +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->state)); + + if (pa_cvolume_equal(&i->volume, volume)) + return; + + i->volume = *volume; + + pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_VOLUME, pa_xnewdup(struct pa_cvolume, volume, 1), 0, NULL, pa_xfree); + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); +} + +const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->state)); + + return &i->volume; +} + +void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute) { + pa_assert(i); + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->state)); + + if (!i->muted == !mute) + return; + + i->muted = mute; + + pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL); + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); +} + +int pa_sink_input_get_mute(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->state)); + + return !!i->muted; +} + +void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->state)); + + sink_input_set_state(i, b ? PA_SINK_INPUT_CORKED : PA_SINK_INPUT_RUNNING); +} + +int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate) { + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->state)); + pa_return_val_if_fail(i->thread_info.resampler, -1); + + if (i->sample_spec.rate == rate) + return 0; + + i->sample_spec.rate = rate; + + pa_asyncmsgq_post(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL); + + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + return 0; +} + +void pa_sink_input_set_name(pa_sink_input *i, const char *name) { + pa_sink_input_assert_ref(i); + + if (!i->name && !name) + return; + + if (i->name && name && !strcmp(i->name, name)) + return; + + pa_xfree(i->name); + i->name = pa_xstrdup(name); + + if (PA_SINK_INPUT_LINKED(i->state)) { + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_NAME_CHANGED], i); + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + } +} + +pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + return i->resample_method; +} + +int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately) { + pa_resampler *new_resampler; + pa_sink *origin; + pa_usec_t silence_usec = 0; + pa_sink_input_move_info info; + pa_sink_input_move_hook_data hook_data; + + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->state)); + pa_sink_assert_ref(dest); + + origin = i->sink; + + if (dest == origin) + return 0; + + if (i->flags & PA_SINK_INPUT_DONT_MOVE) + return -1; + + if (i->sync_next || i->sync_prev) { + pa_log_warn("Moving synchronised streams not supported."); + return -1; + } + + if (pa_idxset_size(dest->inputs) >= PA_MAX_INPUTS_PER_SINK) { + pa_log_warn("Failed to move sink input: too many inputs per sink."); + return -1; + } + + if (i->thread_info.resampler && + pa_sample_spec_equal(&origin->sample_spec, &dest->sample_spec) && + pa_channel_map_equal(&origin->channel_map, &dest->channel_map)) + + /* Try to reuse the old resampler if possible */ + new_resampler = i->thread_info.resampler; + + else if ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) || + !pa_sample_spec_equal(&i->sample_spec, &dest->sample_spec) || + !pa_channel_map_equal(&i->channel_map, &dest->channel_map)) { + + /* Okey, we need a new resampler for the new sink */ + + if (!(new_resampler = pa_resampler_new( + dest->core->mempool, + &i->sample_spec, &i->channel_map, + &dest->sample_spec, &dest->channel_map, + i->resample_method, + ((i->flags & PA_SINK_INPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((i->flags & PA_SINK_INPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (i->core->disable_remixing || (i->flags & PA_SINK_INPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) { + pa_log_warn("Unsupported resampling operation."); + return -1; + } + } else + new_resampler = NULL; + + hook_data.sink_input = i; + hook_data.destination = dest; + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE], &hook_data); + + memset(&info, 0, sizeof(info)); + info.sink_input = i; + + if (!immediately) { + pa_usec_t old_latency, new_latency; + + /* Let's do a little bit of Voodoo for compensating latency + * differences. We assume that the accuracy for our + * estimations is still good enough, even though we do these + * operations non-atomic. */ + + old_latency = pa_sink_get_latency(origin); + new_latency = pa_sink_get_latency(dest); + + /* The already resampled data should go to the old sink */ + + if (old_latency >= new_latency) { + + /* The latency of the old sink is larger than the latency + * of the new sink. Therefore to compensate for the + * difference we to play silence on the new one for a + * while */ + + silence_usec = old_latency - new_latency; + + } else { + + /* The latency of new sink is larger than the latency of + * the old sink. Therefore we have to precompute a little + * and make sure that this is still played on the old + * sink, until we can play the first sample on the new + * sink.*/ + + info.buffer_bytes = pa_usec_to_bytes(new_latency - old_latency, &origin->sample_spec); + } + + /* Okey, let's move it */ + + if (info.buffer_bytes > 0) { + + info.ghost_sink_input = pa_memblockq_sink_input_new( + origin, + "Ghost Stream", + &origin->sample_spec, + &origin->channel_map, + NULL, + NULL); + + info.ghost_sink_input->thread_info.state = info.ghost_sink_input->state = PA_SINK_INPUT_RUNNING; + info.ghost_sink_input->thread_info.volume = info.ghost_sink_input->volume; + info.ghost_sink_input->thread_info.muted = info.ghost_sink_input->muted; + + info.buffer = pa_memblockq_new(0, MOVE_BUFFER_LENGTH, 0, pa_frame_size(&origin->sample_spec), 0, 0, NULL); + } + } + + pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, &info, 0, NULL); + + if (info.ghost_sink_input) { + /* Basically, do what pa_sink_input_put() does ...*/ + + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_NEW, info.ghost_sink_input->index); + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], info.ghost_sink_input); + pa_sink_input_unref(info.ghost_sink_input); + } + + pa_idxset_remove_by_data(origin->inputs, i, NULL); + pa_idxset_put(dest->inputs, i, NULL); + i->sink = dest; + + if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED) { + pa_assert_se(origin->n_corked-- >= 1); + dest->n_corked++; + } + + /* Replace resampler */ + if (new_resampler != i->thread_info.resampler) { + if (i->thread_info.resampler) + pa_resampler_free(i->thread_info.resampler); + i->thread_info.resampler = new_resampler; + + /* if the resampler changed, the silence memblock is + * probably invalid now, too */ + if (i->thread_info.silence_memblock) { + pa_memblock_unref(i->thread_info.silence_memblock); + i->thread_info.silence_memblock = NULL; + } + } + + /* Dump already resampled data */ + if (i->thread_info.resampled_chunk.memblock) { + /* Hmm, this data has already been added to the ghost queue, presumably, hence let's sleep a little bit longer */ + silence_usec += pa_bytes_to_usec(i->thread_info.resampled_chunk.length, &origin->sample_spec); + pa_memblock_unref(i->thread_info.resampled_chunk.memblock); + pa_memchunk_reset(&i->thread_info.resampled_chunk); + } + + /* Calculate the new sleeping time */ + if (immediately) + i->thread_info.move_silence = 0; + else + i->thread_info.move_silence = pa_usec_to_bytes( + pa_bytes_to_usec(i->thread_info.move_silence, &origin->sample_spec) + + silence_usec, + &dest->sample_spec); + + pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i->sink), PA_SINK_MESSAGE_ADD_INPUT, i, 0, NULL); + + pa_sink_update_status(origin); + pa_sink_update_status(dest); + + if (i->moved) + i->moved(i); + + pa_hook_fire(&i->sink->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_POST], i); + + pa_log_debug("Successfully moved sink input %i from %s to %s.", i->index, origin->name, dest->name); + + /* Notify everyone */ + pa_subscription_post(i->sink->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); + + return 0; +} + +/* Called from thread context */ +int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink_input *i = PA_SINK_INPUT(o); + + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_LINKED(i->thread_info.state)); + + switch (code) { + case PA_SINK_INPUT_MESSAGE_SET_VOLUME: + i->thread_info.volume = *((pa_cvolume*) userdata); + return 0; + + case PA_SINK_INPUT_MESSAGE_SET_MUTE: + i->thread_info.muted = PA_PTR_TO_UINT(userdata); + return 0; + + case PA_SINK_INPUT_MESSAGE_GET_LATENCY: { + pa_usec_t *r = userdata; + + if (i->thread_info.resampled_chunk.memblock) + *r += pa_bytes_to_usec(i->thread_info.resampled_chunk.length, &i->sink->sample_spec); + + if (i->thread_info.move_silence) + *r += pa_bytes_to_usec(i->thread_info.move_silence, &i->sink->sample_spec); + + return 0; + } + + case PA_SINK_INPUT_MESSAGE_SET_RATE: + + i->thread_info.sample_spec.rate = PA_PTR_TO_UINT(userdata); + pa_resampler_set_input_rate(i->thread_info.resampler, PA_PTR_TO_UINT(userdata)); + + return 0; + + case PA_SINK_INPUT_MESSAGE_SET_STATE: { + pa_sink_input *ssync; + + if ((PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_DRAINED || PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_RUNNING) && + (i->thread_info.state != PA_SINK_INPUT_DRAINED) && (i->thread_info.state != PA_SINK_INPUT_RUNNING)) + pa_atomic_store(&i->thread_info.drained, 1); + + i->thread_info.state = PA_PTR_TO_UINT(userdata); + + for (ssync = i->thread_info.sync_prev; ssync; ssync = ssync->thread_info.sync_prev) { + if ((PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_DRAINED || PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_RUNNING) && + (ssync->thread_info.state != PA_SINK_INPUT_DRAINED) && (ssync->thread_info.state != PA_SINK_INPUT_RUNNING)) + pa_atomic_store(&ssync->thread_info.drained, 1); + ssync->thread_info.state = PA_PTR_TO_UINT(userdata); + } + + for (ssync = i->thread_info.sync_next; ssync; ssync = ssync->thread_info.sync_next) { + if ((PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_DRAINED || PA_PTR_TO_UINT(userdata) == PA_SINK_INPUT_RUNNING) && + (ssync->thread_info.state != PA_SINK_INPUT_DRAINED) && (ssync->thread_info.state != PA_SINK_INPUT_RUNNING)) + pa_atomic_store(&ssync->thread_info.drained, 1); + ssync->thread_info.state = PA_PTR_TO_UINT(userdata); + } + + return 0; + } + } + + return -1; +} + +pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + if (i->state == PA_SINK_INPUT_RUNNING || i->state == PA_SINK_INPUT_DRAINED) + return pa_atomic_load(&i->thread_info.drained) ? PA_SINK_INPUT_DRAINED : PA_SINK_INPUT_RUNNING; + + return i->state; +} diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h new file mode 100644 index 00000000..8975db9e --- /dev/null +++ b/src/pulsecore/sink-input.h @@ -0,0 +1,251 @@ +#ifndef foopulsesinkinputhfoo +#define foopulsesinkinputhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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> + +typedef struct pa_sink_input pa_sink_input; + +#include <pulse/sample.h> +#include <pulsecore/hook-list.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/resampler.h> +#include <pulsecore/module.h> +#include <pulsecore/client.h> +#include <pulsecore/sink.h> +#include <pulsecore/core.h> + +typedef enum pa_sink_input_state { + PA_SINK_INPUT_INIT, /*< The stream is not active yet, because pa_sink_put() has not been called yet */ + PA_SINK_INPUT_DRAINED, /*< The stream stopped playing because there was no data to play */ + PA_SINK_INPUT_RUNNING, /*< The stream is alive and kicking */ + PA_SINK_INPUT_CORKED, /*< The stream was corked on user request */ + PA_SINK_INPUT_UNLINKED /*< The stream is dead */ +} pa_sink_input_state_t; + +static inline pa_bool_t PA_SINK_INPUT_LINKED(pa_sink_input_state_t x) { + return x == PA_SINK_INPUT_DRAINED || x == PA_SINK_INPUT_RUNNING || x == PA_SINK_INPUT_CORKED; +} + +typedef enum pa_sink_input_flags { + PA_SINK_INPUT_VARIABLE_RATE = 1, + PA_SINK_INPUT_DONT_MOVE = 2, + PA_SINK_INPUT_START_CORKED = 4, + PA_SINK_INPUT_NO_REMAP = 8, + PA_SINK_INPUT_NO_REMIX = 16, + PA_SINK_INPUT_FIX_FORMAT = 32, + PA_SINK_INPUT_FIX_RATE = 64, + PA_SINK_INPUT_FIX_CHANNELS = 128 +} pa_sink_input_flags_t; + +struct pa_sink_input { + pa_msgobject parent; + + uint32_t index; + pa_core *core; + + /* Please note that this state should only be read with + * pa_sink_input_get_state(). That function will transparently + * merge the thread_info.drained value in. */ + pa_sink_input_state_t state; + pa_sink_input_flags_t flags; + + char *name, *driver; /* may be NULL */ + pa_module *module; /* may be NULL */ + pa_client *client; /* may be NULL */ + + pa_sink *sink; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + + pa_sink_input *sync_prev, *sync_next; + + pa_cvolume volume; + pa_bool_t muted; + + /* Returns the chunk of audio data (but doesn't drop it + * yet!). Returns -1 on failure. Called from IO thread context. If + * data needs to be generated from scratch then please in the + * specified length. This is an optimization only. If less data is + * available, it's fine to return a smaller block. If more data is + * already ready, it is better to return the full block.*/ + int (*peek) (pa_sink_input *i, size_t length, pa_memchunk *chunk); + + /* Drops the specified number of bytes, usually called right after + * peek(), but not necessarily. Called from IO thread context. */ + void (*drop) (pa_sink_input *i, size_t length); + + /* If non-NULL this function is called when the input is first + * connected to a sink or when the rtpoll/asyncmsgq fields + * change. You usually don't need to implement this function + * unless you rewrite a sink that is piggy-backed onto + * another. Called from IO thread context */ + void (*attach) (pa_sink_input *i); /* may be NULL */ + + /* If non-NULL this function is called when the output is + * disconnected from its sink. Called from IO thread context */ + void (*detach) (pa_sink_input *i); /* may be NULL */ + + /* If non-NULL called whenever the the sink this input is attached + * to suspends or resumes. Called from main context */ + void (*suspend) (pa_sink_input *i, pa_bool_t b); /* may be NULL */ + + /* If non-NULL called whenever the the sink this input is attached + * to changes. Called from main context */ + void (*moved) (pa_sink_input *i); /* may be NULL */ + + /* Supposed to unlink and destroy this stream. Called from main + * context. */ + void (*kill) (pa_sink_input *i); /* may be NULL */ + + /* Return the current latency (i.e. length of bufferd audio) of + this stream. Called from main context. If NULL a + PA_SINK_INPUT_MESSAGE_GET_LATENCY message is sent to the IO thread + instead. */ + pa_usec_t (*get_latency) (pa_sink_input *i); /* may be NULL */ + + pa_resample_method_t resample_method; + + struct { + pa_sink_input_state_t state; + pa_atomic_t drained; + + pa_bool_t attached; /* True only between ->attach() and ->detach() calls */ + + pa_sample_spec sample_spec; + + pa_memchunk resampled_chunk; + pa_resampler *resampler; /* may be NULL */ + + /* Some silence to play before the actual data. This is used to + * compensate for latency differences when moving a sink input + * "hot" between sinks. */ + size_t move_silence; + pa_memblock *silence_memblock; /* may be NULL */ + + pa_sink_input *sync_prev, *sync_next; + + pa_cvolume volume; + pa_bool_t muted; + } thread_info; + + void *userdata; +}; + +PA_DECLARE_CLASS(pa_sink_input); +#define PA_SINK_INPUT(o) pa_sink_input_cast(o) + +enum { + PA_SINK_INPUT_MESSAGE_SET_VOLUME, + PA_SINK_INPUT_MESSAGE_SET_MUTE, + PA_SINK_INPUT_MESSAGE_GET_LATENCY, + PA_SINK_INPUT_MESSAGE_SET_RATE, + PA_SINK_INPUT_MESSAGE_SET_STATE, + PA_SINK_INPUT_MESSAGE_MAX +}; + +typedef struct pa_sink_input_new_data { + const char *name, *driver; + pa_module *module; + pa_client *client; + + pa_sink *sink; + + pa_sample_spec sample_spec; + pa_bool_t sample_spec_is_set; + pa_channel_map channel_map; + pa_bool_t channel_map_is_set; + + pa_cvolume volume; + pa_bool_t volume_is_set; + pa_bool_t muted; + pa_bool_t muted_is_set; + + pa_resample_method_t resample_method; + + pa_sink_input *sync_base; +} pa_sink_input_new_data; + +typedef struct pa_sink_input_move_hook_data { + pa_sink_input *sink_input; + pa_sink *destination; +} pa_sink_input_move_hook_data; + +pa_sink_input_new_data* pa_sink_input_new_data_init(pa_sink_input_new_data *data); +void pa_sink_input_new_data_set_sample_spec(pa_sink_input_new_data *data, const pa_sample_spec *spec); +void pa_sink_input_new_data_set_channel_map(pa_sink_input_new_data *data, const pa_channel_map *map); +void pa_sink_input_new_data_set_volume(pa_sink_input_new_data *data, const pa_cvolume *volume); +void pa_sink_input_new_data_set_muted(pa_sink_input_new_data *data, pa_bool_t mute); + +/* To be called by the implementing module only */ + +pa_sink_input* pa_sink_input_new( + pa_core *core, + pa_sink_input_new_data *data, + pa_sink_input_flags_t flags); + +void pa_sink_input_put(pa_sink_input *i); +void pa_sink_input_unlink(pa_sink_input* i); + +void pa_sink_input_set_name(pa_sink_input *i, const char *name); + +/* Callable by everyone */ + +/* External code may request disconnection with this function */ +void pa_sink_input_kill(pa_sink_input*i); + +pa_usec_t pa_sink_input_get_latency(pa_sink_input *i); + +void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume); +const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i); +void pa_sink_input_set_mute(pa_sink_input *i, pa_bool_t mute); +int pa_sink_input_get_mute(pa_sink_input *i); + +void pa_sink_input_cork(pa_sink_input *i, pa_bool_t b); + +int pa_sink_input_set_rate(pa_sink_input *i, uint32_t rate); + +pa_resample_method_t pa_sink_input_get_resample_method(pa_sink_input *i); + +int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, int immediately); + +pa_sink_input_state_t pa_sink_input_get_state(pa_sink_input *i); + +/* To be used exclusively by the sink driver thread */ + +int pa_sink_input_peek(pa_sink_input *i, size_t length, pa_memchunk *chunk, pa_cvolume *volume); +void pa_sink_input_drop(pa_sink_input *i, size_t length); +int pa_sink_input_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +typedef struct pa_sink_input_move_info { + pa_sink_input *sink_input; + pa_sink_input *ghost_sink_input; + pa_memblockq *buffer; + size_t buffer_bytes; +} pa_sink_input_move_info; + +#endif diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c new file mode 100644 index 00000000..9adb6097 --- /dev/null +++ b/src/pulsecore/sink.c @@ -0,0 +1,1066 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <string.h> +#include <stdio.h> + +#include <pulse/introspect.h> +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/sink-input.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-util.h> +#include <pulsecore/sample-util.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/play-memblockq.h> + +#include "sink.h" + +#define MAX_MIX_CHANNELS 32 +#define MIX_BUFFER_LENGTH (PA_PAGE_SIZE) +#define SILENCE_BUFFER_LENGTH (PA_PAGE_SIZE*12) + +static PA_DEFINE_CHECK_TYPE(pa_sink, pa_msgobject); + +static void sink_free(pa_object *s); + +pa_sink* pa_sink_new( + pa_core *core, + const char *driver, + const char *name, + int fail, + const pa_sample_spec *spec, + const pa_channel_map *map) { + + pa_sink *s; + char *n = NULL; + char st[256]; + pa_channel_map tmap; + + pa_assert(core); + pa_assert(name); + pa_assert(spec); + + pa_return_null_if_fail(pa_sample_spec_valid(spec)); + + if (!map) + pa_return_null_if_fail((map = pa_channel_map_init_auto(&tmap, spec->channels, PA_CHANNEL_MAP_DEFAULT))); + + pa_return_null_if_fail(map && pa_channel_map_valid(map)); + pa_return_null_if_fail(map->channels == spec->channels); + pa_return_null_if_fail(!driver || pa_utf8_valid(driver)); + pa_return_null_if_fail(name && pa_utf8_valid(name) && *name); + + s = pa_msgobject_new(pa_sink); + + if (!(name = pa_namereg_register(core, name, PA_NAMEREG_SINK, s, fail))) { + pa_xfree(s); + return NULL; + } + + s->parent.parent.free = sink_free; + s->parent.process_msg = pa_sink_process_msg; + + s->core = core; + s->state = PA_SINK_INIT; + s->flags = 0; + s->name = pa_xstrdup(name); + s->description = NULL; + s->driver = pa_xstrdup(driver); + s->module = NULL; + + s->sample_spec = *spec; + s->channel_map = *map; + + s->inputs = pa_idxset_new(NULL, NULL); + s->n_corked = 0; + + pa_cvolume_reset(&s->volume, spec->channels); + s->muted = FALSE; + s->refresh_volume = s->refresh_mute = FALSE; + + s->get_latency = NULL; + s->set_volume = NULL; + s->get_volume = NULL; + s->set_mute = NULL; + s->get_mute = NULL; + s->set_state = NULL; + s->userdata = NULL; + + s->asyncmsgq = NULL; + s->rtpoll = NULL; + s->silence = NULL; + + pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0); + + pa_sample_spec_snprint(st, sizeof(st), spec); + pa_log_info("Created sink %u \"%s\" with sample spec \"%s\"", s->index, s->name, st); + + n = pa_sprintf_malloc("%s.monitor", name); + + if (!(s->monitor_source = pa_source_new(core, driver, n, 0, spec, map))) + pa_log_warn("Failed to create monitor source."); + else { + char *d; + s->monitor_source->monitor_of = s; + d = pa_sprintf_malloc("Monitor Source of %s", s->name); + pa_source_set_description(s->monitor_source, d); + pa_xfree(d); + } + + pa_xfree(n); + + s->thread_info.inputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + s->thread_info.soft_volume = s->volume; + s->thread_info.soft_muted = s->muted; + s->thread_info.state = s->state; + + return s; +} + +static int sink_set_state(pa_sink *s, pa_sink_state_t state) { + int ret; + pa_bool_t suspend_change; + + pa_assert(s); + + if (s->state == state) + return 0; + + suspend_change = + (s->state == PA_SINK_SUSPENDED && PA_SINK_OPENED(state)) || + (PA_SINK_OPENED(s->state) && state == PA_SINK_SUSPENDED); + + if (s->set_state) + if ((ret = s->set_state(s, state)) < 0) + return -1; + + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0) + return -1; + + s->state = state; + + if (suspend_change) { + pa_sink_input *i; + uint32_t idx; + + /* We're suspending or resuming, tell everyone about it */ + + for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) + if (i->suspend) + i->suspend(i, state == PA_SINK_SUSPENDED); + } + + if (state != PA_SINK_UNLINKED) /* if we enter UNLINKED state pa_sink_unlink() will fire the apropriate events */ + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], s); + + return 0; +} + +void pa_sink_put(pa_sink* s) { + pa_sink_assert_ref(s); + + pa_assert(s->state == PA_SINK_INIT); + pa_assert(s->asyncmsgq); + pa_assert(s->rtpoll); + + pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0); + + pa_source_put(s->monitor_source); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_NEW, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_NEW_POST], s); +} + +void pa_sink_unlink(pa_sink* s) { + pa_bool_t linked; + pa_sink_input *i, *j = NULL; + + pa_assert(s); + + /* Please note that pa_sink_unlink() does more than simply + * reversing pa_sink_put(). It also undoes the registrations + * already done in pa_sink_new()! */ + + /* All operations here shall be idempotent, i.e. pa_sink_unlink() + * may be called multiple times on the same sink without bad + * effects. */ + + linked = PA_SINK_LINKED(s->state); + + if (linked) + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK], s); + + if (s->state != PA_SINK_UNLINKED) + pa_namereg_unregister(s->core, s->name); + pa_idxset_remove_by_data(s->core->sinks, s, NULL); + + while ((i = pa_idxset_first(s->inputs, NULL))) { + pa_assert(i != j); + pa_sink_input_kill(i); + j = i; + } + + if (linked) + sink_set_state(s, PA_SINK_UNLINKED); + else + s->state = PA_SINK_UNLINKED; + + s->get_latency = NULL; + s->get_volume = NULL; + s->set_volume = NULL; + s->set_mute = NULL; + s->get_mute = NULL; + s->set_state = NULL; + + if (s->monitor_source) + pa_source_unlink(s->monitor_source); + + if (linked) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK | PA_SUBSCRIPTION_EVENT_REMOVE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_UNLINK_POST], s); + } +} + +static void sink_free(pa_object *o) { + pa_sink *s = PA_SINK(o); + pa_sink_input *i; + + pa_assert(s); + pa_assert(pa_sink_refcnt(s) == 0); + + if (PA_SINK_LINKED(s->state)) + pa_sink_unlink(s); + + pa_log_info("Freeing sink %u \"%s\"", s->index, s->name); + + if (s->monitor_source) { + pa_source_unref(s->monitor_source); + s->monitor_source = NULL; + } + + pa_idxset_free(s->inputs, NULL, NULL); + + while ((i = pa_hashmap_steal_first(s->thread_info.inputs))) + pa_sink_input_unref(i); + + pa_hashmap_free(s->thread_info.inputs, NULL, NULL); + + if (s->silence) + pa_memblock_unref(s->silence); + + pa_xfree(s->name); + pa_xfree(s->description); + pa_xfree(s->driver); + pa_xfree(s); +} + +void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q) { + pa_sink_assert_ref(s); + pa_assert(q); + + s->asyncmsgq = q; + + if (s->monitor_source) + pa_source_set_asyncmsgq(s->monitor_source, q); +} + +void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p) { + pa_sink_assert_ref(s); + pa_assert(p); + + s->rtpoll = p; + if (s->monitor_source) + pa_source_set_rtpoll(s->monitor_source, p); +} + +int pa_sink_update_status(pa_sink*s) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + if (s->state == PA_SINK_SUSPENDED) + return 0; + + return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE); +} + +int pa_sink_suspend(pa_sink *s, pa_bool_t suspend) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + if (suspend) + return sink_set_state(s, PA_SINK_SUSPENDED); + else + return sink_set_state(s, pa_sink_used_by(s) ? PA_SINK_RUNNING : PA_SINK_IDLE); +} + +void pa_sink_ping(pa_sink *s) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_PING, NULL, 0, NULL, NULL); +} + +static unsigned fill_mix_info(pa_sink *s, size_t length, pa_mix_info *info, unsigned maxinfo) { + pa_sink_input *i; + unsigned n = 0; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_assert(info); + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL)) && maxinfo > 0) { + pa_sink_input_assert_ref(i); + + if (pa_sink_input_peek(i, length, &info->chunk, &info->volume) < 0) + continue; + + info->userdata = pa_sink_input_ref(i); + + pa_assert(info->chunk.memblock); + pa_assert(info->chunk.length > 0); + + info++; + n++; + maxinfo--; + } + + return n; +} + +static void inputs_drop(pa_sink *s, pa_mix_info *info, unsigned n, size_t length) { + pa_sink_input *i; + void *state = NULL; + unsigned p = 0; + unsigned n_unreffed = 0; + + pa_sink_assert_ref(s); + + /* We optimize for the case where the order of the inputs has not changed */ + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) { + unsigned j; + pa_mix_info* m; + + pa_sink_input_assert_ref(i); + + m = NULL; + + /* Let's try to find the matching entry info the pa_mix_info array */ + for (j = 0; j < n; j ++) { + + if (info[p].userdata == i) { + m = info + p; + break; + } + + p++; + if (p >= n) + p = 0; + } + + /* Drop read data */ + pa_sink_input_drop(i, length); + + if (m) { + pa_sink_input_unref(m->userdata); + m->userdata = NULL; + if (m->chunk.memblock) + pa_memblock_unref(m->chunk.memblock); + pa_memchunk_reset(&m->chunk); + + n_unreffed += 1; + } + } + + /* Now drop references to entries that are included in the + * pa_mix_info array but don't exist anymore */ + + if (n_unreffed < n) { + for (; n > 0; info++, n--) { + if (info->userdata) + pa_sink_input_unref(info->userdata); + if (info->chunk.memblock) + pa_memblock_unref(info->chunk.memblock); + } + } +} + +void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result) { + pa_mix_info info[MAX_MIX_CHANNELS]; + unsigned n; + size_t block_size_max; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_OPENED(s->thread_info.state)); + pa_assert(pa_frame_aligned(length, &s->sample_spec)); + pa_assert(result); + + pa_sink_ref(s); + + if (length <= 0) + length = pa_frame_align(MIX_BUFFER_LENGTH, &s->sample_spec); + + block_size_max = pa_mempool_block_size_max(s->core->mempool); + if (length > block_size_max) + length = pa_frame_align(block_size_max, &s->sample_spec); + + pa_assert(length > 0); + + n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, length, info, MAX_MIX_CHANNELS) : 0; + + if (n == 0) { + + if (length > SILENCE_BUFFER_LENGTH) + length = pa_frame_align(SILENCE_BUFFER_LENGTH, &s->sample_spec); + + pa_assert(length > 0); + + if (!s->silence || pa_memblock_get_length(s->silence) < length) { + if (s->silence) + pa_memblock_unref(s->silence); + s->silence = pa_silence_memblock_new(s->core->mempool, &s->sample_spec, length); + } + + result->memblock = pa_memblock_ref(s->silence); + result->length = length; + result->index = 0; + + } else if (n == 1) { + pa_cvolume volume; + + *result = info[0].chunk; + pa_memblock_ref(result->memblock); + + if (result->length > length) + result->length = length; + + pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); + + if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&volume)) { + pa_memchunk_make_writable(result, 0); + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&volume)) + pa_silence_memchunk(result, &s->sample_spec); + else + pa_volume_memchunk(result, &s->sample_spec, &volume); + } + } else { + void *ptr; + result->memblock = pa_memblock_new(s->core->mempool, length); + + ptr = pa_memblock_acquire(result->memblock); + result->length = pa_mix(info, n, ptr, length, &s->sample_spec, &s->thread_info.soft_volume, s->thread_info.soft_muted); + pa_memblock_release(result->memblock); + + result->index = 0; + } + + if (s->thread_info.state == PA_SINK_RUNNING) + inputs_drop(s, info, n, result->length); + + if (s->monitor_source && PA_SOURCE_OPENED(pa_source_get_state(s->monitor_source))) + pa_source_post(s->monitor_source, result); + + pa_sink_unref(s); +} + +void pa_sink_render_into(pa_sink*s, pa_memchunk *target) { + pa_mix_info info[MAX_MIX_CHANNELS]; + unsigned n; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_OPENED(s->thread_info.state)); + pa_assert(target); + pa_assert(target->memblock); + pa_assert(target->length > 0); + pa_assert(pa_frame_aligned(target->length, &s->sample_spec)); + + pa_sink_ref(s); + + n = s->thread_info.state == PA_SINK_RUNNING ? fill_mix_info(s, target->length, info, MAX_MIX_CHANNELS) : 0; + + if (n == 0) { + pa_silence_memchunk(target, &s->sample_spec); + } else if (n == 1) { + if (target->length > info[0].chunk.length) + target->length = info[0].chunk.length; + + if (s->thread_info.soft_muted) + pa_silence_memchunk(target, &s->sample_spec); + else { + void *src, *ptr; + pa_cvolume volume; + + ptr = pa_memblock_acquire(target->memblock); + src = pa_memblock_acquire(info[0].chunk.memblock); + + memcpy((uint8_t*) ptr + target->index, + (uint8_t*) src + info[0].chunk.index, + target->length); + + pa_memblock_release(target->memblock); + pa_memblock_release(info[0].chunk.memblock); + + pa_sw_cvolume_multiply(&volume, &s->thread_info.soft_volume, &info[0].volume); + + if (!pa_cvolume_is_norm(&volume)) + pa_volume_memchunk(target, &s->sample_spec, &volume); + } + + } else { + void *ptr; + + ptr = pa_memblock_acquire(target->memblock); + + target->length = pa_mix(info, n, + (uint8_t*) ptr + target->index, + target->length, + &s->sample_spec, + &s->thread_info.soft_volume, + s->thread_info.soft_muted); + + pa_memblock_release(target->memblock); + } + + if (s->thread_info.state == PA_SINK_RUNNING) + inputs_drop(s, info, n, target->length); + + if (s->monitor_source && PA_SOURCE_OPENED(pa_source_get_state(s->monitor_source))) + pa_source_post(s->monitor_source, target); + + pa_sink_unref(s); +} + +void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target) { + pa_memchunk chunk; + size_t l, d; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_OPENED(s->thread_info.state)); + pa_assert(target); + pa_assert(target->memblock); + pa_assert(target->length > 0); + pa_assert(pa_frame_aligned(target->length, &s->sample_spec)); + + pa_sink_ref(s); + + l = target->length; + d = 0; + while (l > 0) { + chunk = *target; + chunk.index += d; + chunk.length -= d; + + pa_sink_render_into(s, &chunk); + + d += chunk.length; + l -= chunk.length; + } + + pa_sink_unref(s); +} + +void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_OPENED(s->thread_info.state)); + pa_assert(length > 0); + pa_assert(pa_frame_aligned(length, &s->sample_spec)); + pa_assert(result); + + /*** This needs optimization ***/ + + result->index = 0; + result->length = length; + result->memblock = pa_memblock_new(s->core->mempool, length); + + pa_sink_render_into_full(s, result); +} + +void pa_sink_skip(pa_sink *s, size_t length) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_OPENED(s->thread_info.state)); + pa_assert(length > 0); + pa_assert(pa_frame_aligned(length, &s->sample_spec)); + + if (pa_source_used_by(s->monitor_source)) { + pa_memchunk chunk; + + /* If something is connected to our monitor source, we have to + * pass valid data to it */ + + while (length > 0) { + pa_sink_render(s, length, &chunk); + pa_memblock_unref(chunk.memblock); + + pa_assert(chunk.length <= length); + length -= chunk.length; + } + + } else { + /* Ok, noone cares about the rendered data, so let's not even render it */ + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) { + pa_sink_input_assert_ref(i); + pa_sink_input_drop(i, length); + } + } +} + +pa_usec_t pa_sink_get_latency(pa_sink *s) { + pa_usec_t usec = 0; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + if (!PA_SINK_OPENED(s->state)) + return 0; + + if (s->get_latency) + return s->get_latency(s); + + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + return 0; + + return usec; +} + +void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume) { + int changed; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + pa_assert(volume); + + changed = !pa_cvolume_equal(volume, &s->volume); + s->volume = *volume; + + if (s->set_volume && s->set_volume(s) < 0) + s->set_volume = NULL; + + if (!s->set_volume) + pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_VOLUME, pa_xnewdup(struct pa_cvolume, volume, 1), 0, NULL, pa_xfree); + + if (changed) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +const pa_cvolume *pa_sink_get_volume(pa_sink *s) { + struct pa_cvolume old_volume; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + old_volume = s->volume; + + if (s->get_volume && s->get_volume(s) < 0) + s->get_volume = NULL; + + if (!s->get_volume && s->refresh_volume) + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_VOLUME, &s->volume, 0, NULL); + + if (!pa_cvolume_equal(&old_volume, &s->volume)) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + return &s->volume; +} + +void pa_sink_set_mute(pa_sink *s, pa_bool_t mute) { + int changed; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + changed = s->muted != mute; + s->muted = mute; + + if (s->set_mute && s->set_mute(s) < 0) + s->set_mute = NULL; + + if (!s->set_mute) + pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL); + + if (changed) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +pa_bool_t pa_sink_get_mute(pa_sink *s) { + pa_bool_t old_muted; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + old_muted = s->muted; + + if (s->get_mute && s->get_mute(s) < 0) + s->get_mute = NULL; + + if (!s->get_mute && s->refresh_mute) + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_GET_MUTE, &s->muted, 0, NULL); + + if (old_muted != s->muted) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + return s->muted; +} + +void pa_sink_set_module(pa_sink *s, pa_module *m) { + pa_sink_assert_ref(s); + + if (s->module == m) + return; + + s->module = m; + + if (s->monitor_source) + pa_source_set_module(s->monitor_source, m); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +void pa_sink_set_description(pa_sink *s, const char *description) { + pa_sink_assert_ref(s); + + if (!description && !s->description) + return; + + if (description && s->description && !strcmp(description, s->description)) + return; + + pa_xfree(s->description); + s->description = pa_xstrdup(description); + + if (s->monitor_source) { + char *n; + + n = pa_sprintf_malloc("Monitor Source of %s", s->description? s->description : s->name); + pa_source_set_description(s->monitor_source, n); + pa_xfree(n); + } + + if (PA_SINK_LINKED(s->state)) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_DESCRIPTION_CHANGED], s); + } +} + +unsigned pa_sink_linked_by(pa_sink *s) { + unsigned ret; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + ret = pa_idxset_size(s->inputs); + + /* We add in the number of streams connected to us here. Please + * not the asymmmetry to pa_sink_used_by()! */ + + if (s->monitor_source) + ret += pa_source_linked_by(s->monitor_source); + + return ret; +} + +unsigned pa_sink_used_by(pa_sink *s) { + unsigned ret; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + ret = pa_idxset_size(s->inputs); + pa_assert(ret >= s->n_corked); + ret -= s->n_corked; + + /* Streams connected to our monitor source do not matter for + * pa_sink_used_by()!.*/ + + return ret; +} + +int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_sink *s = PA_SINK(o); + pa_sink_assert_ref(s); + pa_assert(s->thread_info.state != PA_SINK_UNLINKED); + + switch ((pa_sink_message_t) code) { + + case PA_SINK_MESSAGE_ADD_INPUT: { + pa_sink_input *i = PA_SINK_INPUT(userdata); + pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index), pa_sink_input_ref(i)); + + /* Since the caller sleeps in pa_sink_input_put(), we can + * safely access data outside of thread_info even though + * it is mutable */ + + if ((i->thread_info.sync_prev = i->sync_prev)) { + pa_assert(i->sink == i->thread_info.sync_prev->sink); + pa_assert(i->sync_prev->sync_next == i); + i->thread_info.sync_prev->thread_info.sync_next = i; + } + + if ((i->thread_info.sync_next = i->sync_next)) { + pa_assert(i->sink == i->thread_info.sync_next->sink); + pa_assert(i->sync_next->sync_prev == i); + i->thread_info.sync_next->thread_info.sync_prev = i; + } + + pa_assert(!i->thread_info.attached); + i->thread_info.attached = TRUE; + + if (i->attach) + i->attach(i); + + /* If you change anything here, make sure to change the + * ghost sink input handling a few lines down at + * PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, too. */ + + return 0; + } + + case PA_SINK_MESSAGE_REMOVE_INPUT: { + pa_sink_input *i = PA_SINK_INPUT(userdata); + + /* If you change anything here, make sure to change the + * sink input handling a few lines down at + * PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, too. */ + + if (i->detach) + i->detach(i); + + pa_assert(i->thread_info.attached); + i->thread_info.attached = FALSE; + + /* Since the caller sleeps in pa_sink_input_unlink(), + * we can safely access data outside of thread_info even + * though it is mutable */ + + pa_assert(!i->thread_info.sync_prev); + pa_assert(!i->thread_info.sync_next); + + if (i->thread_info.sync_prev) { + i->thread_info.sync_prev->thread_info.sync_next = i->thread_info.sync_prev->sync_next; + i->thread_info.sync_prev = NULL; + } + + if (i->thread_info.sync_next) { + i->thread_info.sync_next->thread_info.sync_prev = i->thread_info.sync_next->sync_prev; + i->thread_info.sync_next = NULL; + } + + if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(i->index))) + pa_sink_input_unref(i); + + return 0; + } + + case PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER: { + pa_sink_input_move_info *info = userdata; + int volume_is_norm; + + /* We don't support moving synchronized streams. */ + pa_assert(!info->sink_input->sync_prev); + pa_assert(!info->sink_input->sync_next); + pa_assert(!info->sink_input->thread_info.sync_next); + pa_assert(!info->sink_input->thread_info.sync_prev); + + if (info->sink_input->detach) + info->sink_input->detach(info->sink_input); + + pa_assert(info->sink_input->thread_info.attached); + info->sink_input->thread_info.attached = FALSE; + + if (info->ghost_sink_input) { + pa_assert(info->buffer_bytes > 0); + pa_assert(info->buffer); + + volume_is_norm = pa_cvolume_is_norm(&info->sink_input->thread_info.volume); + + pa_log_debug("Buffering %lu bytes ...", (unsigned long) info->buffer_bytes); + + while (info->buffer_bytes > 0) { + pa_memchunk memchunk; + pa_cvolume volume; + size_t n; + + if (pa_sink_input_peek(info->sink_input, info->buffer_bytes, &memchunk, &volume) < 0) + break; + + n = memchunk.length > info->buffer_bytes ? info->buffer_bytes : memchunk.length; + pa_sink_input_drop(info->sink_input, n); + memchunk.length = n; + + if (!volume_is_norm) { + pa_memchunk_make_writable(&memchunk, 0); + pa_volume_memchunk(&memchunk, &s->sample_spec, &volume); + } + + if (pa_memblockq_push(info->buffer, &memchunk) < 0) { + pa_memblock_unref(memchunk.memblock); + break; + } + + pa_memblock_unref(memchunk.memblock); + info->buffer_bytes -= n; + } + + /* Add the remaining already resampled chunk to the buffer */ + if (info->sink_input->thread_info.resampled_chunk.memblock) + pa_memblockq_push(info->buffer, &info->sink_input->thread_info.resampled_chunk); + + pa_memblockq_sink_input_set_queue(info->ghost_sink_input, info->buffer); + + pa_log_debug("Buffered %lu bytes ...", (unsigned long) pa_memblockq_get_length(info->buffer)); + } + + /* Let's remove the sink input ...*/ + if (pa_hashmap_remove(s->thread_info.inputs, PA_UINT32_TO_PTR(info->sink_input->index))) + pa_sink_input_unref(info->sink_input); + + /* .. and add the ghost sink input instead */ + if (info->ghost_sink_input) { + pa_hashmap_put(s->thread_info.inputs, PA_UINT32_TO_PTR(info->ghost_sink_input->index), pa_sink_input_ref(info->ghost_sink_input)); + info->ghost_sink_input->thread_info.sync_prev = info->ghost_sink_input->thread_info.sync_next = NULL; + + pa_assert(!info->ghost_sink_input->thread_info.attached); + info->ghost_sink_input->thread_info.attached = TRUE; + + if (info->ghost_sink_input->attach) + info->ghost_sink_input->attach(info->ghost_sink_input); + } + + return 0; + } + + case PA_SINK_MESSAGE_SET_VOLUME: + s->thread_info.soft_volume = *((pa_cvolume*) userdata); + return 0; + + case PA_SINK_MESSAGE_SET_MUTE: + s->thread_info.soft_muted = PA_PTR_TO_UINT(userdata); + return 0; + + case PA_SINK_MESSAGE_GET_VOLUME: + *((pa_cvolume*) userdata) = s->thread_info.soft_volume; + return 0; + + case PA_SINK_MESSAGE_GET_MUTE: + *((pa_bool_t*) userdata) = s->thread_info.soft_muted; + return 0; + + case PA_SINK_MESSAGE_PING: + return 0; + + case PA_SINK_MESSAGE_SET_STATE: + + s->thread_info.state = PA_PTR_TO_UINT(userdata); + return 0; + + case PA_SINK_MESSAGE_DETACH: + + /* We're detaching all our input streams so that the + * asyncmsgq and rtpoll fields can be changed without + * problems */ + pa_sink_detach_within_thread(s); + break; + + case PA_SINK_MESSAGE_ATTACH: + + /* Reattach all streams */ + pa_sink_attach_within_thread(s); + break; + + case PA_SINK_MESSAGE_GET_LATENCY: + case PA_SINK_MESSAGE_MAX: + ; + } + + return -1; +} + +int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend) { + pa_sink *sink; + uint32_t idx; + int ret = 0; + + pa_core_assert_ref(c); + + for (sink = PA_SINK(pa_idxset_first(c->sinks, &idx)); sink; sink = PA_SINK(pa_idxset_next(c->sinks, &idx))) + ret -= pa_sink_suspend(sink, suspend) < 0; + + return ret; +} + +void pa_sink_detach(pa_sink *s) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_DETACH, NULL, 0, NULL); +} + +void pa_sink_attach(pa_sink *s) { + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->state)); + + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_ATTACH, NULL, 0, NULL); +} + +void pa_sink_detach_within_thread(pa_sink *s) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->thread_info.state)); + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (i->detach) + i->detach(i); + + if (s->monitor_source) + pa_source_detach_within_thread(s->monitor_source); +} + +void pa_sink_attach_within_thread(pa_sink *s) { + pa_sink_input *i; + void *state = NULL; + + pa_sink_assert_ref(s); + pa_assert(PA_SINK_LINKED(s->thread_info.state)); + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (i->attach) + i->attach(i); + + if (s->monitor_source) + pa_source_attach_within_thread(s->monitor_source); +} diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h new file mode 100644 index 00000000..e9969309 --- /dev/null +++ b/src/pulsecore/sink.h @@ -0,0 +1,188 @@ +#ifndef foopulsesinkhfoo +#define foopulsesinkhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +typedef struct pa_sink pa_sink; + +#include <inttypes.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> + +#include <pulsecore/core-def.h> +#include <pulsecore/core.h> +#include <pulsecore/idxset.h> +#include <pulsecore/source.h> +#include <pulsecore/module.h> +#include <pulsecore/refcnt.h> +#include <pulsecore/msgobject.h> +#include <pulsecore/rtpoll.h> + +#define PA_MAX_INPUTS_PER_SINK 32 + +typedef enum pa_sink_state { + PA_SINK_INIT, + PA_SINK_RUNNING, + PA_SINK_SUSPENDED, + PA_SINK_IDLE, + PA_SINK_UNLINKED +} pa_sink_state_t; + +static inline pa_bool_t PA_SINK_OPENED(pa_sink_state_t x) { + return x == PA_SINK_RUNNING || x == PA_SINK_IDLE; +} + +static inline pa_bool_t PA_SINK_LINKED(pa_sink_state_t x) { + return x == PA_SINK_RUNNING || x == PA_SINK_IDLE || x == PA_SINK_SUSPENDED; +} + +struct pa_sink { + pa_msgobject parent; + + uint32_t index; + pa_core *core; + pa_sink_state_t state; + pa_sink_flags_t flags; + + char *name; + char *description, *driver; /* may be NULL */ + + pa_module *module; /* may be NULL */ + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + + pa_idxset *inputs; + unsigned n_corked; + pa_source *monitor_source; + + pa_cvolume volume; + pa_bool_t muted; + pa_bool_t refresh_volume; + pa_bool_t refresh_mute; + + int (*set_state)(pa_sink *s, pa_sink_state_t state); /* may be NULL */ + int (*set_volume)(pa_sink *s); /* dito */ + int (*get_volume)(pa_sink *s); /* dito */ + int (*get_mute)(pa_sink *s); /* dito */ + int (*set_mute)(pa_sink *s); /* dito */ + pa_usec_t (*get_latency)(pa_sink *s); /* dito */ + + pa_asyncmsgq *asyncmsgq; + pa_rtpoll *rtpoll; + + /* Contains copies of the above data so that the real-time worker + * thread can work without access locking */ + struct { + pa_sink_state_t state; + pa_hashmap *inputs; + pa_cvolume soft_volume; + pa_bool_t soft_muted; + } thread_info; + + pa_memblock *silence; + + void *userdata; +}; + +PA_DECLARE_CLASS(pa_sink); +#define PA_SINK(s) (pa_sink_cast(s)) + +typedef enum pa_sink_message { + PA_SINK_MESSAGE_ADD_INPUT, + PA_SINK_MESSAGE_REMOVE_INPUT, + PA_SINK_MESSAGE_GET_VOLUME, + PA_SINK_MESSAGE_SET_VOLUME, + PA_SINK_MESSAGE_GET_MUTE, + PA_SINK_MESSAGE_SET_MUTE, + PA_SINK_MESSAGE_GET_LATENCY, + PA_SINK_MESSAGE_SET_STATE, + PA_SINK_MESSAGE_PING, + PA_SINK_MESSAGE_REMOVE_INPUT_AND_BUFFER, + PA_SINK_MESSAGE_ATTACH, + PA_SINK_MESSAGE_DETACH, + PA_SINK_MESSAGE_MAX +} pa_sink_message_t; + +/* To be called exclusively by the sink driver, from main context */ + +pa_sink* pa_sink_new( + pa_core *core, + const char *driver, + const char *name, + int namereg_fail, + const pa_sample_spec *spec, + const pa_channel_map *map); + +void pa_sink_put(pa_sink *s); +void pa_sink_unlink(pa_sink* s); + +void pa_sink_set_module(pa_sink *sink, pa_module *m); +void pa_sink_set_description(pa_sink *s, const char *description); +void pa_sink_set_asyncmsgq(pa_sink *s, pa_asyncmsgq *q); +void pa_sink_set_rtpoll(pa_sink *s, pa_rtpoll *p); + +void pa_sink_detach(pa_sink *s); +void pa_sink_attach(pa_sink *s); + +/* May be called by everyone, from main context */ + +pa_usec_t pa_sink_get_latency(pa_sink *s); + +int pa_sink_update_status(pa_sink*s); +int pa_sink_suspend(pa_sink *s, pa_bool_t suspend); +int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend); + +/* Sends a ping message to the sink thread, to make it wake up and + * check for data to process even if there is no real message is + * sent */ +void pa_sink_ping(pa_sink *s); + +void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume); +const pa_cvolume *pa_sink_get_volume(pa_sink *sink); +void pa_sink_set_mute(pa_sink *sink, pa_bool_t mute); +pa_bool_t pa_sink_get_mute(pa_sink *sink); + +unsigned pa_sink_linked_by(pa_sink *s); /* Number of connected streams */ +unsigned pa_sink_used_by(pa_sink *s); /* Number of connected streams which are not corked */ +#define pa_sink_get_state(s) ((s)->state) + +/* To be called exclusively by the sink driver, from IO context */ + +void pa_sink_render(pa_sink*s, size_t length, pa_memchunk *result); +void pa_sink_render_full(pa_sink *s, size_t length, pa_memchunk *result); +void pa_sink_render_into(pa_sink*s, pa_memchunk *target); +void pa_sink_render_into_full(pa_sink *s, pa_memchunk *target); + +void pa_sink_skip(pa_sink *s, size_t length); + +int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +void pa_sink_attach_within_thread(pa_sink *s); +void pa_sink_detach_within_thread(pa_sink *s); + +#endif diff --git a/src/pulsecore/sioman.c b/src/pulsecore/sioman.c new file mode 100644 index 00000000..8d4c6fa7 --- /dev/null +++ b/src/pulsecore/sioman.c @@ -0,0 +1,41 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/macro.h> +#include <pulsecore/atomic.h> + +#include "sioman.h" + +static pa_atomic_t stdio_inuse = PA_ATOMIC_INIT(0); + +int pa_stdio_acquire(void) { + return pa_atomic_cmpxchg(&stdio_inuse, 0, 1) ? 0 : -1; +} + +void pa_stdio_release(void) { + pa_assert_se(pa_atomic_cmpxchg(&stdio_inuse, 1, 0)); +} diff --git a/src/pulsecore/sioman.h b/src/pulsecore/sioman.h new file mode 100644 index 00000000..49fffb34 --- /dev/null +++ b/src/pulsecore/sioman.h @@ -0,0 +1,30 @@ +#ifndef foosiomanhfoo +#define foosiomanhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + +int pa_stdio_acquire(void); +void pa_stdio_release(void); + +#endif diff --git a/src/pulsecore/socket-client.c b/src/pulsecore/socket-client.c new file mode 100644 index 00000000..5b5bc5ca --- /dev/null +++ b/src/pulsecore/socket-client.c @@ -0,0 +1,569 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 + +/* #undef HAVE_LIBASYNCNS */ + +#include <unistd.h> +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif + +#ifdef HAVE_LIBASYNCNS +#include <asyncns.h> +#endif + +#include <pulse/timeval.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/winsock.h> +#include <pulsecore/core-error.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/parseaddr.h> +#include <pulsecore/macro.h> +#include <pulsecore/refcnt.h> + +#include "socket-client.h" + +#define CONNECT_TIMEOUT 5 + +struct pa_socket_client { + PA_REFCNT_DECLARE; + pa_mainloop_api *mainloop; + int fd; + pa_io_event *io_event; + pa_time_event *timeout_event; + pa_defer_event *defer_event; + void (*callback)(pa_socket_client*c, pa_iochannel *io, void *userdata); + void *userdata; + int local; +#ifdef HAVE_LIBASYNCNS + asyncns_t *asyncns; + asyncns_query_t * asyncns_query; + pa_io_event *asyncns_io_event; +#endif +}; + +static pa_socket_client*pa_socket_client_new(pa_mainloop_api *m) { + pa_socket_client *c; + pa_assert(m); + + c = pa_xnew(pa_socket_client, 1); + PA_REFCNT_INIT(c); + c->mainloop = m; + c->fd = -1; + c->io_event = NULL; + c->defer_event = NULL; + c->timeout_event = NULL; + c->callback = NULL; + c->userdata = NULL; + c->local = 0; + +#ifdef HAVE_LIBASYNCNS + c->asyncns = NULL; + c->asyncns_io_event = NULL; + c->asyncns_query = NULL; +#endif + + return c; +} + +static void free_events(pa_socket_client *c) { + pa_assert(c); + + if (c->io_event) { + c->mainloop->io_free(c->io_event); + c->io_event = NULL; + } + + if (c->defer_event) { + c->mainloop->defer_free(c->defer_event); + c->defer_event = NULL; + } + + if (c->timeout_event) { + c->mainloop->time_free(c->timeout_event); + c->timeout_event = NULL; + } +} + +static void do_call(pa_socket_client *c) { + pa_iochannel *io = NULL; + int error; + socklen_t lerror; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->callback); + + pa_socket_client_ref(c); + + if (c->fd < 0) + goto finish; + + lerror = sizeof(error); + if (getsockopt(c->fd, SOL_SOCKET, SO_ERROR, (void*)&error, &lerror) < 0) { + pa_log("getsockopt(): %s", pa_cstrerror(errno)); + goto finish; + } + + if (lerror != sizeof(error)) { + pa_log("getsockopt() returned invalid size."); + goto finish; + } + + if (error != 0) { + pa_log_debug("connect(): %s", pa_cstrerror(error)); + errno = error; + goto finish; + } + + io = pa_iochannel_new(c->mainloop, c->fd, c->fd); + pa_assert(io); + +finish: + if (!io && c->fd >= 0) + pa_close(c->fd); + c->fd = -1; + + free_events(c); + + pa_assert(c->callback); + c->callback(c, io, c->userdata); + + pa_socket_client_unref(c); +} + +static void connect_fixed_cb(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + pa_socket_client *c = userdata; + + pa_assert(m); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->defer_event == e); + + do_call(c); +} + +static void connect_io_cb(pa_mainloop_api*m, pa_io_event *e, int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) { + pa_socket_client *c = userdata; + + pa_assert(m); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->io_event == e); + pa_assert(fd >= 0); + + do_call(c); +} + +static int do_connect(pa_socket_client *c, const struct sockaddr *sa, socklen_t len) { + int r; + + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(sa); + pa_assert(len > 0); + + pa_make_fd_nonblock(c->fd); + + if ((r = connect(c->fd, sa, len)) < 0) { +#ifdef OS_IS_WIN32 + if (WSAGetLastError() != EWOULDBLOCK) { + pa_log_debug("connect(): %d", WSAGetLastError()); +#else + if (errno != EINPROGRESS) { + pa_log_debug("connect(): %s (%d)", pa_cstrerror(errno), errno); +#endif + return -1; + } + + pa_assert_se(c->io_event = c->mainloop->io_new(c->mainloop, c->fd, PA_IO_EVENT_OUTPUT, connect_io_cb, c)); + } else + pa_assert_se(c->defer_event = c->mainloop->defer_new(c->mainloop, connect_fixed_cb, c)); + + return 0; +} + +pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port) { + struct sockaddr_in sa; + + pa_assert(m); + pa_assert(port > 0); + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + sa.sin_addr.s_addr = htonl(address); + + return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); +} + +#ifdef HAVE_SYS_UN_H + +pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) { + struct sockaddr_un sa; + + pa_assert(m); + pa_assert(filename); + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path, filename, sizeof(sa.sun_path)-1); + sa.sun_path[sizeof(sa.sun_path) - 1] = 0; + + return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); +} + +#else /* HAVE_SYS_UN_H */ + +pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename) { + return NULL; +} + +#endif /* HAVE_SYS_UN_H */ + +static int sockaddr_prepare(pa_socket_client *c, const struct sockaddr *sa, size_t salen) { + pa_assert(c); + pa_assert(sa); + pa_assert(salen); + + switch (sa->sa_family) { + case AF_UNIX: + c->local = 1; + break; + + case AF_INET: + c->local = ((const struct sockaddr_in*) sa)->sin_addr.s_addr == INADDR_LOOPBACK; + break; + + case AF_INET6: + c->local = memcmp(&((const struct sockaddr_in6*) sa)->sin6_addr, &in6addr_loopback, sizeof(struct in6_addr)) == 0; + break; + + default: + c->local = 0; + } + + if ((c->fd = socket(sa->sa_family, SOCK_STREAM, 0)) < 0) { + pa_log("socket(): %s", pa_cstrerror(errno)); + return -1; + } + + pa_make_fd_cloexec(c->fd); + if (sa->sa_family == AF_INET || sa->sa_family == AF_INET6) + pa_make_tcp_socket_low_delay(c->fd); + else + pa_make_socket_low_delay(c->fd); + + if (do_connect(c, sa, salen) < 0) + return -1; + + return 0; +} + +pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen) { + pa_socket_client *c; + + pa_assert(m); + pa_assert(sa); + pa_assert(salen > 0); + + pa_assert_se(c = pa_socket_client_new(m)); + + if (sockaddr_prepare(c, sa, salen) < 0) + goto fail; + + return c; + +fail: + pa_socket_client_unref(c); + return NULL; +} + +static void socket_client_free(pa_socket_client *c) { + pa_assert(c); + pa_assert(c->mainloop); + + free_events(c); + + if (c->fd >= 0) + pa_close(c->fd); + +#ifdef HAVE_LIBASYNCNS + if (c->asyncns_query) + asyncns_cancel(c->asyncns, c->asyncns_query); + if (c->asyncns) + asyncns_free(c->asyncns); + if (c->asyncns_io_event) + c->mainloop->io_free(c->asyncns_io_event); +#endif + + pa_xfree(c); +} + +void pa_socket_client_unref(pa_socket_client *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + if (PA_REFCNT_DEC(c) <= 0) + socket_client_free(c); +} + +pa_socket_client* pa_socket_client_ref(pa_socket_client *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + PA_REFCNT_INC(c); + return c; +} + +void pa_socket_client_set_callback(pa_socket_client *c, void (*on_connection)(pa_socket_client *c, pa_iochannel*io, void *userdata), void *userdata) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + c->callback = on_connection; + c->userdata = userdata; +} + +pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port) { + struct sockaddr_in6 sa; + + pa_assert(m); + pa_assert(address); + pa_assert(port > 0); + + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(port); + memcpy(&sa.sin6_addr, address, sizeof(sa.sin6_addr)); + + return pa_socket_client_new_sockaddr(m, (struct sockaddr*) &sa, sizeof(sa)); +} + +#ifdef HAVE_LIBASYNCNS + +static void asyncns_cb(pa_mainloop_api*m, pa_io_event *e, int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) { + pa_socket_client *c = userdata; + struct addrinfo *res = NULL; + int ret; + + pa_assert(m); + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + pa_assert(c->asyncns_io_event == e); + pa_assert(fd >= 0); + + if (asyncns_wait(c->asyncns, 0) < 0) + goto fail; + + if (!asyncns_isdone(c->asyncns, c->asyncns_query)) + return; + + ret = asyncns_getaddrinfo_done(c->asyncns, c->asyncns_query, &res); + c->asyncns_query = NULL; + + if (ret != 0 || !res) + goto fail; + + if (res->ai_addr) + sockaddr_prepare(c, res->ai_addr, res->ai_addrlen); + + asyncns_freeaddrinfo(res); + + m->io_free(c->asyncns_io_event); + c->asyncns_io_event = NULL; + return; + +fail: + m->io_free(c->asyncns_io_event); + c->asyncns_io_event = NULL; + + errno = EHOSTUNREACH; + do_call(c); + return; + +} + +#endif + +static void timeout_cb(pa_mainloop_api *m, pa_time_event *e, const struct timeval *tv, void *userdata) { + pa_socket_client *c = userdata; + + pa_assert(m); + pa_assert(e); + pa_assert(tv); + pa_assert(c); + + if (c->fd >= 0) { + pa_close(c->fd); + c->fd = -1; + } + + errno = ETIMEDOUT; + do_call(c); +} + +static void start_timeout(pa_socket_client *c) { + struct timeval tv; + pa_assert(c); + pa_assert(!c->timeout_event); + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, CONNECT_TIMEOUT * 1000000); + c->timeout_event = c->mainloop->time_new(c->mainloop, &tv, timeout_cb, c); +} + +pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char*name, uint16_t default_port) { + pa_socket_client *c = NULL; + pa_parsed_address a; + + pa_assert(m); + pa_assert(name); + + if (pa_parse_address(name, &a) < 0) + return NULL; + + if (!a.port) + a.port = default_port; + + switch (a.type) { + case PA_PARSED_ADDRESS_UNIX: + if ((c = pa_socket_client_new_unix(m, a.path_or_host))) + start_timeout(c); + break; + + case PA_PARSED_ADDRESS_TCP4: /* Fallthrough */ + case PA_PARSED_ADDRESS_TCP6: /* Fallthrough */ + case PA_PARSED_ADDRESS_TCP_AUTO:{ + + struct addrinfo hints; + char port[12]; + + pa_snprintf(port, sizeof(port), "%u", (unsigned) a.port); + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = a.type == PA_PARSED_ADDRESS_TCP4 ? PF_INET : (a.type == PA_PARSED_ADDRESS_TCP6 ? PF_INET6 : PF_UNSPEC); + hints.ai_socktype = SOCK_STREAM; + +#ifdef HAVE_LIBASYNCNS + { + asyncns_t *asyncns; + + if (!(asyncns = asyncns_new(1))) + goto finish; + + c = pa_socket_client_new(m); + c->asyncns = asyncns; + c->asyncns_io_event = m->io_new(m, asyncns_fd(c->asyncns), PA_IO_EVENT_INPUT, asyncns_cb, c); + c->asyncns_query = asyncns_getaddrinfo(c->asyncns, a.path_or_host, port, &hints); + pa_assert(c->asyncns_query); + start_timeout(c); + } +#else /* HAVE_LIBASYNCNS */ + { +#ifdef HAVE_GETADDRINFO + int ret; + struct addrinfo *res = NULL; + + ret = getaddrinfo(a.path_or_host, port, &hints, &res); + + if (ret < 0 || !res) + goto finish; + + if (res->ai_addr) { + if ((c = pa_socket_client_new_sockaddr(m, res->ai_addr, res->ai_addrlen))) + start_timeout(c); + } + + freeaddrinfo(res); +#else /* HAVE_GETADDRINFO */ + struct hostent *host = NULL; + struct sockaddr_in s; + + /* FIXME: PF_INET6 support */ + if (hints.ai_family == PF_INET6) { + pa_log_error("IPv6 is not supported on Windows"); + goto finish; + } + + host = gethostbyname(a.path_or_host); + if (!host) { + unsigned int addr = inet_addr(a.path_or_host); + if (addr != INADDR_NONE) + host = gethostbyaddr((char*)&addr, 4, AF_INET); + } + + if (!host) + goto finish; + + s.sin_family = AF_INET; + memcpy(&s.sin_addr, host->h_addr, sizeof(struct in_addr)); + s.sin_port = htons(a.port); + + if ((c = pa_socket_client_new_sockaddr(m, (struct sockaddr*)&s, sizeof(s)))) + start_timeout(c); +#endif /* HAVE_GETADDRINFO */ + } +#endif /* HAVE_LIBASYNCNS */ + } + } + +finish: + pa_xfree(a.path_or_host); + return c; + +} + +/* Return non-zero when the target sockaddr is considered + local. "local" means UNIX socket or TCP socket on localhost. Other + local IP addresses are not considered local. */ +int pa_socket_client_is_local(pa_socket_client *c) { + pa_assert(c); + pa_assert(PA_REFCNT_VALUE(c) >= 1); + + return c->local; +} diff --git a/src/pulsecore/socket-client.h b/src/pulsecore/socket-client.h new file mode 100644 index 00000000..b1d58eff --- /dev/null +++ b/src/pulsecore/socket-client.h @@ -0,0 +1,50 @@ +#ifndef foosocketclienthfoo +#define foosocketclienthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <inttypes.h> + +#include <pulse/mainloop-api.h> +#include <pulsecore/iochannel.h> + +struct sockaddr; + +typedef struct pa_socket_client pa_socket_client; + +pa_socket_client* pa_socket_client_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port); +pa_socket_client* pa_socket_client_new_ipv6(pa_mainloop_api *m, uint8_t address[16], uint16_t port); +pa_socket_client* pa_socket_client_new_unix(pa_mainloop_api *m, const char *filename); +pa_socket_client* pa_socket_client_new_sockaddr(pa_mainloop_api *m, const struct sockaddr *sa, size_t salen); +pa_socket_client* pa_socket_client_new_string(pa_mainloop_api *m, const char *a, uint16_t default_port); + +void pa_socket_client_unref(pa_socket_client *c); +pa_socket_client* pa_socket_client_ref(pa_socket_client *c); + +void pa_socket_client_set_callback(pa_socket_client *c, void (*on_connection)(pa_socket_client *c, pa_iochannel*io, void *userdata), void *userdata); + +int pa_socket_client_is_local(pa_socket_client *c); + +#endif diff --git a/src/pulsecore/socket-server.c b/src/pulsecore/socket-server.c new file mode 100644 index 00000000..162a1aac --- /dev/null +++ b/src/pulsecore/socket-server.c @@ -0,0 +1,532 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <errno.h> +#include <string.h> +#include <sys/types.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/stat.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#ifndef SUN_LEN +#define SUN_LEN(ptr) \ + ((size_t)(((struct sockaddr_un *) 0)->sun_path) + strlen((ptr)->sun_path)) +#endif +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + +#ifndef HAVE_INET_NTOP +#include "inet_ntop.h" +#endif + +#ifndef HAVE_INET_PTON +#include "inet_pton.h" +#endif + +#include "winsock.h" + +#include <pulse/xmalloc.h> +#include <pulse/util.h> + +#include <pulsecore/socket-util.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-error.h> +#include <pulsecore/refcnt.h> + +#include "socket-server.h" + +struct pa_socket_server { + PA_REFCNT_DECLARE; + int fd; + char *filename; + char *tcpwrap_service; + + void (*on_connection)(pa_socket_server*s, pa_iochannel *io, void *userdata); + void *userdata; + + pa_io_event *io_event; + pa_mainloop_api *mainloop; + enum { SOCKET_SERVER_GENERIC, SOCKET_SERVER_IPV4, SOCKET_SERVER_UNIX, SOCKET_SERVER_IPV6 } type; +}; + +static void callback(pa_mainloop_api *mainloop, pa_io_event *e, int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) { + pa_socket_server *s = userdata; + pa_iochannel *io; + int nfd; + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(s->mainloop == mainloop); + pa_assert(s->io_event == e); + pa_assert(e); + pa_assert(fd >= 0); + pa_assert(fd == s->fd); + + pa_socket_server_ref(s); + + if ((nfd = accept(fd, NULL, NULL)) < 0) { + pa_log("accept(): %s", pa_cstrerror(errno)); + goto finish; + } + + pa_make_fd_cloexec(nfd); + + if (!s->on_connection) { + pa_close(nfd); + goto finish; + } + +#ifdef HAVE_LIBWRAP + + if (s->tcpwrap_service) { + struct request_info req; + + request_init(&req, RQ_DAEMON, s->tcpwrap_service, RQ_FILE, nfd, NULL); + fromhost(&req); + if (!hosts_access(&req)) { + pa_log_warn("TCP connection refused by tcpwrap."); + pa_close(nfd); + goto finish; + } + + pa_log_info("TCP connection accepted by tcpwrap."); + } +#endif + + /* There should be a check for socket type here */ + if (s->type == SOCKET_SERVER_IPV4) + pa_make_tcp_socket_low_delay(fd); + else + pa_make_socket_low_delay(fd); + + pa_assert_se(io = pa_iochannel_new(s->mainloop, nfd, nfd)); + s->on_connection(s, io, s->userdata); + +finish: + pa_socket_server_unref(s); +} + +pa_socket_server* pa_socket_server_new(pa_mainloop_api *m, int fd) { + pa_socket_server *s; + + pa_assert(m); + pa_assert(fd >= 0); + + s = pa_xnew(pa_socket_server, 1); + PA_REFCNT_INIT(s); + s->fd = fd; + s->filename = NULL; + s->on_connection = NULL; + s->userdata = NULL; + s->tcpwrap_service = NULL; + + s->mainloop = m; + pa_assert_se(s->io_event = m->io_new(m, fd, PA_IO_EVENT_INPUT, callback, s)); + + s->type = SOCKET_SERVER_GENERIC; + + return s; +} + +pa_socket_server* pa_socket_server_ref(pa_socket_server *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_REFCNT_INC(s); + return s; +} + +#ifdef HAVE_SYS_UN_H + +pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *filename) { + int fd = -1; + struct sockaddr_un sa; + pa_socket_server *s; + + pa_assert(m); + pa_assert(filename); + + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + pa_log("socket(): %s", pa_cstrerror(errno)); + goto fail; + } + + pa_make_fd_cloexec(fd); + + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path, filename, sizeof(sa.sun_path)-1); + sa.sun_path[sizeof(sa.sun_path) - 1] = 0; + + pa_make_socket_low_delay(fd); + + if (bind(fd, (struct sockaddr*) &sa, SUN_LEN(&sa)) < 0) { + pa_log("bind(): %s", pa_cstrerror(errno)); + goto fail; + } + + /* Allow access from all clients. Sockets like this one should + * always be put inside a directory with proper access rights, + * because not all OS check the access rights on the socket + * inodes. */ + chmod(filename, 0777); + + if (listen(fd, 5) < 0) { + pa_log("listen(): %s", pa_cstrerror(errno)); + goto fail; + } + + pa_assert_se(s = pa_socket_server_new(m, fd)); + + s->filename = pa_xstrdup(filename); + s->type = SOCKET_SERVER_UNIX; + + return s; + +fail: + if (fd >= 0) + pa_close(fd); + + return NULL; +} + +#else /* HAVE_SYS_UN_H */ + +pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *filename) { + return NULL; +} + +#endif /* HAVE_SYS_UN_H */ + +pa_socket_server* pa_socket_server_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port, const char *tcpwrap_service) { + pa_socket_server *ss; + int fd = -1; + struct sockaddr_in sa; + int on = 1; + + pa_assert(m); + pa_assert(port); + + if ((fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) { + pa_log("socket(PF_INET): %s", pa_cstrerror(errno)); + goto fail; + } + + pa_make_fd_cloexec(fd); + +#ifdef SO_REUSEADDR + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + pa_log("setsockopt(): %s", pa_cstrerror(errno)); +#endif + + pa_make_tcp_socket_low_delay(fd); + + memset(&sa, 0, sizeof(sa)); + sa.sin_family = AF_INET; + sa.sin_port = htons(port); + sa.sin_addr.s_addr = htonl(address); + + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + pa_log("bind(): %s", pa_cstrerror(errno)); + goto fail; + } + + if (listen(fd, 5) < 0) { + pa_log("listen(): %s", pa_cstrerror(errno)); + goto fail; + } + + if ((ss = pa_socket_server_new(m, fd))) { + ss->type = SOCKET_SERVER_IPV4; + ss->tcpwrap_service = pa_xstrdup(tcpwrap_service); + } + + return ss; + +fail: + if (fd >= 0) + pa_close(fd); + + return NULL; +} + +pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t address[16], uint16_t port, const char *tcpwrap_service) { + pa_socket_server *ss; + int fd = -1; + struct sockaddr_in6 sa; + int on = 1; + + pa_assert(m); + pa_assert(port > 0); + + if ((fd = socket(PF_INET6, SOCK_STREAM, 0)) < 0) { + pa_log("socket(PF_INET6): %s", pa_cstrerror(errno)); + goto fail; + } + + pa_make_fd_cloexec(fd); + +#ifdef IPV6_V6ONLY + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) + pa_log("setsockopt(IPPROTO_IPV6, IPV6_V6ONLY): %s", pa_cstrerror(errno)); +#endif + +#ifdef SO_REUSEADDR + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) + pa_log("setsockopt(SOL_SOCKET, SO_REUSEADDR, 1): %s", pa_cstrerror(errno)); +#endif + + pa_make_tcp_socket_low_delay(fd); + + memset(&sa, 0, sizeof(sa)); + sa.sin6_family = AF_INET6; + sa.sin6_port = htons(port); + memcpy(sa.sin6_addr.s6_addr, address, 16); + + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { + pa_log("bind(): %s", pa_cstrerror(errno)); + goto fail; + } + + if (listen(fd, 5) < 0) { + pa_log("listen(): %s", pa_cstrerror(errno)); + goto fail; + } + + if ((ss = pa_socket_server_new(m, fd))) { + ss->type = SOCKET_SERVER_IPV6; + ss->tcpwrap_service = pa_xstrdup(tcpwrap_service); + } + + return ss; + +fail: + if (fd >= 0) + pa_close(fd); + + return NULL; +} + +pa_socket_server* pa_socket_server_new_ipv4_loopback(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service) { + pa_assert(m); + pa_assert(port > 0); + + return pa_socket_server_new_ipv4(m, INADDR_LOOPBACK, port, tcpwrap_service); +} + +pa_socket_server* pa_socket_server_new_ipv6_loopback(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service) { + pa_assert(m); + pa_assert(port > 0); + + return pa_socket_server_new_ipv6(m, in6addr_loopback.s6_addr, port, tcpwrap_service); +} + +pa_socket_server* pa_socket_server_new_ipv4_any(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service) { + pa_assert(m); + pa_assert(port > 0); + + return pa_socket_server_new_ipv4(m, INADDR_ANY, port, tcpwrap_service); +} + +pa_socket_server* pa_socket_server_new_ipv6_any(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service) { + pa_assert(m); + pa_assert(port > 0); + + return pa_socket_server_new_ipv6(m, in6addr_any.s6_addr, port, tcpwrap_service); +} + +pa_socket_server* pa_socket_server_new_ipv4_string(pa_mainloop_api *m, const char *name, uint16_t port, const char *tcpwrap_service) { + struct in_addr ipv4; + + pa_assert(m); + pa_assert(name); + pa_assert(port > 0); + + if (inet_pton(AF_INET, name, &ipv4) > 0) + return pa_socket_server_new_ipv4(m, ntohl(ipv4.s_addr), port, tcpwrap_service); + + return NULL; +} + +pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const char *name, uint16_t port, const char *tcpwrap_service) { + struct in6_addr ipv6; + + pa_assert(m); + pa_assert(name); + pa_assert(port > 0); + + if (inet_pton(AF_INET6, name, &ipv6) > 0) + return pa_socket_server_new_ipv6(m, ipv6.s6_addr, port, tcpwrap_service); + + return NULL; +} + +static void socket_server_free(pa_socket_server*s) { + pa_assert(s); + + if (s->filename) { + unlink(s->filename); + pa_xfree(s->filename); + } + + pa_close(s->fd); + + pa_xfree(s->tcpwrap_service); + + s->mainloop->io_free(s->io_event); + pa_xfree(s); +} + +void pa_socket_server_unref(pa_socket_server *s) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + if (PA_REFCNT_DEC(s) <= 0) + socket_server_free(s); +} + +void pa_socket_server_set_callback(pa_socket_server*s, void (*on_connection)(pa_socket_server*s, pa_iochannel *io, void *userdata), void *userdata) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + s->on_connection = on_connection; + s->userdata = userdata; +} + +char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + pa_assert(c); + pa_assert(l > 0); + + switch (s->type) { + case SOCKET_SERVER_IPV6: { + struct sockaddr_in6 sa; + socklen_t sa_len = sizeof(sa); + + if (getsockname(s->fd, (struct sockaddr*) &sa, &sa_len) < 0) { + pa_log("getsockname(): %s", pa_cstrerror(errno)); + return NULL; + } + + if (memcmp(&in6addr_any, &sa.sin6_addr, sizeof(in6addr_any)) == 0) { + char fqdn[256]; + if (!pa_get_fqdn(fqdn, sizeof(fqdn))) + return NULL; + + pa_snprintf(c, l, "tcp6:%s:%u", fqdn, (unsigned) ntohs(sa.sin6_port)); + + } else if (memcmp(&in6addr_loopback, &sa.sin6_addr, sizeof(in6addr_loopback)) == 0) { + char hn[256]; + if (!pa_get_host_name(hn, sizeof(hn))) + return NULL; + + pa_snprintf(c, l, "{%s}tcp6:localhost:%u", hn, (unsigned) ntohs(sa.sin6_port)); + } else { + char ip[INET6_ADDRSTRLEN]; + + if (!inet_ntop(AF_INET6, &sa.sin6_addr, ip, sizeof(ip))) { + pa_log("inet_ntop(): %s", pa_cstrerror(errno)); + return NULL; + } + + pa_snprintf(c, l, "tcp6:[%s]:%u", ip, (unsigned) ntohs(sa.sin6_port)); + } + + return c; + } + + case SOCKET_SERVER_IPV4: { + struct sockaddr_in sa; + socklen_t sa_len = sizeof(sa); + + if (getsockname(s->fd, (struct sockaddr*) &sa, &sa_len) < 0) { + pa_log("getsockname(): %s", pa_cstrerror(errno)); + return NULL; + } + + if (sa.sin_addr.s_addr == INADDR_ANY) { + char fqdn[256]; + if (!pa_get_fqdn(fqdn, sizeof(fqdn))) + return NULL; + + pa_snprintf(c, l, "tcp:%s:%u", fqdn, (unsigned) ntohs(sa.sin_port)); + } else if (sa.sin_addr.s_addr == INADDR_LOOPBACK) { + char hn[256]; + if (!pa_get_host_name(hn, sizeof(hn))) + return NULL; + + pa_snprintf(c, l, "{%s}tcp:localhost:%u", hn, (unsigned) ntohs(sa.sin_port)); + } else { + char ip[INET_ADDRSTRLEN]; + + if (!inet_ntop(AF_INET, &sa.sin_addr, ip, sizeof(ip))) { + pa_log("inet_ntop(): %s", pa_cstrerror(errno)); + return NULL; + } + + pa_snprintf(c, l, "tcp:[%s]:%u", ip, (unsigned) ntohs(sa.sin_port)); + + } + + return c; + } + + case SOCKET_SERVER_UNIX: { + char hn[256]; + + if (!s->filename) + return NULL; + + if (!pa_get_host_name(hn, sizeof(hn))) + return NULL; + + pa_snprintf(c, l, "{%s}unix:%s", hn, s->filename); + return c; + } + + default: + return NULL; + } +} diff --git a/src/pulsecore/socket-server.h b/src/pulsecore/socket-server.h new file mode 100644 index 00000000..777599e5 --- /dev/null +++ b/src/pulsecore/socket-server.h @@ -0,0 +1,54 @@ +#ifndef foosocketserverhfoo +#define foosocketserverhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/mainloop-api.h> +#include <pulsecore/iochannel.h> + +/* It is safe to destroy the calling socket_server object from the callback */ + +typedef struct pa_socket_server pa_socket_server; + +pa_socket_server* pa_socket_server_new(pa_mainloop_api *m, int fd); +pa_socket_server* pa_socket_server_new_unix(pa_mainloop_api *m, const char *filename); +pa_socket_server* pa_socket_server_new_ipv4(pa_mainloop_api *m, uint32_t address, uint16_t port, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv6(pa_mainloop_api *m, const uint8_t address[16], uint16_t port, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv4_loopback(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv6_loopback(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv4_any(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv6_any(pa_mainloop_api *m, uint16_t port, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv4_string(pa_mainloop_api *m, const char *name, uint16_t port, const char *tcpwrap_service); +pa_socket_server* pa_socket_server_new_ipv6_string(pa_mainloop_api *m, const char *name, uint16_t port, const char *tcpwrap_service); + +void pa_socket_server_unref(pa_socket_server*s); +pa_socket_server* pa_socket_server_ref(pa_socket_server *s); + +void pa_socket_server_set_callback(pa_socket_server*s, void (*on_connection)(pa_socket_server*s, pa_iochannel *io, void *userdata), void *userdata); + +char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l); + +#endif diff --git a/src/pulsecore/socket-util.c b/src/pulsecore/socket-util.c new file mode 100644 index 00000000..456accb8 --- /dev/null +++ b/src/pulsecore/socket-util.c @@ -0,0 +1,286 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2004 Joe Marcus Clarke + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <stdarg.h> +#include <stdlib.h> +#include <signal.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/stat.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_SYS_UN_H +#include <sys/un.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN_SYSTM_H +#include <netinet/in_systm.h> +#endif +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif +#ifdef HAVE_NETINET_TCP_H +#include <netinet/tcp.h> +#endif +#ifdef HAVE_NETDB_H +#include <netdb.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#ifndef HAVE_INET_NTOP +#include "inet_ntop.h" +#endif + +#include "winsock.h" + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> + +#include "socket-util.h" + +void pa_socket_peer_to_string(int fd, char *c, size_t l) { + struct stat st; + + pa_assert(fd >= 0); + pa_assert(c); + pa_assert(l > 0); + +#ifndef OS_IS_WIN32 + pa_assert_se(fstat(fd, &st) == 0); +#endif + +#ifndef OS_IS_WIN32 + if (S_ISSOCK(st.st_mode)) { +#endif + union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; +#ifdef HAVE_SYS_UN_H + struct sockaddr_un un; +#endif + } sa; + socklen_t sa_len = sizeof(sa); + + if (getpeername(fd, &sa.sa, &sa_len) >= 0) { + + if (sa.sa.sa_family == AF_INET) { + uint32_t ip = ntohl(sa.in.sin_addr.s_addr); + + pa_snprintf(c, l, "TCP/IP client from %i.%i.%i.%i:%u", + ip >> 24, + (ip >> 16) & 0xFF, + (ip >> 8) & 0xFF, + ip & 0xFF, + ntohs(sa.in.sin_port)); + return; + } else if (sa.sa.sa_family == AF_INET6) { + char buf[INET6_ADDRSTRLEN]; + const char *res; + + res = inet_ntop(AF_INET6, &sa.in6.sin6_addr, buf, sizeof(buf)); + if (res) { + pa_snprintf(c, l, "TCP/IP client from [%s]:%u", buf, ntohs(sa.in6.sin6_port)); + return; + } +#ifdef HAVE_SYS_UN_H + } else if (sa.sa.sa_family == AF_UNIX) { + pa_snprintf(c, l, "UNIX socket client"); + return; +#endif + } + + } +#ifndef OS_IS_WIN32 + pa_snprintf(c, l, "Unknown network client"); + return; + } else if (S_ISCHR(st.st_mode) && (fd == 0 || fd == 1)) { + pa_snprintf(c, l, "STDIN/STDOUT client"); + return; + } +#endif /* OS_IS_WIN32 */ + + pa_snprintf(c, l, "Unknown client"); +} + +void pa_make_socket_low_delay(int fd) { + +#ifdef SO_PRIORITY + int priority; + pa_assert(fd >= 0); + + priority = 6; + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, (void*)&priority, sizeof(priority)) < 0) + pa_log_warn("SO_PRIORITY failed: %s", pa_cstrerror(errno)); +#endif +} + +void pa_make_tcp_socket_low_delay(int fd) { + pa_assert(fd >= 0); + + pa_make_socket_low_delay(fd); + +#if defined(SOL_TCP) || defined(IPPROTO_TCP) + { + int on = 1; +#if defined(SOL_TCP) + if (setsockopt(fd, SOL_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) < 0) +#else + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void*)&on, sizeof(on)) < 0) +#endif + pa_log_warn("TCP_NODELAY failed: %s", pa_cstrerror(errno)); + } +#endif + +#if defined(IPTOS_LOWDELAY) && defined(IP_TOS) && (defined(SOL_IP) || defined(IPPROTO_IP)) + { + int tos = IPTOS_LOWDELAY; +#ifdef SOL_IP + if (setsockopt(fd, SOL_IP, IP_TOS, (void*)&tos, sizeof(tos)) < 0) +#else + if (setsockopt(fd, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) < 0) +#endif + pa_log_warn("IP_TOS failed: %s", pa_cstrerror(errno)); + } +#endif +} + +void pa_make_udp_socket_low_delay(int fd) { + pa_assert(fd >= 0); + + pa_make_socket_low_delay(fd); + +#if defined(IPTOS_LOWDELAY) && defined(IP_TOS) && (defined(SOL_IP) || defined(IPPROTO_IP)) + { + int tos = IPTOS_LOWDELAY; +#ifdef SOL_IP + if (setsockopt(fd, SOL_IP, IP_TOS, (void*)&tos, sizeof(tos)) < 0) +#else + if (setsockopt(fd, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) < 0) +#endif + pa_log_warn("IP_TOS failed: %s", pa_cstrerror(errno)); + } +#endif +} + +int pa_socket_set_rcvbuf(int fd, size_t l) { + pa_assert(fd >= 0); + + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&l, sizeof(l)) < 0) { + pa_log_warn("SO_RCVBUF: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +int pa_socket_set_sndbuf(int fd, size_t l) { + pa_assert(fd >= 0); + + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&l, sizeof(l)) < 0) { + pa_log("SO_SNDBUF: %s", pa_cstrerror(errno)); + return -1; + } + + return 0; +} + +#ifdef HAVE_SYS_UN_H + +int pa_unix_socket_is_stale(const char *fn) { + struct sockaddr_un sa; + int fd = -1, ret = -1; + + pa_assert(fn); + + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + pa_log("socket(): %s", pa_cstrerror(errno)); + goto finish; + } + + sa.sun_family = AF_UNIX; + strncpy(sa.sun_path, fn, sizeof(sa.sun_path)-1); + sa.sun_path[sizeof(sa.sun_path) - 1] = 0; + + if (connect(fd, (struct sockaddr*) &sa, sizeof(sa)) < 0) { + if (errno == ECONNREFUSED) + ret = 1; + } else + ret = 0; + +finish: + if (fd >= 0) + pa_close(fd); + + return ret; +} + +int pa_unix_socket_remove_stale(const char *fn) { + int r; + + pa_assert(fn); + + if ((r = pa_unix_socket_is_stale(fn)) < 0) + return errno != ENOENT ? -1 : 0; + + if (!r) + return 0; + + /* Yes, here is a race condition. But who cares? */ + if (unlink(fn) < 0) + return -1; + + return 0; +} + +#else /* HAVE_SYS_UN_H */ + +int pa_unix_socket_is_stale(const char *fn) { + return -1; +} + +int pa_unix_socket_remove_stale(const char *fn) { + return -1; +} + +#endif /* HAVE_SYS_UN_H */ diff --git a/src/pulsecore/socket-util.h b/src/pulsecore/socket-util.h new file mode 100644 index 00000000..a0344c68 --- /dev/null +++ b/src/pulsecore/socket-util.h @@ -0,0 +1,42 @@ +#ifndef foosocketutilhfoo +#define foosocketutilhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <sys/types.h> + +void pa_socket_peer_to_string(int fd, char *c, size_t l); + +void pa_make_socket_low_delay(int fd); +void pa_make_tcp_socket_low_delay(int fd); +void pa_make_udp_socket_low_delay(int fd); + +int pa_socket_set_sndbuf(int fd, size_t l); +int pa_socket_set_rcvbuf(int fd, size_t l); + +int pa_unix_socket_is_stale(const char *fn); +int pa_unix_socket_remove_stale(const char *fn); + +#endif diff --git a/src/pulsecore/sound-file-stream.c b/src/pulsecore/sound-file-stream.c new file mode 100644 index 00000000..bb1f3e9a --- /dev/null +++ b/src/pulsecore/sound-file-stream.c @@ -0,0 +1,339 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <stdio.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <sndfile.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/core-error.h> +#include <pulsecore/sink-input.h> +#include <pulsecore/log.h> +#include <pulsecore/thread-mq.h> +#include <pulsecore/core-util.h> + +#include "sound-file-stream.h" + +typedef struct file_stream { + pa_msgobject parent; + pa_core *core; + SNDFILE *sndfile; + pa_sink_input *sink_input; + pa_memchunk memchunk; + sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames); + size_t drop; +} file_stream; + +enum { + FILE_STREAM_MESSAGE_UNLINK +}; + +PA_DECLARE_CLASS(file_stream); +#define FILE_STREAM(o) (file_stream_cast(o)) +static PA_DEFINE_CHECK_TYPE(file_stream, pa_msgobject); + +static void file_stream_unlink(file_stream *u) { + pa_assert(u); + + if (!u->sink_input) + return; + + pa_sink_input_unlink(u->sink_input); + + pa_sink_input_unref(u->sink_input); + u->sink_input = NULL; + + /* Make sure we don't decrease the ref count twice. */ + file_stream_unref(u); +} + +static void file_stream_free(pa_object *o) { + file_stream *u = FILE_STREAM(o); + pa_assert(u); + + file_stream_unlink(u); + + if (u->memchunk.memblock) + pa_memblock_unref(u->memchunk.memblock); + + if (u->sndfile) + sf_close(u->sndfile); + + pa_xfree(u); +} + +static int file_stream_process_msg(pa_msgobject *o, int code, void*userdata, int64_t offset, pa_memchunk *chunk) { + file_stream *u = FILE_STREAM(o); + file_stream_assert_ref(u); + + switch (code) { + case FILE_STREAM_MESSAGE_UNLINK: + file_stream_unlink(u); + break; + } + + return 0; +} + +static void sink_input_kill_cb(pa_sink_input *i) { + pa_sink_input_assert_ref(i); + + file_stream_unlink(FILE_STREAM(i->userdata)); +} + +static int sink_input_peek_cb(pa_sink_input *i, size_t length, pa_memchunk *chunk) { + file_stream *u; + + pa_assert(i); + pa_assert(chunk); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + if (!u->sndfile) + return -1; + + for (;;) { + + if (!u->memchunk.memblock) { + + u->memchunk.memblock = pa_memblock_new(i->sink->core->mempool, length); + u->memchunk.index = 0; + + if (u->readf_function) { + sf_count_t n; + void *p; + size_t fs = pa_frame_size(&i->sample_spec); + + p = pa_memblock_acquire(u->memchunk.memblock); + n = u->readf_function(u->sndfile, p, length/fs); + pa_memblock_release(u->memchunk.memblock); + + if (n <= 0) + n = 0; + + u->memchunk.length = n * fs; + } else { + sf_count_t n; + void *p; + + p = pa_memblock_acquire(u->memchunk.memblock); + n = sf_read_raw(u->sndfile, p, length); + pa_memblock_release(u->memchunk.memblock); + + if (n <= 0) + n = 0; + + u->memchunk.length = n; + } + + if (u->memchunk.length <= 0) { + + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + + pa_asyncmsgq_post(pa_thread_mq_get()->outq, PA_MSGOBJECT(u), FILE_STREAM_MESSAGE_UNLINK, NULL, 0, NULL, NULL); + + sf_close(u->sndfile); + u->sndfile = NULL; + + return -1; + } + } + + pa_assert(u->memchunk.memblock); + pa_assert(u->memchunk.length > 0); + + if (u->drop < u->memchunk.length) { + u->memchunk.index += u->drop; + u->memchunk.length -= u->drop; + u->drop = 0; + break; + } + + u->drop -= u->memchunk.length; + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + *chunk = u->memchunk; + pa_memblock_ref(chunk->memblock); + + pa_assert(chunk->length > 0); + pa_assert(u->drop <= 0); + + return 0; +} + +static void sink_input_drop_cb(pa_sink_input *i, size_t length) { + file_stream *u; + + pa_assert(i); + pa_assert(length > 0); + u = FILE_STREAM(i->userdata); + file_stream_assert_ref(u); + + if (u->memchunk.memblock) { + + if (length < u->memchunk.length) { + u->memchunk.index += length; + u->memchunk.length -= length; + return; + } + + length -= u->memchunk.length; + pa_memblock_unref(u->memchunk.memblock); + pa_memchunk_reset(&u->memchunk); + } + + u->drop += length; +} + +int pa_play_file( + pa_sink *sink, + const char *fname, + const pa_cvolume *volume) { + + file_stream *u = NULL; + SF_INFO sfinfo; + pa_sample_spec ss; + pa_sink_input_new_data data; + int fd; + + pa_assert(sink); + pa_assert(fname); + + u = pa_msgobject_new(file_stream); + u->parent.parent.free = file_stream_free; + u->parent.process_msg = file_stream_process_msg; + u->core = sink->core; + u->sink_input = NULL; + pa_memchunk_reset(&u->memchunk); + u->sndfile = NULL; + u->readf_function = NULL; + u->drop = 0; + + memset(&sfinfo, 0, sizeof(sfinfo)); + + if ((fd = open(fname, O_RDONLY +#ifdef O_NOCTTY + |O_NOCTTY +#endif + )) < 0) { + pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno)); + goto fail; + } + + /* FIXME: For now we just use posix_fadvise to avoid page faults + * when accessing the file data. Eventually we should move the + * file reader into the main event loop and pass the data over the + * asyncmsgq. */ + +#ifdef HAVE_POSIX_FADVISE + if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL) < 0) { + pa_log_warn("POSIX_FADV_SEQUENTIAL failed: %s", pa_cstrerror(errno)); + goto fail; + } else + pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded."); + + if (posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED) < 0) { + pa_log_warn("POSIX_FADV_WILLNEED failed: %s", pa_cstrerror(errno)); + goto fail; + } else + pa_log_debug("POSIX_FADV_WILLNEED succeeded."); +#endif + + if (!(u->sndfile = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { + pa_log("Failed to open file %s", fname); + pa_close(fd); + goto fail; + } + + switch (sfinfo.format & 0xFF) { + case SF_FORMAT_PCM_16: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_PCM_S8: + ss.format = PA_SAMPLE_S16NE; + u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_short; + break; + + case SF_FORMAT_ULAW: + ss.format = PA_SAMPLE_ULAW; + break; + + case SF_FORMAT_ALAW: + ss.format = PA_SAMPLE_ALAW; + break; + + case SF_FORMAT_FLOAT: + default: + ss.format = PA_SAMPLE_FLOAT32NE; + u->readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_float; + break; + } + + ss.rate = sfinfo.samplerate; + ss.channels = sfinfo.channels; + + if (!pa_sample_spec_valid(&ss)) { + pa_log("Unsupported sample format in file %s", fname); + goto fail; + } + + pa_sink_input_new_data_init(&data); + data.sink = sink; + data.driver = __FILE__; + data.name = fname; + pa_sink_input_new_data_set_sample_spec(&data, &ss); + pa_sink_input_new_data_set_volume(&data, volume); + + if (!(u->sink_input = pa_sink_input_new(sink->core, &data, 0))) + goto fail; + + u->sink_input->peek = sink_input_peek_cb; + u->sink_input->drop = sink_input_drop_cb; + u->sink_input->kill = sink_input_kill_cb; + u->sink_input->userdata = u; + + pa_sink_input_put(u->sink_input); + + /* The reference to u is dangling here, because we want to keep + * this stream around until it is fully played. */ + + return 0; + +fail: + if (u) + file_stream_unref(u); + + return -1; +} diff --git a/src/pulsecore/sound-file-stream.h b/src/pulsecore/sound-file-stream.h new file mode 100644 index 00000000..189e242d --- /dev/null +++ b/src/pulsecore/sound-file-stream.h @@ -0,0 +1,31 @@ +#ifndef foosoundfilestreamhfoo +#define foosoundfilestreamhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/sink.h> + +int pa_play_file(pa_sink *sink, const char *fname, const pa_cvolume *volume); + +#endif diff --git a/src/pulsecore/sound-file.c b/src/pulsecore/sound-file.c new file mode 100644 index 00000000..3e6f683d --- /dev/null +++ b/src/pulsecore/sound-file.c @@ -0,0 +1,211 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> + +#include <sndfile.h> + +#include <pulse/sample.h> +#include <pulsecore/log.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-error.h> +#include <pulsecore/core-util.h> + +#include "sound-file.h" +#include "core-scache.h" + +int pa_sound_file_load( + pa_mempool *pool, + const char *fname, + pa_sample_spec *ss, + pa_channel_map *map, + pa_memchunk *chunk) { + + SNDFILE *sf = NULL; + SF_INFO sfinfo; + int ret = -1; + size_t l; + sf_count_t (*readf_function)(SNDFILE *sndfile, void *ptr, sf_count_t frames) = NULL; + void *ptr = NULL; + int fd; + + pa_assert(fname); + pa_assert(ss); + pa_assert(chunk); + + pa_memchunk_reset(chunk); + memset(&sfinfo, 0, sizeof(sfinfo)); + + if ((fd = open(fname, O_RDONLY +#ifdef O_NOCTTY + |O_NOCTTY +#endif + )) < 0) { + pa_log("Failed to open file %s: %s", fname, pa_cstrerror(errno)); + goto finish; + } + +#ifdef HAVE_POSIX_FADVISE + if (posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL) < 0) { + pa_log_warn("POSIX_FADV_SEQUENTIAL failed: %s", pa_cstrerror(errno)); + goto finish; + } else + pa_log_debug("POSIX_FADV_SEQUENTIAL succeeded."); +#endif + + if (!(sf = sf_open_fd(fd, SFM_READ, &sfinfo, 1))) { + pa_log("Failed to open file %s", fname); + pa_close(fd); + goto finish; + } + + switch (sfinfo.format & SF_FORMAT_SUBMASK) { + case SF_FORMAT_PCM_16: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_PCM_S8: + ss->format = PA_SAMPLE_S16NE; + readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_short; + break; + + case SF_FORMAT_ULAW: + ss->format = PA_SAMPLE_ULAW; + break; + + case SF_FORMAT_ALAW: + ss->format = PA_SAMPLE_ALAW; + break; + + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + default: + ss->format = PA_SAMPLE_FLOAT32NE; + readf_function = (sf_count_t (*)(SNDFILE *sndfile, void *ptr, sf_count_t frames)) sf_readf_float; + break; + } + + ss->rate = sfinfo.samplerate; + ss->channels = sfinfo.channels; + + if (!pa_sample_spec_valid(ss)) { + pa_log("Unsupported sample format in file %s", fname); + goto finish; + } + + if (map) + if (!pa_channel_map_init_auto(map, ss->channels, PA_CHANNEL_MAP_DEFAULT)) { + pa_log("Unsupported channel map in file %s", fname); + goto finish; + } + + if ((l = pa_frame_size(ss) * sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + pa_log("File too large"); + goto finish; + } + + chunk->memblock = pa_memblock_new(pool, l); + chunk->index = 0; + chunk->length = l; + + ptr = pa_memblock_acquire(chunk->memblock); + + if ((readf_function && readf_function(sf, ptr, sfinfo.frames) != sfinfo.frames) || + (!readf_function && sf_read_raw(sf, ptr, l) != (sf_count_t) l)) { + pa_log("Premature file end"); + goto finish; + } + + ret = 0; + +finish: + + if (sf) + sf_close(sf); + + if (ptr) + pa_memblock_release(chunk->memblock); + + if (ret != 0 && chunk->memblock) + pa_memblock_unref(chunk->memblock); + + return ret; +} + +int pa_sound_file_too_big_to_cache(const char *fname) { + + SNDFILE*sf = NULL; + SF_INFO sfinfo; + pa_sample_spec ss; + + pa_assert(fname); + + if (!(sf = sf_open(fname, SFM_READ, &sfinfo))) { + pa_log("Failed to open file %s", fname); + return -1; + } + + sf_close(sf); + + switch (sfinfo.format & SF_FORMAT_SUBMASK) { + case SF_FORMAT_PCM_16: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_PCM_S8: + ss.format = PA_SAMPLE_S16NE; + break; + + case SF_FORMAT_ULAW: + ss.format = PA_SAMPLE_ULAW; + break; + + case SF_FORMAT_ALAW: + ss.format = PA_SAMPLE_ALAW; + break; + + case SF_FORMAT_DOUBLE: + case SF_FORMAT_FLOAT: + default: + ss.format = PA_SAMPLE_FLOAT32NE; + break; + } + + ss.rate = sfinfo.samplerate; + ss.channels = sfinfo.channels; + + if (!pa_sample_spec_valid(&ss)) { + pa_log("Unsupported sample format in file %s", fname); + return -1; + } + + if ((pa_frame_size(&ss) * sfinfo.frames) > PA_SCACHE_ENTRY_SIZE_MAX) { + pa_log("File too large: %s", fname); + return 1; + } + + return 0; +} diff --git a/src/pulsecore/sound-file.h b/src/pulsecore/sound-file.h new file mode 100644 index 00000000..46763bd8 --- /dev/null +++ b/src/pulsecore/sound-file.h @@ -0,0 +1,35 @@ +#ifndef soundfilehfoo +#define soundfilehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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/sample.h> +#include <pulse/channelmap.h> +#include <pulsecore/memchunk.h> + +int pa_sound_file_load(pa_mempool *pool, const char *fname, pa_sample_spec *ss, pa_channel_map *map, pa_memchunk *chunk); + +int pa_sound_file_too_big_to_cache(const char *fname); + +#endif diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c new file mode 100644 index 00000000..88c11469 --- /dev/null +++ b/src/pulsecore/source-output.c @@ -0,0 +1,515 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/namereg.h> + +#include "source-output.h" + +static PA_DEFINE_CHECK_TYPE(pa_source_output, pa_msgobject); + +static void source_output_free(pa_object* mo); + +pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data) { + pa_assert(data); + + memset(data, 0, sizeof(*data)); + data->resample_method = PA_RESAMPLER_INVALID; + return data; +} + +void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map) { + pa_assert(data); + + if ((data->channel_map_is_set = !!map)) + data->channel_map = *map; +} + +void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec) { + pa_assert(data); + + if ((data->sample_spec_is_set = !!spec)) + data->sample_spec = *spec; +} + +pa_source_output* pa_source_output_new( + pa_core *core, + pa_source_output_new_data *data, + pa_source_output_flags_t flags) { + + pa_source_output *o; + pa_resampler *resampler = NULL; + char st[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + pa_assert(core); + pa_assert(data); + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], data) < 0) + return NULL; + + pa_return_null_if_fail(!data->driver || pa_utf8_valid(data->driver)); + pa_return_null_if_fail(!data->name || pa_utf8_valid(data->name)); + + if (!data->source) + data->source = pa_namereg_get(core, NULL, PA_NAMEREG_SOURCE, 1); + + pa_return_null_if_fail(data->source); + pa_return_null_if_fail(pa_source_get_state(data->source) != PA_SOURCE_UNLINKED); + + if (!data->sample_spec_is_set) + data->sample_spec = data->source->sample_spec; + + pa_return_null_if_fail(pa_sample_spec_valid(&data->sample_spec)); + + if (!data->channel_map_is_set) { + if (data->source->channel_map.channels == data->sample_spec.channels) + data->channel_map = data->source->channel_map; + else + pa_return_null_if_fail(pa_channel_map_init_auto(&data->channel_map, data->sample_spec.channels, PA_CHANNEL_MAP_DEFAULT)); + } + + pa_return_null_if_fail(pa_channel_map_valid(&data->channel_map)); + pa_return_null_if_fail(data->channel_map.channels == data->sample_spec.channels); + + if (flags & PA_SOURCE_OUTPUT_FIX_FORMAT) + data->sample_spec.format = data->source->sample_spec.format; + + if (flags & PA_SOURCE_OUTPUT_FIX_RATE) + data->sample_spec.rate = data->source->sample_spec.rate; + + if (flags & PA_SOURCE_OUTPUT_FIX_CHANNELS) { + data->sample_spec.channels = data->source->sample_spec.channels; + data->channel_map = data->source->channel_map; + } + + pa_assert(pa_sample_spec_valid(&data->sample_spec)); + pa_assert(pa_channel_map_valid(&data->channel_map)); + + if (data->resample_method == PA_RESAMPLER_INVALID) + data->resample_method = core->resample_method; + + pa_return_null_if_fail(data->resample_method < PA_RESAMPLER_MAX); + + if (pa_hook_fire(&core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE], data) < 0) + return NULL; + + if (pa_idxset_size(data->source->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) { + pa_log("Failed to create source output: too many outputs per source."); + return NULL; + } + + if ((flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) || + !pa_sample_spec_equal(&data->sample_spec, &data->source->sample_spec) || + !pa_channel_map_equal(&data->channel_map, &data->source->channel_map)) { + + if (!(resampler = pa_resampler_new( + core->mempool, + &data->source->sample_spec, &data->source->channel_map, + &data->sample_spec, &data->channel_map, + data->resample_method, + ((flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (core->disable_remixing || (flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) { + pa_log_warn("Unsupported resampling operation."); + return NULL; + } + + data->resample_method = pa_resampler_get_method(resampler); + } + + o = pa_msgobject_new(pa_source_output); + o->parent.parent.free = source_output_free; + o->parent.process_msg = pa_source_output_process_msg; + + o->core = core; + o->state = PA_SOURCE_OUTPUT_INIT; + o->flags = flags; + o->name = pa_xstrdup(data->name); + o->driver = pa_xstrdup(data->driver); + o->module = data->module; + o->source = data->source; + o->client = data->client; + + o->resample_method = data->resample_method; + o->sample_spec = data->sample_spec; + o->channel_map = data->channel_map; + + o->push = NULL; + o->kill = NULL; + o->get_latency = NULL; + o->detach = NULL; + o->attach = NULL; + o->suspend = NULL; + o->moved = NULL; + o->userdata = NULL; + + o->thread_info.state = o->state; + o->thread_info.attached = FALSE; + o->thread_info.sample_spec = o->sample_spec; + o->thread_info.resampler = resampler; + + pa_assert_se(pa_idxset_put(core->source_outputs, o, &o->index) == 0); + pa_assert_se(pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL) == 0); + + pa_log_info("Created output %u \"%s\" on %s with sample spec %s and channel map %s", + o->index, + o->name, + o->source->name, + pa_sample_spec_snprint(st, sizeof(st), &o->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &o->channel_map)); + + /* Don't forget to call pa_source_output_put! */ + + return o; +} + +static int source_output_set_state(pa_source_output *o, pa_source_output_state_t state) { + pa_assert(o); + + if (o->state == state) + return 0; + + if (pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0) + return -1; + + if (o->state == PA_SOURCE_OUTPUT_CORKED && state != PA_SOURCE_OUTPUT_CORKED) + pa_assert_se(o->source->n_corked -- >= 1); + else if (o->state != PA_SOURCE_OUTPUT_CORKED && state == PA_SOURCE_OUTPUT_CORKED) + o->source->n_corked++; + + pa_source_update_status(o->source); + + o->state = state; + + if (state != PA_SOURCE_OUTPUT_UNLINKED) + pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED], o); + + return 0; +} + +void pa_source_output_unlink(pa_source_output*o) { + pa_bool_t linked; + pa_assert(o); + + /* See pa_sink_unlink() for a couple of comments how this function + * works */ + + pa_source_output_ref(o); + + linked = PA_SOURCE_OUTPUT_LINKED(o->state); + + if (linked) + pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK], o); + + pa_idxset_remove_by_data(o->source->core->source_outputs, o, NULL); + if (pa_idxset_remove_by_data(o->source->outputs, o, NULL)) + pa_source_output_unref(o); + + if (linked) { + pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); + source_output_set_state(o, PA_SOURCE_OUTPUT_UNLINKED); + pa_source_update_status(o->source); + } else + o->state = PA_SOURCE_OUTPUT_UNLINKED; + + o->push = NULL; + o->kill = NULL; + o->get_latency = NULL; + o->attach = NULL; + o->detach = NULL; + o->suspend = NULL; + o->moved = NULL; + + if (linked) { + pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_REMOVE, o->index); + pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], o); + } + + o->source = NULL; + pa_source_output_unref(o); +} + +static void source_output_free(pa_object* mo) { + pa_source_output *o = PA_SOURCE_OUTPUT(mo); + + pa_assert(pa_source_output_refcnt(o) == 0); + + if (PA_SOURCE_OUTPUT_LINKED(o->state)) + pa_source_output_unlink(o); + + pa_log_info("Freeing output %u \"%s\"", o->index, o->name); + + pa_assert(!o->thread_info.attached); + + if (o->thread_info.resampler) + pa_resampler_free(o->thread_info.resampler); + + pa_xfree(o->name); + pa_xfree(o->driver); + pa_xfree(o); +} + +void pa_source_output_put(pa_source_output *o) { + pa_source_output_assert_ref(o); + + pa_assert(o->state == PA_SOURCE_OUTPUT_INIT); + pa_assert(o->push); + + o->thread_info.state = o->state = o->flags & PA_SOURCE_OUTPUT_START_CORKED ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING; + + if (o->state == PA_SOURCE_OUTPUT_CORKED) + o->source->n_corked++; + + pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL); + pa_source_update_status(o->source); + + pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_NEW, o->index); + + pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], o); +} + +void pa_source_output_kill(pa_source_output*o) { + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state)); + + if (o->kill) + o->kill(o); +} + +pa_usec_t pa_source_output_get_latency(pa_source_output *o) { + pa_usec_t r = 0; + + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state)); + + if (pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, &r, 0, NULL) < 0) + r = 0; + + if (o->get_latency) + r += o->get_latency(o); + + return r; +} + +/* Called from thread context */ +void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) { + pa_memchunk rchunk; + + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_LINKED(o->thread_info.state)); + pa_assert(chunk); + pa_assert(chunk->length); + + if (!o->push || o->state == PA_SOURCE_OUTPUT_CORKED) + return; + + pa_assert(o->state == PA_SOURCE_OUTPUT_RUNNING); + + if (!o->thread_info.resampler) { + o->push(o, chunk); + return; + } + + pa_resampler_run(o->thread_info.resampler, chunk, &rchunk); + if (!rchunk.length) + return; + + pa_assert(rchunk.memblock); + o->push(o, &rchunk); + pa_memblock_unref(rchunk.memblock); +} + +void pa_source_output_cork(pa_source_output *o, pa_bool_t b) { + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state)); + + source_output_set_state(o, b ? PA_SOURCE_OUTPUT_CORKED : PA_SOURCE_OUTPUT_RUNNING); +} + +int pa_source_output_set_rate(pa_source_output *o, uint32_t rate) { + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state)); + pa_return_val_if_fail(o->thread_info.resampler, -1); + + if (o->sample_spec.rate == rate) + return 0; + + o->sample_spec.rate = rate; + + pa_asyncmsgq_post(o->source->asyncmsgq, PA_MSGOBJECT(o), PA_SOURCE_OUTPUT_MESSAGE_SET_RATE, PA_UINT_TO_PTR(rate), 0, NULL, NULL); + + pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + return 0; +} + +void pa_source_output_set_name(pa_source_output *o, const char *name) { + pa_source_output_assert_ref(o); + + if (!o->name && !name) + return; + + if (o->name && name && !strcmp(o->name, name)) + return; + + pa_xfree(o->name); + o->name = pa_xstrdup(name); + + if (PA_SOURCE_OUTPUT_LINKED(o->state)) { + pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NAME_CHANGED], o); + pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + } +} + +pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o) { + pa_source_output_assert_ref(o); + + return o->resample_method; +} + +int pa_source_output_move_to(pa_source_output *o, pa_source *dest) { + pa_source *origin; + pa_resampler *new_resampler = NULL; + pa_source_output_move_hook_data hook_data; + + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_LINKED(o->state)); + pa_source_assert_ref(dest); + + origin = o->source; + + if (dest == origin) + return 0; + + if (o->flags & PA_SOURCE_OUTPUT_DONT_MOVE) + return -1; + + if (pa_idxset_size(dest->outputs) >= PA_MAX_OUTPUTS_PER_SOURCE) { + pa_log_warn("Failed to move source output: too many outputs per source."); + return -1; + } + + if (o->thread_info.resampler && + pa_sample_spec_equal(&origin->sample_spec, &dest->sample_spec) && + pa_channel_map_equal(&origin->channel_map, &dest->channel_map)) + + /* Try to reuse the old resampler if possible */ + new_resampler = o->thread_info.resampler; + + else if ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) || + !pa_sample_spec_equal(&o->sample_spec, &dest->sample_spec) || + !pa_channel_map_equal(&o->channel_map, &dest->channel_map)) { + + /* Okey, we need a new resampler for the new source */ + + if (!(new_resampler = pa_resampler_new( + dest->core->mempool, + &dest->sample_spec, &dest->channel_map, + &o->sample_spec, &o->channel_map, + o->resample_method, + ((o->flags & PA_SOURCE_OUTPUT_VARIABLE_RATE) ? PA_RESAMPLER_VARIABLE_RATE : 0) | + ((o->flags & PA_SOURCE_OUTPUT_NO_REMAP) ? PA_RESAMPLER_NO_REMAP : 0) | + (o->core->disable_remixing || (o->flags & PA_SOURCE_OUTPUT_NO_REMIX) ? PA_RESAMPLER_NO_REMIX : 0)))) { + pa_log_warn("Unsupported resampling operation."); + return -1; + } + } + + hook_data.source_output = o; + hook_data.destination = dest; + pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE], &hook_data); + + /* Okey, let's move it */ + pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_REMOVE_OUTPUT, o, 0, NULL); + + pa_idxset_remove_by_data(origin->outputs, o, NULL); + pa_idxset_put(dest->outputs, o, NULL); + o->source = dest; + + if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED) { + pa_assert_se(origin->n_corked-- >= 1); + dest->n_corked++; + } + + /* Replace resampler */ + if (new_resampler != o->thread_info.resampler) { + if (o->thread_info.resampler) + pa_resampler_free(o->thread_info.resampler); + o->thread_info.resampler = new_resampler; + } + + pa_asyncmsgq_send(o->source->asyncmsgq, PA_MSGOBJECT(o->source), PA_SOURCE_MESSAGE_ADD_OUTPUT, o, 0, NULL); + + pa_source_update_status(origin); + pa_source_update_status(dest); + + if (o->moved) + o->moved(o); + + pa_hook_fire(&o->source->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_POST], o); + + pa_log_debug("Successfully moved source output %i from %s to %s.", o->index, origin->name, dest->name); + + /* Notify everyone */ + pa_subscription_post(o->source->core, PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT|PA_SUBSCRIPTION_EVENT_CHANGE, o->index); + + return 0; +} + +/* Called from thread context */ +int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk* chunk) { + pa_source_output *o = PA_SOURCE_OUTPUT(mo); + + pa_source_output_assert_ref(o); + pa_assert(PA_SOURCE_OUTPUT_LINKED(o->thread_info.state)); + + switch (code) { + + case PA_SOURCE_OUTPUT_MESSAGE_SET_RATE: { + + o->thread_info.sample_spec.rate = PA_PTR_TO_UINT(userdata); + pa_resampler_set_output_rate(o->thread_info.resampler, PA_PTR_TO_UINT(userdata)); + + return 0; + } + + case PA_SOURCE_OUTPUT_MESSAGE_SET_STATE: { + o->thread_info.state = PA_PTR_TO_UINT(userdata); + + return 0; + } + } + + return -1; +} diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h new file mode 100644 index 00000000..d6da8d00 --- /dev/null +++ b/src/pulsecore/source-output.h @@ -0,0 +1,191 @@ +#ifndef foopulsesourceoutputhfoo +#define foopulsesourceoutputhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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> + +typedef struct pa_source_output pa_source_output; + +#include <pulse/sample.h> +#include <pulsecore/source.h> +#include <pulsecore/memblockq.h> +#include <pulsecore/resampler.h> +#include <pulsecore/module.h> +#include <pulsecore/client.h> + +typedef enum pa_source_output_state { + PA_SOURCE_OUTPUT_INIT, + PA_SOURCE_OUTPUT_RUNNING, + PA_SOURCE_OUTPUT_CORKED, + PA_SOURCE_OUTPUT_UNLINKED +} pa_source_output_state_t; + +static inline pa_bool_t PA_SOURCE_OUTPUT_LINKED(pa_source_output_state_t x) { + return x == PA_SOURCE_OUTPUT_RUNNING || x == PA_SOURCE_OUTPUT_CORKED; +} + +typedef enum pa_source_output_flags { + PA_SOURCE_OUTPUT_VARIABLE_RATE = 1, + PA_SOURCE_OUTPUT_DONT_MOVE = 2, + PA_SOURCE_OUTPUT_START_CORKED = 4, + PA_SOURCE_OUTPUT_NO_REMAP = 8, + PA_SOURCE_OUTPUT_NO_REMIX = 16, + PA_SOURCE_OUTPUT_FIX_FORMAT = 32, + PA_SOURCE_OUTPUT_FIX_RATE = 64, + PA_SOURCE_OUTPUT_FIX_CHANNELS = 128 +} pa_source_output_flags_t; + +struct pa_source_output { + pa_msgobject parent; + + uint32_t index; + pa_core *core; + pa_source_output_state_t state; + pa_source_output_flags_t flags; + + char *name, *driver; /* may be NULL */ + pa_module *module; /* may be NULL */ + pa_client *client; /* may be NULL */ + + pa_source *source; + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + + /* Pushes a new memchunk into the output. Called from IO thread + * context. */ + void (*push)(pa_source_output *o, const pa_memchunk *chunk); + + /* If non-NULL this function is called when the output is first + * connected to a source. Called from IO thread context */ + void (*attach) (pa_source_output *o); /* may be NULL */ + + /* If non-NULL this function is called when the output is + * disconnected from its source. Called from IO thread context */ + void (*detach) (pa_source_output *o); /* may be NULL */ + + /* If non-NULL called whenever the the source this output is attached + * to changes. Called from main context */ + void (*moved) (pa_source_output *o); /* may be NULL */ + + /* If non-NULL called whenever the the source this output is attached + * to suspends or resumes. Called from main context */ + void (*suspend) (pa_source_output *o, pa_bool_t b); /* may be NULL */ + + /* Supposed to unlink and destroy this stream. Called from main + * context. */ + void (*kill)(pa_source_output* o); /* may be NULL */ + + /* Return the current latency (i.e. length of bufferd audio) of + this stream. Called from main context. If NULL a + PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY message is sent to the IO + thread instead. */ + pa_usec_t (*get_latency) (pa_source_output *o); /* may be NULL */ + + pa_resample_method_t resample_method; + + struct { + pa_source_output_state_t state; + + pa_bool_t attached; /* True only between ->attach() and ->detach() calls */ + + pa_sample_spec sample_spec; + + pa_resampler* resampler; /* may be NULL */ + } thread_info; + + void *userdata; +}; + +PA_DECLARE_CLASS(pa_source_output); +#define PA_SOURCE_OUTPUT(o) pa_source_output_cast(o) + +enum { + PA_SOURCE_OUTPUT_MESSAGE_GET_LATENCY, + PA_SOURCE_OUTPUT_MESSAGE_SET_RATE, + PA_SOURCE_OUTPUT_MESSAGE_SET_STATE, + PA_SOURCE_OUTPUT_MESSAGE_MAX +}; + +typedef struct pa_source_output_new_data { + const char *name, *driver; + pa_module *module; + pa_client *client; + + pa_source *source; + + pa_sample_spec sample_spec; + pa_bool_t sample_spec_is_set; + pa_channel_map channel_map; + pa_bool_t channel_map_is_set; + + pa_resample_method_t resample_method; +} pa_source_output_new_data; + +typedef struct pa_source_output_move_hook_data { + pa_source_output *source_output; + pa_source *destination; +} pa_source_output_move_hook_data; + +pa_source_output_new_data* pa_source_output_new_data_init(pa_source_output_new_data *data); +void pa_source_output_new_data_set_sample_spec(pa_source_output_new_data *data, const pa_sample_spec *spec); +void pa_source_output_new_data_set_channel_map(pa_source_output_new_data *data, const pa_channel_map *map); +void pa_source_output_new_data_set_volume(pa_source_output_new_data *data, const pa_cvolume *volume); + +/* To be called by the implementing module only */ + +pa_source_output* pa_source_output_new( + pa_core *core, + pa_source_output_new_data *data, + pa_source_output_flags_t flags); + +void pa_source_output_put(pa_source_output *o); +void pa_source_output_unlink(pa_source_output*o); + +void pa_source_output_set_name(pa_source_output *i, const char *name); + +/* Callable by everyone */ + +/* External code may request disconnection with this funcion */ +void pa_source_output_kill(pa_source_output*o); + +pa_usec_t pa_source_output_get_latency(pa_source_output *i); + +void pa_source_output_cork(pa_source_output *i, pa_bool_t b); + +int pa_source_output_set_rate(pa_source_output *o, uint32_t rate); + +pa_resample_method_t pa_source_output_get_resample_method(pa_source_output *o); + +int pa_source_output_move_to(pa_source_output *o, pa_source *dest); + +#define pa_source_output_get_state(o) ((o)->state) + +/* To be used exclusively by the source driver thread */ + +void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk); +int pa_source_output_process_msg(pa_msgobject *mo, int code, void *userdata, int64_t offset, pa_memchunk *chunk); + +#endif diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c new file mode 100644 index 00000000..d707ad86 --- /dev/null +++ b/src/pulsecore/source.c @@ -0,0 +1,594 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pulse/utf8.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/source-output.h> +#include <pulsecore/namereg.h> +#include <pulsecore/core-subscribe.h> +#include <pulsecore/log.h> +#include <pulsecore/sample-util.h> + +#include "source.h" + +static PA_DEFINE_CHECK_TYPE(pa_source, pa_msgobject); + +static void source_free(pa_object *o); + +pa_source* pa_source_new( + pa_core *core, + const char *driver, + const char *name, + int fail, + const pa_sample_spec *spec, + const pa_channel_map *map) { + + pa_source *s; + char st[256]; + pa_channel_map tmap; + + pa_assert(core); + pa_assert(name); + pa_assert(spec); + + pa_return_null_if_fail(pa_sample_spec_valid(spec)); + + if (!map) + pa_return_null_if_fail(map = pa_channel_map_init_auto(&tmap, spec->channels, PA_CHANNEL_MAP_DEFAULT)); + + pa_return_null_if_fail(map && pa_channel_map_valid(map)); + pa_return_null_if_fail(map->channels == spec->channels); + pa_return_null_if_fail(!driver || pa_utf8_valid(driver)); + pa_return_null_if_fail(pa_utf8_valid(name) && *name); + + s = pa_msgobject_new(pa_source); + + if (!(name = pa_namereg_register(core, name, PA_NAMEREG_SOURCE, s, fail))) { + pa_xfree(s); + return NULL; + } + + s->parent.parent.free = source_free; + s->parent.process_msg = pa_source_process_msg; + + s->core = core; + s->state = PA_SOURCE_INIT; + s->flags = 0; + s->name = pa_xstrdup(name); + s->description = NULL; + s->driver = pa_xstrdup(driver); + s->module = NULL; + + s->sample_spec = *spec; + s->channel_map = *map; + + s->outputs = pa_idxset_new(NULL, NULL); + s->n_corked = 0; + s->monitor_of = NULL; + + pa_cvolume_reset(&s->volume, spec->channels); + s->muted = FALSE; + s->refresh_volume = s->refresh_muted = FALSE; + + s->get_latency = NULL; + s->set_volume = NULL; + s->get_volume = NULL; + s->set_mute = NULL; + s->get_mute = NULL; + s->set_state = NULL; + s->userdata = NULL; + + s->asyncmsgq = NULL; + s->rtpoll = NULL; + + pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0); + + pa_sample_spec_snprint(st, sizeof(st), spec); + pa_log_info("Created source %u \"%s\" with sample spec \"%s\"", s->index, s->name, st); + + s->thread_info.outputs = pa_hashmap_new(pa_idxset_trivial_hash_func, pa_idxset_trivial_compare_func); + s->thread_info.soft_volume = s->volume; + s->thread_info.soft_muted = s->muted; + s->thread_info.state = s->state; + + return s; +} + +static int source_set_state(pa_source *s, pa_source_state_t state) { + int ret; + pa_bool_t suspend_change; + + pa_assert(s); + + if (s->state == state) + return 0; + + suspend_change = + (s->state == PA_SOURCE_SUSPENDED && PA_SOURCE_OPENED(state)) || + (PA_SOURCE_OPENED(s->state) && state == PA_SOURCE_SUSPENDED); + + if (s->set_state) + if ((ret = s->set_state(s, state)) < 0) + return -1; + + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_STATE, PA_UINT_TO_PTR(state), 0, NULL) < 0) + return -1; + + s->state = state; + + if (suspend_change) { + pa_source_output *o; + uint32_t idx; + + /* We're suspending or resuming, tell everyone about it */ + + for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx))) + if (o->suspend) + o->suspend(o, state == PA_SINK_SUSPENDED); + } + + if (state != PA_SOURCE_UNLINKED) /* if we enter UNLINKED state pa_source_unlink() will fire the apropriate events */ + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], s); + + return 0; +} + +void pa_source_put(pa_source *s) { + pa_source_assert_ref(s); + + pa_assert(s->state == PA_SINK_INIT); + pa_assert(s->rtpoll); + pa_assert(s->asyncmsgq); + + pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0); + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_NEW_POST], s); +} + +void pa_source_unlink(pa_source *s) { + pa_bool_t linked; + pa_source_output *o, *j = NULL; + + pa_assert(s); + + /* See pa_sink_unlink() for a couple of comments how this function + * works. */ + + linked = PA_SOURCE_LINKED(s->state); + + if (linked) + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK], s); + + if (s->state != PA_SOURCE_UNLINKED) + pa_namereg_unregister(s->core, s->name); + pa_idxset_remove_by_data(s->core->sources, s, NULL); + + while ((o = pa_idxset_first(s->outputs, NULL))) { + pa_assert(o != j); + pa_source_output_kill(o); + j = o; + } + + if (linked) + source_set_state(s, PA_SOURCE_UNLINKED); + else + s->state = PA_SOURCE_UNLINKED; + + s->get_latency = NULL; + s->get_volume = NULL; + s->set_volume = NULL; + s->set_mute = NULL; + s->get_mute = NULL; + s->set_state = NULL; + + if (linked) { + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_REMOVE, s->index); + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_UNLINK_POST], s); + } +} + +static void source_free(pa_object *o) { + pa_source_output *so; + pa_source *s = PA_SOURCE(o); + + pa_assert(s); + pa_assert(pa_source_refcnt(s) == 0); + + if (PA_SOURCE_LINKED(s->state)) + pa_source_unlink(s); + + pa_log_info("Freeing source %u \"%s\"", s->index, s->name); + + pa_idxset_free(s->outputs, NULL, NULL); + + while ((so = pa_hashmap_steal_first(s->thread_info.outputs))) + pa_source_output_unref(so); + + pa_hashmap_free(s->thread_info.outputs, NULL, NULL); + + pa_xfree(s->name); + pa_xfree(s->description); + pa_xfree(s->driver); + pa_xfree(s); +} + +int pa_source_update_status(pa_source*s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + if (s->state == PA_SOURCE_SUSPENDED) + return 0; + + return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE); +} + +int pa_source_suspend(pa_source *s, pa_bool_t suspend) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + if (suspend) + return source_set_state(s, PA_SOURCE_SUSPENDED); + else + return source_set_state(s, pa_source_used_by(s) ? PA_SOURCE_RUNNING : PA_SOURCE_IDLE); +} + +void pa_source_ping(pa_source *s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_PING, NULL, 0, NULL, NULL); +} + +void pa_source_post(pa_source*s, const pa_memchunk *chunk) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_OPENED(s->thread_info.state)); + pa_assert(chunk); + + if (s->thread_info.state != PA_SOURCE_RUNNING) + return; + + if (s->thread_info.soft_muted || !pa_cvolume_is_norm(&s->thread_info.soft_volume)) { + pa_memchunk vchunk = *chunk; + + pa_memblock_ref(vchunk.memblock); + pa_memchunk_make_writable(&vchunk, 0); + + if (s->thread_info.soft_muted || pa_cvolume_is_muted(&s->thread_info.soft_volume)) + pa_silence_memchunk(&vchunk, &s->sample_spec); + else + pa_volume_memchunk(&vchunk, &s->sample_spec, &s->thread_info.soft_volume); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + pa_source_output_push(o, &vchunk); + + pa_memblock_unref(vchunk.memblock); + } else { + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + pa_source_output_push(o, chunk); + } +} + +pa_usec_t pa_source_get_latency(pa_source *s) { + pa_usec_t usec; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + if (!PA_SOURCE_OPENED(s->state)) + return 0; + + if (s->get_latency) + return s->get_latency(s); + + if (pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_LATENCY, &usec, 0, NULL) < 0) + return 0; + + return usec; +} + +void pa_source_set_volume(pa_source *s, const pa_cvolume *volume) { + int changed; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + pa_assert(volume); + + changed = !pa_cvolume_equal(volume, &s->volume); + s->volume = *volume; + + if (s->set_volume && s->set_volume(s) < 0) + s->set_volume = NULL; + + if (!s->set_volume) + pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_VOLUME, pa_xnewdup(struct pa_cvolume, volume, 1), 0, NULL, pa_xfree); + + if (changed) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +const pa_cvolume *pa_source_get_volume(pa_source *s) { + pa_cvolume old_volume; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + old_volume = s->volume; + + if (s->get_volume && s->get_volume(s) < 0) + s->get_volume = NULL; + + if (!s->get_volume && s->refresh_volume) + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_VOLUME, &s->volume, 0, NULL); + + if (!pa_cvolume_equal(&old_volume, &s->volume)) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + return &s->volume; +} + +void pa_source_set_mute(pa_source *s, pa_bool_t mute) { + int changed; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + changed = s->muted != mute; + s->muted = mute; + + if (s->set_mute && s->set_mute(s) < 0) + s->set_mute = NULL; + + if (!s->set_mute) + pa_asyncmsgq_post(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_MUTE, PA_UINT_TO_PTR(mute), 0, NULL, NULL); + + if (changed) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +pa_bool_t pa_source_get_mute(pa_source *s) { + pa_bool_t old_muted; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + old_muted = s->muted; + + if (s->get_mute && s->get_mute(s) < 0) + s->get_mute = NULL; + + if (!s->get_mute && s->refresh_muted) + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_GET_MUTE, &s->muted, 0, NULL); + + if (old_muted != s->muted) + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + + return s->muted; +} + +void pa_source_set_module(pa_source *s, pa_module *m) { + pa_source_assert_ref(s); + + if (m == s->module) + return; + + s->module = m; + + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); +} + +void pa_source_set_description(pa_source *s, const char *description) { + pa_source_assert_ref(s); + + if (!description && !s->description) + return; + + if (description && s->description && !strcmp(description, s->description)) + return; + + pa_xfree(s->description); + s->description = pa_xstrdup(description); + + if (PA_SOURCE_LINKED(s->state)) { + pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_DESCRIPTION_CHANGED], s); + pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); + } +} + +void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q) { + pa_source_assert_ref(s); + pa_assert(q); + + s->asyncmsgq = q; +} + +void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p) { + pa_source_assert_ref(s); + pa_assert(p); + + s->rtpoll = p; +} + +unsigned pa_source_linked_by(pa_source *s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + return pa_idxset_size(s->outputs); +} + +unsigned pa_source_used_by(pa_source *s) { + unsigned ret; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + ret = pa_idxset_size(s->outputs); + pa_assert(ret >= s->n_corked); + + return ret - s->n_corked; +} + +int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_t offset, pa_memchunk *chunk) { + pa_source *s = PA_SOURCE(object); + pa_source_assert_ref(s); + pa_assert(s->thread_info.state != PA_SOURCE_UNLINKED); + + switch ((pa_source_message_t) code) { + case PA_SOURCE_MESSAGE_ADD_OUTPUT: { + pa_source_output *o = PA_SOURCE_OUTPUT(userdata); + pa_hashmap_put(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index), pa_source_output_ref(o)); + + pa_assert(!o->thread_info.attached); + o->thread_info.attached = TRUE; + + if (o->attach) + o->attach(o); + + return 0; + } + + case PA_SOURCE_MESSAGE_REMOVE_OUTPUT: { + pa_source_output *o = PA_SOURCE_OUTPUT(userdata); + + if (o->detach) + o->detach(o); + + pa_assert(o->thread_info.attached); + o->thread_info.attached = FALSE; + + if (pa_hashmap_remove(s->thread_info.outputs, PA_UINT32_TO_PTR(o->index))) + pa_source_output_unref(o); + + return 0; + } + + case PA_SOURCE_MESSAGE_SET_VOLUME: + s->thread_info.soft_volume = *((pa_cvolume*) userdata); + return 0; + + case PA_SOURCE_MESSAGE_SET_MUTE: + s->thread_info.soft_muted = PA_PTR_TO_UINT(userdata); + return 0; + + case PA_SOURCE_MESSAGE_GET_VOLUME: + *((pa_cvolume*) userdata) = s->thread_info.soft_volume; + return 0; + + case PA_SOURCE_MESSAGE_GET_MUTE: + *((pa_bool_t*) userdata) = s->thread_info.soft_muted; + return 0; + + case PA_SOURCE_MESSAGE_PING: + return 0; + + case PA_SOURCE_MESSAGE_SET_STATE: + s->thread_info.state = PA_PTR_TO_UINT(userdata); + return 0; + + case PA_SOURCE_MESSAGE_DETACH: + + /* We're detaching all our output streams so that the + * asyncmsgq and rtpoll fields can be changed without + * problems */ + pa_source_detach_within_thread(s); + break; + + case PA_SOURCE_MESSAGE_ATTACH: + + /* Reattach all streams */ + pa_source_attach_within_thread(s); + break; + + case PA_SOURCE_MESSAGE_GET_LATENCY: + case PA_SOURCE_MESSAGE_MAX: + ; + } + + return -1; +} + +int pa_source_suspend_all(pa_core *c, pa_bool_t suspend) { + uint32_t idx; + pa_source *source; + int ret = 0; + + pa_core_assert_ref(c); + + for (source = PA_SOURCE(pa_idxset_first(c->sources, &idx)); source; source = PA_SOURCE(pa_idxset_next(c->sources, &idx))) + ret -= pa_source_suspend(source, suspend) < 0; + + return ret; +} + +void pa_source_detach(pa_source *s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_DETACH, NULL, 0, NULL); +} + +void pa_source_attach(pa_source *s) { + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->state)); + + pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_ATTACH, NULL, 0, NULL); +} + +void pa_source_detach_within_thread(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->thread_info.state)); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->detach) + o->detach(o); +} + +void pa_source_attach_within_thread(pa_source *s) { + pa_source_output *o; + void *state = NULL; + + pa_source_assert_ref(s); + pa_assert(PA_SOURCE_LINKED(s->thread_info.state)); + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->attach) + o->attach(o); + +} diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h new file mode 100644 index 00000000..bd0a9122 --- /dev/null +++ b/src/pulsecore/source.h @@ -0,0 +1,179 @@ +#ifndef foopulsesourcehfoo +#define foopulsesourcehfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +typedef struct pa_source pa_source; + +#include <inttypes.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> + +#include <pulsecore/core-def.h> +#include <pulsecore/core.h> +#include <pulsecore/idxset.h> +#include <pulsecore/memblock.h> +#include <pulsecore/memchunk.h> +#include <pulsecore/sink.h> +#include <pulsecore/module.h> +#include <pulsecore/asyncmsgq.h> +#include <pulsecore/msgobject.h> +#include <pulsecore/rtpoll.h> + +#define PA_MAX_OUTPUTS_PER_SOURCE 32 + +typedef enum pa_source_state { + PA_SOURCE_INIT, + PA_SOURCE_RUNNING, + PA_SOURCE_SUSPENDED, + PA_SOURCE_IDLE, + PA_SOURCE_UNLINKED +} pa_source_state_t; + +static inline pa_bool_t PA_SOURCE_OPENED(pa_source_state_t x) { + return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE; +} + +static inline pa_bool_t PA_SOURCE_LINKED(pa_source_state_t x) { + return x == PA_SOURCE_RUNNING || x == PA_SOURCE_IDLE || x == PA_SOURCE_SUSPENDED; +} + +struct pa_source { + pa_msgobject parent; + + uint32_t index; + pa_core *core; + pa_source_state_t state; + pa_source_flags_t flags; + + char *name; + char *description, *driver; /* may be NULL */ + + pa_module *module; /* may be NULL */ + + pa_sample_spec sample_spec; + pa_channel_map channel_map; + + pa_idxset *outputs; + unsigned n_corked; + pa_sink *monitor_of; /* may be NULL */ + + pa_cvolume volume; + pa_bool_t muted; + pa_bool_t refresh_volume; + pa_bool_t refresh_muted; + + int (*set_state)(pa_source*source, pa_source_state_t state); /* may be NULL */ + int (*set_volume)(pa_source *s); /* dito */ + int (*get_volume)(pa_source *s); /* dito */ + int (*set_mute)(pa_source *s); /* dito */ + int (*get_mute)(pa_source *s); /* dito */ + pa_usec_t (*get_latency)(pa_source *s); /* dito */ + + pa_asyncmsgq *asyncmsgq; + pa_rtpoll *rtpoll; + + /* Contains copies of the above data so that the real-time worker + * thread can work without access locking */ + struct { + pa_source_state_t state; + pa_hashmap *outputs; + pa_cvolume soft_volume; + pa_bool_t soft_muted; + } thread_info; + + void *userdata; +}; + +PA_DECLARE_CLASS(pa_source); +#define PA_SOURCE(s) pa_source_cast(s) + +typedef enum pa_source_message { + PA_SOURCE_MESSAGE_ADD_OUTPUT, + PA_SOURCE_MESSAGE_REMOVE_OUTPUT, + PA_SOURCE_MESSAGE_GET_VOLUME, + PA_SOURCE_MESSAGE_SET_VOLUME, + PA_SOURCE_MESSAGE_GET_MUTE, + PA_SOURCE_MESSAGE_SET_MUTE, + PA_SOURCE_MESSAGE_GET_LATENCY, + PA_SOURCE_MESSAGE_SET_STATE, + PA_SOURCE_MESSAGE_PING, + PA_SOURCE_MESSAGE_ATTACH, + PA_SOURCE_MESSAGE_DETACH, + PA_SOURCE_MESSAGE_MAX +} pa_source_message_t; + +/* To be called exclusively by the source driver, from main context */ + +pa_source* pa_source_new( + pa_core *core, + const char *driver, + const char *name, + int namereg_fail, + const pa_sample_spec *spec, + const pa_channel_map *map); + +void pa_source_put(pa_source *s); +void pa_source_unlink(pa_source *s); + +void pa_source_set_module(pa_source *s, pa_module *m); +void pa_source_set_description(pa_source *s, const char *description); +void pa_source_set_asyncmsgq(pa_source *s, pa_asyncmsgq *q); +void pa_source_set_rtpoll(pa_source *s, pa_rtpoll *p); + +void pa_source_detach(pa_source *s); +void pa_source_attach(pa_source *s); + +/* May be called by everyone, from main context */ + +pa_usec_t pa_source_get_latency(pa_source *s); + +int pa_source_update_status(pa_source*s); +int pa_source_suspend(pa_source *s, pa_bool_t suspend); +int pa_source_suspend_all(pa_core *c, pa_bool_t suspend); + +void pa_source_ping(pa_source *s); + +void pa_source_set_volume(pa_source *source, const pa_cvolume *volume); +const pa_cvolume *pa_source_get_volume(pa_source *source); +void pa_source_set_mute(pa_source *source, pa_bool_t mute); +pa_bool_t pa_source_get_mute(pa_source *source); + +unsigned pa_source_linked_by(pa_source *s); /* Number of connected streams */ +unsigned pa_source_used_by(pa_source *s); /* Number of connected streams that are not corked */ +#define pa_source_get_state(s) ((pa_source_state_t) (s)->state) + +/* To be called exclusively by the source driver, from IO context */ + +void pa_source_post(pa_source*s, const pa_memchunk *b); + +int pa_source_process_msg(pa_msgobject *o, int code, void *userdata, int64_t, pa_memchunk *chunk); + +void pa_source_attach_within_thread(pa_source *s); +void pa_source_detach_within_thread(pa_source *s); + +#endif diff --git a/src/pulsecore/speex/Makefile b/src/pulsecore/speex/Makefile new file mode 100644 index 00000000..316beb72 --- /dev/null +++ b/src/pulsecore/speex/Makefile @@ -0,0 +1,13 @@ +# This is a dirty trick just to ease compilation with emacs +# +# This file is not intended to be distributed or anything +# +# So: don't touch it, even better ignore it! + +all: + $(MAKE) -C ../.. + +clean: + $(MAKE) -C ../.. clean + +.PHONY: all clean diff --git a/src/pulsecore/speex/arch.h b/src/pulsecore/speex/arch.h new file mode 100644 index 00000000..9987c8fb --- /dev/null +++ b/src/pulsecore/speex/arch.h @@ -0,0 +1,241 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file arch.h + @brief Various architecture definitions Speex +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef ARCH_H +#define ARCH_H + +#ifndef SPEEX_VERSION +#define SPEEX_MAJOR_VERSION 1 /**< Major Speex version. */ +#define SPEEX_MINOR_VERSION 1 /**< Minor Speex version. */ +#define SPEEX_MICRO_VERSION 15 /**< Micro Speex version. */ +#define SPEEX_EXTRA_VERSION "" /**< Extra Speex version. */ +#define SPEEX_VERSION "speex-1.2beta3" /**< Speex version string. */ +#endif + +/* A couple test to catch stupid option combinations */ +#ifdef FIXED_POINT + +#ifdef FLOATING_POINT +#error You cannot compile as floating point and fixed point at the same time +#endif +#ifdef _USE_SSE +#error SSE is only for floating-point +#endif +#if ((defined (ARM4_ASM)||defined (ARM4_ASM)) && defined(BFIN_ASM)) || (defined (ARM4_ASM)&&defined(ARM5E_ASM)) +#error Make up your mind. What CPU do you have? +#endif +#ifdef VORBIS_PSYCHO +#error Vorbis-psy model currently not implemented in fixed-point +#endif + +#else + +#ifndef FLOATING_POINT +#error You now need to define either FIXED_POINT or FLOATING_POINT +#endif +#if defined (ARM4_ASM) || defined(ARM5E_ASM) || defined(BFIN_ASM) +#error I suppose you can have a [ARM4/ARM5E/Blackfin] that has float instructions? +#endif +#ifdef FIXED_POINT_DEBUG +#error "Don't you think enabling fixed-point is a good thing to do if you want to debug that?" +#endif + + +#endif + +#ifndef OUTSIDE_SPEEX +#include "speex/speex_types.h" +#endif + +#define ABS(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute integer value. */ +#define ABS16(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 16-bit value. */ +#define MIN16(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define MAX16(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 16-bit value. */ +#define ABS32(x) ((x) < 0 ? (-(x)) : (x)) /**< Absolute 32-bit value. */ +#define MIN32(a,b) ((a) < (b) ? (a) : (b)) /**< Maximum 32-bit value. */ +#define MAX32(a,b) ((a) > (b) ? (a) : (b)) /**< Maximum 32-bit value. */ + +#ifdef FIXED_POINT + +typedef spx_int16_t spx_word16_t; +typedef spx_int32_t spx_word32_t; +typedef spx_word32_t spx_mem_t; +typedef spx_word16_t spx_coef_t; +typedef spx_word16_t spx_lsp_t; +typedef spx_word32_t spx_sig_t; + +#define Q15ONE 32767 + +#define LPC_SCALING 8192 +#define SIG_SCALING 16384 +#define LSP_SCALING 8192. +#define GAMMA_SCALING 32768. +#define GAIN_SCALING 64 +#define GAIN_SCALING_1 0.015625 + +#define LPC_SHIFT 13 +#define LSP_SHIFT 13 +#define SIG_SHIFT 14 +#define GAIN_SHIFT 6 + +#define VERY_SMALL 0 +#define VERY_LARGE32 ((spx_word32_t)2147483647) +#define VERY_LARGE16 ((spx_word16_t)32767) +#define Q15_ONE ((spx_word16_t)32767) + + +#ifdef FIXED_DEBUG +#include "fixed_debug.h" +#else + +#include "fixed_generic.h" + +#ifdef ARM5E_ASM +#include "fixed_arm5e.h" +#elif defined (ARM4_ASM) +#include "fixed_arm4.h" +#elif defined (ARM5E_ASM) +#include "fixed_arm5e.h" +#elif defined (BFIN_ASM) +#include "fixed_bfin.h" +#endif + +#endif + + +#else + +typedef float spx_mem_t; +typedef float spx_coef_t; +typedef float spx_lsp_t; +typedef float spx_sig_t; +typedef float spx_word16_t; +typedef float spx_word32_t; + +#define Q15ONE 1.0f +#define LPC_SCALING 1.f +#define SIG_SCALING 1.f +#define LSP_SCALING 1.f +#define GAMMA_SCALING 1.f +#define GAIN_SCALING 1.f +#define GAIN_SCALING_1 1.f + + +#define VERY_SMALL 1e-15f +#define VERY_LARGE32 1e15f +#define VERY_LARGE16 1e15f +#define Q15_ONE ((spx_word16_t)1.f) + +#define QCONST16(x,bits) (x) +#define QCONST32(x,bits) (x) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define EXTRACT16(x) (x) +#define EXTEND32(x) (x) +#define SHR16(a,shift) (a) +#define SHL16(a,shift) (a) +#define SHR32(a,shift) (a) +#define SHL32(a,shift) (a) +#define PSHR16(a,shift) (a) +#define PSHR32(a,shift) (a) +#define VSHR32(a,shift) (a) +#define SATURATE16(x,a) (x) +#define SATURATE32(x,a) (x) + +#define PSHR(a,shift) (a) +#define SHR(a,shift) (a) +#define SHL(a,shift) (a) +#define SATURATE(x,a) (x) + +#define ADD16(a,b) ((a)+(b)) +#define SUB16(a,b) ((a)-(b)) +#define ADD32(a,b) ((a)+(b)) +#define SUB32(a,b) ((a)-(b)) +#define MULT16_16_16(a,b) ((a)*(b)) +#define MULT16_16(a,b) ((spx_word32_t)(a)*(spx_word32_t)(b)) +#define MAC16_16(c,a,b) ((c)+(spx_word32_t)(a)*(spx_word32_t)(b)) + +#define MULT16_32_Q11(a,b) ((a)*(b)) +#define MULT16_32_Q13(a,b) ((a)*(b)) +#define MULT16_32_Q14(a,b) ((a)*(b)) +#define MULT16_32_Q15(a,b) ((a)*(b)) +#define MULT16_32_P15(a,b) ((a)*(b)) + +#define MAC16_32_Q11(c,a,b) ((c)+(a)*(b)) +#define MAC16_32_Q15(c,a,b) ((c)+(a)*(b)) + +#define MAC16_16_Q11(c,a,b) ((c)+(a)*(b)) +#define MAC16_16_Q13(c,a,b) ((c)+(a)*(b)) +#define MAC16_16_P13(c,a,b) ((c)+(a)*(b)) +#define MULT16_16_Q11_32(a,b) ((a)*(b)) +#define MULT16_16_Q13(a,b) ((a)*(b)) +#define MULT16_16_Q14(a,b) ((a)*(b)) +#define MULT16_16_Q15(a,b) ((a)*(b)) +#define MULT16_16_P15(a,b) ((a)*(b)) +#define MULT16_16_P13(a,b) ((a)*(b)) +#define MULT16_16_P14(a,b) ((a)*(b)) + +#define DIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) +#define PDIV32_16(a,b) (((spx_word32_t)(a))/(spx_word16_t)(b)) +#define DIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) +#define PDIV32(a,b) (((spx_word32_t)(a))/(spx_word32_t)(b)) + + +#endif + + +#if defined (CONFIG_TI_C54X) || defined (CONFIG_TI_C55X) + +/* 2 on TI C5x DSP */ +#define BYTES_PER_CHAR 2 +#define BITS_PER_CHAR 16 +#define LOG2_BITS_PER_CHAR 4 + +#else + +#define BYTES_PER_CHAR 1 +#define BITS_PER_CHAR 8 +#define LOG2_BITS_PER_CHAR 3 + +#endif + + + +#ifdef FIXED_DEBUG +long long spx_mips=0; +#endif + + +#endif diff --git a/src/pulsecore/speex/fixed_generic.h b/src/pulsecore/speex/fixed_generic.h new file mode 100644 index 00000000..547e22c7 --- /dev/null +++ b/src/pulsecore/speex/fixed_generic.h @@ -0,0 +1,106 @@ +/* Copyright (C) 2003 Jean-Marc Valin */ +/** + @file fixed_generic.h + @brief Generic fixed-point operations +*/ +/* + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + - Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + - Neither the name of the Xiph.org Foundation nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FIXED_GENERIC_H +#define FIXED_GENERIC_H + +#define QCONST16(x,bits) ((spx_word16_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) +#define QCONST32(x,bits) ((spx_word32_t)(.5+(x)*(((spx_word32_t)1)<<(bits)))) + +#define NEG16(x) (-(x)) +#define NEG32(x) (-(x)) +#define EXTRACT16(x) ((spx_word16_t)(x)) +#define EXTEND32(x) ((spx_word32_t)(x)) +#define SHR16(a,shift) ((a) >> (shift)) +#define SHL16(a,shift) ((a) << (shift)) +#define SHR32(a,shift) ((a) >> (shift)) +#define SHL32(a,shift) ((a) << (shift)) +#define PSHR16(a,shift) (SHR16((a)+((1<<((shift))>>1)),shift)) +#define PSHR32(a,shift) (SHR32((a)+((1<<((shift))>>1)),shift)) +#define VSHR32(a, shift) (((shift)>0) ? SHR32(a, shift) : SHL32(a, -(shift))) +#define SATURATE16(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) +#define SATURATE32(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + +#define SHR(a,shift) ((a) >> (shift)) +#define SHL(a,shift) ((spx_word32_t)(a) << (shift)) +#define PSHR(a,shift) (SHR((a)+((1<<((shift))>>1)),shift)) +#define SATURATE(x,a) (((x)>(a) ? (a) : (x)<-(a) ? -(a) : (x))) + + +#define ADD16(a,b) ((spx_word16_t)((spx_word16_t)(a)+(spx_word16_t)(b))) +#define SUB16(a,b) ((spx_word16_t)(a)-(spx_word16_t)(b)) +#define ADD32(a,b) ((spx_word32_t)(a)+(spx_word32_t)(b)) +#define SUB32(a,b) ((spx_word32_t)(a)-(spx_word32_t)(b)) + + +/* result fits in 16 bits */ +#define MULT16_16_16(a,b) ((((spx_word16_t)(a))*((spx_word16_t)(b)))) + +/* (spx_word32_t)(spx_word16_t) gives TI compiler a hint that it's 16x16->32 multiply */ +#define MULT16_16(a,b) (((spx_word32_t)(spx_word16_t)(a))*((spx_word32_t)(spx_word16_t)(b))) + +#define MAC16_16(c,a,b) (ADD32((c),MULT16_16((a),(b)))) +#define MULT16_32_Q12(a,b) ADD32(MULT16_16((a),SHR((b),12)), SHR(MULT16_16((a),((b)&0x00000fff)),12)) +#define MULT16_32_Q13(a,b) ADD32(MULT16_16((a),SHR((b),13)), SHR(MULT16_16((a),((b)&0x00001fff)),13)) +#define MULT16_32_Q14(a,b) ADD32(MULT16_16((a),SHR((b),14)), SHR(MULT16_16((a),((b)&0x00003fff)),14)) + +#define MULT16_32_Q11(a,b) ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11)) +#define MAC16_32_Q11(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),11)), SHR(MULT16_16((a),((b)&0x000007ff)),11))) + +#define MULT16_32_P15(a,b) ADD32(MULT16_16((a),SHR((b),15)), PSHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MULT16_32_Q15(a,b) ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15)) +#define MAC16_32_Q15(c,a,b) ADD32(c,ADD32(MULT16_16((a),SHR((b),15)), SHR(MULT16_16((a),((b)&0x00007fff)),15))) + + +#define MAC16_16_Q11(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),11))) +#define MAC16_16_Q13(c,a,b) (ADD32((c),SHR(MULT16_16((a),(b)),13))) +#define MAC16_16_P13(c,a,b) (ADD32((c),SHR(ADD32(4096,MULT16_16((a),(b))),13))) + +#define MULT16_16_Q11_32(a,b) (SHR(MULT16_16((a),(b)),11)) +#define MULT16_16_Q13(a,b) (SHR(MULT16_16((a),(b)),13)) +#define MULT16_16_Q14(a,b) (SHR(MULT16_16((a),(b)),14)) +#define MULT16_16_Q15(a,b) (SHR(MULT16_16((a),(b)),15)) + +#define MULT16_16_P13(a,b) (SHR(ADD32(4096,MULT16_16((a),(b))),13)) +#define MULT16_16_P14(a,b) (SHR(ADD32(8192,MULT16_16((a),(b))),14)) +#define MULT16_16_P15(a,b) (SHR(ADD32(16384,MULT16_16((a),(b))),15)) + +#define MUL_16_32_R15(a,bh,bl) ADD32(MULT16_16((a),(bh)), SHR(MULT16_16((a),(bl)),15)) + +#define DIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a))/((spx_word16_t)(b)))) +#define PDIV32_16(a,b) ((spx_word16_t)(((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word16_t)(b)))) +#define DIV32(a,b) (((spx_word32_t)(a))/((spx_word32_t)(b))) +#define PDIV32(a,b) (((spx_word32_t)(a)+((spx_word16_t)(b)>>1))/((spx_word32_t)(b))) + +#endif diff --git a/src/pulsecore/speex/resample.c b/src/pulsecore/speex/resample.c new file mode 100644 index 00000000..1e592002 --- /dev/null +++ b/src/pulsecore/speex/resample.c @@ -0,0 +1,1121 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: resample.c + Arbitrary resampling code + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + The design goals of this code are: + - Very fast algorithm + - SIMD-friendly algorithm + - Low memory requirement + - Good *perceptual* quality (and not best SNR) + + Warning: This resampler is relatively new. Although I think I got rid of + all the major bugs and I don't expect the API to change anymore, there + may be something I've missed. So use with caution. + + This algorithm is based on this original resampling algorithm: + Smith, Julius O. Digital Audio Resampling Home Page + Center for Computer Research in Music and Acoustics (CCRMA), + Stanford University, 2007. + Web published at http://www-ccrma.stanford.edu/~jos/resample/. + + There is one main difference, though. This resampler uses cubic + interpolation instead of linear interpolation in the above paper. This + makes the table much smaller and makes it possible to compute that table + on a per-stream basis. In turn, being able to tweak the table for each + stream makes it possible to both reduce complexity on simple ratios + (e.g. 2/3), and get rid of the rounding operations in the inner loop. + The latter both reduces CPU time and makes the algorithm more SIMD-friendly. +*/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef OUTSIDE_SPEEX +#include <stdlib.h> +static void *speex_alloc (int size) {return calloc(size,1);} +static void *speex_realloc (void *ptr, int size) {return realloc(ptr, size);} +static void speex_free (void *ptr) {free(ptr);} +#include "speex_resampler.h" +#include "arch.h" +#else /* OUTSIDE_SPEEX */ + +#include "speex/speex_resampler.h" +#include "arch.h" +#include "os_support.h" +#endif /* OUTSIDE_SPEEX */ + +#include <math.h> + +#ifndef M_PI +#define M_PI 3.14159263 +#endif + +#ifdef FIXED_POINT +#define WORD2INT(x) ((x) < -32767 ? -32768 : ((x) > 32766 ? 32767 : (x))) +#else +#define WORD2INT(x) ((x) < -32767.5f ? -32768 : ((x) > 32766.5f ? 32767 : floor(.5+(x)))) +#endif + +/*#define float double*/ +#define FILTER_SIZE 64 +#define OVERSAMPLE 8 + +#define IMAX(a,b) ((a) > (b) ? (a) : (b)) +#define IMIN(a,b) ((a) < (b) ? (a) : (b)) + +#ifndef NULL +#define NULL 0 +#endif + +typedef int (*resampler_basic_func)(SpeexResamplerState *, spx_uint32_t , const spx_word16_t *, spx_uint32_t *, spx_word16_t *, spx_uint32_t *); + +struct SpeexResamplerState_ { + spx_uint32_t in_rate; + spx_uint32_t out_rate; + spx_uint32_t num_rate; + spx_uint32_t den_rate; + + int quality; + spx_uint32_t nb_channels; + spx_uint32_t filt_len; + spx_uint32_t mem_alloc_size; + int int_advance; + int frac_advance; + float cutoff; + spx_uint32_t oversample; + int initialised; + int started; + + /* These are per-channel */ + spx_int32_t *last_sample; + spx_uint32_t *samp_frac_num; + spx_uint32_t *magic_samples; + + spx_word16_t *mem; + spx_word16_t *sinc_table; + spx_uint32_t sinc_table_length; + resampler_basic_func resampler_ptr; + + int in_stride; + int out_stride; +} ; + +static double kaiser12_table[68] = { + 0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076, + 0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014, + 0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601, + 0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014, + 0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490, + 0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546, + 0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178, + 0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947, + 0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058, + 0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438, + 0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734, + 0.00001000, 0.00000000}; +/* +static double kaiser12_table[36] = { + 0.99440475, 1.00000000, 0.99440475, 0.97779076, 0.95066529, 0.91384741, + 0.86843014, 0.81573067, 0.75723148, 0.69451601, 0.62920216, 0.56287762, + 0.49704014, 0.43304576, 0.37206735, 0.31506490, 0.26276832, 0.21567274, + 0.17404546, 0.13794294, 0.10723616, 0.08164178, 0.06075685, 0.04409466, + 0.03111947, 0.02127838, 0.01402878, 0.00886058, 0.00531256, 0.00298291, + 0.00153438, 0.00069463, 0.00025272, 0.0000527734, 0.00000500, 0.00000000}; +*/ +static double kaiser10_table[36] = { + 0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446, + 0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347, + 0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962, + 0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451, + 0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739, + 0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000}; + +static double kaiser8_table[36] = { + 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200, + 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126, + 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272, + 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758, + 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490, + 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000}; + +static double kaiser6_table[36] = { + 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003, + 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565, + 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561, + 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058, + 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600, + 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000}; + +struct FuncDef { + double *table; + int oversample; +}; + +static struct FuncDef _KAISER12 = {kaiser12_table, 64}; +#define KAISER12 (&_KAISER12) +/*static struct FuncDef _KAISER12 = {kaiser12_table, 32}; +#define KAISER12 (&_KAISER12)*/ +static struct FuncDef _KAISER10 = {kaiser10_table, 32}; +#define KAISER10 (&_KAISER10) +static struct FuncDef _KAISER8 = {kaiser8_table, 32}; +#define KAISER8 (&_KAISER8) +static struct FuncDef _KAISER6 = {kaiser6_table, 32}; +#define KAISER6 (&_KAISER6) + +struct QualityMapping { + int base_length; + int oversample; + float downsample_bandwidth; + float upsample_bandwidth; + struct FuncDef *window_func; +}; + + +/* This table maps conversion quality to internal parameters. There are two + reasons that explain why the up-sampling bandwidth is larger than the + down-sampling bandwidth: + 1) When up-sampling, we can assume that the spectrum is already attenuated + close to the Nyquist rate (from an A/D or a previous resampling filter) + 2) Any aliasing that occurs very close to the Nyquist rate will be masked + by the sinusoids/noise just below the Nyquist rate (guaranteed only for + up-sampling). +*/ +static const struct QualityMapping quality_map[11] = { + { 8, 4, 0.830f, 0.860f, KAISER6 }, /* Q0 */ + { 16, 4, 0.850f, 0.880f, KAISER6 }, /* Q1 */ + { 32, 4, 0.882f, 0.910f, KAISER6 }, /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */ + { 48, 8, 0.895f, 0.917f, KAISER8 }, /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */ + { 64, 8, 0.921f, 0.940f, KAISER8 }, /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */ + { 80, 16, 0.922f, 0.940f, KAISER10}, /* Q5 */ /* 89.1% cutoff (~100 dB stop) 10 */ + { 96, 16, 0.940f, 0.945f, KAISER10}, /* Q6 */ /* 91.5% cutoff (~100 dB stop) 10 */ + {128, 16, 0.950f, 0.950f, KAISER10}, /* Q7 */ /* 93.1% cutoff (~100 dB stop) 10 */ + {160, 16, 0.960f, 0.960f, KAISER10}, /* Q8 */ /* 94.5% cutoff (~100 dB stop) 10 */ + {192, 32, 0.968f, 0.968f, KAISER12}, /* Q9 */ /* 95.5% cutoff (~100 dB stop) 10 */ + {256, 32, 0.975f, 0.975f, KAISER12}, /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */ +}; +/*8,24,40,56,80,104,128,160,200,256,320*/ +static double compute_func(float x, struct FuncDef *func) +{ + float y, frac; + double interp[4]; + int ind; + y = x*func->oversample; + ind = (int)floor(y); + frac = (y-ind); + /* CSE with handle the repeated powers */ + interp[3] = -0.1666666667*frac + 0.1666666667*(frac*frac*frac); + interp[2] = frac + 0.5*(frac*frac) - 0.5*(frac*frac*frac); + /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/ + interp[0] = -0.3333333333*frac + 0.5*(frac*frac) - 0.1666666667*(frac*frac*frac); + /* Just to make sure we don't have rounding problems */ + interp[1] = 1.f-interp[3]-interp[2]-interp[0]; + + /*sum = frac*accum[1] + (1-frac)*accum[2];*/ + return interp[0]*func->table[ind] + interp[1]*func->table[ind+1] + interp[2]*func->table[ind+2] + interp[3]*func->table[ind+3]; +} + +#if 0 +#include <stdio.h> +int main(int argc, char **argv) +{ + int i; + for (i=0;i<256;i++) + { + printf ("%f\n", compute_func(i/256., KAISER12)); + } + return 0; +} +#endif + +#ifdef FIXED_POINT +/* The slow way of computing a sinc for the table. Should improve that some day */ +static spx_word16_t sinc(float cutoff, float x, int N, struct FuncDef *window_func) +{ + /*fprintf (stderr, "%f ", x);*/ + float xx = x * cutoff; + if (fabs(x)<1e-6f) + return WORD2INT(32768.*cutoff); + else if (fabs(x) > .5f*N) + return 0; + /*FIXME: Can it really be any slower than this? */ + return WORD2INT(32768.*cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func)); +} +#else +/* The slow way of computing a sinc for the table. Should improve that some day */ +static spx_word16_t sinc(float cutoff, float x, int N, struct FuncDef *window_func) +{ + /*fprintf (stderr, "%f ", x);*/ + float xx = x * cutoff; + if (fabs(x)<1e-6) + return cutoff; + else if (fabs(x) > .5*N) + return 0; + /*FIXME: Can it really be any slower than this? */ + return cutoff*sin(M_PI*xx)/(M_PI*xx) * compute_func(fabs(2.*x/N), window_func); +} +#endif + +#ifdef FIXED_POINT +static void cubic_coef(spx_word16_t x, spx_word16_t interp[4]) +{ + /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation + but I know it's MMSE-optimal on a sinc */ + spx_word16_t x2, x3; + x2 = MULT16_16_P15(x, x); + x3 = MULT16_16_P15(x, x2); + interp[0] = PSHR32(MULT16_16(QCONST16(-0.16667f, 15),x) + MULT16_16(QCONST16(0.16667f, 15),x3),15); + interp[1] = EXTRACT16(EXTEND32(x) + SHR32(SUB32(EXTEND32(x2),EXTEND32(x3)),1)); + interp[3] = PSHR32(MULT16_16(QCONST16(-0.33333f, 15),x) + MULT16_16(QCONST16(.5f,15),x2) - MULT16_16(QCONST16(0.16667f, 15),x3),15); + /* Just to make sure we don't have rounding problems */ + interp[2] = Q15_ONE-interp[0]-interp[1]-interp[3]; + if (interp[2]<32767) + interp[2]+=1; +} +#else +static void cubic_coef(spx_word16_t frac, spx_word16_t interp[4]) +{ + /* Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation + but I know it's MMSE-optimal on a sinc */ + interp[0] = -0.16667f*frac + 0.16667f*frac*frac*frac; + interp[1] = frac + 0.5f*frac*frac - 0.5f*frac*frac*frac; + /*interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;*/ + interp[3] = -0.33333f*frac + 0.5f*frac*frac - 0.16667f*frac*frac*frac; + /* Just to make sure we don't have rounding problems */ + interp[2] = 1.-interp[0]-interp[1]-interp[3]; +} +#endif + +static int resampler_basic_direct_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + mem = st->mem + channel_index * st->mem_alloc_size; + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + int j; + spx_word32_t sum=0; + + /* We already have all the filter coefficients pre-computed in the table */ + const spx_word16_t *ptr; + /* Do the memory part */ + for (j=0;last_sample-N+1+j < 0;j++) + { + sum += MULT16_16(mem[last_sample+j],st->sinc_table[samp_frac_num*st->filt_len+j]); + } + + /* Do the new part */ + ptr = in+st->in_stride*(last_sample-N+1+j); + for (;j<N;j++) + { + sum += MULT16_16(*ptr,st->sinc_table[samp_frac_num*st->filt_len+j]); + ptr += st->in_stride; + } + + *out = PSHR32(sum,15); + out += st->out_stride; + out_sample++; + last_sample += st->int_advance; + samp_frac_num += st->frac_advance; + if (samp_frac_num >= st->den_rate) + { + samp_frac_num -= st->den_rate; + last_sample++; + } + } + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} + +#ifdef FIXED_POINT +#else +/* This is the same as the previous function, except with a double-precision accumulator */ +static int resampler_basic_direct_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + mem = st->mem + channel_index * st->mem_alloc_size; + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + int j; + double sum=0; + + /* We already have all the filter coefficients pre-computed in the table */ + const spx_word16_t *ptr; + /* Do the memory part */ + for (j=0;last_sample-N+1+j < 0;j++) + { + sum += MULT16_16(mem[last_sample+j],(double)st->sinc_table[samp_frac_num*st->filt_len+j]); + } + + /* Do the new part */ + ptr = in+st->in_stride*(last_sample-N+1+j); + for (;j<N;j++) + { + sum += MULT16_16(*ptr,(double)st->sinc_table[samp_frac_num*st->filt_len+j]); + ptr += st->in_stride; + } + + *out = sum; + out += st->out_stride; + out_sample++; + last_sample += st->int_advance; + samp_frac_num += st->frac_advance; + if (samp_frac_num >= st->den_rate) + { + samp_frac_num -= st->den_rate; + last_sample++; + } + } + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} +#endif + +static int resampler_basic_interpolate_single(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + mem = st->mem + channel_index * st->mem_alloc_size; + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + int j; + spx_word32_t sum=0; + + /* We need to interpolate the sinc filter */ + spx_word32_t accum[4] = {0.f,0.f, 0.f, 0.f}; + spx_word16_t interp[4]; + const spx_word16_t *ptr; + int offset; + spx_word16_t frac; + offset = samp_frac_num*st->oversample/st->den_rate; +#ifdef FIXED_POINT + frac = PDIV32(SHL32((samp_frac_num*st->oversample) % st->den_rate,15),st->den_rate); +#else + frac = ((float)((samp_frac_num*st->oversample) % st->den_rate))/st->den_rate; +#endif + /* This code is written like this to make it easy to optimise with SIMD. + For most DSPs, it would be best to split the loops in two because most DSPs + have only two accumulators */ + for (j=0;last_sample-N+1+j < 0;j++) + { + spx_word16_t curr_mem = mem[last_sample+j]; + accum[0] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + ptr = in+st->in_stride*(last_sample-N+1+j); + /* Do the new part */ + for (;j<N;j++) + { + spx_word16_t curr_in = *ptr; + ptr += st->in_stride; + accum[0] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + cubic_coef(frac, interp); + sum = MULT16_32_Q15(interp[0],accum[0]) + MULT16_32_Q15(interp[1],accum[1]) + MULT16_32_Q15(interp[2],accum[2]) + MULT16_32_Q15(interp[3],accum[3]); + + *out = PSHR32(sum,15); + out += st->out_stride; + out_sample++; + last_sample += st->int_advance; + samp_frac_num += st->frac_advance; + if (samp_frac_num >= st->den_rate) + { + samp_frac_num -= st->den_rate; + last_sample++; + } + } + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} + +#ifdef FIXED_POINT +#else +/* This is the same as the previous function, except with a double-precision accumulator */ +static int resampler_basic_interpolate_double(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + int last_sample = st->last_sample[channel_index]; + spx_uint32_t samp_frac_num = st->samp_frac_num[channel_index]; + mem = st->mem + channel_index * st->mem_alloc_size; + while (!(last_sample >= (spx_int32_t)*in_len || out_sample >= (spx_int32_t)*out_len)) + { + int j; + spx_word32_t sum=0; + + /* We need to interpolate the sinc filter */ + double accum[4] = {0.f,0.f, 0.f, 0.f}; + float interp[4]; + const spx_word16_t *ptr; + float alpha = ((float)samp_frac_num)/st->den_rate; + int offset = samp_frac_num*st->oversample/st->den_rate; + float frac = alpha*st->oversample - offset; + /* This code is written like this to make it easy to optimise with SIMD. + For most DSPs, it would be best to split the loops in two because most DSPs + have only two accumulators */ + for (j=0;last_sample-N+1+j < 0;j++) + { + double curr_mem = mem[last_sample+j]; + accum[0] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_mem,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + ptr = in+st->in_stride*(last_sample-N+1+j); + /* Do the new part */ + for (;j<N;j++) + { + double curr_in = *ptr; + ptr += st->in_stride; + accum[0] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-2]); + accum[1] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset-1]); + accum[2] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset]); + accum[3] += MULT16_16(curr_in,st->sinc_table[4+(j+1)*st->oversample-offset+1]); + } + cubic_coef(frac, interp); + sum = interp[0]*accum[0] + interp[1]*accum[1] + interp[2]*accum[2] + interp[3]*accum[3]; + + *out = PSHR32(sum,15); + out += st->out_stride; + out_sample++; + last_sample += st->int_advance; + samp_frac_num += st->frac_advance; + if (samp_frac_num >= st->den_rate) + { + samp_frac_num -= st->den_rate; + last_sample++; + } + } + st->last_sample[channel_index] = last_sample; + st->samp_frac_num[channel_index] = samp_frac_num; + return out_sample; +} +#endif + +static void update_filter(SpeexResamplerState *st) +{ + spx_uint32_t old_length; + + old_length = st->filt_len; + st->oversample = quality_map[st->quality].oversample; + st->filt_len = quality_map[st->quality].base_length; + + if (st->num_rate > st->den_rate) + { + /* down-sampling */ + st->cutoff = quality_map[st->quality].downsample_bandwidth * st->den_rate / st->num_rate; + /* FIXME: divide the numerator and denominator by a certain amount if they're too large */ + st->filt_len = st->filt_len*st->num_rate / st->den_rate; + /* Round down to make sure we have a multiple of 4 */ + st->filt_len &= (~0x3); + if (2*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (4*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (8*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (16*st->den_rate < st->num_rate) + st->oversample >>= 1; + if (st->oversample < 1) + st->oversample = 1; + } else { + /* up-sampling */ + st->cutoff = quality_map[st->quality].upsample_bandwidth; + } + + /* Choose the resampling type that requires the least amount of memory */ + if (st->den_rate <= st->oversample) + { + spx_uint32_t i; + if (!st->sinc_table) + st->sinc_table = (spx_word16_t *)speex_alloc(st->filt_len*st->den_rate*sizeof(spx_word16_t)); + else if (st->sinc_table_length < st->filt_len*st->den_rate) + { + st->sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,st->filt_len*st->den_rate*sizeof(spx_word16_t)); + st->sinc_table_length = st->filt_len*st->den_rate; + } + for (i=0;i<st->den_rate;i++) + { + spx_int32_t j; + for (j=0;j<st->filt_len;j++) + { + st->sinc_table[i*st->filt_len+j] = sinc(st->cutoff,((j-(spx_int32_t)st->filt_len/2+1)-((float)i)/st->den_rate), st->filt_len, quality_map[st->quality].window_func); + } + } +#ifdef FIXED_POINT + st->resampler_ptr = resampler_basic_direct_single; +#else + if (st->quality>8) + st->resampler_ptr = resampler_basic_direct_double; + else + st->resampler_ptr = resampler_basic_direct_single; +#endif + /*fprintf (stderr, "resampler uses direct sinc table and normalised cutoff %f\n", cutoff);*/ + } else { + spx_int32_t i; + if (!st->sinc_table) + st->sinc_table = (spx_word16_t *)speex_alloc((st->filt_len*st->oversample+8)*sizeof(spx_word16_t)); + else if (st->sinc_table_length < st->filt_len*st->oversample+8) + { + st->sinc_table = (spx_word16_t *)speex_realloc(st->sinc_table,(st->filt_len*st->oversample+8)*sizeof(spx_word16_t)); + st->sinc_table_length = st->filt_len*st->oversample+8; + } + for (i=-4;i<(spx_int32_t)(st->oversample*st->filt_len+4);i++) + st->sinc_table[i+4] = sinc(st->cutoff,(i/(float)st->oversample - st->filt_len/2), st->filt_len, quality_map[st->quality].window_func); +#ifdef FIXED_POINT + st->resampler_ptr = resampler_basic_interpolate_single; +#else + if (st->quality>8) + st->resampler_ptr = resampler_basic_interpolate_double; + else + st->resampler_ptr = resampler_basic_interpolate_single; +#endif + /*fprintf (stderr, "resampler uses interpolated sinc table and normalised cutoff %f\n", cutoff);*/ + } + st->int_advance = st->num_rate/st->den_rate; + st->frac_advance = st->num_rate%st->den_rate; + + + /* Here's the place where we update the filter memory to take into account + the change in filter length. It's probably the messiest part of the code + due to handling of lots of corner cases. */ + if (!st->mem) + { + spx_uint32_t i; + st->mem = (spx_word16_t*)speex_alloc(st->nb_channels*(st->filt_len-1) * sizeof(spx_word16_t)); + for (i=0;i<st->nb_channels*(st->filt_len-1);i++) + st->mem[i] = 0; + st->mem_alloc_size = st->filt_len-1; + /*speex_warning("init filter");*/ + } else if (!st->started) + { + spx_uint32_t i; + st->mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*(st->filt_len-1) * sizeof(spx_word16_t)); + for (i=0;i<st->nb_channels*(st->filt_len-1);i++) + st->mem[i] = 0; + st->mem_alloc_size = st->filt_len-1; + /*speex_warning("reinit filter");*/ + } else if (st->filt_len > old_length) + { + spx_int32_t i; + /* Increase the filter length */ + /*speex_warning("increase filter size");*/ + int old_alloc_size = st->mem_alloc_size; + if (st->filt_len-1 > st->mem_alloc_size) + { + st->mem = (spx_word16_t*)speex_realloc(st->mem, st->nb_channels*(st->filt_len-1) * sizeof(spx_word16_t)); + st->mem_alloc_size = st->filt_len-1; + } + for (i=st->nb_channels-1;i>=0;i--) + { + spx_int32_t j; + spx_uint32_t olen = old_length; + /*if (st->magic_samples[i])*/ + { + /* Try and remove the magic samples as if nothing had happened */ + + /* FIXME: This is wrong but for now we need it to avoid going over the array bounds */ + olen = old_length + 2*st->magic_samples[i]; + for (j=old_length-2+st->magic_samples[i];j>=0;j--) + st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j]; + for (j=0;j<st->magic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = 0; + st->magic_samples[i] = 0; + } + if (st->filt_len > olen) + { + /* If the new filter length is still bigger than the "augmented" length */ + /* Copy data going backward */ + for (j=0;j<olen-1;j++) + st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = st->mem[i*st->mem_alloc_size+(olen-2-j)]; + /* Then put zeros for lack of anything better */ + for (;j<st->filt_len-1;j++) + st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = 0; + /* Adjust last_sample */ + st->last_sample[i] += (st->filt_len - olen)/2; + } else { + /* Put back some of the magic! */ + st->magic_samples[i] = (olen - st->filt_len)/2; + for (j=0;j<st->filt_len-1+st->magic_samples[i];j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + } + } + } else if (st->filt_len < old_length) + { + spx_uint32_t i; + /* Reduce filter length, this a bit tricky. We need to store some of the memory as "magic" + samples so they can be used directly as input the next time(s) */ + for (i=0;i<st->nb_channels;i++) + { + spx_uint32_t j; + spx_uint32_t old_magic = st->magic_samples[i]; + st->magic_samples[i] = (old_length - st->filt_len)/2; + /* We must copy some of the memory that's no longer used */ + /* Copy data going backward */ + for (j=0;j<st->filt_len-1+st->magic_samples[i]+old_magic;j++) + st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]]; + st->magic_samples[i] += old_magic; + } + } + +} + +SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) +{ + return speex_resampler_init_frac(nb_channels, in_rate, out_rate, in_rate, out_rate, quality, err); +} + +SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err) +{ + spx_uint32_t i; + SpeexResamplerState *st; + if (quality > 10 || quality < 0) + { + if (err) + *err = RESAMPLER_ERR_INVALID_ARG; + return NULL; + } + st = (SpeexResamplerState *)speex_alloc(sizeof(SpeexResamplerState)); + st->initialised = 0; + st->started = 0; + st->in_rate = 0; + st->out_rate = 0; + st->num_rate = 0; + st->den_rate = 0; + st->quality = -1; + st->sinc_table_length = 0; + st->mem_alloc_size = 0; + st->filt_len = 0; + st->mem = 0; + st->resampler_ptr = 0; + + st->cutoff = 1.f; + st->nb_channels = nb_channels; + st->in_stride = 1; + st->out_stride = 1; + + /* Per channel data */ + st->last_sample = (spx_int32_t*)speex_alloc(nb_channels*sizeof(int)); + st->magic_samples = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(int)); + st->samp_frac_num = (spx_uint32_t*)speex_alloc(nb_channels*sizeof(int)); + for (i=0;i<nb_channels;i++) + { + st->last_sample[i] = 0; + st->magic_samples[i] = 0; + st->samp_frac_num[i] = 0; + } + + speex_resampler_set_quality(st, quality); + speex_resampler_set_rate_frac(st, ratio_num, ratio_den, in_rate, out_rate); + + + update_filter(st); + + st->initialised = 1; + if (err) + *err = RESAMPLER_ERR_SUCCESS; + + return st; +} + +void speex_resampler_destroy(SpeexResamplerState *st) +{ + speex_free(st->mem); + speex_free(st->sinc_table); + speex_free(st->last_sample); + speex_free(st->magic_samples); + speex_free(st->samp_frac_num); + speex_free(st); +} + + + +static int speex_resampler_process_native(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_word16_t *in, spx_uint32_t *in_len, spx_word16_t *out, spx_uint32_t *out_len) +{ + int j=0; + int N = st->filt_len; + int out_sample = 0; + spx_word16_t *mem; + spx_uint32_t tmp_out_len = 0; + mem = st->mem + channel_index * st->mem_alloc_size; + st->started = 1; + + /* Handle the case where we have samples left from a reduction in filter length */ + if (st->magic_samples[channel_index]) + { + int istride_save; + spx_uint32_t tmp_in_len; + spx_uint32_t tmp_magic; + + istride_save = st->in_stride; + tmp_in_len = st->magic_samples[channel_index]; + tmp_out_len = *out_len; + /* magic_samples needs to be set to zero to avoid infinite recursion */ + tmp_magic = st->magic_samples[channel_index]; + st->magic_samples[channel_index] = 0; + st->in_stride = 1; + speex_resampler_process_native(st, channel_index, mem+N-1, &tmp_in_len, out, &tmp_out_len); + st->in_stride = istride_save; + /*speex_warning_int("extra samples:", tmp_out_len);*/ + /* If we couldn't process all "magic" input samples, save the rest for next time */ + if (tmp_in_len < tmp_magic) + { + spx_uint32_t i; + st->magic_samples[channel_index] = tmp_magic-tmp_in_len; + for (i=0;i<st->magic_samples[channel_index];i++) + mem[N-1+i]=mem[N-1+i+tmp_in_len]; + } + out += tmp_out_len*st->out_stride; + *out_len -= tmp_out_len; + } + + /* Call the right resampler through the function ptr */ + out_sample = st->resampler_ptr(st, channel_index, in, in_len, out, out_len); + + if (st->last_sample[channel_index] < (spx_int32_t)*in_len) + *in_len = st->last_sample[channel_index]; + *out_len = out_sample+tmp_out_len; + st->last_sample[channel_index] -= *in_len; + + for (j=0;j<N-1-(spx_int32_t)*in_len;j++) + mem[j] = mem[j+*in_len]; + for (;j<N-1;j++) + mem[j] = in[st->in_stride*(j+*in_len-N+1)]; + + return RESAMPLER_ERR_SUCCESS; +} + +#define FIXED_STACK_ALLOC 1024 + +#ifdef FIXED_POINT +int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; +#ifdef VAR_ARRAYS + spx_word16_t x[*in_len]; + spx_word16_t y[*out_len]; + /*VARDECL(spx_word16_t *x); + VARDECL(spx_word16_t *y); + ALLOC(x, *in_len, spx_word16_t); + ALLOC(y, *out_len, spx_word16_t);*/ + istride_save = st->in_stride; + ostride_save = st->out_stride; + for (i=0;i<*in_len;i++) + x[i] = WORD2INT(in[i*st->in_stride]); + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, x, in_len, y, out_len); + st->in_stride = istride_save; + st->out_stride = ostride_save; + for (i=0;i<*out_len;i++) + out[i*st->out_stride] = y[i]; +#else + spx_word16_t x[FIXED_STACK_ALLOC]; + spx_word16_t y[FIXED_STACK_ALLOC]; + spx_uint32_t ilen=*in_len, olen=*out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + while (ilen && olen) + { + spx_uint32_t ichunk, ochunk; + ichunk = ilen; + ochunk = olen; + if (ichunk>FIXED_STACK_ALLOC) + ichunk=FIXED_STACK_ALLOC; + if (ochunk>FIXED_STACK_ALLOC) + ochunk=FIXED_STACK_ALLOC; + for (i=0;i<ichunk;i++) + x[i] = WORD2INT(in[i*st->in_stride]); + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, x, &ichunk, y, &ochunk); + st->in_stride = istride_save; + st->out_stride = ostride_save; + for (i=0;i<ochunk;i++) + out[i*st->out_stride] = y[i]; + out += ochunk; + in += ichunk; + ilen -= ichunk; + olen -= ochunk; + } + *in_len -= ilen; + *out_len -= olen; +#endif + return RESAMPLER_ERR_SUCCESS; +} +int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +{ + return speex_resampler_process_native(st, channel_index, in, in_len, out, out_len); +} +#else +int speex_resampler_process_float(SpeexResamplerState *st, spx_uint32_t channel_index, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +{ + return speex_resampler_process_native(st, channel_index, in, in_len, out, out_len); +} +int speex_resampler_process_int(SpeexResamplerState *st, spx_uint32_t channel_index, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; +#ifdef VAR_ARRAYS + spx_word16_t x[*in_len]; + spx_word16_t y[*out_len]; + /*VARDECL(spx_word16_t *x); + VARDECL(spx_word16_t *y); + ALLOC(x, *in_len, spx_word16_t); + ALLOC(y, *out_len, spx_word16_t);*/ + istride_save = st->in_stride; + ostride_save = st->out_stride; + for (i=0;i<*in_len;i++) + x[i] = in[i*st->in_stride]; + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, x, in_len, y, out_len); + st->in_stride = istride_save; + st->out_stride = ostride_save; + for (i=0;i<*out_len;i++) + out[i*st->out_stride] = WORD2INT(y[i]); +#else + spx_word16_t x[FIXED_STACK_ALLOC]; + spx_word16_t y[FIXED_STACK_ALLOC]; + spx_uint32_t ilen=*in_len, olen=*out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + while (ilen && olen) + { + spx_uint32_t ichunk, ochunk; + ichunk = ilen; + ochunk = olen; + if (ichunk>FIXED_STACK_ALLOC) + ichunk=FIXED_STACK_ALLOC; + if (ochunk>FIXED_STACK_ALLOC) + ochunk=FIXED_STACK_ALLOC; + for (i=0;i<ichunk;i++) + x[i] = in[i*st->in_stride]; + st->in_stride = st->out_stride = 1; + speex_resampler_process_native(st, channel_index, x, &ichunk, y, &ochunk); + st->in_stride = istride_save; + st->out_stride = ostride_save; + for (i=0;i<ochunk;i++) + out[i*st->out_stride] = WORD2INT(y[i]); + out += ochunk; + in += ichunk; + ilen -= ichunk; + olen -= ochunk; + } + *in_len -= ilen; + *out_len -= olen; +#endif + return RESAMPLER_ERR_SUCCESS; +} +#endif + +int speex_resampler_process_interleaved_float(SpeexResamplerState *st, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; + spx_uint32_t bak_len = *out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + st->in_stride = st->out_stride = st->nb_channels; + for (i=0;i<st->nb_channels;i++) + { + *out_len = bak_len; + speex_resampler_process_float(st, i, in+i, in_len, out+i, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + return RESAMPLER_ERR_SUCCESS; +} + + +int speex_resampler_process_interleaved_int(SpeexResamplerState *st, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len) +{ + spx_uint32_t i; + int istride_save, ostride_save; + spx_uint32_t bak_len = *out_len; + istride_save = st->in_stride; + ostride_save = st->out_stride; + st->in_stride = st->out_stride = st->nb_channels; + for (i=0;i<st->nb_channels;i++) + { + *out_len = bak_len; + speex_resampler_process_int(st, i, in+i, in_len, out+i, out_len); + } + st->in_stride = istride_save; + st->out_stride = ostride_save; + return RESAMPLER_ERR_SUCCESS; +} + +int speex_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + return speex_resampler_set_rate_frac(st, in_rate, out_rate, in_rate, out_rate); +} + +void speex_resampler_get_rate(SpeexResamplerState *st, spx_uint32_t *in_rate, spx_uint32_t *out_rate) +{ + *in_rate = st->in_rate; + *out_rate = st->out_rate; +} + +int speex_resampler_set_rate_frac(SpeexResamplerState *st, spx_uint32_t ratio_num, spx_uint32_t ratio_den, spx_uint32_t in_rate, spx_uint32_t out_rate) +{ + spx_uint32_t fact; + spx_uint32_t old_den; + spx_uint32_t i; + if (st->in_rate == in_rate && st->out_rate == out_rate && st->num_rate == ratio_num && st->den_rate == ratio_den) + return RESAMPLER_ERR_SUCCESS; + + old_den = st->den_rate; + st->in_rate = in_rate; + st->out_rate = out_rate; + st->num_rate = ratio_num; + st->den_rate = ratio_den; + /* FIXME: This is terribly inefficient, but who cares (at least for now)? */ + for (fact=2;fact<=IMIN(st->num_rate, st->den_rate);fact++) + { + while ((st->num_rate % fact == 0) && (st->den_rate % fact == 0)) + { + st->num_rate /= fact; + st->den_rate /= fact; + } + } + + if (old_den > 0) + { + for (i=0;i<st->nb_channels;i++) + { + st->samp_frac_num[i]=st->samp_frac_num[i]*st->den_rate/old_den; + /* Safety net */ + if (st->samp_frac_num[i] >= st->den_rate) + st->samp_frac_num[i] = st->den_rate-1; + } + } + + if (st->initialised) + update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} + +void speex_resampler_get_ratio(SpeexResamplerState *st, spx_uint32_t *ratio_num, spx_uint32_t *ratio_den) +{ + *ratio_num = st->num_rate; + *ratio_den = st->den_rate; +} + +int speex_resampler_set_quality(SpeexResamplerState *st, int quality) +{ + if (quality > 10 || quality < 0) + return RESAMPLER_ERR_INVALID_ARG; + if (st->quality == quality) + return RESAMPLER_ERR_SUCCESS; + st->quality = quality; + if (st->initialised) + update_filter(st); + return RESAMPLER_ERR_SUCCESS; +} + +void speex_resampler_get_quality(SpeexResamplerState *st, int *quality) +{ + *quality = st->quality; +} + +void speex_resampler_set_input_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->in_stride = stride; +} + +void speex_resampler_get_input_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->in_stride; +} + +void speex_resampler_set_output_stride(SpeexResamplerState *st, spx_uint32_t stride) +{ + st->out_stride = stride; +} + +void speex_resampler_get_output_stride(SpeexResamplerState *st, spx_uint32_t *stride) +{ + *stride = st->out_stride; +} + +int speex_resampler_skip_zeros(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;i<st->nb_channels;i++) + st->last_sample[i] = st->filt_len/2; + return RESAMPLER_ERR_SUCCESS; +} + +int speex_resampler_reset_mem(SpeexResamplerState *st) +{ + spx_uint32_t i; + for (i=0;i<st->nb_channels*(st->filt_len-1);i++) + st->mem[i] = 0; + return RESAMPLER_ERR_SUCCESS; +} + +const char *speex_resampler_strerror(int err) +{ + switch (err) + { + case RESAMPLER_ERR_SUCCESS: + return "Success."; + case RESAMPLER_ERR_ALLOC_FAILED: + return "Memory allocation failed."; + case RESAMPLER_ERR_BAD_STATE: + return "Bad resampler state."; + case RESAMPLER_ERR_INVALID_ARG: + return "Invalid argument."; + case RESAMPLER_ERR_PTR_OVERLAP: + return "Input and output buffers overlap."; + default: + return "Unknown error. Bad error code or strange version mismatch."; + } +} diff --git a/src/pulsecore/speex/speex_resampler.h b/src/pulsecore/speex/speex_resampler.h new file mode 100644 index 00000000..8629eeb3 --- /dev/null +++ b/src/pulsecore/speex/speex_resampler.h @@ -0,0 +1,328 @@ +/* Copyright (C) 2007 Jean-Marc Valin + + File: speex_resampler.h + Resampling code + + The design goals of this code are: + - Very fast algorithm + - Low memory requirement + - Good *perceptual* quality (and not best SNR) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + + +#ifndef SPEEX_RESAMPLER_H +#define SPEEX_RESAMPLER_H + +#ifdef OUTSIDE_SPEEX + +/********* WARNING: MENTAL SANITY ENDS HERE *************/ + +/* If the resampler is defined outside of Speex, we change the symbol names so that + there won't be any clash if linking with Speex later on. */ + +/* #define RANDOM_PREFIX your software name here */ +#ifndef RANDOM_PREFIX +#error "Please define RANDOM_PREFIX (above) to something specific to your project to prevent symbol name clashes" +#endif + +#define CAT_PREFIX2(a,b) a ## b +#define CAT_PREFIX(a,b) CAT_PREFIX2(a, b) + +#define speex_resampler_init CAT_PREFIX(RANDOM_PREFIX,_resampler_init) +#define speex_resampler_init_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_init_frac) +#define speex_resampler_destroy CAT_PREFIX(RANDOM_PREFIX,_resampler_destroy) +#define speex_resampler_process_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_float) +#define speex_resampler_process_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_int) +#define speex_resampler_process_interleaved_float CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_float) +#define speex_resampler_process_interleaved_int CAT_PREFIX(RANDOM_PREFIX,_resampler_process_interleaved_int) +#define speex_resampler_set_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate) +#define speex_resampler_get_rate CAT_PREFIX(RANDOM_PREFIX,_resampler_get_rate) +#define speex_resampler_set_rate_frac CAT_PREFIX(RANDOM_PREFIX,_resampler_set_rate_frac) +#define speex_resampler_get_ratio CAT_PREFIX(RANDOM_PREFIX,_resampler_get_ratio) +#define speex_resampler_set_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_set_quality) +#define speex_resampler_get_quality CAT_PREFIX(RANDOM_PREFIX,_resampler_get_quality) +#define speex_resampler_set_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_input_stride) +#define speex_resampler_get_input_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_input_stride) +#define speex_resampler_set_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_set_output_stride) +#define speex_resampler_get_output_stride CAT_PREFIX(RANDOM_PREFIX,_resampler_get_output_stride) +#define speex_resampler_skip_zeros CAT_PREFIX(RANDOM_PREFIX,_resampler_skip_zeros) +#define speex_resampler_reset_mem CAT_PREFIX(RANDOM_PREFIX,_resampler_reset_mem) +#define speex_resampler_strerror CAT_PREFIX(RANDOM_PREFIX,_resampler_strerror) + +#define spx_int16_t short +#define spx_int32_t int +#define spx_uint16_t unsigned short +#define spx_uint32_t unsigned int + +#else /* OUTSIDE_SPEEX */ + +#include "speex/speex_types.h" + +#endif /* OUTSIDE_SPEEX */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPEEX_RESAMPLER_QUALITY_MAX 10 +#define SPEEX_RESAMPLER_QUALITY_MIN 0 +#define SPEEX_RESAMPLER_QUALITY_DEFAULT 4 +#define SPEEX_RESAMPLER_QUALITY_VOIP 3 +#define SPEEX_RESAMPLER_QUALITY_DESKTOP 5 + +enum { + RESAMPLER_ERR_SUCCESS = 0, + RESAMPLER_ERR_ALLOC_FAILED = 1, + RESAMPLER_ERR_BAD_STATE = 2, + RESAMPLER_ERR_INVALID_ARG = 3, + RESAMPLER_ERR_PTR_OVERLAP = 4, + + RESAMPLER_ERR_MAX_ERROR +}; + +struct SpeexResamplerState_; +typedef struct SpeexResamplerState_ SpeexResamplerState; + +/** Create a new resampler with integer input and output rates. + * @param nb_channels Number of channels to be processed + * @param in_rate Input sampling rate (integer number of Hz). + * @param out_rate Output sampling rate (integer number of Hz). + * @param quality Resampling quality between 0 and 10, where 0 has poor quality + * and 10 has very high quality. + * @return Newly created resampler state + * @retval NULL Error: not enough memory + */ +SpeexResamplerState *speex_resampler_init(spx_uint32_t nb_channels, + spx_uint32_t in_rate, + spx_uint32_t out_rate, + int quality, + int *err); + +/** Create a new resampler with fractional input/output rates. The sampling + * rate ratio is an arbitrary rational number with both the numerator and + * denominator being 32-bit integers. + * @param nb_channels Number of channels to be processed + * @param ratio_num Numerator of the sampling rate ratio + * @param ratio_den Denominator of the sampling rate ratio + * @param in_rate Input sampling rate rounded to the nearest integer (in Hz). + * @param out_rate Output sampling rate rounded to the nearest integer (in Hz). + * @param quality Resampling quality between 0 and 10, where 0 has poor quality + * and 10 has very high quality. + * @return Newly created resampler state + * @retval NULL Error: not enough memory + */ +SpeexResamplerState *speex_resampler_init_frac(spx_uint32_t nb_channels, + spx_uint32_t ratio_num, + spx_uint32_t ratio_den, + spx_uint32_t in_rate, + spx_uint32_t out_rate, + int quality, + int *err); + +/** Destroy a resampler state. + * @param st Resampler state + */ +void speex_resampler_destroy(SpeexResamplerState *st); + +/** Resample a float array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param channel_index Index of the channel to process for the multi-channel + * base (0 otherwise) + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the + * number of samples processed + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written + */ +int speex_resampler_process_float(SpeexResamplerState *st, + spx_uint32_t channel_index, + const float *in, + spx_uint32_t *in_len, + float *out, + spx_uint32_t *out_len); + +/** Resample an int array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param channel_index Index of the channel to process for the multi-channel + * base (0 otherwise) + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written + */ +int speex_resampler_process_int(SpeexResamplerState *st, + spx_uint32_t channel_index, + const spx_int16_t *in, + spx_uint32_t *in_len, + spx_int16_t *out, + spx_uint32_t *out_len); + +/** Resample an interleaved float array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed. This is all per-channel. + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written. + * This is all per-channel. + */ +int speex_resampler_process_interleaved_float(SpeexResamplerState *st, + const float *in, + spx_uint32_t *in_len, + float *out, + spx_uint32_t *out_len); + +/** Resample an interleaved int array. The input and output buffers must *not* overlap. + * @param st Resampler state + * @param in Input buffer + * @param in_len Number of input samples in the input buffer. Returns the number + * of samples processed. This is all per-channel. + * @param out Output buffer + * @param out_len Size of the output buffer. Returns the number of samples written. + * This is all per-channel. + */ +int speex_resampler_process_interleaved_int(SpeexResamplerState *st, + const spx_int16_t *in, + spx_uint32_t *in_len, + spx_int16_t *out, + spx_uint32_t *out_len); + +/** Set (change) the input/output sampling rates (integer value). + * @param st Resampler state + * @param in_rate Input sampling rate (integer number of Hz). + * @param out_rate Output sampling rate (integer number of Hz). + */ +int speex_resampler_set_rate(SpeexResamplerState *st, + spx_uint32_t in_rate, + spx_uint32_t out_rate); + +/** Get the current input/output sampling rates (integer value). + * @param st Resampler state + * @param in_rate Input sampling rate (integer number of Hz) copied. + * @param out_rate Output sampling rate (integer number of Hz) copied. + */ +void speex_resampler_get_rate(SpeexResamplerState *st, + spx_uint32_t *in_rate, + spx_uint32_t *out_rate); + +/** Set (change) the input/output sampling rates and resampling ratio + * (fractional values in Hz supported). + * @param st Resampler state + * @param ratio_num Numerator of the sampling rate ratio + * @param ratio_den Denominator of the sampling rate ratio + * @param in_rate Input sampling rate rounded to the nearest integer (in Hz). + * @param out_rate Output sampling rate rounded to the nearest integer (in Hz). + */ +int speex_resampler_set_rate_frac(SpeexResamplerState *st, + spx_uint32_t ratio_num, + spx_uint32_t ratio_den, + spx_uint32_t in_rate, + spx_uint32_t out_rate); + +/** Get the current resampling ratio. This will be reduced to the least + * common denominator. + * @param st Resampler state + * @param ratio_num Numerator of the sampling rate ratio copied + * @param ratio_den Denominator of the sampling rate ratio copied + */ +void speex_resampler_get_ratio(SpeexResamplerState *st, + spx_uint32_t *ratio_num, + spx_uint32_t *ratio_den); + +/** Set (change) the conversion quality. + * @param st Resampler state + * @param quality Resampling quality between 0 and 10, where 0 has poor + * quality and 10 has very high quality. + */ +int speex_resampler_set_quality(SpeexResamplerState *st, + int quality); + +/** Get the conversion quality. + * @param st Resampler state + * @param quality Resampling quality between 0 and 10, where 0 has poor + * quality and 10 has very high quality. + */ +void speex_resampler_get_quality(SpeexResamplerState *st, + int *quality); + +/** Set (change) the input stride. + * @param st Resampler state + * @param stride Input stride + */ +void speex_resampler_set_input_stride(SpeexResamplerState *st, + spx_uint32_t stride); + +/** Get the input stride. + * @param st Resampler state + * @param stride Input stride copied + */ +void speex_resampler_get_input_stride(SpeexResamplerState *st, + spx_uint32_t *stride); + +/** Set (change) the output stride. + * @param st Resampler state + * @param stride Output stride + */ +void speex_resampler_set_output_stride(SpeexResamplerState *st, + spx_uint32_t stride); + +/** Get the output stride. + * @param st Resampler state copied + * @param stride Output stride + */ +void speex_resampler_get_output_stride(SpeexResamplerState *st, + spx_uint32_t *stride); + +/** Make sure that the first samples to go out of the resamplers don't have + * leading zeros. This is only useful before starting to use a newly created + * resampler. It is recommended to use that when resampling an audio file, as + * it will generate a file with the same length. For real-time processing, + * it is probably easier not to use this call (so that the output duration + * is the same for the first frame). + * @param st Resampler state + */ +int speex_resampler_skip_zeros(SpeexResamplerState *st); + +/** Reset a resampler so a new (unrelated) stream can be processed. + * @param st Resampler state + */ +int speex_resampler_reset_mem(SpeexResamplerState *st); + +/** Returns the English meaning for an error code + * @param err Error code + * @return English string + */ +const char *speex_resampler_strerror(int err); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/pulsecore/speexwrap.h b/src/pulsecore/speexwrap.h new file mode 100644 index 00000000..df73edf0 --- /dev/null +++ b/src/pulsecore/speexwrap.h @@ -0,0 +1,50 @@ +#ifndef foopulsespeexwraphfoo +#define foopulsespeexwraphfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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. +***/ + +/* We define a minimal version of speex_resampler.h however define one + * version for fixed and another one for float. Yes, somewhat ugly */ + +#define spx_int16_t short +#define spx_int32_t int +#define spx_uint16_t unsigned short +#define spx_uint32_t unsigned int + +typedef struct SpeexResamplerState_ SpeexResamplerState; + +SpeexResamplerState *paspfx_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err); +void paspfx_resampler_destroy(SpeexResamplerState *st); +int paspfx_resampler_process_interleaved_int(SpeexResamplerState *st, const spx_int16_t *in, spx_uint32_t *in_len, spx_int16_t *out, spx_uint32_t *out_len); +int paspfx_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate); +int paspfx_resampler_reset_mem(SpeexResamplerState *st); + +SpeexResamplerState *paspfl_resampler_init(spx_uint32_t nb_channels, spx_uint32_t in_rate, spx_uint32_t out_rate, int quality, int *err); +void paspfl_resampler_destroy(SpeexResamplerState *st); +int paspfl_resampler_process_interleaved_float(SpeexResamplerState *st, const float *in, spx_uint32_t *in_len, float *out, spx_uint32_t *out_len); +int paspfl_resampler_set_rate(SpeexResamplerState *st, spx_uint32_t in_rate, spx_uint32_t out_rate); +int paspfl_resampler_reset_mem(SpeexResamplerState *st); + +#endif diff --git a/src/pulsecore/start-child.c b/src/pulsecore/start-child.c new file mode 100644 index 00000000..e01011d6 --- /dev/null +++ b/src/pulsecore/start-child.c @@ -0,0 +1,162 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <errno.h> +#include <stdio.h> +#include <sys/types.h> +#include <dirent.h> +#include <unistd.h> +#include <fcntl.h> +#include <signal.h> + +#ifdef HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif +#ifdef HAVE_SYS_RESOURCE_H +#include <sys/resource.h> +#endif + +#include <pulsecore/core-util.h> +#include <pulsecore/core-error.h> + +#include "start-child.h" + +int pa_start_child_for_read(const char *name, const char *argv1, pid_t *pid) { + pid_t child; + int pipe_fds[2] = { -1, -1 }; + + if (pipe(pipe_fds) < 0) { + pa_log("pipe() failed: %s", pa_cstrerror(errno)); + goto fail; + } + + if ((child = fork()) == (pid_t) -1) { + pa_log("fork() failed: %s", pa_cstrerror(errno)); + goto fail; + + } else if (child != 0) { + + /* Parent */ + pa_assert_se(pa_close(pipe_fds[1]) == 0); + + if (pid) + *pid = child; + + return pipe_fds[0]; + } else { +#ifdef __linux__ + DIR* d; +#endif + int max_fd, i; + + /* child */ + + pa_reset_priority(); + + pa_assert_se(pa_close(pipe_fds[0]) == 0); + pa_assert_se(dup2(pipe_fds[1], 1) == 1); + + if (pipe_fds[1] != 1) + pa_assert_se(pa_close(pipe_fds[1]) == 0); + + pa_close(0); + pa_assert_se(open("/dev/null", O_RDONLY) == 0); + + pa_close(2); + pa_assert_se(open("/dev/null", O_WRONLY) == 2); + +#ifdef __linux__ + + if ((d = opendir("/proc/self/fd/"))) { + + struct dirent *de; + + while ((de = readdir(d))) { + char *e = NULL; + int fd; + + if (de->d_name[0] == '.') + continue; + + errno = 0; + fd = strtol(de->d_name, &e, 10); + pa_assert(errno == 0 && e && *e == 0); + + if (fd >= 3 && dirfd(d) != fd) + pa_close(fd); + } + + closedir(d); + } else { + +#endif + + max_fd = 1024; + +#ifdef HAVE_SYS_RESOURCE_H + { + struct rlimit r; + if (getrlimit(RLIMIT_NOFILE, &r) == 0) + max_fd = r.rlim_max; + } +#endif + + for (i = 3; i < max_fd; i++) + pa_close(i); + +#ifdef __linux__ + } +#endif + +#ifdef PR_SET_PDEATHSIG + /* On Linux we can use PR_SET_PDEATHSIG to have the helper + process killed when the daemon dies abnormally. On non-Linux + machines the client will die as soon as it writes data to + stdout again (SIGPIPE) */ + + prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0); +#endif + +#ifdef SIGPIPE + /* Make sure that SIGPIPE kills the child process */ + signal(SIGPIPE, SIG_DFL); +#endif + +#ifdef SIGTERM + /* Make sure that SIGTERM kills the child process */ + signal(SIGTERM, SIG_DFL); +#endif + + execl(name, name, argv1, NULL); + _exit(1); + } + +fail: + pa_close_pipe(pipe_fds); + + return -1; +} diff --git a/src/pulsecore/start-child.h b/src/pulsecore/start-child.h new file mode 100644 index 00000000..359b5044 --- /dev/null +++ b/src/pulsecore/start-child.h @@ -0,0 +1,32 @@ +#ifndef foopulsestartchildhfoo +#define foopulsestartchildhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <sys/types.h> +#include <unistd.h> + +int pa_start_child_for_read(const char *name, const char *argv1, pid_t *pid); + +#endif diff --git a/src/pulsecore/strbuf.c b/src/pulsecore/strbuf.c new file mode 100644 index 00000000..7c576c67 --- /dev/null +++ b/src/pulsecore/strbuf.c @@ -0,0 +1,184 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <sys/types.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +#include "strbuf.h" + +/* A chunk of the linked list that makes up the string */ +struct chunk { + struct chunk *next; + size_t length; +}; + +#define CHUNK_TO_TEXT(c) ((char*) (c) + PA_ALIGN(sizeof(struct chunk))) + +struct pa_strbuf { + size_t length; + struct chunk *head, *tail; +}; + +pa_strbuf *pa_strbuf_new(void) { + pa_strbuf *sb; + + sb = pa_xnew(pa_strbuf, 1); + sb->length = 0; + sb->head = sb->tail = NULL; + + return sb; +} + +void pa_strbuf_free(pa_strbuf *sb) { + pa_assert(sb); + + while (sb->head) { + struct chunk *c = sb->head; + sb->head = sb->head->next; + pa_xfree(c); + } + + pa_xfree(sb); +} + +/* Make a C string from the string buffer. The caller has to free + * string with pa_xfree(). */ +char *pa_strbuf_tostring(pa_strbuf *sb) { + char *t, *e; + struct chunk *c; + + pa_assert(sb); + + e = t = pa_xnew(char, sb->length+1); + + for (c = sb->head; c; c = c->next) { + pa_assert((size_t) (e-t) <= sb->length); + memcpy(e, CHUNK_TO_TEXT(c), c->length); + e += c->length; + } + + /* Trailing NUL */ + *e = 0; + + pa_assert(e == t+sb->length); + + return t; +} + +/* Combination of pa_strbuf_free() and pa_strbuf_tostring() */ +char *pa_strbuf_tostring_free(pa_strbuf *sb) { + char *t; + + pa_assert(sb); + t = pa_strbuf_tostring(sb); + pa_strbuf_free(sb); + + return t; +} + +/* Append a string to the string buffer */ +void pa_strbuf_puts(pa_strbuf *sb, const char *t) { + + pa_assert(sb); + pa_assert(t); + + pa_strbuf_putsn(sb, t, strlen(t)); +} + +/* Append a new chunk to the linked list */ +static void append(pa_strbuf *sb, struct chunk *c) { + pa_assert(sb); + pa_assert(c); + + if (sb->tail) { + pa_assert(sb->head); + sb->tail->next = c; + } else { + pa_assert(!sb->head); + sb->head = c; + } + + sb->tail = c; + sb->length += c->length; + c->next = NULL; +} + +/* Append up to l bytes of a string to the string buffer */ +void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t l) { + struct chunk *c; + + pa_assert(sb); + pa_assert(t); + + if (!l) + return; + + c = pa_xmalloc(PA_ALIGN(sizeof(struct chunk)) + l); + c->length = l; + memcpy(CHUNK_TO_TEXT(c), t, l); + + append(sb, c); +} + +/* Append a printf() style formatted string to the string buffer. */ +/* The following is based on an example from the GNU libc documentation */ +int pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) { + int size = 100; + struct chunk *c = NULL; + + pa_assert(sb); + pa_assert(format); + + for(;;) { + va_list ap; + int r; + + c = pa_xrealloc(c, PA_ALIGN(sizeof(struct chunk)) + size); + + va_start(ap, format); + r = vsnprintf(CHUNK_TO_TEXT(c), size, format, ap); + CHUNK_TO_TEXT(c)[size-1] = 0; + va_end(ap); + + if (r > -1 && r < size) { + c->length = r; + append(sb, c); + return r; + } + + if (r > -1) /* glibc 2.1 */ + size = r+1; + else /* glibc 2.0 */ + size *= 2; + } +} diff --git a/src/pulsecore/strbuf.h b/src/pulsecore/strbuf.h new file mode 100644 index 00000000..1c0850b1 --- /dev/null +++ b/src/pulsecore/strbuf.h @@ -0,0 +1,40 @@ +#ifndef foostrbufhfoo +#define foostrbufhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulsecore/gccmacro.h> + +typedef struct pa_strbuf pa_strbuf; + +pa_strbuf *pa_strbuf_new(void); +void pa_strbuf_free(pa_strbuf *sb); +char *pa_strbuf_tostring(pa_strbuf *sb); +char *pa_strbuf_tostring_free(pa_strbuf *sb); + +int pa_strbuf_printf(pa_strbuf *sb, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); +void pa_strbuf_puts(pa_strbuf *sb, const char *t); +void pa_strbuf_putsn(pa_strbuf *sb, const char *t, size_t m); + +#endif diff --git a/src/pulsecore/strlist.c b/src/pulsecore/strlist.c new file mode 100644 index 00000000..ac83f6b1 --- /dev/null +++ b/src/pulsecore/strlist.c @@ -0,0 +1,163 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/strbuf.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +#include "strlist.h" + +struct pa_strlist { + pa_strlist *next; +}; + +#define ITEM_TO_TEXT(c) ((char*) (c) + PA_ALIGN(sizeof(pa_strlist))) + +pa_strlist* pa_strlist_prepend(pa_strlist *l, const char *s) { + pa_strlist *n; + size_t size; + + pa_assert(s); + size = strlen(s); + n = pa_xmalloc(PA_ALIGN(sizeof(pa_strlist)) + size + 1); + memcpy(ITEM_TO_TEXT(n), s, size + 1); + n->next = l; + + return n; +} + +char *pa_strlist_tostring(pa_strlist *l) { + int first = 1; + pa_strbuf *b; + + b = pa_strbuf_new(); + for (; l; l = l->next) { + if (!first) + pa_strbuf_puts(b, " "); + first = 0; + pa_strbuf_puts(b, ITEM_TO_TEXT(l)); + } + + return pa_strbuf_tostring_free(b); +} + +pa_strlist* pa_strlist_remove(pa_strlist *l, const char *s) { + pa_strlist *ret = l, *prev = NULL; + + pa_assert(l); + pa_assert(s); + + while (l) { + if (!strcmp(ITEM_TO_TEXT(l), s)) { + pa_strlist *n = l->next; + + if (!prev) { + pa_assert(ret == l); + ret = n; + } else + prev->next = n; + + pa_xfree(l); + + l = n; + + } else { + prev = l; + l = l->next; + } + } + + return ret; +} + +void pa_strlist_free(pa_strlist *l) { + while (l) { + pa_strlist *c = l; + l = l->next; + pa_xfree(c); + } +} + +pa_strlist* pa_strlist_pop(pa_strlist *l, char **s) { + pa_strlist *r; + + pa_assert(s); + + if (!l) { + *s = NULL; + return NULL; + } + + *s = pa_xstrdup(ITEM_TO_TEXT(l)); + r = l->next; + pa_xfree(l); + return r; +} + +pa_strlist* pa_strlist_parse(const char *s) { + pa_strlist *head = NULL, *p = NULL; + const char *state = NULL; + char *r; + + while ((r = pa_split_spaces(s, &state))) { + pa_strlist *n; + size_t size = strlen(r); + + n = pa_xmalloc(PA_ALIGN(sizeof(pa_strlist)) + size + 1); + n->next = NULL; + memcpy(ITEM_TO_TEXT(n), r, size+1); + pa_xfree(r); + + if (p) + p->next = n; + else + head = n; + + p = n; + } + + return head; +} + +pa_strlist *pa_strlist_reverse(pa_strlist *l) { + pa_strlist *r = NULL; + + while (l) { + pa_strlist *n; + + n = l->next; + l->next = r; + r = l; + l = n; + } + + return r; +} diff --git a/src/pulsecore/strlist.h b/src/pulsecore/strlist.h new file mode 100644 index 00000000..6e6e2d4a --- /dev/null +++ b/src/pulsecore/strlist.h @@ -0,0 +1,52 @@ +#ifndef foostrlisthfoo +#define foostrlisthfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + +typedef struct pa_strlist pa_strlist; + +/* Add the specified server string to the list, return the new linked list head */ +pa_strlist* pa_strlist_prepend(pa_strlist *l, const char *s); + +/* Remove the specified string from the list, return the new linked list head */ +pa_strlist* pa_strlist_remove(pa_strlist *l, const char *s); + +/* Make a whitespace separated string of all server stringes. Returned memory has to be freed with pa_xfree() */ +char *pa_strlist_tostring(pa_strlist *l); + +/* Free the entire list */ +void pa_strlist_free(pa_strlist *l); + +/* Return the next entry in the list in *string and remove it from + * the list. Returns the new list head. The memory *string points to + * has to be freed with pa_xfree() */ +pa_strlist* pa_strlist_pop(pa_strlist *l, char **s); + +/* Parse a whitespace separated server list */ +pa_strlist* pa_strlist_parse(const char *s); + +/* Reverse string list */ +pa_strlist *pa_strlist_reverse(pa_strlist *l); + +#endif diff --git a/src/pulsecore/tagstruct.c b/src/pulsecore/tagstruct.c new file mode 100644 index 00000000..556fe806 --- /dev/null +++ b/src/pulsecore/tagstruct.c @@ -0,0 +1,673 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/time.h> +#include <stdarg.h> + +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include <pulse/xmalloc.h> + +#include <pulsecore/winsock.h> +#include <pulsecore/macro.h> + +#include "tagstruct.h" + +struct pa_tagstruct { + uint8_t *data; + size_t length, allocated; + size_t rindex; + + int dynamic; +}; + +pa_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length) { + pa_tagstruct*t; + + pa_assert(!data || (data && length)); + + t = pa_xnew(pa_tagstruct, 1); + t->data = (uint8_t*) data; + t->allocated = t->length = data ? length : 0; + t->rindex = 0; + t->dynamic = !data; + + return t; +} + +void pa_tagstruct_free(pa_tagstruct*t) { + pa_assert(t); + + if (t->dynamic) + pa_xfree(t->data); + pa_xfree(t); +} + +uint8_t* pa_tagstruct_free_data(pa_tagstruct*t, size_t *l) { + uint8_t *p; + + pa_assert(t); + pa_assert(t->dynamic); + pa_assert(l); + + p = t->data; + *l = t->length; + pa_xfree(t); + return p; +} + +static void extend(pa_tagstruct*t, size_t l) { + pa_assert(t); + pa_assert(t->dynamic); + + if (t->length+l <= t->allocated) + return; + + t->data = pa_xrealloc(t->data, t->allocated = t->length+l+100); +} + +void pa_tagstruct_puts(pa_tagstruct*t, const char *s) { + size_t l; + pa_assert(t); + + if (s) { + l = strlen(s)+2; + extend(t, l); + t->data[t->length] = PA_TAG_STRING; + strcpy((char*) (t->data+t->length+1), s); + t->length += l; + } else { + extend(t, 1); + t->data[t->length] = PA_TAG_STRING_NULL; + t->length += 1; + } +} + +void pa_tagstruct_putu32(pa_tagstruct*t, uint32_t i) { + pa_assert(t); + + extend(t, 5); + t->data[t->length] = PA_TAG_U32; + i = htonl(i); + memcpy(t->data+t->length+1, &i, 4); + t->length += 5; +} + +void pa_tagstruct_putu8(pa_tagstruct*t, uint8_t c) { + pa_assert(t); + + extend(t, 2); + t->data[t->length] = PA_TAG_U8; + *(t->data+t->length+1) = c; + t->length += 2; +} + +void pa_tagstruct_put_sample_spec(pa_tagstruct *t, const pa_sample_spec *ss) { + uint32_t rate; + + pa_assert(t); + pa_assert(ss); + + extend(t, 7); + t->data[t->length] = PA_TAG_SAMPLE_SPEC; + t->data[t->length+1] = (uint8_t) ss->format; + t->data[t->length+2] = ss->channels; + rate = htonl(ss->rate); + memcpy(t->data+t->length+3, &rate, 4); + t->length += 7; +} + +void pa_tagstruct_put_arbitrary(pa_tagstruct *t, const void *p, size_t length) { + uint32_t tmp; + + pa_assert(t); + pa_assert(p); + + extend(t, 5+length); + t->data[t->length] = PA_TAG_ARBITRARY; + tmp = htonl(length); + memcpy(t->data+t->length+1, &tmp, 4); + if (length) + memcpy(t->data+t->length+5, p, length); + t->length += 5+length; +} + +void pa_tagstruct_put_boolean(pa_tagstruct*t, int b) { + pa_assert(t); + + extend(t, 1); + t->data[t->length] = b ? PA_TAG_BOOLEAN_TRUE : PA_TAG_BOOLEAN_FALSE; + t->length += 1; +} + +void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv) { + uint32_t tmp; + pa_assert(t); + + extend(t, 9); + t->data[t->length] = PA_TAG_TIMEVAL; + tmp = htonl(tv->tv_sec); + memcpy(t->data+t->length+1, &tmp, 4); + tmp = htonl(tv->tv_usec); + memcpy(t->data+t->length+5, &tmp, 4); + t->length += 9; +} + +void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u) { + uint32_t tmp; + + pa_assert(t); + + extend(t, 9); + t->data[t->length] = PA_TAG_USEC; + tmp = htonl((uint32_t) (u >> 32)); + memcpy(t->data+t->length+1, &tmp, 4); + tmp = htonl((uint32_t) u); + memcpy(t->data+t->length+5, &tmp, 4); + t->length += 9; +} + +void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t u) { + uint32_t tmp; + + pa_assert(t); + + extend(t, 9); + t->data[t->length] = PA_TAG_U64; + tmp = htonl((uint32_t) (u >> 32)); + memcpy(t->data+t->length+1, &tmp, 4); + tmp = htonl((uint32_t) u); + memcpy(t->data+t->length+5, &tmp, 4); + t->length += 9; +} + +void pa_tagstruct_puts64(pa_tagstruct*t, int64_t u) { + uint32_t tmp; + + pa_assert(t); + + extend(t, 9); + t->data[t->length] = PA_TAG_S64; + tmp = htonl((uint32_t) ((uint64_t) u >> 32)); + memcpy(t->data+t->length+1, &tmp, 4); + tmp = htonl((uint32_t) ((uint64_t) u)); + memcpy(t->data+t->length+5, &tmp, 4); + t->length += 9; +} + +void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map) { + unsigned i; + + pa_assert(t); + extend(t, 2 + map->channels); + + t->data[t->length++] = PA_TAG_CHANNEL_MAP; + t->data[t->length++] = map->channels; + + for (i = 0; i < map->channels; i ++) + t->data[t->length++] = (uint8_t) map->map[i]; +} + +void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume) { + unsigned i; + pa_volume_t vol; + + pa_assert(t); + extend(t, 2 + cvolume->channels * sizeof(pa_volume_t)); + + t->data[t->length++] = PA_TAG_CVOLUME; + t->data[t->length++] = cvolume->channels; + + for (i = 0; i < cvolume->channels; i ++) { + vol = htonl(cvolume->values[i]); + memcpy(t->data + t->length, &vol, sizeof(pa_volume_t)); + t->length += sizeof(pa_volume_t); + } +} + +int pa_tagstruct_gets(pa_tagstruct*t, const char **s) { + int error = 0; + size_t n; + char *c; + + pa_assert(t); + pa_assert(s); + + if (t->rindex+1 > t->length) + return -1; + + if (t->data[t->rindex] == PA_TAG_STRING_NULL) { + t->rindex++; + *s = NULL; + return 0; + } + + if (t->rindex+2 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_STRING) + return -1; + + error = 1; + for (n = 0, c = (char*) (t->data+t->rindex+1); t->rindex+1+n < t->length; n++, c++) + if (!*c) { + error = 0; + break; + } + + if (error) + return -1; + + *s = (char*) (t->data+t->rindex+1); + + t->rindex += n+2; + return 0; +} + +int pa_tagstruct_getu32(pa_tagstruct*t, uint32_t *i) { + pa_assert(t); + pa_assert(i); + + if (t->rindex+5 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_U32) + return -1; + + memcpy(i, t->data+t->rindex+1, 4); + *i = ntohl(*i); + t->rindex += 5; + return 0; +} + +int pa_tagstruct_getu8(pa_tagstruct*t, uint8_t *c) { + pa_assert(t); + pa_assert(c); + + if (t->rindex+2 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_U8) + return -1; + + *c = t->data[t->rindex+1]; + t->rindex +=2; + return 0; +} + +int pa_tagstruct_get_sample_spec(pa_tagstruct *t, pa_sample_spec *ss) { + pa_assert(t); + pa_assert(ss); + + if (t->rindex+7 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_SAMPLE_SPEC) + return -1; + + ss->format = t->data[t->rindex+1]; + ss->channels = t->data[t->rindex+2]; + memcpy(&ss->rate, t->data+t->rindex+3, 4); + ss->rate = ntohl(ss->rate); + + t->rindex += 7; + return 0; +} + +int pa_tagstruct_get_arbitrary(pa_tagstruct *t, const void **p, size_t length) { + uint32_t len; + + pa_assert(t); + pa_assert(p); + + if (t->rindex+5+length > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_ARBITRARY) + return -1; + + memcpy(&len, t->data+t->rindex+1, 4); + if (ntohl(len) != length) + return -1; + + *p = t->data+t->rindex+5; + t->rindex += 5+length; + return 0; +} + +int pa_tagstruct_eof(pa_tagstruct*t) { + pa_assert(t); + + return t->rindex >= t->length; +} + +const uint8_t* pa_tagstruct_data(pa_tagstruct*t, size_t *l) { + pa_assert(t); + pa_assert(t->dynamic); + pa_assert(l); + + *l = t->length; + return t->data; +} + +int pa_tagstruct_get_boolean(pa_tagstruct*t, int *b) { + pa_assert(t); + pa_assert(b); + + if (t->rindex+1 > t->length) + return -1; + + if (t->data[t->rindex] == PA_TAG_BOOLEAN_TRUE) + *b = 1; + else if (t->data[t->rindex] == PA_TAG_BOOLEAN_FALSE) + *b = 0; + else + return -1; + + t->rindex +=1; + return 0; +} + +int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv) { + + pa_assert(t); + pa_assert(tv); + + if (t->rindex+9 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_TIMEVAL) + return -1; + + memcpy(&tv->tv_sec, t->data+t->rindex+1, 4); + tv->tv_sec = ntohl(tv->tv_sec); + memcpy(&tv->tv_usec, t->data+t->rindex+5, 4); + tv->tv_usec = ntohl(tv->tv_usec); + t->rindex += 9; + return 0; +} + +int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u) { + uint32_t tmp; + + pa_assert(t); + pa_assert(u); + + if (t->rindex+9 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_USEC) + return -1; + + memcpy(&tmp, t->data+t->rindex+1, 4); + *u = (pa_usec_t) ntohl(tmp) << 32; + memcpy(&tmp, t->data+t->rindex+5, 4); + *u |= (pa_usec_t) ntohl(tmp); + t->rindex +=9; + return 0; +} + +int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *u) { + uint32_t tmp; + + pa_assert(t); + pa_assert(u); + + if (t->rindex+9 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_U64) + return -1; + + memcpy(&tmp, t->data+t->rindex+1, 4); + *u = (uint64_t) ntohl(tmp) << 32; + memcpy(&tmp, t->data+t->rindex+5, 4); + *u |= (uint64_t) ntohl(tmp); + t->rindex +=9; + return 0; +} + +int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *u) { + uint32_t tmp; + + pa_assert(t); + pa_assert(u); + + if (t->rindex+9 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_S64) + return -1; + + memcpy(&tmp, t->data+t->rindex+1, 4); + *u = (int64_t) ((uint64_t) ntohl(tmp) << 32); + memcpy(&tmp, t->data+t->rindex+5, 4); + *u |= (int64_t) ntohl(tmp); + t->rindex +=9; + return 0; +} + +int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map) { + unsigned i; + + pa_assert(t); + pa_assert(map); + + if (t->rindex+2 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_CHANNEL_MAP) + return -1; + + if ((map->channels = t->data[t->rindex+1]) > PA_CHANNELS_MAX) + return -1; + + if (t->rindex+2+map->channels > t->length) + return -1; + + for (i = 0; i < map->channels; i ++) + map->map[i] = (int8_t) t->data[t->rindex + 2 + i]; + + t->rindex += 2 + map->channels; + return 0; +} + +int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *cvolume) { + unsigned i; + pa_volume_t vol; + + pa_assert(t); + pa_assert(cvolume); + + if (t->rindex+2 > t->length) + return -1; + + if (t->data[t->rindex] != PA_TAG_CVOLUME) + return -1; + + if ((cvolume->channels = t->data[t->rindex+1]) > PA_CHANNELS_MAX) + return -1; + + if (t->rindex+2+cvolume->channels*sizeof(pa_volume_t) > t->length) + return -1; + + for (i = 0; i < cvolume->channels; i ++) { + memcpy(&vol, t->data + t->rindex + 2 + i * sizeof(pa_volume_t), sizeof(pa_volume_t)); + cvolume->values[i] = (pa_volume_t) ntohl(vol); + } + + t->rindex += 2 + cvolume->channels * sizeof(pa_volume_t); + return 0; +} + +void pa_tagstruct_put(pa_tagstruct *t, ...) { + va_list va; + pa_assert(t); + + va_start(va, t); + + for (;;) { + int tag = va_arg(va, int); + + if (tag == PA_TAG_INVALID) + break; + + switch (tag) { + case PA_TAG_STRING: + case PA_TAG_STRING_NULL: + pa_tagstruct_puts(t, va_arg(va, char*)); + break; + + case PA_TAG_U32: + pa_tagstruct_putu32(t, va_arg(va, uint32_t)); + break; + + case PA_TAG_U8: + pa_tagstruct_putu8(t, (uint8_t) va_arg(va, int)); + break; + + case PA_TAG_U64: + pa_tagstruct_putu64(t, va_arg(va, uint64_t)); + break; + + case PA_TAG_SAMPLE_SPEC: + pa_tagstruct_put_sample_spec(t, va_arg(va, pa_sample_spec*)); + break; + + case PA_TAG_ARBITRARY: { + void *p = va_arg(va, void*); + size_t size = va_arg(va, size_t); + pa_tagstruct_put_arbitrary(t, p, size); + break; + } + + case PA_TAG_BOOLEAN_TRUE: + case PA_TAG_BOOLEAN_FALSE: + pa_tagstruct_put_boolean(t, va_arg(va, int)); + break; + + case PA_TAG_TIMEVAL: + pa_tagstruct_put_timeval(t, va_arg(va, struct timeval*)); + break; + + case PA_TAG_USEC: + pa_tagstruct_put_usec(t, va_arg(va, pa_usec_t)); + break; + + case PA_TAG_CHANNEL_MAP: + pa_tagstruct_put_channel_map(t, va_arg(va, pa_channel_map *)); + break; + + case PA_TAG_CVOLUME: + pa_tagstruct_put_cvolume(t, va_arg(va, pa_cvolume *)); + break; + + default: + pa_assert_not_reached(); + } + } + + va_end(va); +} + +int pa_tagstruct_get(pa_tagstruct *t, ...) { + va_list va; + int ret = 0; + + pa_assert(t); + + va_start(va, t); + while (ret == 0) { + int tag = va_arg(va, int); + + if (tag == PA_TAG_INVALID) + break; + + switch (tag) { + case PA_TAG_STRING: + case PA_TAG_STRING_NULL: + ret = pa_tagstruct_gets(t, va_arg(va, const char**)); + break; + + case PA_TAG_U32: + ret = pa_tagstruct_getu32(t, va_arg(va, uint32_t*)); + break; + + case PA_TAG_U8: + ret = pa_tagstruct_getu8(t, va_arg(va, uint8_t*)); + break; + + case PA_TAG_U64: + ret = pa_tagstruct_getu64(t, va_arg(va, uint64_t*)); + break; + + case PA_TAG_SAMPLE_SPEC: + ret = pa_tagstruct_get_sample_spec(t, va_arg(va, pa_sample_spec*)); + break; + + case PA_TAG_ARBITRARY: { + const void **p = va_arg(va, const void**); + size_t size = va_arg(va, size_t); + ret = pa_tagstruct_get_arbitrary(t, p, size); + break; + } + + case PA_TAG_BOOLEAN_TRUE: + case PA_TAG_BOOLEAN_FALSE: + ret = pa_tagstruct_get_boolean(t, va_arg(va, int*)); + break; + + case PA_TAG_TIMEVAL: + ret = pa_tagstruct_get_timeval(t, va_arg(va, struct timeval*)); + break; + + case PA_TAG_USEC: + ret = pa_tagstruct_get_usec(t, va_arg(va, pa_usec_t*)); + break; + + case PA_TAG_CHANNEL_MAP: + ret = pa_tagstruct_get_channel_map(t, va_arg(va, pa_channel_map *)); + break; + + case PA_TAG_CVOLUME: + ret = pa_tagstruct_get_cvolume(t, va_arg(va, pa_cvolume *)); + break; + + default: + pa_assert_not_reached(); + } + + } + + va_end(va); + return ret; +} diff --git a/src/pulsecore/tagstruct.h b/src/pulsecore/tagstruct.h new file mode 100644 index 00000000..e9bb9ac8 --- /dev/null +++ b/src/pulsecore/tagstruct.h @@ -0,0 +1,95 @@ +#ifndef footagstructhfoo +#define footagstructhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <inttypes.h> +#include <sys/types.h> +#include <sys/time.h> +#include <time.h> + +#include <pulse/sample.h> +#include <pulse/channelmap.h> +#include <pulse/volume.h> + +typedef struct pa_tagstruct pa_tagstruct; + +enum { + PA_TAG_INVALID = 0, + PA_TAG_STRING = 't', + PA_TAG_STRING_NULL = 'N', + PA_TAG_U32 = 'L', + PA_TAG_U8 = 'B', + PA_TAG_U64 = 'R', + PA_TAG_S64 = 'r', + PA_TAG_SAMPLE_SPEC = 'a', + PA_TAG_ARBITRARY = 'x', + PA_TAG_BOOLEAN_TRUE = '1', + PA_TAG_BOOLEAN_FALSE = '0', + PA_TAG_BOOLEAN = PA_TAG_BOOLEAN_TRUE, + PA_TAG_TIMEVAL = 'T', + PA_TAG_USEC = 'U' /* 64bit unsigned */, + PA_TAG_CHANNEL_MAP = 'm', + PA_TAG_CVOLUME = 'v' +}; + +pa_tagstruct *pa_tagstruct_new(const uint8_t* data, size_t length); +void pa_tagstruct_free(pa_tagstruct*t); +uint8_t* pa_tagstruct_free_data(pa_tagstruct*t, size_t *l); + +int pa_tagstruct_eof(pa_tagstruct*t); +const uint8_t* pa_tagstruct_data(pa_tagstruct*t, size_t *l); + +void pa_tagstruct_put(pa_tagstruct *t, ...); + +void pa_tagstruct_puts(pa_tagstruct*t, const char *s); +void pa_tagstruct_putu8(pa_tagstruct*t, uint8_t c); +void pa_tagstruct_putu32(pa_tagstruct*t, uint32_t i); +void pa_tagstruct_putu64(pa_tagstruct*t, uint64_t i); +void pa_tagstruct_puts64(pa_tagstruct*t, int64_t i); +void pa_tagstruct_put_sample_spec(pa_tagstruct *t, const pa_sample_spec *ss); +void pa_tagstruct_put_arbitrary(pa_tagstruct*t, const void *p, size_t length); +void pa_tagstruct_put_boolean(pa_tagstruct*t, int b); +void pa_tagstruct_put_timeval(pa_tagstruct*t, const struct timeval *tv); +void pa_tagstruct_put_usec(pa_tagstruct*t, pa_usec_t u); +void pa_tagstruct_put_channel_map(pa_tagstruct *t, const pa_channel_map *map); +void pa_tagstruct_put_cvolume(pa_tagstruct *t, const pa_cvolume *cvolume); + +int pa_tagstruct_get(pa_tagstruct *t, ...); + +int pa_tagstruct_gets(pa_tagstruct*t, const char **s); +int pa_tagstruct_getu8(pa_tagstruct*t, uint8_t *c); +int pa_tagstruct_getu32(pa_tagstruct*t, uint32_t *i); +int pa_tagstruct_getu64(pa_tagstruct*t, uint64_t *i); +int pa_tagstruct_gets64(pa_tagstruct*t, int64_t *i); +int pa_tagstruct_get_sample_spec(pa_tagstruct *t, pa_sample_spec *ss); +int pa_tagstruct_get_arbitrary(pa_tagstruct *t, const void **p, size_t length); +int pa_tagstruct_get_boolean(pa_tagstruct *t, int *b); +int pa_tagstruct_get_timeval(pa_tagstruct*t, struct timeval *tv); +int pa_tagstruct_get_usec(pa_tagstruct*t, pa_usec_t *u); +int pa_tagstruct_get_channel_map(pa_tagstruct *t, pa_channel_map *map); +int pa_tagstruct_get_cvolume(pa_tagstruct *t, pa_cvolume *v); + + +#endif diff --git a/src/pulsecore/thread-mq.c b/src/pulsecore/thread-mq.c new file mode 100644 index 00000000..9b879425 --- /dev/null +++ b/src/pulsecore/thread-mq.c @@ -0,0 +1,112 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 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 <unistd.h> +#include <errno.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/atomic.h> +#include <pulsecore/once.h> +#include <pulsecore/log.h> +#include <pulsecore/thread.h> +#include <pulsecore/semaphore.h> +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> +#include <pulsecore/flist.h> + +#include "thread-mq.h" + +PA_STATIC_TLS_DECLARE_NO_FREE(thread_mq); + +static void asyncmsgq_cb(pa_mainloop_api*api, pa_io_event* e, int fd, pa_io_event_flags_t events, void *userdata) { + pa_thread_mq *q = userdata; + pa_asyncmsgq *aq; + + pa_assert(pa_asyncmsgq_get_fd(q->outq) == fd); + pa_assert(events == PA_IO_EVENT_INPUT); + + pa_asyncmsgq_ref(aq = q->outq); + pa_asyncmsgq_after_poll(aq); + + for (;;) { + pa_msgobject *object; + int code; + void *data; + int64_t offset; + pa_memchunk chunk; + + /* Check whether there is a message for us to process */ + while (pa_asyncmsgq_get(aq, &object, &code, &data, &offset, &chunk, 0) == 0) { + int ret; + + ret = pa_asyncmsgq_dispatch(object, code, data, offset, &chunk); + pa_asyncmsgq_done(aq, ret); + } + + if (pa_asyncmsgq_before_poll(aq) == 0) + break; + } + + pa_asyncmsgq_unref(aq); +} + +void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop) { + pa_assert(q); + pa_assert(mainloop); + + q->mainloop = mainloop; + pa_assert_se(q->inq = pa_asyncmsgq_new(0)); + pa_assert_se(q->outq = pa_asyncmsgq_new(0)); + + pa_assert_se(pa_asyncmsgq_before_poll(q->outq) == 0); + pa_assert_se(q->io_event = mainloop->io_new(mainloop, pa_asyncmsgq_get_fd(q->outq), PA_IO_EVENT_INPUT, asyncmsgq_cb, q)); +} + +void pa_thread_mq_done(pa_thread_mq *q) { + pa_assert(q); + + q->mainloop->io_free(q->io_event); + q->io_event = NULL; + + pa_asyncmsgq_unref(q->inq); + pa_asyncmsgq_unref(q->outq); + q->inq = q->outq = NULL; + + q->mainloop = NULL; +} + +void pa_thread_mq_install(pa_thread_mq *q) { + pa_assert(q); + + pa_assert(!(PA_STATIC_TLS_GET(thread_mq))); + PA_STATIC_TLS_SET(thread_mq, q); +} + +pa_thread_mq *pa_thread_mq_get(void) { + return PA_STATIC_TLS_GET(thread_mq); +} diff --git a/src/pulsecore/thread-mq.h b/src/pulsecore/thread-mq.h new file mode 100644 index 00000000..13b6e01f --- /dev/null +++ b/src/pulsecore/thread-mq.h @@ -0,0 +1,49 @@ +#ifndef foopulsethreadmqhfoo +#define foopulsethreadmqhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 <pulse/mainloop-api.h> +#include <pulsecore/asyncmsgq.h> + +/* Two way communication between a thread and a mainloop. Before the + * thread is started a pa_pthread_mq should be initialized and than + * attached to the thread using pa_thread_mq_install(). */ + +typedef struct pa_thread_mq { + pa_mainloop_api *mainloop; + pa_asyncmsgq *inq, *outq; + pa_io_event *io_event; +} pa_thread_mq; + +void pa_thread_mq_init(pa_thread_mq *q, pa_mainloop_api *mainloop); +void pa_thread_mq_done(pa_thread_mq *q); + +/* Install the specified pa_thread_mq object for the current thread */ +void pa_thread_mq_install(pa_thread_mq *q); + +/* Return the pa_thread_mq object that is set for the current thread */ +pa_thread_mq *pa_thread_mq_get(void); + +#endif diff --git a/src/pulsecore/thread-posix.c b/src/pulsecore/thread-posix.c new file mode 100644 index 00000000..7f43f43e --- /dev/null +++ b/src/pulsecore/thread-posix.c @@ -0,0 +1,197 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <pthread.h> +#include <sched.h> +#include <errno.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/mutex.h> +#include <pulsecore/once.h> +#include <pulsecore/atomic.h> +#include <pulsecore/macro.h> + +#include "thread.h" + +struct pa_thread { + pthread_t id; + pa_thread_func_t thread_func; + void *userdata; + pa_atomic_t running; +}; + +struct pa_tls { + pthread_key_t key; +}; + +static void thread_free_cb(void *p) { + pa_thread *t = p; + + pa_assert(t); + + if (!t->thread_func) + /* This is a foreign thread, we need to free the struct */ + pa_xfree(t); +} + +PA_STATIC_TLS_DECLARE(current_thread, thread_free_cb); + +static void* internal_thread_func(void *userdata) { + pa_thread *t = userdata; + pa_assert(t); + + t->id = pthread_self(); + + PA_STATIC_TLS_SET(current_thread, t); + + pa_atomic_inc(&t->running); + t->thread_func(t->userdata); + pa_atomic_sub(&t->running, 2); + + return NULL; +} + +pa_thread* pa_thread_new(pa_thread_func_t thread_func, void *userdata) { + pa_thread *t; + + pa_assert(thread_func); + + t = pa_xnew(pa_thread, 1); + t->thread_func = thread_func; + t->userdata = userdata; + pa_atomic_store(&t->running, 0); + + if (pthread_create(&t->id, NULL, internal_thread_func, t) < 0) { + pa_xfree(t); + return NULL; + } + + pa_atomic_inc(&t->running); + + return t; +} + +int pa_thread_is_running(pa_thread *t) { + pa_assert(t); + + /* Unfortunately there is no way to tell whether a "foreign" + * thread is still running. See + * http://udrepper.livejournal.com/16844.html for more + * information */ + pa_assert(t->thread_func); + + return pa_atomic_load(&t->running) > 0; +} + +void pa_thread_free(pa_thread *t) { + pa_assert(t); + + pa_thread_join(t); + pa_xfree(t); +} + +int pa_thread_join(pa_thread *t) { + pa_assert(t); + + return pthread_join(t->id, NULL); +} + +pa_thread* pa_thread_self(void) { + pa_thread *t; + + if ((t = PA_STATIC_TLS_GET(current_thread))) + return t; + + /* This is a foreign thread, let's create a pthread structure to + * make sure that we can always return a sensible pointer */ + + t = pa_xnew(pa_thread, 1); + t->id = pthread_self(); + t->thread_func = NULL; + t->userdata = NULL; + pa_atomic_store(&t->running, 2); + + PA_STATIC_TLS_SET(current_thread, t); + + return t; +} + +void* pa_thread_get_data(pa_thread *t) { + pa_assert(t); + + return t->userdata; +} + +void pa_thread_set_data(pa_thread *t, void *userdata) { + pa_assert(t); + + t->userdata = userdata; +} + +void pa_thread_yield(void) { +#ifdef HAVE_PTHREAD_YIELD + pthread_yield(); +#else + pa_assert_se(sched_yield() == 0); +#endif +} + +pa_tls* pa_tls_new(pa_free_cb_t free_cb) { + pa_tls *t; + + t = pa_xnew(pa_tls, 1); + + if (pthread_key_create(&t->key, free_cb) < 0) { + pa_xfree(t); + return NULL; + } + + return t; +} + +void pa_tls_free(pa_tls *t) { + pa_assert(t); + + pa_assert_se(pthread_key_delete(t->key) == 0); + pa_xfree(t); +} + +void *pa_tls_get(pa_tls *t) { + pa_assert(t); + + return pthread_getspecific(t->key); +} + +void *pa_tls_set(pa_tls *t, void *userdata) { + void *r; + + r = pthread_getspecific(t->key); + pa_assert_se(pthread_setspecific(t->key, userdata) == 0); + return r; +} + diff --git a/src/pulsecore/thread-win32.c b/src/pulsecore/thread-win32.c new file mode 100644 index 00000000..cad1420a --- /dev/null +++ b/src/pulsecore/thread-win32.c @@ -0,0 +1,216 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <windows.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/log.h> +#include <pulsecore/once.h> + +#include "thread.h" + +struct pa_thread { + HANDLE thread; + pa_thread_func_t thread_func; + void *userdata; +}; + +struct pa_tls { + DWORD index; + pa_free_cb_t free_func; +}; + +struct pa_tls_monitor { + HANDLE thread; + pa_free_cb_t free_func; + void *data; +}; + +static pa_tls *thread_tls; +static pa_once thread_tls_once = PA_ONCE_INIT; +static pa_tls *monitor_tls; + +static void thread_tls_once_func(void) { + thread_tls = pa_tls_new(NULL); + assert(thread_tls); +} + +static DWORD WINAPI internal_thread_func(LPVOID param) { + pa_thread *t = param; + assert(t); + + pa_run_once(&thread_tls_once, thread_tls_once_func); + pa_tls_set(thread_tls, t); + + t->thread_func(t->userdata); + + return 0; +} + +pa_thread* pa_thread_new(pa_thread_func_t thread_func, void *userdata) { + pa_thread *t; + + assert(thread_func); + + t = pa_xnew(pa_thread, 1); + t->thread_func = thread_func; + t->userdata = userdata; + + t->thread = CreateThread(NULL, 0, internal_thread_func, t, 0, NULL); + + if (!t->thread) { + pa_xfree(t); + return NULL; + } + + return t; +} + +int pa_thread_is_running(pa_thread *t) { + DWORD code; + + assert(t); + + if (!GetExitCodeThread(t->thread, &code)) + return 0; + + return code == STILL_ACTIVE; +} + +void pa_thread_free(pa_thread *t) { + assert(t); + + pa_thread_join(t); + CloseHandle(t->thread); + pa_xfree(t); +} + +int pa_thread_join(pa_thread *t) { + assert(t); + + if (WaitForSingleObject(t->thread, INFINITE) == WAIT_FAILED) + return -1; + + return 0; +} + +pa_thread* pa_thread_self(void) { + pa_run_once(&thread_tls_once, thread_tls_once_func); + return pa_tls_get(thread_tls); +} + +void pa_thread_yield(void) { + Sleep(0); +} + +static DWORD WINAPI monitor_thread_func(LPVOID param) { + struct pa_tls_monitor *m = param; + assert(m); + + WaitForSingleObject(m->thread, INFINITE); + + CloseHandle(m->thread); + + m->free_func(m->data); + + pa_xfree(m); + + return 0; +} + +pa_tls* pa_tls_new(pa_free_cb_t free_cb) { + pa_tls *t; + + t = pa_xnew(pa_tls, 1); + t->index = TlsAlloc(); + t->free_func = free_cb; + + if (t->index == TLS_OUT_OF_INDEXES) { + pa_xfree(t); + return NULL; + } + + return t; +} + +void pa_tls_free(pa_tls *t) { + assert(t); + + TlsFree(t->index); + pa_xfree(t); +} + +void *pa_tls_get(pa_tls *t) { + assert(t); + + return TlsGetValue(t->index); +} + +void *pa_tls_set(pa_tls *t, void *userdata) { + void *r; + + assert(t); + + r = TlsGetValue(t->index); + + TlsSetValue(t->index, userdata); + + if (t->free_func) { + struct pa_tls_monitor *m; + + PA_ONCE_BEGIN { + monitor_tls = pa_tls_new(NULL); + assert(monitor_tls); + pa_tls_set(monitor_tls, NULL); + } PA_ONCE_END; + + m = pa_tls_get(monitor_tls); + if (!m) { + HANDLE thread; + + m = pa_xnew(struct pa_tls_monitor, 1); + + DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), + GetCurrentProcess(), &m->thread, 0, FALSE, + DUPLICATE_SAME_ACCESS); + + m->free_func = t->free_func; + + pa_tls_set(monitor_tls, m); + + thread = CreateThread(NULL, 0, monitor_thread_func, m, 0, NULL); + assert(thread); + CloseHandle(thread); + } + + m->data = userdata; + } + + return r; +} diff --git a/src/pulsecore/thread.h b/src/pulsecore/thread.h new file mode 100644 index 00000000..54ef320e --- /dev/null +++ b/src/pulsecore/thread.h @@ -0,0 +1,112 @@ +#ifndef foopulsethreadhfoo +#define foopulsethreadhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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/def.h> +#include <pulsecore/once.h> + +#ifndef PACKAGE +#error "Please include config.h before including this file!" +#endif + +typedef struct pa_thread pa_thread; + +typedef void (*pa_thread_func_t) (void *userdata); + +pa_thread* pa_thread_new(pa_thread_func_t thread_func, void *userdata); +void pa_thread_free(pa_thread *t); +int pa_thread_join(pa_thread *t); +int pa_thread_is_running(pa_thread *t); +pa_thread *pa_thread_self(void); +void pa_thread_yield(void); + +void* pa_thread_get_data(pa_thread *t); +void pa_thread_set_data(pa_thread *t, void *userdata); + +typedef struct pa_tls pa_tls; + +pa_tls* pa_tls_new(pa_free_cb_t free_cb); +void pa_tls_free(pa_tls *t); +void * pa_tls_get(pa_tls *t); +void *pa_tls_set(pa_tls *t, void *userdata); + +#define PA_STATIC_TLS_DECLARE(name, free_cb) \ + static struct { \ + pa_once once; \ + pa_tls *tls; \ + } name##_tls = { \ + .once = PA_ONCE_INIT, \ + .tls = NULL \ + }; \ + static void name##_tls_init(void) { \ + name##_tls.tls = pa_tls_new(free_cb); \ + } \ + static inline pa_tls* name##_tls_obj(void) { \ + pa_run_once(&name##_tls.once, name##_tls_init); \ + return name##_tls.tls; \ + } \ + static void name##_tls_destructor(void) PA_GCC_DESTRUCTOR; \ + static void name##_tls_destructor(void) { \ + static void (*_free_cb)(void*) = free_cb; \ + if (!name##_tls.tls) \ + return; \ + if (_free_cb) { \ + void *p; \ + if ((p = pa_tls_get(name##_tls.tls))) \ + _free_cb(p); \ + } \ + pa_tls_free(name##_tls.tls); \ + } \ + static inline void* name##_tls_get(void) { \ + return pa_tls_get(name##_tls_obj()); \ + } \ + static inline void* name##_tls_set(void *p) { \ + return pa_tls_set(name##_tls_obj(), p); \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon + +#ifdef HAVE_TLS_BUILTIN +/* An optimized version of the above that requires no dynamic + * allocation if the compiler supports __thread */ +#define PA_STATIC_TLS_DECLARE_NO_FREE(name) \ + static __thread void *name##_tls = NULL; \ + static inline void* name##_tls_get(void) { \ + return name##_tls; \ + } \ + static inline void* name##_tls_set(void *p) { \ + void *r = name##_tls; \ + name##_tls = p; \ + return r; \ + } \ + struct __stupid_useless_struct_to_allow_trailing_semicolon +#else +#define PA_STATIC_TLS_DECLARE_NO_FREE(name) PA_STATIC_TLS_DECLARE(name, NULL) +#endif + +#define PA_STATIC_TLS_GET(name) (name##_tls_get()) +#define PA_STATIC_TLS_SET(name, p) (name##_tls_set(p)) + +#endif diff --git a/src/pulsecore/time-smoother.c b/src/pulsecore/time-smoother.c new file mode 100644 index 00000000..4cebded4 --- /dev/null +++ b/src/pulsecore/time-smoother.c @@ -0,0 +1,382 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <stdio.h> + +#include <pulse/sample.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/macro.h> + +#include "time-smoother.h" + +#define HISTORY_MAX 50 + +/* + * Implementation of a time smoothing algorithm to synchronize remote + * clocks to a local one. Evens out noise, adjusts to clock skew and + * allows cheap estimations of the remote time while clock updates may + * be seldom and recieved in non-equidistant intervals. + * + * Basically, we estimate the gradient of received clock samples in a + * certain history window (of size 'history_time') with linear + * regression. With that info we estimate the remote time in + * 'adjust_time' ahead and smoothen our current estimation function + * towards that point with a 3rd order polynomial interpolation with + * fitting derivatives. (more or less a b-spline) + * + * The larger 'history_time' is chosen the better we will surpress + * noise -- but we'll adjust to clock skew slower.. + * + * The larger 'adjust_time' is chosen the smoother our estimation + * function will be -- but we'll adjust to clock skew slower, too. + * + * If 'monotonic' is TRUE the resulting estimation function is + * guaranteed to be monotonic. + */ + +struct pa_smoother { + pa_usec_t adjust_time, history_time; + pa_bool_t monotonic; + + pa_usec_t time_offset; + + pa_usec_t px, py; /* Point p, where we want to reach stability */ + double dp; /* Gradient we want at point p */ + + pa_usec_t ex, ey; /* Point e, which we estimated before and need to smooth to */ + double de; /* Gradient we estimated for point e */ + + /* History of last measurements */ + pa_usec_t history_x[HISTORY_MAX], history_y[HISTORY_MAX]; + unsigned history_idx, n_history; + + /* To even out for monotonicity */ + pa_usec_t last_y; + + /* Cached parameters for our interpolation polynomial y=ax^3+b^2+cx */ + double a, b, c; + pa_bool_t abc_valid; + + pa_bool_t paused; + pa_usec_t pause_time; +}; + +pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic) { + pa_smoother *s; + + pa_assert(adjust_time > 0); + pa_assert(history_time > 0); + + s = pa_xnew(pa_smoother, 1); + s->adjust_time = adjust_time; + s->history_time = history_time; + s->time_offset = 0; + s->monotonic = monotonic; + + s->px = s->py = 0; + s->dp = 1; + + s->ex = s->ey = 0; + s->de = 1; + + s->history_idx = 0; + s->n_history = 0; + + s->last_y = 0; + + s->abc_valid = FALSE; + + s->paused = FALSE; + + return s; +} + +void pa_smoother_free(pa_smoother* s) { + pa_assert(s); + + pa_xfree(s); +} + +static void drop_old(pa_smoother *s, pa_usec_t x) { + unsigned j; + + /* First drop items from history which are too old, but make sure + * to always keep two entries in the history */ + + for (j = s->n_history; j > 2; j--) { + + if (s->history_x[s->history_idx] + s->history_time >= x) { + /* This item is still valid, and thus all following ones + * are too, so let's quit this loop */ + break; + } + + /* Item is too old, let's drop it */ + s->history_idx ++; + while (s->history_idx >= HISTORY_MAX) + s->history_idx -= HISTORY_MAX; + + s->n_history --; + } +} + +static void add_to_history(pa_smoother *s, pa_usec_t x, pa_usec_t y) { + unsigned j; + pa_assert(s); + + drop_old(s, x); + + /* Calculate position for new entry */ + j = s->history_idx + s->n_history; + while (j >= HISTORY_MAX) + j -= HISTORY_MAX; + + /* Fill in entry */ + s->history_x[j] = x; + s->history_y[j] = y; + + /* Adjust counter */ + s->n_history ++; + + /* And make sure we don't store more entries than fit in */ + if (s->n_history >= HISTORY_MAX) { + s->history_idx += s->n_history - HISTORY_MAX; + s->n_history = HISTORY_MAX; + } +} + +static double avg_gradient(pa_smoother *s, pa_usec_t x) { + unsigned i, j, c = 0; + int64_t ax = 0, ay = 0, k, t; + double r; + + drop_old(s, x); + + /* First, calculate average of all measurements */ + i = s->history_idx; + for (j = s->n_history; j > 0; j--) { + + ax += s->history_x[i]; + ay += s->history_y[i]; + c++; + + i++; + while (i >= HISTORY_MAX) + i -= HISTORY_MAX; + } + + /* Too few measurements, assume gradient of 1 */ + if (c < 2) + return 1; + + ax /= c; + ay /= c; + + /* Now, do linear regression */ + k = t = 0; + + i = s->history_idx; + for (j = s->n_history; j > 0; j--) { + int64_t dx, dy; + + dx = (int64_t) s->history_x[i] - ax; + dy = (int64_t) s->history_y[i] - ay; + + k += dx*dy; + t += dx*dx; + + i++; + while (i >= HISTORY_MAX) + i -= HISTORY_MAX; + } + + r = (double) k / t; + + return s->monotonic && r < 0 ? 0 : r; +} + +static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) { + pa_assert(s); + pa_assert(y); + + if (x >= s->px) { + int64_t t; + + /* The requested point is right of the point where we wanted + * to be on track again, thus just linearly estimate */ + + t = (int64_t) s->py + (int64_t) (s->dp * (x - s->px)); + + if (t < 0) + t = 0; + + *y = (pa_usec_t) t; + + if (deriv) + *deriv = s->dp; + + } else { + + if (!s->abc_valid) { + pa_usec_t ex, ey, px, py; + int64_t kx, ky; + double de, dp; + + /* Ok, we're not yet on track, thus let's interpolate, and + * make sure that the first derivative is smooth */ + + /* We have two points: (ex|ey) and (px|py) with two gradients + * at these points de and dp. We do a polynomial interpolation + * of degree 3 with these 6 values */ + + ex = s->ex; ey = s->ey; + px = s->px; py = s->py; + de = s->de; dp = s->dp; + + pa_assert(ex < px); + + /* To increase the dynamic range and symplify calculation, we + * move these values to the origin */ + kx = (int64_t) px - (int64_t) ex; + ky = (int64_t) py - (int64_t) ey; + + /* Calculate a, b, c for y=ax^3+b^2+cx */ + s->c = de; + s->b = (((double) (3*ky)/kx - dp - 2*de)) / kx; + s->a = (dp/kx - 2*s->b - de/kx) / (3*kx); + + s->abc_valid = TRUE; + } + + /* Move to origin */ + x -= s->ex; + + /* Horner scheme */ + *y = (pa_usec_t) ((double) x * (s->c + (double) x * (s->b + (double) x * s->a))); + + /* Move back from origin */ + *y += s->ey; + + /* Horner scheme */ + if (deriv) + *deriv = s->c + ((double) x * (s->b*2 + (double) x * s->a*3)); + } + + /* Guarantee monotonicity */ + if (s->monotonic) { + + if (*y < s->last_y) + *y = s->last_y; + else + s->last_y = *y; + + if (deriv && *deriv < 0) + *deriv = 0; + } +} + +void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) { + pa_usec_t ney; + double nde; + + pa_assert(s); + pa_assert(x >= s->time_offset); + + /* Fix up x value */ + if (s->paused) + x = s->pause_time; + + pa_assert(x >= s->time_offset); + x -= s->time_offset; + + pa_assert(x >= s->ex); + + /* First, we calculate the position we'd estimate for x, so that + * we can adjust our position smoothly from this one */ + estimate(s, x, &ney, &nde); + s->ex = x; s->ey = ney; s->de = nde; + + /* Then, we add the new measurement to our history */ + add_to_history(s, x, y); + + /* And determine the average gradient of the history */ + s->dp = avg_gradient(s, x); + + /* And calculate when we want to be on track again */ + s->px = x + s->adjust_time; + s->py = y + s->dp *s->adjust_time; + + s->abc_valid = FALSE; +} + +pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) { + pa_usec_t y; + + pa_assert(s); + pa_assert(x >= s->time_offset); + + /* Fix up x value */ + if (s->paused) + x = s->pause_time; + + pa_assert(x >= s->time_offset); + x -= s->time_offset; + + pa_assert(x >= s->ex); + + estimate(s, x, &y, NULL); + return y; +} + +void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) { + pa_assert(s); + + s->time_offset = offset; +} + +void pa_smoother_pause(pa_smoother *s, pa_usec_t x) { + pa_assert(s); + + if (s->paused) + return; + + s->paused = TRUE; + s->pause_time = x; +} + +void pa_smoother_resume(pa_smoother *s, pa_usec_t x) { + pa_assert(s); + + if (!s->paused) + return; + + pa_assert(x >= s->pause_time); + + s->paused = FALSE; + s->time_offset += x - s->pause_time; +} diff --git a/src/pulsecore/time-smoother.h b/src/pulsecore/time-smoother.h new file mode 100644 index 00000000..8b8512e2 --- /dev/null +++ b/src/pulsecore/time-smoother.h @@ -0,0 +1,43 @@ +#ifndef foopulsetimesmootherhfoo +#define foopulsetimesmootherhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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> +#include <pulse/sample.h> + +typedef struct pa_smoother pa_smoother; + +pa_smoother* pa_smoother_new(pa_usec_t adjust_time, pa_usec_t history_time, pa_bool_t monotonic); +void pa_smoother_free(pa_smoother* s); + +void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y); +pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x); + +void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset); + +void pa_smoother_pause(pa_smoother *s, pa_usec_t x); +void pa_smoother_resume(pa_smoother *s, pa_usec_t x); + +#endif diff --git a/src/pulsecore/tokenizer.c b/src/pulsecore/tokenizer.c new file mode 100644 index 00000000..f79c19c5 --- /dev/null +++ b/src/pulsecore/tokenizer.c @@ -0,0 +1,90 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> +#include <stdlib.h> + +#include <pulse/xmalloc.h> + +#include <pulsecore/dynarray.h> +#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> + +#include "tokenizer.h" + +static void token_free(void *p, PA_GCC_UNUSED void *userdata) { + pa_xfree(p); +} + +static void parse(pa_dynarray*a, const char *s, unsigned args) { + int infty = 0; + const char delimiter[] = " \t\n\r"; + const char *p; + + pa_assert(a); + pa_assert(s); + + if (args == 0) + infty = 1; + + p = s+strspn(s, delimiter); + while (*p && (infty || args >= 2)) { + size_t l = strcspn(p, delimiter); + char *n = pa_xstrndup(p, l); + pa_dynarray_append(a, n); + p += l; + p += strspn(p, delimiter); + args--; + } + + if (args && *p) { + char *n = pa_xstrdup(p); + pa_dynarray_append(a, n); + } +} + +pa_tokenizer* pa_tokenizer_new(const char *s, unsigned args) { + pa_dynarray *a; + + a = pa_dynarray_new(); + parse(a, s, args); + return (pa_tokenizer*) a; +} + +void pa_tokenizer_free(pa_tokenizer *t) { + pa_dynarray *a = (pa_dynarray*) t; + + pa_assert(a); + pa_dynarray_free(a, token_free, NULL); +} + +const char *pa_tokenizer_get(pa_tokenizer *t, unsigned i) { + pa_dynarray *a = (pa_dynarray*) t; + + pa_assert(a); + return pa_dynarray_get(a, i); +} diff --git a/src/pulsecore/tokenizer.h b/src/pulsecore/tokenizer.h new file mode 100644 index 00000000..68a8db49 --- /dev/null +++ b/src/pulsecore/tokenizer.h @@ -0,0 +1,34 @@ +#ifndef footokenizerhfoo +#define footokenizerhfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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. +***/ + +typedef struct pa_tokenizer pa_tokenizer; + +pa_tokenizer* pa_tokenizer_new(const char *s, unsigned args); +void pa_tokenizer_free(pa_tokenizer *t); + +const char *pa_tokenizer_get(pa_tokenizer *t, unsigned i); + +#endif diff --git a/src/pulsecore/winsock.h b/src/pulsecore/winsock.h new file mode 100644 index 00000000..0352bf4d --- /dev/null +++ b/src/pulsecore/winsock.h @@ -0,0 +1,26 @@ +#ifndef foowinsockhfoo +#define foowinsockhfoo + +#ifdef HAVE_WINSOCK2_H +#include <winsock2.h> + +#define ESHUTDOWN WSAESHUTDOWN +#define ECONNRESET WSAECONNRESET +#define ECONNABORTED WSAECONNABORTED +#define ENETRESET WSAENETRESET +#define EINPROGRESS WSAEINPROGRESS +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define ETIMEDOUT WSAETIMEDOUT +#define ECONNREFUSED WSAECONNREFUSED +#define EHOSTUNREACH WSAEHOSTUNREACH +#define EWOULDBLOCK WSAEWOULDBLOCK + +typedef long suseconds_t; + +#endif + +#ifdef HAVE_WS2TCPIP_H +#include <ws2tcpip.h> +#endif + +#endif diff --git a/src/pulsecore/x11prop.c b/src/pulsecore/x11prop.c new file mode 100644 index 00000000..a740e39b --- /dev/null +++ b/src/pulsecore/x11prop.c @@ -0,0 +1,71 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <string.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#include "x11prop.h" + +void pa_x11_set_prop(Display *d, const char *name, const char *data) { + Atom a = XInternAtom(d, name, False); + XChangeProperty(d, RootWindow(d, 0), a, XA_STRING, 8, PropModeReplace, (const unsigned char*) data, strlen(data)+1); +} + +void pa_x11_del_prop(Display *d, const char *name) { + Atom a = XInternAtom(d, name, False); + XDeleteProperty(d, RootWindow(d, 0), a); +} + +char* pa_x11_get_prop(Display *d, const char *name, char *p, size_t l) { + Atom actual_type; + int actual_format; + unsigned long nitems; + unsigned long nbytes_after; + unsigned char *prop = NULL; + char *ret = NULL; + + Atom a = XInternAtom(d, name, False); + if (XGetWindowProperty(d, RootWindow(d, 0), a, 0, (l+2)/4, False, XA_STRING, &actual_type, &actual_format, &nitems, &nbytes_after, &prop) != Success) + goto finish; + + if (actual_type != XA_STRING) + goto finish; + + memcpy(p, prop, nitems); + p[nitems] = 0; + + ret = p; + +finish: + + if (prop) + XFree(prop); + + return ret; +} diff --git a/src/pulsecore/x11prop.h b/src/pulsecore/x11prop.h new file mode 100644 index 00000000..388c5a34 --- /dev/null +++ b/src/pulsecore/x11prop.h @@ -0,0 +1,35 @@ +#ifndef foox11prophfoo +#define foox11prophfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <sys/types.h> + +#include <X11/Xlib.h> + +void pa_x11_set_prop(Display *d, const char *name, const char *data); +void pa_x11_del_prop(Display *d, const char *name); +char* pa_x11_get_prop(Display *d, const char *name, char *p, size_t l); + +#endif diff --git a/src/pulsecore/x11wrap.c b/src/pulsecore/x11wrap.c new file mode 100644 index 00000000..800a9458 --- /dev/null +++ b/src/pulsecore/x11wrap.c @@ -0,0 +1,277 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <pulse/xmalloc.h> + +#include <pulsecore/llist.h> +#include <pulsecore/log.h> +#include <pulsecore/props.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +#include "x11wrap.h" + +typedef struct pa_x11_internal pa_x11_internal; + +struct pa_x11_internal { + PA_LLIST_FIELDS(pa_x11_internal); + pa_x11_wrapper *wrapper; + pa_io_event* io_event; + int fd; +}; + +struct pa_x11_wrapper { + PA_REFCNT_DECLARE; + pa_core *core; + + char *property_name; + Display *display; + + pa_defer_event* defer_event; + pa_io_event* io_event; + + PA_LLIST_HEAD(pa_x11_client, clients); + PA_LLIST_HEAD(pa_x11_internal, internals); +}; + +struct pa_x11_client { + PA_LLIST_FIELDS(pa_x11_client); + pa_x11_wrapper *wrapper; + int (*callback)(pa_x11_wrapper *w, XEvent *e, void *userdata); + void *userdata; +}; + +/* Dispatch all pending X11 events */ +static void work(pa_x11_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + while (XPending(w->display)) { + pa_x11_client *c; + XEvent e; + XNextEvent(w->display, &e); + + for (c = w->clients; c; c = c->next) { + pa_assert(c->callback); + if (c->callback(w, &e, c->userdata) != 0) + break; + } + } +} + +/* IO notification event for the X11 display connection */ +static void display_io_event(pa_mainloop_api *m, pa_io_event *e, int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) { + pa_x11_wrapper *w = userdata; + + pa_assert(m); + pa_assert(e); + pa_assert(fd >= 0); + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + work(w); +} + +/* Deferred notification event. Called once each main loop iteration */ +static void defer_event(pa_mainloop_api *m, pa_defer_event *e, void *userdata) { + pa_x11_wrapper *w = userdata; + + pa_assert(m); + pa_assert(e); + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + m->defer_enable(e, 0); + + work(w); +} + +/* IO notification event for X11 internal connections */ +static void internal_io_event(pa_mainloop_api *m, pa_io_event *e, int fd, PA_GCC_UNUSED pa_io_event_flags_t f, void *userdata) { + pa_x11_wrapper *w = userdata; + + pa_assert(m); + pa_assert(e); + pa_assert(fd >= 0); + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + XProcessInternalConnection(w->display, fd); + + work(w); +} + +/* Add a new IO source for the specified X11 internal connection */ +static pa_x11_internal* x11_internal_add(pa_x11_wrapper *w, int fd) { + pa_x11_internal *i; + pa_assert(fd >= 0); + + i = pa_xnew(pa_x11_internal, 1); + i->wrapper = w; + i->io_event = w->core->mainloop->io_new(w->core->mainloop, fd, PA_IO_EVENT_INPUT, internal_io_event, w); + i->fd = fd; + + PA_LLIST_PREPEND(pa_x11_internal, w->internals, i); + return i; +} + +/* Remove an IO source for an X11 internal connection */ +static void x11_internal_remove(pa_x11_wrapper *w, pa_x11_internal *i) { + pa_assert(i); + + PA_LLIST_REMOVE(pa_x11_internal, w->internals, i); + w->core->mainloop->io_free(i->io_event); + pa_xfree(i); +} + +/* Implementation of XConnectionWatchProc */ +static void x11_watch(Display *display, XPointer userdata, int fd, Bool opening, XPointer *watch_data) { + pa_x11_wrapper *w = (pa_x11_wrapper*) userdata; + + pa_assert(display); + pa_assert(w); + pa_assert(fd >= 0); + + if (opening) + *watch_data = (XPointer) x11_internal_add(w, fd); + else + x11_internal_remove(w, (pa_x11_internal*) *watch_data); +} + +static pa_x11_wrapper* x11_wrapper_new(pa_core *c, const char *name, const char *t) { + pa_x11_wrapper*w; + Display *d; + + if (!(d = XOpenDisplay(name))) { + pa_log("XOpenDisplay() failed"); + return NULL; + } + + w = pa_xnew(pa_x11_wrapper, 1); + PA_REFCNT_INIT(w); + w->core = c; + w->property_name = pa_xstrdup(t); + w->display = d; + + PA_LLIST_HEAD_INIT(pa_x11_client, w->clients); + PA_LLIST_HEAD_INIT(pa_x11_internal, w->internals); + + w->defer_event = c->mainloop->defer_new(c->mainloop, defer_event, w); + w->io_event = c->mainloop->io_new(c->mainloop, ConnectionNumber(d), PA_IO_EVENT_INPUT, display_io_event, w); + + XAddConnectionWatch(d, x11_watch, (XPointer) w); + + pa_assert_se(pa_property_set(c, w->property_name, w) >= 0); + + return w; +} + +static void x11_wrapper_free(pa_x11_wrapper*w) { + pa_assert(w); + + pa_assert_se(pa_property_remove(w->core, w->property_name) >= 0); + + pa_assert(!w->clients); + + XRemoveConnectionWatch(w->display, x11_watch, (XPointer) w); + XCloseDisplay(w->display); + + w->core->mainloop->io_free(w->io_event); + w->core->mainloop->defer_free(w->defer_event); + + while (w->internals) + x11_internal_remove(w, w->internals); + + pa_xfree(w->property_name); + pa_xfree(w); +} + +pa_x11_wrapper* pa_x11_wrapper_get(pa_core *c, const char *name) { + char t[256]; + pa_x11_wrapper *w; + + pa_core_assert_ref(c); + + pa_snprintf(t, sizeof(t), "x11-wrapper%s%s", name ? "-" : "", name ? name : ""); + if ((w = pa_property_get(c, t))) + return pa_x11_wrapper_ref(w); + + return x11_wrapper_new(c, name, t); +} + +pa_x11_wrapper* pa_x11_wrapper_ref(pa_x11_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + PA_REFCNT_INC(w); + return w; +} + +void pa_x11_wrapper_unref(pa_x11_wrapper* w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + if (PA_REFCNT_DEC(w) <= 0) + x11_wrapper_free(w); +} + +Display *pa_x11_wrapper_get_display(pa_x11_wrapper *w) { + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + /* Somebody is using us, schedule a output buffer flush */ + w->core->mainloop->defer_enable(w->defer_event, 1); + + return w->display; +} + +pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, int (*cb)(pa_x11_wrapper *w, XEvent *e, void *userdata), void *userdata) { + pa_x11_client *c; + + pa_assert(w); + pa_assert(PA_REFCNT_VALUE(w) >= 1); + + c = pa_xnew(pa_x11_client, 1); + c->wrapper = w; + c->callback = cb; + c->userdata = userdata; + + PA_LLIST_PREPEND(pa_x11_client, w->clients, c); + + return c; +} + +void pa_x11_client_free(pa_x11_client *c) { + pa_assert(c); + pa_assert(c->wrapper); + pa_assert(PA_REFCNT_VALUE(c->wrapper) >= 1); + + PA_LLIST_REMOVE(pa_x11_client, c->wrapper->clients, c); + pa_xfree(c); +} diff --git a/src/pulsecore/x11wrap.h b/src/pulsecore/x11wrap.h new file mode 100644 index 00000000..9bed2fce --- /dev/null +++ b/src/pulsecore/x11wrap.h @@ -0,0 +1,54 @@ +#ifndef foox11wraphfoo +#define foox11wraphfoo + +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <X11/Xlib.h> + +#include <pulsecore/core.h> + +typedef struct pa_x11_wrapper pa_x11_wrapper; + +/* Return the X11 wrapper for this core. In case no wrapper was + existant before, allocate a new one */ +pa_x11_wrapper* pa_x11_wrapper_get(pa_core *c, const char *name); + +/* Increase the wrapper's reference count by one */ +pa_x11_wrapper* pa_x11_wrapper_ref(pa_x11_wrapper *w); + +/* Decrease the reference counter of an X11 wrapper object */ +void pa_x11_wrapper_unref(pa_x11_wrapper* w); + +/* Return the X11 display object for this connection */ +Display *pa_x11_wrapper_get_display(pa_x11_wrapper *w); + +typedef struct pa_x11_client pa_x11_client; + +/* Register an X11 client, that is called for each X11 event */ +pa_x11_client* pa_x11_client_new(pa_x11_wrapper *w, int (*cb)(pa_x11_wrapper *w, XEvent *e, void *userdata), void *userdata); + +/* Free an X11 client object */ +void pa_x11_client_free(pa_x11_client *c); + +#endif diff --git a/src/tests/Makefile b/src/tests/Makefile new file mode 120000 index 00000000..c110232d --- /dev/null +++ b/src/tests/Makefile @@ -0,0 +1 @@ +../pulse/Makefile
\ No newline at end of file diff --git a/src/tests/asyncmsgq-test.c b/src/tests/asyncmsgq-test.c new file mode 100644 index 00000000..380c5e7f --- /dev/null +++ b/src/tests/asyncmsgq-test.c @@ -0,0 +1,110 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <assert.h> +#include <stdlib.h> +#include <unistd.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> +#include <pulsecore/asyncmsgq.h> +#include <pulsecore/thread.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +enum { + OPERATION_A, + OPERATION_B, + OPERATION_C, + QUIT +}; + +static void the_thread(void *_q) { + pa_asyncmsgq *q = _q; + int quit = 0; + + do { + int code = 0; + + pa_assert_se(pa_asyncmsgq_get(q, NULL, &code, NULL, NULL, NULL, 1) == 0); + + switch (code) { + + case OPERATION_A: + printf("Operation A\n"); + break; + + case OPERATION_B: + printf("Operation B\n"); + break; + + case OPERATION_C: + printf("Operation C\n"); + break; + + case QUIT: + printf("quit\n"); + quit = 1; + break; + } + + pa_asyncmsgq_done(q, 0); + + } while (!quit); +} + +int main(int argc, char *argv[]) { + pa_asyncmsgq *q; + pa_thread *t; + + pa_assert_se(q = pa_asyncmsgq_new(0)); + + pa_assert_se(t = pa_thread_new(the_thread, q)); + + printf("Operation A post\n"); + pa_asyncmsgq_post(q, NULL, OPERATION_A, NULL, 0, NULL, NULL); + + pa_thread_yield(); + + printf("Operation B post\n"); + pa_asyncmsgq_post(q, NULL, OPERATION_B, NULL, 0, NULL, NULL); + + pa_thread_yield(); + + printf("Operation C send\n"); + pa_asyncmsgq_send(q, NULL, OPERATION_C, NULL, 0, NULL); + + pa_thread_yield(); + + printf("Quit post\n"); + pa_asyncmsgq_post(q, NULL, QUIT, NULL, 0, NULL, NULL); + + pa_thread_free(t); + + pa_asyncmsgq_unref(q); + + return 0; +} diff --git a/src/tests/asyncq-test.c b/src/tests/asyncq-test.c new file mode 100644 index 00000000..09b20047 --- /dev/null +++ b/src/tests/asyncq-test.c @@ -0,0 +1,87 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <assert.h> +#include <stdlib.h> +#include <unistd.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> +#include <pulsecore/asyncq.h> +#include <pulsecore/thread.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +static void producer(void *_q) { + pa_asyncq *q = _q; + int i; + + for (i = 0; i < 1000; i++) { + printf("pushing %i\n", i); + pa_asyncq_push(q, PA_UINT_TO_PTR(i+1), 1); + } + + pa_asyncq_push(q, PA_UINT_TO_PTR(-1), 1); + printf("pushed end\n"); +} + +static void consumer(void *_q) { + pa_asyncq *q = _q; + void *p; + int i; + + sleep(1); + + for (i = 0;; i++) { + p = pa_asyncq_pop(q, 1); + + if (p == PA_UINT_TO_PTR(-1)) + break; + + pa_assert(p == PA_UINT_TO_PTR(i+1)); + + printf("popped %i\n", i); + } + + printf("popped end\n"); +} + +int main(int argc, char *argv[]) { + pa_asyncq *q; + pa_thread *t1, *t2; + + pa_assert_se(q = pa_asyncq_new(0)); + + pa_assert_se(t1 = pa_thread_new(producer, q)); + pa_assert_se(t2 = pa_thread_new(consumer, q)); + + pa_thread_free(t1); + pa_thread_free(t2); + + pa_asyncq_free(q, NULL); + + return 0; +} diff --git a/src/tests/channelmap-test.c b/src/tests/channelmap-test.c new file mode 100644 index 00000000..98f36b61 --- /dev/null +++ b/src/tests/channelmap-test.c @@ -0,0 +1,33 @@ +/* $Id$ */ + +#include <stdio.h> +#include <assert.h> + +#include <pulse/channelmap.h> +#include <pulsecore/gccmacro.h> + +int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { + char cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_channel_map map, map2; + + pa_channel_map_init_auto(&map, 6, PA_CHANNEL_MAP_AIFF); + + fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map)); + + pa_channel_map_init_auto(&map, 6, PA_CHANNEL_MAP_AUX); + + fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map)); + + pa_channel_map_init_auto(&map, 6, PA_CHANNEL_MAP_ALSA); + + fprintf(stderr, "map: <%s>\n", pa_channel_map_snprint(cm, sizeof(cm), &map)); + + pa_channel_map_parse(&map2, cm); + + assert(pa_channel_map_equal(&map, &map2)); + + pa_channel_map_parse(&map2, "left,test"); + + + return 0; +} diff --git a/src/tests/cpulimit-test.c b/src/tests/cpulimit-test.c new file mode 100644 index 00000000..d582e9c5 --- /dev/null +++ b/src/tests/cpulimit-test.c @@ -0,0 +1,93 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <assert.h> +#include <sys/time.h> +#include <stdlib.h> +#include <stdio.h> +#include <signal.h> + +#include <pulse/mainloop.h> +#include <pulsecore/gccmacro.h> + +#ifdef TEST2 +#include <pulse/mainloop-signal.h> +#endif + +#include "../daemon/cpulimit.h" + +/* A simple example for testing the cpulimit subsystem */ + +static time_t start; + +#ifdef TEST2 + +static void func(pa_mainloop_api *m, PA_GCC_UNUSED pa_signal_event *e, PA_GCC_UNUSED int sig, PA_GCC_UNUSED void *userdata) { + time_t now; + time(&now); + + if ((now - start) >= 30) { + m->quit(m, 1); + fprintf(stderr, "Test failed\n"); + } else + raise(SIGUSR1); +} + +#endif + +int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { + pa_mainloop *m; + + m = pa_mainloop_new(); + assert(m); + + pa_cpu_limit_init(pa_mainloop_get_api(m)); + + time(&start); + +#ifdef TEST2 + pa_signal_init(pa_mainloop_get_api(m)); + pa_signal_new(SIGUSR1, func, NULL); + raise(SIGUSR1); + pa_mainloop_run(m, NULL); + pa_signal_done(); +#else + for (;;) { + time_t now; + time(&now); + + if ((now - start) >= 30) { + fprintf(stderr, "Test failed\n"); + break; + } + } +#endif + + pa_cpu_limit_done(); + + pa_mainloop_free(m); + + return 0; +} diff --git a/src/tests/envelope-test.c b/src/tests/envelope-test.c new file mode 100644 index 00000000..240747d7 --- /dev/null +++ b/src/tests/envelope-test.c @@ -0,0 +1,248 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <pulse/sample.h> +#include <pulse/volume.h> +#include <pulse/timeval.h> + +#include <pulsecore/envelope.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> +#include <pulsecore/memblock.h> +#include <pulsecore/sample-util.h> + +#include <liboil/liboil.h> + +const pa_envelope_def ramp_down = { + .n_points = 2, + .points_x = { 100*PA_USEC_PER_MSEC, 300*PA_USEC_PER_MSEC }, + .points_y = { + .f = { 1.0, 0.2 }, + .i = { 0x10000, 0x10000/5 } + } +}; + +const pa_envelope_def ramp_up = { + .n_points = 2, + .points_x = { 100*PA_USEC_PER_MSEC, 300*PA_USEC_PER_MSEC }, + .points_y = { + .f = { 0.2, 1.0 }, + .i = { 0x10000/5, 0x10000 } + } +}; + +const pa_envelope_def ramp_down2 = { + .n_points = 2, + .points_x = { 50*PA_USEC_PER_MSEC, 900*PA_USEC_PER_MSEC }, + .points_y = { + .f = { 0.8, 0.7 }, + .i = { 0x10000*4/5, 0x10000*7/10 } + } +}; + +const pa_envelope_def ramp_up2 = { + .n_points = 2, + .points_x = { 50*PA_USEC_PER_MSEC, 900*PA_USEC_PER_MSEC }, + .points_y = { + .f = { 0.7, 0.9 }, + .i = { 0x10000*7/10, 0x10000*9/10 } + } +}; + +static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) { + void *d; + unsigned i; + + static unsigned j = 0; + + d = pa_memblock_acquire(chunk->memblock); + + switch (ss->format) { + + case PA_SAMPLE_U8: + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: { + uint8_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) + printf("0x%02x ", *(u++)); + + break; + } + + case PA_SAMPLE_S16NE: + case PA_SAMPLE_S16RE: { + int16_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) + printf("%i\t%i\n", j++, *(u++)); + + break; + } + + case PA_SAMPLE_S32NE: + case PA_SAMPLE_S32RE: { + int32_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) + printf("%i\t%i\n", j++, *(u++)); + + break; + } + + case PA_SAMPLE_FLOAT32NE: + case PA_SAMPLE_FLOAT32RE: { + float *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) { + printf("%i\t%1.3g\n", j++, PA_MAYBE_FLOAT32_SWAP(ss->format == PA_SAMPLE_FLOAT32RE, *u)); + u++; + } + + break; + } + + default: + pa_assert_not_reached(); + } + + printf("\n"); + + pa_memblock_release(chunk->memblock); +} + +static pa_memblock * generate_block(pa_mempool *pool, const pa_sample_spec *ss) { + pa_memblock *block; + void *d; + unsigned n_samples; + + block = pa_memblock_new(pool, pa_bytes_per_second(ss)); + n_samples = pa_memblock_get_length(block) / pa_sample_size(ss); + + d = pa_memblock_acquire(block); + + switch (ss->format) { + + case PA_SAMPLE_S16NE: + case PA_SAMPLE_S16RE: { + int16_t *i; + + for (i = d; n_samples > 0; n_samples--, i++) + *i = 0x7FFF; + + break; + } + + case PA_SAMPLE_S32NE: + case PA_SAMPLE_S32RE: { + int32_t *i; + + for (i = d; n_samples > 0; n_samples--, i++) + *i = 0x7FFFFFFF; + + break; + } + + case PA_SAMPLE_FLOAT32RE: + case PA_SAMPLE_FLOAT32NE: { + float *f; + + for (f = d; n_samples > 0; n_samples--, f++) + *f = PA_MAYBE_FLOAT32_SWAP(ss->format == PA_SAMPLE_FLOAT32RE, 1.0); + + break; + } + + default: + pa_assert_not_reached(); + } + + pa_memblock_release(block); + return block; +} + +int main(int argc, char *argv[]) { + pa_mempool *pool; + pa_memblock *block; + pa_memchunk chunk; + pa_envelope *envelope; + pa_envelope_item *item1, *item2; + + const pa_sample_spec ss = { + .format = PA_SAMPLE_S16NE, + .channels = 1, + .rate = 200 + }; + + const pa_cvolume v = { + .channels = 1, + .values = { PA_VOLUME_NORM, PA_VOLUME_NORM/2 } + }; + + oil_init(); + pa_log_set_maximal_level(PA_LOG_DEBUG); + + pa_assert_se(pool = pa_mempool_new(FALSE)); + pa_assert_se(envelope = pa_envelope_new(&ss)); + + block = generate_block(pool, &ss); + + chunk.memblock = pa_memblock_ref(block); + chunk.length = pa_memblock_get_length(block); + chunk.index = 0; + + pa_volume_memchunk(&chunk, &ss, &v); + + item1 = pa_envelope_add(envelope, &ramp_down); + item2 = pa_envelope_add(envelope, &ramp_down2); + pa_envelope_apply(envelope, &chunk); + dump_block(&ss, &chunk); + + pa_memblock_unref(chunk.memblock); + + chunk.memblock = pa_memblock_ref(block); + chunk.length = pa_memblock_get_length(block); + chunk.index = 0; + + item1 = pa_envelope_replace(envelope, item1, &ramp_up); + item2 = pa_envelope_replace(envelope, item2, &ramp_up2); + pa_envelope_apply(envelope, &chunk); + dump_block(&ss, &chunk); + + pa_memblock_unref(chunk.memblock); + + pa_envelope_remove(envelope, item1); + pa_envelope_remove(envelope, item2); + pa_envelope_free(envelope); + + pa_memblock_unref(block); + + pa_mempool_free(pool); + + return 0; +} diff --git a/src/tests/flist-test.c b/src/tests/flist-test.c new file mode 100644 index 00000000..7e54454e --- /dev/null +++ b/src/tests/flist-test.c @@ -0,0 +1,105 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <assert.h> +#include <stdlib.h> +#include <unistd.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> +#include <pulsecore/flist.h> +#include <pulsecore/thread.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> + +#define THREADS_MAX 20 + +static pa_flist *flist; +static int quit = 0; + +static void spin(void) { + int k; + + /* Spin a little */ + k = rand() % 10000; + for (; k > 0; k--) + pa_thread_yield(); +} + +static void thread_func(void *data) { + char *s = data; + int n = 0; + int b = 1; + + while (!quit) { + char *text; + + /* Allocate some memory, if possible take it from the flist */ + if (b && (text = pa_flist_pop(flist))) + pa_log("%s: popped '%s'", s, text); + else { + text = pa_sprintf_malloc("Block %i, allocated by %s", n++, s); + pa_log("%s: allocated '%s'", s, text); + } + + b = !b; + + spin(); + + /* Give it back to the flist if possible */ + if (pa_flist_push(flist, text) < 0) { + pa_log("%s: failed to push back '%s'", s, text); + pa_xfree(text); + } else + pa_log("%s: pushed", s); + + spin(); + } + + if (pa_flist_push(flist, s) < 0) + pa_xfree(s); +} + +int main(int argc, char* argv[]) { + pa_thread *threads[THREADS_MAX]; + int i; + + flist = pa_flist_new(0); + + for (i = 0; i < THREADS_MAX; i++) { + threads[i] = pa_thread_new(thread_func, pa_sprintf_malloc("Thread #%i", i+1)); + assert(threads[i]); + } + + pa_msleep(60000); + quit = 1; + + for (i = 0; i < THREADS_MAX; i++) + pa_thread_free(threads[i]); + + pa_flist_free(flist, pa_xfree); + + return 0; +} diff --git a/src/tests/get-binary-name-test.c b/src/tests/get-binary-name-test.c new file mode 100644 index 00000000..29ebbe22 --- /dev/null +++ b/src/tests/get-binary-name-test.c @@ -0,0 +1,36 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <limits.h> +#include <stdio.h> + +#include <pulse/util.h> + +int main(int argc, char *argv[]) { + char exename[PATH_MAX]; + + printf("%s\n", pa_get_binary_name(exename, sizeof(exename))); + return 0; +} diff --git a/src/tests/hook-list-test.c b/src/tests/hook-list-test.c new file mode 100644 index 00000000..8628f521 --- /dev/null +++ b/src/tests/hook-list-test.c @@ -0,0 +1,39 @@ +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <pulsecore/hook-list.h> +#include <pulsecore/log.h> + +static pa_hook_result_t func1(const char*hook_data, const char*call_data, const char*slot_data) { + pa_log("(func1) hook=%s call=%s slot=%s", hook_data, call_data, slot_data); + return PA_HOOK_OK; +} + +static pa_hook_result_t func2(const char*hook_data, const char*call_data, const char*slot_data) { + pa_log("(func2) hook=%s call=%s slot=%s", hook_data, call_data, slot_data); + return PA_HOOK_OK; +} + +int main(int argc, char *argv[]) { + pa_hook hook; + pa_hook_slot *slot; + + pa_hook_init(&hook, (void*) "hook"); + + pa_hook_connect(&hook, (pa_hook_cb_t) func1, (void*) "slot1"); + slot = pa_hook_connect(&hook, (pa_hook_cb_t) func2, (void*) "slot2"); + pa_hook_connect(&hook, (pa_hook_cb_t) func1, (void*) "slot3"); + + pa_hook_fire(&hook, (void*) "call1"); + + pa_hook_slot_free(slot); + + pa_hook_fire(&hook, (void*) "call2"); + + pa_hook_free(&hook); + + return 0; +} diff --git a/src/tests/interpol-test.c b/src/tests/interpol-test.c new file mode 100644 index 00000000..85a509d4 --- /dev/null +++ b/src/tests/interpol-test.c @@ -0,0 +1,169 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <signal.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <math.h> + +#include <pulse/pulseaudio.h> +#include <pulse/mainloop.h> + +#include <pulsecore/thread.h> + +static pa_context *context = NULL; +static pa_stream *stream = NULL; +static pa_mainloop_api *mainloop_api = NULL; + +static void stream_write_cb(pa_stream *p, size_t length, void *userdata) { + + /* Just some silence */ + pa_stream_write(p, pa_xmalloc0(length), length, pa_xfree, 0, PA_SEEK_RELATIVE); +} + +/* This is called whenever the context status changes */ +static void context_state_callback(pa_context *c, void *userdata) { + assert(c); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: { + + static const pa_sample_spec ss = { + .format = PA_SAMPLE_S16LE, + .rate = 44100, + .channels = 1 + }; + + fprintf(stderr, "Connection established.\n"); + + stream = pa_stream_new(c, "interpol-test", &ss, NULL); + assert(stream); + + pa_stream_connect_playback(stream, NULL, NULL, PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE, NULL, NULL); + pa_stream_set_write_callback(stream, stream_write_cb, NULL); + + break; + } + + case PA_CONTEXT_TERMINATED: + break; + + case PA_CONTEXT_FAILED: + default: + fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); + abort(); + } +} + +int main(int argc, char *argv[]) { + pa_threaded_mainloop* m = NULL; + int k, r; + struct timeval start, last_info = { 0, 0 }; + pa_usec_t old_t = 0, old_rtc = 0; + + /* Set up a new main loop */ + m = pa_threaded_mainloop_new(); + assert(m); + + mainloop_api = pa_threaded_mainloop_get_api(m); + + context = pa_context_new(mainloop_api, argv[0]); + assert(context); + + pa_context_set_state_callback(context, context_state_callback, NULL); + + r = pa_context_connect(context, NULL, 0, NULL); + assert(r >= 0); + + pa_gettimeofday(&start); + + pa_threaded_mainloop_start(m); + + for (k = 0; k < 5000; k++) { + int success = 0, changed = 0; + pa_usec_t t, rtc; + struct timeval now, tv; + + pa_threaded_mainloop_lock(m); + + if (stream) { + const pa_timing_info *info; + + if (pa_stream_get_time(stream, &t) >= 0) + success = 1; + + if ((info = pa_stream_get_timing_info(stream))) + if (last_info.tv_usec != info->timestamp.tv_usec || last_info.tv_sec != info->timestamp.tv_sec) { + changed = 1; + last_info = info->timestamp; + } + } + + pa_threaded_mainloop_unlock(m); + + if (success) { + pa_gettimeofday(&now); + + rtc = pa_timeval_diff(&now, &start); + printf("%i\t%llu\t%llu\t%llu\t%llu\t%u\n", k, (unsigned long long) rtc, (unsigned long long) t, (unsigned long long) (rtc-old_rtc), (unsigned long long) (t-old_t), changed); + old_t = t; + old_rtc = rtc; + } + + /* Spin loop, ugly but normal usleep() is just too badly grained */ + + tv = now; + while (pa_timeval_diff(pa_gettimeofday(&now), &tv) < 1000) + pa_thread_yield(); + } + + if (m) + pa_threaded_mainloop_stop(m); + + if (stream) { + pa_stream_disconnect(stream); + pa_stream_unref(stream); + } + + if (context) { + pa_context_disconnect(context); + pa_context_unref(context); + } + + if (m) + pa_threaded_mainloop_free(m); + + return 0; +} diff --git a/src/tests/ipacl-test.c b/src/tests/ipacl-test.c new file mode 100644 index 00000000..d1bcb3e3 --- /dev/null +++ b/src/tests/ipacl-test.c @@ -0,0 +1,136 @@ +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#include <stdio.h> +#include <unistd.h> +#include <assert.h> +#include <string.h> + +#ifdef HAVE_SYS_SOCKET_H +#include <sys/socket.h> +#endif +#ifdef HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif +#ifdef HAVE_NETINET_IN_SYSTM_H +#include <netinet/in_systm.h> +#endif +#ifdef HAVE_NETINET_IP_H +#include <netinet/ip.h> +#endif +#ifdef HAVE_ARPA_INET_H +#include <arpa/inet.h> +#endif + +#include "../pulsecore/winsock.h" + +#include <pulsecore/ipacl.h> + +int main(int argc, char *argv[]) { + struct sockaddr_in sa; + struct sockaddr_in6 sa6; + int fd; + int r; + pa_ip_acl *acl; + + fd = socket(PF_INET, SOCK_STREAM, 0); + assert(fd >= 0); + + sa.sin_family = AF_INET; + sa.sin_port = htons(22); + sa.sin_addr.s_addr = inet_addr("127.0.0.1"); + + r = connect(fd, (struct sockaddr*) &sa, sizeof(sa)); + assert(r >= 0); + + acl = pa_ip_acl_new("127.0.0.1"); + assert(acl); + printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("127.0.0.2/0"); + assert(acl); + printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("127.0.0.1/32"); + assert(acl); + printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("127.0.0.1/7"); + assert(acl); + printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("127.0.0.2"); + assert(acl); + printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("127.0.0.0/8;0.0.0.0/32"); + assert(acl); + printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("128.0.0.2/9"); + assert(acl); + printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("::1/9"); + assert(acl); + printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + close(fd); + + fd = socket(PF_INET6, SOCK_STREAM, 0); + assert(fd >= 0); + + memset(&sa6, 0, sizeof(sa6)); + sa6.sin6_family = AF_INET6; + sa6.sin6_port = htons(22); + inet_pton(AF_INET6, "::1", &sa6.sin6_addr); + + r = connect(fd, (struct sockaddr*) &sa6, sizeof(sa6)); + assert(r >= 0); + + acl = pa_ip_acl_new("::1"); + assert(acl); + printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("::1/9"); + assert(acl); + printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("::/0"); + assert(acl); + printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("::2/128"); + assert(acl); + printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("::2/127"); + assert(acl); + printf("result=%u (should be 0)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + acl = pa_ip_acl_new("::2/126"); + assert(acl); + printf("result=%u (should be 1)\n", pa_ip_acl_check(acl, fd)); + pa_ip_acl_free(acl); + + close(fd); + + return 0; +} diff --git a/src/tests/mainloop-test.c b/src/tests/mainloop-test.c new file mode 100644 index 00000000..c386251c --- /dev/null +++ b/src/tests/mainloop-test.c @@ -0,0 +1,126 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <unistd.h> +#include <sys/time.h> +#include <assert.h> + +#include <pulse/timeval.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/gccmacro.h> + +#ifdef GLIB_MAIN_LOOP + +#include <glib.h> +#include <pulse/glib-mainloop.h> + +static GMainLoop* glib_main_loop = NULL; + +#else /* GLIB_MAIN_LOOP */ +#include <pulse/mainloop.h> +#endif /* GLIB_MAIN_LOOP */ + +static pa_defer_event *de; + +static void iocb(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + unsigned char c; + read(fd, &c, sizeof(c)); + fprintf(stderr, "IO EVENT: %c\n", c < 32 ? '.' : c); + a->defer_enable(de, 1); +} + +static void dcb(pa_mainloop_api*a, pa_defer_event *e, void *userdata) { + fprintf(stderr, "DEFER EVENT\n"); + a->defer_enable(e, 0); +} + +static void tcb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) { + fprintf(stderr, "TIME EVENT\n"); + +#if defined(GLIB_MAIN_LOOP) + g_main_loop_quit(glib_main_loop); +#else + a->quit(a, 0); +#endif +} + +int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { + pa_mainloop_api *a; + pa_io_event *ioe; + pa_time_event *te; + struct timeval tv; + +#ifdef GLIB_MAIN_LOOP + pa_glib_mainloop *g; + + glib_main_loop = g_main_loop_new(NULL, FALSE); + assert(glib_main_loop); + + g = pa_glib_mainloop_new(NULL); + assert(g); + + a = pa_glib_mainloop_get_api(g); + assert(a); +#else /* GLIB_MAIN_LOOP */ + pa_mainloop *m; + + m = pa_mainloop_new(); + assert(m); + + a = pa_mainloop_get_api(m); + assert(a); +#endif /* GLIB_MAIN_LOOP */ + + ioe = a->io_new(a, 0, PA_IO_EVENT_INPUT, iocb, NULL); + assert(ioe); + + de = a->defer_new(a, dcb, NULL); + assert(de); + + pa_gettimeofday(&tv); + tv.tv_sec += 10; + te = a->time_new(a, &tv, tcb, NULL); + +#if defined(GLIB_MAIN_LOOP) + g_main_loop_run(glib_main_loop); +#else + pa_mainloop_run(m, NULL); +#endif + + a->time_free(te); + a->defer_free(de); + a->io_free(ioe); + +#ifdef GLIB_MAIN_LOOP + pa_glib_mainloop_free(g); + g_main_loop_unref(glib_main_loop); +#else + pa_mainloop_free(m); +#endif + + return 0; +} diff --git a/src/tests/mcalign-test.c b/src/tests/mcalign-test.c new file mode 100644 index 00000000..d1013118 --- /dev/null +++ b/src/tests/mcalign-test.c @@ -0,0 +1,112 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <assert.h> +#include <unistd.h> +#include <sys/types.h> +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <time.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/mcalign.h> +#include <pulsecore/gccmacro.h> + +/* A simple program for testing pa_mcalign */ + +int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { + pa_mempool *p; + pa_mcalign *a; + pa_memchunk c; + + p = pa_mempool_new(0); + + a = pa_mcalign_new(11); + + pa_memchunk_reset(&c); + + srand(time(NULL)); + + for (;;) { + ssize_t r; + size_t l; + + if (!c.memblock) { + c.memblock = pa_memblock_new(p, 2048); + c.index = c.length = 0; + } + + assert(c.index < pa_memblock_get_length(c.memblock)); + + l = pa_memblock_get_length(c.memblock) - c.index; + + l = l <= 1 ? l : rand() % (l-1) +1 ; + + p = pa_memblock_acquire(c.memblock); + + if ((r = read(STDIN_FILENO, (uint8_t*) p + c.index, l)) <= 0) { + pa_memblock_release(c.memblock); + fprintf(stderr, "read() failed: %s\n", r < 0 ? strerror(errno) : "EOF"); + break; + } + + pa_memblock_release(c.memblock); + + c.length = r; + pa_mcalign_push(a, &c); + fprintf(stderr, "Read %ld bytes\n", (long)r); + + c.index += r; + + if (c.index >= pa_memblock_get_length(c.memblock)) { + pa_memblock_unref(c.memblock); + pa_memchunk_reset(&c); + } + + for (;;) { + pa_memchunk t; + + if (pa_mcalign_pop(a, &t) < 0) + break; + + p = pa_memblock_acquire(t.memblock); + pa_loop_write(STDOUT_FILENO, (uint8_t*) p + t.index, t.length, NULL); + pa_memblock_release(t.memblock); + fprintf(stderr, "Wrote %lu bytes.\n", (unsigned long) t.length); + + pa_memblock_unref(t.memblock); + } + } + + pa_mcalign_free(a); + + if (c.memblock) + pa_memblock_unref(c.memblock); + + pa_mempool_free(p); + + return 0; +} diff --git a/src/tests/memblock-test.c b/src/tests/memblock-test.c new file mode 100644 index 00000000..2b9d3401 --- /dev/null +++ b/src/tests/memblock-test.c @@ -0,0 +1,176 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <unistd.h> + +#include <pulsecore/memblock.h> +#include <pulsecore/macro.h> +#include <pulse/xmalloc.h> + +static void release_cb(pa_memimport *i, uint32_t block_id, void *userdata) { + printf("%s: Imported block %u is released.\n", (char*) userdata, block_id); +} + +static void revoke_cb(pa_memexport *e, uint32_t block_id, void *userdata) { + printf("%s: Exported block %u is revoked.\n", (char*) userdata, block_id); +} + +static void print_stats(pa_mempool *p, const char *text) { + const pa_mempool_stat*s = pa_mempool_get_stat(p); + + printf("%s = {\n" + "n_allocated = %u\n" + "n_accumulated = %u\n" + "n_imported = %u\n" + "n_exported = %u\n" + "allocated_size = %u\n" + "accumulated_size = %u\n" + "imported_size = %u\n" + "exported_size = %u\n" + "n_too_large_for_pool = %u\n" + "n_pool_full = %u\n" + "}\n", + text, + (unsigned) pa_atomic_load(&s->n_allocated), + (unsigned) pa_atomic_load(&s->n_accumulated), + (unsigned) pa_atomic_load(&s->n_imported), + (unsigned) pa_atomic_load(&s->n_exported), + (unsigned) pa_atomic_load(&s->allocated_size), + (unsigned) pa_atomic_load(&s->accumulated_size), + (unsigned) pa_atomic_load(&s->imported_size), + (unsigned) pa_atomic_load(&s->exported_size), + (unsigned) pa_atomic_load(&s->n_too_large_for_pool), + (unsigned) pa_atomic_load(&s->n_pool_full)); +} + +int main(int argc, char *argv[]) { + pa_mempool *pool_a, *pool_b, *pool_c; + unsigned id_a, id_b, id_c; + pa_memexport *export_a, *export_b; + pa_memimport *import_b, *import_c; + pa_memblock *mb_a, *mb_b, *mb_c; + int r, i; + pa_memblock* blocks[5]; + uint32_t id, shm_id; + size_t offset, size; + char *x; + + const char txt[] = "This is a test!"; + + pool_a = pa_mempool_new(1); + pool_b = pa_mempool_new(1); + pool_c = pa_mempool_new(1); + + pa_mempool_get_shm_id(pool_a, &id_a); + pa_mempool_get_shm_id(pool_b, &id_b); + pa_mempool_get_shm_id(pool_c, &id_c); + + pa_assert(pool_a && pool_b && pool_c); + + blocks[0] = pa_memblock_new_fixed(pool_a, (void*) txt, sizeof(txt), 1); + + blocks[1] = pa_memblock_new(pool_a, sizeof(txt)); + x = pa_memblock_acquire(blocks[1]); + snprintf(x, pa_memblock_get_length(blocks[1]), "%s", txt); + pa_memblock_release(blocks[1]); + + blocks[2] = pa_memblock_new_pool(pool_a, sizeof(txt)); + x = pa_memblock_acquire(blocks[2]); + snprintf(x, pa_memblock_get_length(blocks[2]), "%s", txt); + pa_memblock_release(blocks[2]); + + blocks[3] = pa_memblock_new_malloced(pool_a, pa_xstrdup(txt), sizeof(txt)); + blocks[4] = NULL; + + for (i = 0; blocks[i]; i++) { + printf("Memory block %u\n", i); + + mb_a = blocks[i]; + pa_assert(mb_a); + + export_a = pa_memexport_new(pool_a, revoke_cb, (void*) "A"); + export_b = pa_memexport_new(pool_b, revoke_cb, (void*) "B"); + + pa_assert(export_a && export_b); + + import_b = pa_memimport_new(pool_b, release_cb, (void*) "B"); + import_c = pa_memimport_new(pool_c, release_cb, (void*) "C"); + + pa_assert(import_b && import_c); + + r = pa_memexport_put(export_a, mb_a, &id, &shm_id, &offset, &size); + pa_assert(r >= 0); + pa_assert(shm_id == id_a); + + printf("A: Memory block exported as %u\n", id); + + mb_b = pa_memimport_get(import_b, id, shm_id, offset, size); + pa_assert(mb_b); + r = pa_memexport_put(export_b, mb_b, &id, &shm_id, &offset, &size); + pa_assert(r >= 0); + pa_assert(shm_id == id_a || shm_id == id_b); + pa_memblock_unref(mb_b); + + printf("B: Memory block exported as %u\n", id); + + mb_c = pa_memimport_get(import_c, id, shm_id, offset, size); + pa_assert(mb_c); + x = pa_memblock_acquire(mb_c); + printf("1 data=%s\n", x); + pa_memblock_release(mb_c); + + print_stats(pool_a, "A"); + print_stats(pool_b, "B"); + print_stats(pool_c, "C"); + + pa_memexport_free(export_b); + x = pa_memblock_acquire(mb_c); + printf("2 data=%s\n", x); + pa_memblock_release(mb_c); + pa_memblock_unref(mb_c); + + pa_memimport_free(import_b); + + pa_memblock_unref(mb_a); + + pa_memimport_free(import_c); + pa_memexport_free(export_a); + } + + printf("vaccuuming...\n"); + + pa_mempool_vacuum(pool_a); + pa_mempool_vacuum(pool_b); + pa_mempool_vacuum(pool_c); + + printf("vaccuuming done...\n"); + + pa_mempool_free(pool_a); + pa_mempool_free(pool_b); + pa_mempool_free(pool_c); + + return 0; +} diff --git a/src/tests/memblockq-test.c b/src/tests/memblockq-test.c new file mode 100644 index 00000000..25ea399b --- /dev/null +++ b/src/tests/memblockq-test.c @@ -0,0 +1,153 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <assert.h> +#include <stdio.h> +#include <signal.h> + +#include <pulsecore/memblockq.h> +#include <pulsecore/log.h> + +int main(int argc, char *argv[]) { + int ret; + + pa_mempool *p; + pa_memblockq *bq; + pa_memchunk chunk1, chunk2, chunk3, chunk4; + pa_memblock *silence; + + pa_log_set_maximal_level(PA_LOG_DEBUG); + + p = pa_mempool_new(0); + + silence = pa_memblock_new_fixed(p, (char*) "__", 2, 1); + assert(silence); + + bq = pa_memblockq_new(0, 40, 10, 2, 4, 4, silence); + assert(bq); + + chunk1.memblock = pa_memblock_new_fixed(p, (char*) "11", 2, 1); + chunk1.index = 0; + chunk1.length = 2; + assert(chunk1.memblock); + + chunk2.memblock = pa_memblock_new_fixed(p, (char*) "XX22", 4, 1); + chunk2.index = 2; + chunk2.length = 2; + assert(chunk2.memblock); + + chunk3.memblock = pa_memblock_new_fixed(p, (char*) "3333", 4, 1); + chunk3.index = 0; + chunk3.length = 4; + assert(chunk3.memblock); + + chunk4.memblock = pa_memblock_new_fixed(p, (char*) "44444444", 8, 1); + chunk4.index = 0; + chunk4.length = 8; + assert(chunk4.memblock); + + ret = pa_memblockq_push(bq, &chunk1); + assert(ret == 0); + + ret = pa_memblockq_push(bq, &chunk1); + assert(ret == 0); + + ret = pa_memblockq_push(bq, &chunk2); + assert(ret == 0); + + ret = pa_memblockq_push(bq, &chunk2); + assert(ret == 0); + + pa_memblockq_seek(bq, -6, 0); + ret = pa_memblockq_push(bq, &chunk3); + assert(ret == 0); + + pa_memblockq_seek(bq, -2, 0); + ret = pa_memblockq_push(bq, &chunk3); + assert(ret == 0); + + pa_memblockq_seek(bq, -10, 0); + ret = pa_memblockq_push(bq, &chunk4); + assert(ret == 0); + + pa_memblockq_seek(bq, 10, 0); + + ret = pa_memblockq_push(bq, &chunk1); + assert(ret == 0); + + pa_memblockq_seek(bq, -6, 0); + ret = pa_memblockq_push(bq, &chunk2); + assert(ret == 0); + + /* Test splitting */ + pa_memblockq_seek(bq, -12, 0); + ret = pa_memblockq_push(bq, &chunk1); + assert(ret == 0); + + pa_memblockq_seek(bq, 20, 0); + + /* Test merging */ + ret = pa_memblockq_push(bq, &chunk3); + assert(ret == 0); + pa_memblockq_seek(bq, -2, 0); + + chunk3.index += 2; + chunk3.length -= 2; + ret = pa_memblockq_push(bq, &chunk3); + assert(ret == 0); + + pa_memblockq_shorten(bq, pa_memblockq_get_length(bq)-2); + + printf(">"); + + for (;;) { + pa_memchunk out; + char *e; + size_t n; + + if (pa_memblockq_peek(bq, &out) < 0) + break; + + p = pa_memblock_acquire(out.memblock); + for (e = (char*) p + out.index, n = 0; n < out.length; n++) + printf("%c", *e); + pa_memblock_release(out.memblock); + + pa_memblock_unref(out.memblock); + pa_memblockq_drop(bq, out.length); + } + + printf("<\n"); + + pa_memblockq_free(bq); + pa_memblock_unref(silence); + pa_memblock_unref(chunk1.memblock); + pa_memblock_unref(chunk2.memblock); + pa_memblock_unref(chunk3.memblock); + pa_memblock_unref(chunk4.memblock); + + return 0; +} diff --git a/src/tests/mix-test.c b/src/tests/mix-test.c new file mode 100644 index 00000000..d07b1b0c --- /dev/null +++ b/src/tests/mix-test.c @@ -0,0 +1,261 @@ +/* $Id: resampler-test.c 2037 2007-11-09 02:45:07Z lennart $ */ + +/*** + This file is part of PulseAudio. + + 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 <pulse/sample.h> +#include <pulse/volume.h> + +#include <pulsecore/resampler.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> +#include <pulsecore/memblock.h> +#include <pulsecore/sample-util.h> + +#include <liboil/liboil.h> + +static float swap_float(float a) { + uint32_t *b = (uint32_t*) &a; + *b = PA_UINT32_SWAP(*b); + return a; +} + +static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) { + void *d; + unsigned i; + + d = pa_memblock_acquire(chunk->memblock); + + switch (ss->format) { + + case PA_SAMPLE_U8: + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: { + uint8_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) + printf("0x%02x ", *(u++)); + + break; + } + + case PA_SAMPLE_S16NE: + case PA_SAMPLE_S16RE: { + uint16_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) + printf("0x%04x ", *(u++)); + + break; + } + + case PA_SAMPLE_S32NE: + case PA_SAMPLE_S32RE: { + uint32_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) + printf("0x%08x ", *(u++)); + + break; + } + + case PA_SAMPLE_FLOAT32NE: + case PA_SAMPLE_FLOAT32RE: { + float *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) { + printf("%1.5f ", ss->format == PA_SAMPLE_FLOAT32NE ? *u : swap_float(*u)); + u++; + } + + break; + } + + default: + pa_assert_not_reached(); + } + + printf("\n"); + + pa_memblock_release(chunk->memblock); +} + +static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) { + pa_memblock *r; + void *d; + unsigned i; + + pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * 10)); + d = pa_memblock_acquire(r); + + switch (ss->format) { + + case PA_SAMPLE_U8: + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: { + uint8_t *u = d; + + u[0] = 0x00; + u[1] = 0xFF; + u[2] = 0x7F; + u[3] = 0x80; + u[4] = 0x9f; + u[5] = 0x3f; + u[6] = 0x1; + u[7] = 0xF0; + u[8] = 0x20; + u[9] = 0x21; + break; + } + + case PA_SAMPLE_S16NE: + case PA_SAMPLE_S16RE: { + uint16_t *u = d; + + u[0] = 0x0000; + u[1] = 0xFFFF; + u[2] = 0x7FFF; + u[3] = 0x8000; + u[4] = 0x9fff; + u[5] = 0x3fff; + u[6] = 0x1; + u[7] = 0xF000; + u[8] = 0x20; + u[9] = 0x21; + break; + } + + case PA_SAMPLE_S32NE: + case PA_SAMPLE_S32RE: { + uint32_t *u = d; + + u[0] = 0x00000001; + u[1] = 0xFFFF0002; + u[2] = 0x7FFF0003; + u[3] = 0x80000004; + u[4] = 0x9fff0005; + u[5] = 0x3fff0006; + u[6] = 0x10007; + u[7] = 0xF0000008; + u[8] = 0x200009; + u[9] = 0x21000A; + break; + } + + case PA_SAMPLE_FLOAT32NE: + case PA_SAMPLE_FLOAT32RE: { + float *u = d; + + u[0] = 0.0; + u[1] = -1.0; + u[2] = 1.0; + u[3] = 4711; + u[4] = 0.222; + u[5] = 0.33; + u[6] = -.3; + u[7] = 99; + u[8] = -0.555; + u[9] = -.123; + + if (ss->format == PA_SAMPLE_FLOAT32RE) + for (i = 0; i < 10; i++) + u[i] = swap_float(u[i]); + + break; + } + + default: + pa_assert_not_reached(); + } + + pa_memblock_release(r); + + return r; +} + +int main(int argc, char *argv[]) { + pa_mempool *pool; + pa_sample_spec a; + pa_cvolume v; + + oil_init(); + pa_log_set_maximal_level(PA_LOG_DEBUG); + + pa_assert_se(pool = pa_mempool_new(FALSE)); + + a.channels = 1; + a.rate = 44100; + + v.channels = a.channels; + v.values[0] = pa_sw_volume_from_linear(0.9); + + for (a.format = 0; a.format < PA_SAMPLE_MAX; a.format ++) { + pa_memchunk i, j, k; + pa_mix_info m[2]; + void *ptr; + + printf("=== mixing: %s\n", pa_sample_format_to_string(a.format)); + + /* Generate block */ + i.memblock = generate_block(pool, &a); + i.length = pa_memblock_get_length(i.memblock); + i.index = 0; + + /* Make a copy */ + j = i; + pa_memblock_ref(j.memblock); + pa_memchunk_make_writable(&j, 0); + + /* Adjust volume of the copy */ + pa_volume_memchunk(&j, &a, &v); + + m[0].chunk = i; + m[0].volume.values[0] = PA_VOLUME_NORM; + m[0].volume.channels = a.channels; + m[1].chunk = j; + m[1].volume.values[0] = PA_VOLUME_NORM; + m[1].volume.channels = a.channels; + + k.memblock = pa_memblock_new(pool, i.length); + k.length = i.length; + k.index = 0; + + ptr = (uint8_t*) pa_memblock_acquire(k.memblock) + k.index; + pa_mix(m, 2, ptr, k.length, &a, NULL, FALSE); + pa_memblock_release(k.memblock); + + dump_block(&a, &i); + dump_block(&a, &j); + dump_block(&a, &k); + + pa_memblock_unref(i.memblock); + pa_memblock_unref(j.memblock); + pa_memblock_unref(k.memblock); + } + + pa_mempool_free(pool); + + return 0; +} diff --git a/src/tests/pacat-simple.c b/src/tests/pacat-simple.c new file mode 100644 index 00000000..2da67c1a --- /dev/null +++ b/src/tests/pacat-simple.c @@ -0,0 +1,119 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <unistd.h> +#include <string.h> +#include <errno.h> +#include <fcntl.h> + +#include <pulse/simple.h> +#include <pulse/error.h> +#include <pulsecore/gccmacro.h> + +#define BUFSIZE 1024 + +int main(PA_GCC_UNUSED int argc, char*argv[]) { + + /* The Sample format to use */ + static const pa_sample_spec ss = { + .format = PA_SAMPLE_S16LE, + .rate = 44100, + .channels = 2 + }; + + pa_simple *s = NULL; + int ret = 1; + int error; + + /* replace STDIN with the specified file if needed */ + if (argc > 1) { + int fd; + + if ((fd = open(argv[1], O_RDONLY)) < 0) { + fprintf(stderr, __FILE__": open() failed: %s\n", strerror(errno)); + goto finish; + } + + if (dup2(fd, STDIN_FILENO) < 0) { + fprintf(stderr, __FILE__": dup2() failed: %s\n", strerror(errno)); + goto finish; + } + + close(fd); + } + + /* Create a new playback stream */ + if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_PLAYBACK, NULL, "playback", &ss, NULL, NULL, &error))) { + fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); + goto finish; + } + + for (;;) { + uint8_t buf[BUFSIZE]; + ssize_t r; + +#if 0 + pa_usec_t latency; + + if ((latency = pa_simple_get_latency(s, &error)) == (pa_usec_t) -1) { + fprintf(stderr, __FILE__": pa_simple_get_latency() failed: %s\n", pa_strerror(error)); + goto finish; + } + + fprintf(stderr, "%0.0f usec \r", (float)latency); +#endif + + /* Read some data ... */ + if ((r = read(STDIN_FILENO, buf, sizeof(buf))) <= 0) { + if (r == 0) /* EOF */ + break; + + fprintf(stderr, __FILE__": read() failed: %s\n", strerror(errno)); + goto finish; + } + + /* ... and play it */ + if (pa_simple_write(s, buf, r, &error) < 0) { + fprintf(stderr, __FILE__": pa_simple_write() failed: %s\n", pa_strerror(error)); + goto finish; + } + } + + /* Make sure that every single sample was played */ + if (pa_simple_drain(s, &error) < 0) { + fprintf(stderr, __FILE__": pa_simple_drain() failed: %s\n", pa_strerror(error)); + goto finish; + } + + ret = 0; + +finish: + + if (s) + pa_simple_free(s); + + return ret; +} diff --git a/src/tests/parec-simple.c b/src/tests/parec-simple.c new file mode 100644 index 00000000..d7d88360 --- /dev/null +++ b/src/tests/parec-simple.c @@ -0,0 +1,100 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <unistd.h> +#include <string.h> +#include <errno.h> + +#include <pulse/simple.h> +#include <pulse/error.h> +#include <pulsecore/gccmacro.h> + +#define BUFSIZE 1024 + +/* A simple routine calling UNIX write() in a loop */ +static ssize_t loop_write(int fd, const void*data, size_t size) { + ssize_t ret = 0; + + while (size > 0) { + ssize_t r; + + if ((r = write(fd, data, size)) < 0) + return r; + + if (r == 0) + break; + + ret += r; + data = (const uint8_t*) data + r; + size -= r; + } + + return ret; +} + +int main(PA_GCC_UNUSED int argc, char*argv[]) { + /* The sample type to use */ + static const pa_sample_spec ss = { + .format = PA_SAMPLE_S16LE, + .rate = 44100, + .channels = 2 + }; + pa_simple *s = NULL; + int ret = 1; + int error; + + /* Create the recording stream */ + if (!(s = pa_simple_new(NULL, argv[0], PA_STREAM_RECORD, NULL, "record", &ss, NULL, NULL, &error))) { + fprintf(stderr, __FILE__": pa_simple_new() failed: %s\n", pa_strerror(error)); + goto finish; + } + + for (;;) { + uint8_t buf[BUFSIZE]; + ssize_t r; + + /* Record some data ... */ + if (pa_simple_read(s, buf, sizeof(buf), &error) < 0) { + fprintf(stderr, __FILE__": pa_simple_read() failed: %s\n", pa_strerror(error)); + goto finish; + } + + /* And write it to STDOUT */ + if ((r = loop_write(STDOUT_FILENO, buf, sizeof(buf))) <= 0) { + fprintf(stderr, __FILE__": write() failed: %s\n", strerror(errno)); + goto finish; + } + } + + ret = 0; + +finish: + + if (s) + pa_simple_free(s); + + return ret; +} diff --git a/src/tests/proplist-test.c b/src/tests/proplist-test.c new file mode 100644 index 00000000..b88f4e5e --- /dev/null +++ b/src/tests/proplist-test.c @@ -0,0 +1,61 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <pulse/proplist.h> +#include <pulse/xmalloc.h> +#include <pulsecore/macro.h> + +int main(int argc, char*argv[]) { + pa_proplist *a, *b; + char *s, *t; + + a = pa_proplist_new(); + pa_assert_se(pa_proplist_puts(a, PA_PROP_MEDIA_TITLE, "Brandenburgische Konzerte") == 0); + pa_assert_se(pa_proplist_puts(a, PA_PROP_MEDIA_ARTIST, "Johann Sebastian Bach") == 0); + + b = pa_proplist_new(); + pa_assert_se(pa_proplist_puts(b, PA_PROP_MEDIA_TITLE, "Goldbergvariationen") == 0); + pa_assert_se(pa_proplist_put(b, PA_PROP_MEDIA_ICON, "\0\1\2\3\4\5\6\7", 8) == 0); + + pa_proplist_merge(a, b); + + pa_assert_se(!pa_proplist_gets(a, PA_PROP_MEDIA_ICON)); + + printf("%s\n", pa_strnull(pa_proplist_gets(a, PA_PROP_MEDIA_TITLE))); + pa_assert_se(pa_proplist_remove(b, PA_PROP_MEDIA_TITLE) == 0); + + s = pa_proplist_to_string(a); + t = pa_proplist_to_string(b); + printf("---\n%s---\n%s", s, t); + pa_xfree(s); + pa_xfree(t); + + pa_proplist_free(a); + pa_proplist_free(b); + + return 0; +} diff --git a/src/tests/queue-test.c b/src/tests/queue-test.c new file mode 100644 index 00000000..b357ab10 --- /dev/null +++ b/src/tests/queue-test.c @@ -0,0 +1,69 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <assert.h> +#include <stdlib.h> +#include <unistd.h> + +#include <pulse/util.h> +#include <pulse/xmalloc.h> +#include <pulsecore/queue.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulsecore/macro.h> + +int main(int argc, char *argv[]) { + pa_queue *q; + + pa_assert_se(q = pa_queue_new()); + + pa_assert(pa_queue_is_empty(q)); + + pa_queue_push(q, (void*) "eins"); + pa_log("%s\n", (char*) pa_queue_pop(q)); + + pa_assert(pa_queue_is_empty(q)); + + pa_queue_push(q, (void*) "zwei"); + pa_queue_push(q, (void*) "drei"); + pa_queue_push(q, (void*) "vier"); + + pa_log("%s\n", (char*) pa_queue_pop(q)); + pa_log("%s\n", (char*) pa_queue_pop(q)); + + pa_queue_push(q, (void*) "fuenf"); + + pa_log("%s\n", (char*) pa_queue_pop(q)); + pa_log("%s\n", (char*) pa_queue_pop(q)); + + pa_assert(pa_queue_is_empty(q)); + + pa_queue_push(q, (void*) "sechs"); + pa_queue_push(q, (void*) "sieben"); + + pa_queue_free(q, NULL, NULL); + + return 0; +} diff --git a/src/tests/remix-test.c b/src/tests/remix-test.c new file mode 100644 index 00000000..d2fa6943 --- /dev/null +++ b/src/tests/remix-test.c @@ -0,0 +1,91 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <pulse/sample.h> +#include <pulse/volume.h> + +#include <pulsecore/resampler.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> +#include <pulsecore/memblock.h> +#include <pulsecore/sample-util.h> + +#include <liboil/liboil.h> + +int main(int argc, char *argv[]) { + + static const pa_channel_map maps[] = { + { 1, { PA_CHANNEL_POSITION_MONO } }, + { 2, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT } }, + { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER } }, + { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_LFE } }, + { 3, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_CENTER } }, + { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_LFE } }, + { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_REAR_CENTER } }, + { 4, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT } }, + { 5, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_CENTER } }, + { 5, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE } }, + { 6, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_CENTER } }, + { 8, { PA_CHANNEL_POSITION_LEFT, PA_CHANNEL_POSITION_RIGHT, PA_CHANNEL_POSITION_REAR_LEFT, PA_CHANNEL_POSITION_REAR_RIGHT, PA_CHANNEL_POSITION_LFE, PA_CHANNEL_POSITION_CENTER, PA_CHANNEL_POSITION_SIDE_LEFT, PA_CHANNEL_POSITION_SIDE_RIGHT } }, + { 0, { 0 } } + }; + + unsigned i, j; + pa_mempool *pool; + + oil_init(); + pa_log_set_maximal_level(PA_LOG_DEBUG); + + pa_assert_se(pool = pa_mempool_new(FALSE)); + + for (i = 0; maps[i].channels > 0; i++) + for (j = 0; maps[j].channels > 0; j++) { + char a[PA_CHANNEL_MAP_SNPRINT_MAX], b[PA_CHANNEL_MAP_SNPRINT_MAX]; + pa_resampler *r; + pa_sample_spec ss1, ss2; + + pa_log_info("Converting from '%s' to '%s'.\n", pa_channel_map_snprint(a, sizeof(a), &maps[i]), pa_channel_map_snprint(b, sizeof(b), &maps[j])); + + ss1.channels = maps[i].channels; + ss2.channels = maps[j].channels; + + ss1.rate = ss2.rate = 44100; + ss1.format = ss2.format = PA_SAMPLE_S16NE; + + r = pa_resampler_new(pool, &ss1, &maps[i], &ss2, &maps[j], PA_RESAMPLER_AUTO, 0); + + /* We don't really care for the resampler. We just want to + * see the remixing debug output. */ + + pa_resampler_free(r); + } + + + pa_mempool_free(pool); + + return 0; +} diff --git a/src/tests/resampler-test.c b/src/tests/resampler-test.c new file mode 100644 index 00000000..820a0c1e --- /dev/null +++ b/src/tests/resampler-test.c @@ -0,0 +1,254 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <pulse/sample.h> +#include <pulse/volume.h> + +#include <pulsecore/resampler.h> +#include <pulsecore/macro.h> +#include <pulsecore/endianmacros.h> +#include <pulsecore/memblock.h> +#include <pulsecore/sample-util.h> + +#include <liboil/liboil.h> + +static float swap_float(float a) { + uint32_t *b = (uint32_t*) &a; + *b = PA_UINT32_SWAP(*b); + return a; +} + +static void dump_block(const pa_sample_spec *ss, const pa_memchunk *chunk) { + void *d; + unsigned i; + + d = pa_memblock_acquire(chunk->memblock); + + switch (ss->format) { + + case PA_SAMPLE_U8: + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: { + uint8_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) + printf("0x%02x ", *(u++)); + + break; + } + + case PA_SAMPLE_S16NE: + case PA_SAMPLE_S16RE: { + uint16_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) + printf("0x%04x ", *(u++)); + + break; + } + + case PA_SAMPLE_S32NE: + case PA_SAMPLE_S32RE: { + uint32_t *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) + printf("0x%08x ", *(u++)); + + break; + } + + case PA_SAMPLE_FLOAT32NE: + case PA_SAMPLE_FLOAT32RE: { + float *u = d; + + for (i = 0; i < chunk->length / pa_frame_size(ss); i++) { + printf("%1.3g ", ss->format == PA_SAMPLE_FLOAT32NE ? *u : swap_float(*u)); + u++; + } + + break; + } + + default: + pa_assert_not_reached(); + } + + printf("\n"); + + pa_memblock_release(chunk->memblock); +} + +static pa_memblock* generate_block(pa_mempool *pool, const pa_sample_spec *ss) { + pa_memblock *r; + void *d; + unsigned i; + + pa_assert_se(r = pa_memblock_new(pool, pa_frame_size(ss) * 10)); + d = pa_memblock_acquire(r); + + switch (ss->format) { + + case PA_SAMPLE_U8: + case PA_SAMPLE_ULAW: + case PA_SAMPLE_ALAW: { + uint8_t *u = d; + + u[0] = 0x00; + u[1] = 0xFF; + u[2] = 0x7F; + u[3] = 0x80; + u[4] = 0x9f; + u[5] = 0x3f; + u[6] = 0x1; + u[7] = 0xF0; + u[8] = 0x20; + u[9] = 0x21; + break; + } + + case PA_SAMPLE_S16NE: + case PA_SAMPLE_S16RE: { + uint16_t *u = d; + + u[0] = 0x0000; + u[1] = 0xFFFF; + u[2] = 0x7FFF; + u[3] = 0x8000; + u[4] = 0x9fff; + u[5] = 0x3fff; + u[6] = 0x1; + u[7] = 0xF000; + u[8] = 0x20; + u[9] = 0x21; + break; + } + + case PA_SAMPLE_S32NE: + case PA_SAMPLE_S32RE: { + uint32_t *u = d; + + u[0] = 0x00000001; + u[1] = 0xFFFF0002; + u[2] = 0x7FFF0003; + u[3] = 0x80000004; + u[4] = 0x9fff0005; + u[5] = 0x3fff0006; + u[6] = 0x10007; + u[7] = 0xF0000008; + u[8] = 0x200009; + u[9] = 0x21000A; + break; + } + + case PA_SAMPLE_FLOAT32NE: + case PA_SAMPLE_FLOAT32RE: { + float *u = d; + + u[0] = 0.0; + u[1] = -1.0; + u[2] = 1.0; + u[3] = 4711; + u[4] = 0.222; + u[5] = 0.33; + u[6] = -.3; + u[7] = 99; + u[8] = -0.555; + u[9] = -.123; + + if (ss->format == PA_SAMPLE_FLOAT32RE) + for (i = 0; i < 10; i++) + u[i] = swap_float(u[i]); + + break; + } + + default: + pa_assert_not_reached(); + } + + pa_memblock_release(r); + + return r; +} + +int main(int argc, char *argv[]) { + pa_mempool *pool; + pa_sample_spec a, b; + pa_cvolume v; + + oil_init(); + pa_log_set_maximal_level(PA_LOG_DEBUG); + + pa_assert_se(pool = pa_mempool_new(FALSE)); + + a.channels = b.channels = 1; + a.rate = b.rate = 44100; + + v.channels = a.channels; + v.values[0] = pa_sw_volume_from_linear(0.5); + + for (a.format = 0; a.format < PA_SAMPLE_MAX; a.format ++) { + for (b.format = 0; b.format < PA_SAMPLE_MAX; b.format ++) { + + pa_resampler *forth, *back; + pa_memchunk i, j, k; + + printf("=== %s -> %s -> %s -> /2\n", + pa_sample_format_to_string(a.format), + pa_sample_format_to_string(b.format), + pa_sample_format_to_string(a.format)); + + pa_assert_se(forth = pa_resampler_new(pool, &a, NULL, &b, NULL, PA_RESAMPLER_AUTO, 0)); + pa_assert_se(back = pa_resampler_new(pool, &b, NULL, &a, NULL, PA_RESAMPLER_AUTO, 0)); + + i.memblock = generate_block(pool, &a); + i.length = pa_memblock_get_length(i.memblock); + i.index = 0; + pa_resampler_run(forth, &i, &j); + pa_resampler_run(back, &j, &k); + + dump_block(&a, &i); + dump_block(&b, &j); + dump_block(&a, &k); + + pa_memblock_unref(j.memblock); + pa_memblock_unref(k.memblock); + + pa_volume_memchunk(&i, &a, &v); + dump_block(&a, &i); + + pa_memblock_unref(i.memblock); + + pa_resampler_free(forth); + pa_resampler_free(back); + } + } + + pa_mempool_free(pool); + + return 0; +} diff --git a/src/tests/rtpoll-test.c b/src/tests/rtpoll-test.c new file mode 100644 index 00000000..e6493771 --- /dev/null +++ b/src/tests/rtpoll-test.c @@ -0,0 +1,93 @@ +/* $Id: thread-test.c 1621 2007-08-10 22:00:22Z lennart $ */ + +/*** + This file is part of PulseAudio. + + 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 <signal.h> +#include <poll.h> + +#include <pulsecore/log.h> +#include <pulsecore/rtpoll.h> +#include <pulsecore/rtsig.h> + +static int before(pa_rtpoll_item *i) { + pa_log("before"); + return 0; +} + +static void after(pa_rtpoll_item *i) { + pa_log("after"); +} + +static int worker(pa_rtpoll_item *w) { + pa_log("worker"); + return 0; +} + +int main(int argc, char *argv[]) { + pa_rtpoll *p; + pa_rtpoll_item *i, *w; + struct pollfd *pollfd; + +#ifdef SIGRTMIN + pa_rtsig_configure(SIGRTMIN+10, SIGRTMAX); +#endif + + p = pa_rtpoll_new(); + + i = pa_rtpoll_item_new(p, PA_RTPOLL_EARLY, 1); + pa_rtpoll_item_set_before_callback(i, before); + pa_rtpoll_item_set_after_callback(i, after); + + pollfd = pa_rtpoll_item_get_pollfd(i, NULL); + pollfd->fd = 0; + pollfd->events = POLLIN; + + w = pa_rtpoll_item_new(p, PA_RTPOLL_NORMAL, 0); + pa_rtpoll_item_set_before_callback(w, worker); + + pa_rtpoll_install(p); + pa_rtpoll_set_timer_periodic(p, 10000000); /* 10 s */ + + pa_rtpoll_run(p, 1); + + pa_rtpoll_item_free(i); + + i = pa_rtpoll_item_new(p, PA_RTPOLL_EARLY, 1); + pa_rtpoll_item_set_before_callback(i, before); + pa_rtpoll_item_set_after_callback(i, after); + + pollfd = pa_rtpoll_item_get_pollfd(i, NULL); + pollfd->fd = 0; + pollfd->events = POLLIN; + + pa_rtpoll_run(p, 1); + + pa_rtpoll_item_free(i); + + pa_rtpoll_item_free(w); + + pa_rtpoll_free(p); + + return 0; +} diff --git a/src/tests/sig2str-test.c b/src/tests/sig2str-test.c new file mode 100644 index 00000000..52cb9db4 --- /dev/null +++ b/src/tests/sig2str-test.c @@ -0,0 +1,39 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <signal.h> +#include <stdio.h> + +#include <pulsecore/macro.h> +#include <pulsecore/core-util.h> + +int main(int argc, char *argv[]) { + int sig; + + for (sig = -1; sig <= NSIG; sig++) + printf("%i = %s\n", sig, pa_sig2str(sig)); + + return 0; +} diff --git a/src/tests/smoother-test.c b/src/tests/smoother-test.c new file mode 100644 index 00000000..caa7df70 --- /dev/null +++ b/src/tests/smoother-test.c @@ -0,0 +1,80 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <pulsecore/time-smoother.h> +#include <pulse/timeval.h> + +int main(int argc, char*argv[]) { + pa_usec_t x; + unsigned u = 0; + pa_smoother *s; + int m; + +/* unsigned msec[] = { */ +/* 200, 200, */ +/* 300, 320, */ +/* 400, 400, */ +/* 500, 480, */ +/* 0, 0 */ +/* }; */ + + int msec[200]; + + srand(0); + + for (m = 0, u = 0; u < PA_ELEMENTSOF(msec)-2; u+= 2) { + + msec[u] = m+1; + msec[u+1] = m + rand() % 2000 - 1000; + + m += rand() % 100; + + if (msec[u+1] < 0) + msec[u+1] = 0; + } + + msec[PA_ELEMENTSOF(msec)-2] = 0; + msec[PA_ELEMENTSOF(msec)-1] = 0; + + s = pa_smoother_new(1000*PA_USEC_PER_MSEC, 2000*PA_USEC_PER_MSEC, TRUE); + + for (x = 0, u = 0; x < PA_USEC_PER_SEC * 10; x += PA_USEC_PER_MSEC) { + + while (msec[u] > 0 && (pa_usec_t) msec[u]*PA_USEC_PER_MSEC < x) { + pa_smoother_put(s, msec[u]*PA_USEC_PER_MSEC, msec[u+1]*PA_USEC_PER_MSEC); + printf("%i\t\t%i\n", msec[u], msec[u+1]); + u += 2; + } + + printf("%llu\t%llu\n", x/PA_USEC_PER_MSEC, pa_smoother_get(s, x)/PA_USEC_PER_MSEC); + } + + pa_smoother_free(s); + + return 0; +} diff --git a/src/tests/strlist-test.c b/src/tests/strlist-test.c new file mode 100644 index 00000000..47770b5d --- /dev/null +++ b/src/tests/strlist-test.c @@ -0,0 +1,42 @@ +#include <stdio.h> + +#include <pulse/xmalloc.h> +#include <pulsecore/strlist.h> +#include <pulsecore/gccmacro.h> + +int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char* argv[]) { + char *t, *u; + pa_strlist *l = NULL; + + l = pa_strlist_prepend(l, "e"); + l = pa_strlist_prepend(l, "d"); + l = pa_strlist_prepend(l, "c"); + l = pa_strlist_prepend(l, "b"); + l = pa_strlist_prepend(l, "a"); + + t = pa_strlist_tostring(l); + pa_strlist_free(l); + + fprintf(stderr, "1: %s\n", t); + + l = pa_strlist_parse(t); + pa_xfree(t); + + t = pa_strlist_tostring(l); + fprintf(stderr, "2: %s\n", t); + pa_xfree(t); + + l = pa_strlist_pop(l, &u); + fprintf(stderr, "3: %s\n", u); + pa_xfree(u); + + l = pa_strlist_remove(l, "c"); + + t = pa_strlist_tostring(l); + fprintf(stderr, "4: %s\n", t); + pa_xfree(t); + + pa_strlist_free(l); + + return 0; +} diff --git a/src/tests/sync-playback.c b/src/tests/sync-playback.c new file mode 100644 index 00000000..63510eb6 --- /dev/null +++ b/src/tests/sync-playback.c @@ -0,0 +1,192 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <signal.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <math.h> + +#include <pulse/pulseaudio.h> +#include <pulse/mainloop.h> + +#define NSTREAMS 4 +#define SINE_HZ 440 +#define SAMPLE_HZ 8000 + +static pa_context *context = NULL; +static pa_stream *streams[NSTREAMS]; +static pa_mainloop_api *mainloop_api = NULL; + +static float data[SAMPLE_HZ]; /* one second space */ + +static int n_streams_ready = 0; + +static const pa_sample_spec sample_spec = { + .format = PA_SAMPLE_FLOAT32, + .rate = SAMPLE_HZ, + .channels = 1 +}; + +static const pa_buffer_attr buffer_attr = { + .maxlength = SAMPLE_HZ*sizeof(float)*NSTREAMS, /* exactly space for the entire play time */ + .tlength = 0, + .prebuf = 0, /* Setting prebuf to 0 guarantees us the the streams will run synchronously, no matter what */ + .minreq = 0 +}; + +static void nop_free_cb(void *p) {} + +static void underflow_cb(struct pa_stream *s, void *userdata) { + int i = (int) (long) userdata; + + fprintf(stderr, "Stream %i finished\n", i); + + if (++n_streams_ready >= 2*NSTREAMS) { + fprintf(stderr, "We're done\n"); + mainloop_api->quit(mainloop_api, 0); + } +} + +/* This routine is called whenever the stream state changes */ +static void stream_state_callback(pa_stream *s, void *userdata) { + assert(s); + + switch (pa_stream_get_state(s)) { + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_READY: { + + int r, i = (int) (long) userdata; + + fprintf(stderr, "Writing data to stream %i.\n", i); + + r = pa_stream_write(s, data, sizeof(data), nop_free_cb, sizeof(data) * i, PA_SEEK_ABSOLUTE); + assert(r == 0); + + /* Be notified when this stream is drained */ + pa_stream_set_underflow_callback(s, underflow_cb, userdata); + + /* All streams have been set up, let's go! */ + if (++n_streams_ready >= NSTREAMS) { + fprintf(stderr, "Uncorking\n"); + pa_operation_unref(pa_stream_cork(s, 0, NULL, NULL)); + } + + break; + } + + default: + case PA_STREAM_FAILED: + fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + abort(); + } +} + +/* This is called whenever the context status changes */ +static void context_state_callback(pa_context *c, void *userdata) { + assert(c); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: { + + int i; + fprintf(stderr, "Connection established.\n"); + + for (i = 0; i < NSTREAMS; i++) { + char name[64]; + + fprintf(stderr, "Creating stream %i\n", i); + + snprintf(name, sizeof(name), "stream #%i", i); + + streams[i] = pa_stream_new(c, name, &sample_spec, NULL); + assert(streams[i]); + pa_stream_set_state_callback(streams[i], stream_state_callback, (void*) (long) i); + pa_stream_connect_playback(streams[i], NULL, &buffer_attr, PA_STREAM_START_CORKED, NULL, i == 0 ? NULL : streams[0]); + } + + break; + } + + case PA_CONTEXT_TERMINATED: + mainloop_api->quit(mainloop_api, 0); + break; + + case PA_CONTEXT_FAILED: + default: + fprintf(stderr, "Context error: %s\n", pa_strerror(pa_context_errno(c))); + abort(); + } +} + +int main(int argc, char *argv[]) { + pa_mainloop* m = NULL; + int i, ret = 0; + + for (i = 0; i < SAMPLE_HZ; i++) + data[i] = (float) sin(((double) i/SAMPLE_HZ)*2*M_PI*SINE_HZ)/2; + + for (i = 0; i < NSTREAMS; i++) + streams[i] = NULL; + + /* Set up a new main loop */ + m = pa_mainloop_new(); + assert(m); + + mainloop_api = pa_mainloop_get_api(m); + + context = pa_context_new(mainloop_api, argv[0]); + assert(context); + + pa_context_set_state_callback(context, context_state_callback, NULL); + + pa_context_connect(context, NULL, 0, NULL); + + if (pa_mainloop_run(m, &ret) < 0) + fprintf(stderr, "pa_mainloop_run() failed.\n"); + + pa_context_unref(context); + + for (i = 0; i < NSTREAMS; i++) + if (streams[i]) + pa_stream_unref(streams[i]); + + pa_mainloop_free(m); + + return ret; +} diff --git a/src/tests/thread-mainloop-test.c b/src/tests/thread-mainloop-test.c new file mode 100644 index 00000000..558e53a5 --- /dev/null +++ b/src/tests/thread-mainloop-test.c @@ -0,0 +1,79 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <unistd.h> +#include <stdio.h> + +#include <pulse/timeval.h> +#include <pulse/util.h> +#include <pulse/thread-mainloop.h> + +#include <pulsecore/gccmacro.h> +#include <pulsecore/macro.h> + +static void tcb(pa_mainloop_api*a, pa_time_event *e, const struct timeval *tv, void *userdata) { + pa_assert_se(pa_threaded_mainloop_in_thread(userdata)); + fprintf(stderr, "TIME EVENT START\n"); + pa_threaded_mainloop_signal(userdata, 1); + fprintf(stderr, "TIME EVENT END\n"); +} + +int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { + pa_mainloop_api *a; + pa_threaded_mainloop *m; + struct timeval tv; + + pa_assert_se(m = pa_threaded_mainloop_new()); + pa_assert_se(a = pa_threaded_mainloop_get_api(m)); + + pa_threaded_mainloop_start(m); + + pa_threaded_mainloop_lock(m); + + pa_assert_se(!pa_threaded_mainloop_in_thread(m)); + + pa_gettimeofday(&tv); + tv.tv_sec += 5; + a->time_new(a, &tv, tcb, m); + + fprintf(stderr, "waiting 5s (signal)\n"); + pa_threaded_mainloop_wait(m); + fprintf(stderr, "wait completed\n"); + pa_threaded_mainloop_accept(m); + fprintf(stderr, "signal accepted\n"); + + pa_threaded_mainloop_unlock(m); + + fprintf(stderr, "waiting 5s (sleep)\n"); + pa_msleep(5000); + + fprintf(stderr, "shutting down\n"); + + pa_threaded_mainloop_stop(m); + + pa_threaded_mainloop_free(m); + return 0; +} diff --git a/src/tests/thread-test.c b/src/tests/thread-test.c new file mode 100644 index 00000000..72dde6cb --- /dev/null +++ b/src/tests/thread-test.c @@ -0,0 +1,144 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + 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 <pulsecore/thread.h> +#include <pulsecore/mutex.h> +#include <pulsecore/once.h> +#include <pulsecore/log.h> +#include <pulsecore/core-util.h> +#include <pulse/xmalloc.h> + +static pa_mutex *mutex = NULL; +static pa_cond *cond1 = NULL, *cond2 = NULL; +static pa_tls *tls = NULL; + +static int magic_number = 0; + +#define THREADS_MAX 20 + +static void once_func(void) { + pa_log("once!"); +} + +static pa_once once = PA_ONCE_INIT; + +static void thread_func(void *data) { + pa_tls_set(tls, data); + + pa_log("thread_func() for %s starting...", (char*) pa_tls_get(tls)); + + pa_mutex_lock(mutex); + + for (;;) { + int k, n; + + pa_log("%s waiting ...", (char*) pa_tls_get(tls)); + + for (;;) { + + if (magic_number < 0) + goto quit; + + if (magic_number != 0) + break; + + pa_cond_wait(cond1, mutex); + } + + k = magic_number; + magic_number = 0; + + pa_mutex_unlock(mutex); + + pa_run_once(&once, once_func); + + pa_cond_signal(cond2, 0); + + pa_log("%s got number %i", (char*) pa_tls_get(tls), k); + + /* Spin! */ + for (n = 0; n < k; n++) + pa_thread_yield(); + + pa_mutex_lock(mutex); + } + +quit: + + pa_mutex_unlock(mutex); + + pa_log("thread_func() for %s done...", (char*) pa_tls_get(tls)); +} + +int main(int argc, char *argv[]) { + int i, k; + pa_thread* t[THREADS_MAX]; + + assert(pa_thread_is_running(pa_thread_self())); + + mutex = pa_mutex_new(FALSE, FALSE); + cond1 = pa_cond_new(); + cond2 = pa_cond_new(); + tls = pa_tls_new(pa_xfree); + + for (i = 0; i < THREADS_MAX; i++) { + t[i] = pa_thread_new(thread_func, pa_sprintf_malloc("Thread #%i", i+1)); + assert(t[i]); + } + + pa_mutex_lock(mutex); + + pa_log("loop-init"); + + for (k = 0; k < 100; k++) { + assert(magic_number == 0); + + + magic_number = (int) rand() % 0x10000; + + pa_log("iteration %i (%i)", k, magic_number); + + pa_cond_signal(cond1, 0); + + pa_cond_wait(cond2, mutex); + } + + pa_log("loop-exit"); + + magic_number = -1; + pa_cond_signal(cond1, 1); + + pa_mutex_unlock(mutex); + + for (i = 0; i < THREADS_MAX; i++) + pa_thread_free(t[i]); + + pa_mutex_free(mutex); + pa_cond_free(cond1); + pa_cond_free(cond2); + pa_tls_free(tls); + + return 0; +} diff --git a/src/tests/utf8-test.c b/src/tests/utf8-test.c new file mode 100644 index 00000000..b9594dcc --- /dev/null +++ b/src/tests/utf8-test.c @@ -0,0 +1,26 @@ +/* $Id$ */ + +#include <stdio.h> +#include <assert.h> + +#include <pulse/utf8.h> +#include <pulse/xmalloc.h> + +int main(int argc, char *argv[]) { + char *c; + + assert(pa_utf8_valid("hallo")); + assert(pa_utf8_valid("hallo\n")); + assert(!pa_utf8_valid("hüpfburg\n")); + assert(pa_utf8_valid("hallo\n")); + assert(pa_utf8_valid("hüpfburg\n")); + + printf("LATIN1: %s\n", c = pa_utf8_filter("hüpfburg")); + pa_xfree(c); + printf("UTF8: %sx\n", c = pa_utf8_filter("hüpfburg")); + pa_xfree(c); + printf("LATIN1: %sx\n", c = pa_utf8_filter("üxknärzmörzeltörszß³§dsjkfh")); + pa_xfree(c); + + return 0; +} diff --git a/src/tests/voltest.c b/src/tests/voltest.c new file mode 100644 index 00000000..dcc1ec51 --- /dev/null +++ b/src/tests/voltest.c @@ -0,0 +1,22 @@ +/* $Id$ */ + +#include <stdio.h> + +#include <pulse/volume.h> +#include <pulsecore/gccmacro.h> + +int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char *argv[]) { + pa_volume_t v; + + for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 256) { + + double dB = pa_sw_volume_to_dB(v); + double f = pa_sw_volume_to_linear(v); + + printf("Volume: %3i; percent: %i%%; decibel %0.2f; linear = %0.2f; volume(decibel): %3i; volume(linear): %3i\n", + v, (v*100)/PA_VOLUME_NORM, dB, f, pa_sw_volume_from_dB(dB), pa_sw_volume_from_linear(f)); + + } + + return 0; +} diff --git a/src/utils/Makefile b/src/utils/Makefile new file mode 120000 index 00000000..c110232d --- /dev/null +++ b/src/utils/Makefile @@ -0,0 +1 @@ +../pulse/Makefile
\ No newline at end of file diff --git a/src/utils/pabrowse.c b/src/utils/pabrowse.c new file mode 100644 index 00000000..d88001ef --- /dev/null +++ b/src/utils/pabrowse.c @@ -0,0 +1,156 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <assert.h> +#include <signal.h> + +#include <pulse/pulseaudio.h> +#include <pulse/browser.h> + +static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { + fprintf(stderr, "Got signal, exiting\n"); + m->quit(m, 0); +} + +static void dump_server(const pa_browse_info *i) { + char t[16]; + + if (i->cookie) + snprintf(t, sizeof(t), "0x%08x", *i->cookie); + + printf("server: %s\n" + "server-version: %s\n" + "user-name: %s\n" + "fqdn: %s\n" + "cookie: %s\n", + i->server, + i->server_version ? i->server_version : "n/a", + i->user_name ? i->user_name : "n/a", + i->fqdn ? i->fqdn : "n/a", + i->cookie ? t : "n/a"); +} + +static void dump_device(const pa_browse_info *i) { + char ss[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + if (i->sample_spec) + pa_sample_spec_snprint(ss, sizeof(ss), i->sample_spec); + + printf("device: %s\n" + "description: %s\n" + "sample spec: %s\n", + i->device, + i->description ? i->description : "n/a", + i->sample_spec ? ss : "n/a"); + +} + +static void browser_callback(pa_browser *b, pa_browse_opcode_t c, const pa_browse_info *i, void *userdata) { + assert(b && i); + + switch (c) { + + case PA_BROWSE_NEW_SERVER: + printf("\n=> new server <%s>\n", i->name); + dump_server(i); + break; + + case PA_BROWSE_NEW_SINK: + printf("\n=> new sink <%s>\n", i->name); + dump_server(i); + dump_device(i); + break; + + case PA_BROWSE_NEW_SOURCE: + printf("\n=> new source <%s>\n", i->name); + dump_server(i); + dump_device(i); + break; + + case PA_BROWSE_REMOVE_SERVER: + printf("\n=> removed server <%s>\n", i->name); + break; + + case PA_BROWSE_REMOVE_SINK: + printf("\n=> removed sink <%s>\n", i->name); + break; + + case PA_BROWSE_REMOVE_SOURCE: + printf("\n=> removed source <%s>\n", i->name); + break; + + default: + ; + } +} + +static void error_callback(pa_browser *b, const char *s, void *userdata) { + pa_mainloop_api*m = userdata; + + fprintf(stderr, "Failure: %s\n", s); + m->quit(m, 1); +} + +int main(int argc, char *argv[]) { + pa_mainloop *mainloop = NULL; + pa_browser *browser = NULL; + int ret = 1, r; + const char *s; + + if (!(mainloop = pa_mainloop_new())) + goto finish; + + r = pa_signal_init(pa_mainloop_get_api(mainloop)); + assert(r == 0); + pa_signal_new(SIGINT, exit_signal_callback, NULL); + pa_signal_new(SIGTERM, exit_signal_callback, NULL); + signal(SIGPIPE, SIG_IGN); + + if (!(browser = pa_browser_new_full(pa_mainloop_get_api(mainloop), PA_BROWSE_FOR_SERVERS|PA_BROWSE_FOR_SINKS|PA_BROWSE_FOR_SOURCES, &s))) { + fprintf(stderr, "pa_browse_new_full(): %s\n", s); + goto finish; + } + + pa_browser_set_callback(browser, browser_callback, NULL); + pa_browser_set_error_callback(browser, error_callback, pa_mainloop_get_api(mainloop)); + + ret = 0; + pa_mainloop_run(mainloop, &ret); + +finish: + + if (browser) + pa_browser_unref(browser); + + if (mainloop) { + pa_signal_done(); + pa_mainloop_free(mainloop); + } + + return ret; +} diff --git a/src/utils/pacat.c b/src/utils/pacat.c new file mode 100644 index 00000000..68e308d8 --- /dev/null +++ b/src/utils/pacat.c @@ -0,0 +1,769 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <signal.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <fcntl.h> + +#include <pulse/pulseaudio.h> + +#define TIME_EVENT_USEC 50000 + +#if PA_API_VERSION < 10 +#error Invalid PulseAudio API version +#endif + +static enum { RECORD, PLAYBACK } mode = PLAYBACK; + +static pa_context *context = NULL; +static pa_stream *stream = NULL; +static pa_mainloop_api *mainloop_api = NULL; + +static void *buffer = NULL; +static size_t buffer_length = 0, buffer_index = 0; + +static pa_io_event* stdio_event = NULL; + +static char *stream_name = NULL, *client_name = NULL, *device = NULL; + +static int verbose = 0; +static pa_volume_t volume = PA_VOLUME_NORM; + +static pa_sample_spec sample_spec = { + .format = PA_SAMPLE_S16LE, + .rate = 44100, + .channels = 2 +}; + +static pa_channel_map channel_map; +static int channel_map_set = 0; + +static pa_stream_flags_t flags = 0; + +/* A shortcut for terminating the application */ +static void quit(int ret) { + assert(mainloop_api); + mainloop_api->quit(mainloop_api, ret); +} + +/* Write some data to the stream */ +static void do_stream_write(size_t length) { + size_t l; + assert(length); + + if (!buffer || !buffer_length) + return; + + l = length; + if (l > buffer_length) + l = buffer_length; + + if (pa_stream_write(stream, (uint8_t*) buffer + buffer_index, l, NULL, 0, PA_SEEK_RELATIVE) < 0) { + fprintf(stderr, "pa_stream_write() failed: %s\n", pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + buffer_length -= l; + buffer_index += l; + + if (!buffer_length) { + pa_xfree(buffer); + buffer = NULL; + buffer_index = buffer_length = 0; + } +} + +/* This is called whenever new data may be written to the stream */ +static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { + assert(s); + assert(length > 0); + + if (stdio_event) + mainloop_api->io_enable(stdio_event, PA_IO_EVENT_INPUT); + + if (!buffer) + return; + + do_stream_write(length); +} + +/* This is called whenever new data may is available */ +static void stream_read_callback(pa_stream *s, size_t length, void *userdata) { + const void *data; + assert(s); + assert(length > 0); + + if (stdio_event) + mainloop_api->io_enable(stdio_event, PA_IO_EVENT_OUTPUT); + + if (pa_stream_peek(s, &data, &length) < 0) { + fprintf(stderr, "pa_stream_peek() failed: %s\n", pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + assert(data); + assert(length > 0); + + if (buffer) { + fprintf(stderr, "Buffer overrun, dropping incoming data\n"); + if (pa_stream_drop(s) < 0) { + fprintf(stderr, "pa_stream_drop() failed: %s\n", pa_strerror(pa_context_errno(context))); + quit(1); + } + return; + } + + buffer = pa_xmalloc(buffer_length = length); + memcpy(buffer, data, length); + buffer_index = 0; + pa_stream_drop(s); +} + +/* This routine is called whenever the stream state changes */ +static void stream_state_callback(pa_stream *s, void *userdata) { + assert(s); + + switch (pa_stream_get_state(s)) { + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_READY: + if (verbose) { + const pa_buffer_attr *a; + char cmt[PA_CHANNEL_MAP_SNPRINT_MAX], sst[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + fprintf(stderr, "Stream successfully created.\n"); + + if (!(a = pa_stream_get_buffer_attr(s))) + fprintf(stderr, "pa_stream_get_buffer_attr() failed: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + else { + + if (mode == PLAYBACK) + fprintf(stderr, "Buffer metrics: maxlength=%u, tlength=%u, prebuf=%u, minreq=%u\n", a->maxlength, a->tlength, a->prebuf, a->minreq); + else { + assert(mode == RECORD); + fprintf(stderr, "Buffer metrics: maxlength=%u, fragsize=%u\n", a->maxlength, a->fragsize); + } + } + + fprintf(stderr, "Using sample spec '%s', channel map '%s'.\n", + pa_sample_spec_snprint(sst, sizeof(sst), pa_stream_get_sample_spec(s)), + pa_channel_map_snprint(cmt, sizeof(cmt), pa_stream_get_channel_map(s))); + + fprintf(stderr, "Connected to device %s (%u, %ssuspended).\n", + pa_stream_get_device_name(s), + pa_stream_get_device_index(s), + pa_stream_is_suspended(s) ? "" : "not "); + } + + break; + + case PA_STREAM_FAILED: + default: + fprintf(stderr, "Stream error: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + quit(1); + } +} + +static void stream_suspended_callback(pa_stream *s, void *userdata) { + assert(s); + + if (verbose) { + if (pa_stream_is_suspended(s)) + fprintf(stderr, "Stream device suspended.\n"); + else + fprintf(stderr, "Stream device resumed.\n"); + } +} + +static void stream_moved_callback(pa_stream *s, void *userdata) { + assert(s); + + if (verbose) + fprintf(stderr, "Stream moved to device %s (%u, %ssuspended).\n", pa_stream_get_device_name(s), pa_stream_get_device_index(s), pa_stream_is_suspended(s) ? "" : "not "); +} + +/* This is called whenever the context status changes */ +static void context_state_callback(pa_context *c, void *userdata) { + assert(c); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: { + int r; + + assert(c); + assert(!stream); + + if (verbose) + fprintf(stderr, "Connection established.\n"); + + if (!(stream = pa_stream_new(c, stream_name, &sample_spec, channel_map_set ? &channel_map : NULL))) { + fprintf(stderr, "pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(c))); + goto fail; + } + + pa_stream_set_state_callback(stream, stream_state_callback, NULL); + pa_stream_set_write_callback(stream, stream_write_callback, NULL); + pa_stream_set_read_callback(stream, stream_read_callback, NULL); + pa_stream_set_suspended_callback(stream, stream_suspended_callback, NULL); + pa_stream_set_moved_callback(stream, stream_moved_callback, NULL); + + if (mode == PLAYBACK) { + pa_cvolume cv; + if ((r = pa_stream_connect_playback(stream, device, NULL, flags, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL)) < 0) { + fprintf(stderr, "pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(c))); + goto fail; + } + + } else { + if ((r = pa_stream_connect_record(stream, device, NULL, flags)) < 0) { + fprintf(stderr, "pa_stream_connect_record() failed: %s\n", pa_strerror(pa_context_errno(c))); + goto fail; + } + } + + break; + } + + case PA_CONTEXT_TERMINATED: + quit(0); + break; + + case PA_CONTEXT_FAILED: + default: + fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(c))); + goto fail; + } + + return; + +fail: + quit(1); + +} + +/* Connection draining complete */ +static void context_drain_complete(pa_context*c, void *userdata) { + pa_context_disconnect(c); +} + +/* Stream draining complete */ +static void stream_drain_complete(pa_stream*s, int success, void *userdata) { + pa_operation *o; + + if (!success) { + fprintf(stderr, "Failed to drain stream: %s\n", pa_strerror(pa_context_errno(context))); + quit(1); + } + + if (verbose) + fprintf(stderr, "Playback stream drained.\n"); + + pa_stream_disconnect(stream); + pa_stream_unref(stream); + stream = NULL; + + if (!(o = pa_context_drain(context, context_drain_complete, NULL))) + pa_context_disconnect(context); + else { + if (verbose) + fprintf(stderr, "Draining connection to server.\n"); + } +} + +/* New data on STDIN **/ +static void stdin_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + size_t l, w = 0; + ssize_t r; + + assert(a == mainloop_api); + assert(e); + assert(stdio_event == e); + + if (buffer) { + mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); + return; + } + + if (!stream || pa_stream_get_state(stream) != PA_STREAM_READY || !(l = w = pa_stream_writable_size(stream))) + l = 4096; + + buffer = pa_xmalloc(l); + + if ((r = read(fd, buffer, l)) <= 0) { + if (r == 0) { + if (verbose) + fprintf(stderr, "Got EOF.\n"); + + if (stream) { + pa_operation *o; + + if (!(o = pa_stream_drain(stream, stream_drain_complete, NULL))) { + fprintf(stderr, "pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + pa_operation_unref(o); + } else + quit(0); + + } else { + fprintf(stderr, "read() failed: %s\n", strerror(errno)); + quit(1); + } + + mainloop_api->io_free(stdio_event); + stdio_event = NULL; + return; + } + + buffer_length = r; + buffer_index = 0; + + if (w) + do_stream_write(w); +} + +/* Some data may be written to STDOUT */ +static void stdout_callback(pa_mainloop_api*a, pa_io_event *e, int fd, pa_io_event_flags_t f, void *userdata) { + ssize_t r; + + assert(a == mainloop_api); + assert(e); + assert(stdio_event == e); + + if (!buffer) { + mainloop_api->io_enable(stdio_event, PA_IO_EVENT_NULL); + return; + } + + assert(buffer_length); + + if ((r = write(fd, (uint8_t*) buffer+buffer_index, buffer_length)) <= 0) { + fprintf(stderr, "write() failed: %s\n", strerror(errno)); + quit(1); + + mainloop_api->io_free(stdio_event); + stdio_event = NULL; + return; + } + + buffer_length -= r; + buffer_index += r; + + if (!buffer_length) { + pa_xfree(buffer); + buffer = NULL; + buffer_length = buffer_index = 0; + } +} + +/* UNIX signal to quit recieved */ +static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { + if (verbose) + fprintf(stderr, "Got signal, exiting.\n"); + quit(0); +} + +/* Show the current latency */ +static void stream_update_timing_callback(pa_stream *s, int success, void *userdata) { + pa_usec_t latency, usec; + int negative = 0; + + assert(s); + + if (!success || + pa_stream_get_time(s, &usec) < 0 || + pa_stream_get_latency(s, &latency, &negative) < 0) { + fprintf(stderr, "Failed to get latency: %s\n", pa_strerror(pa_context_errno(context))); + quit(1); + return; + } + + fprintf(stderr, "Time: %0.3f sec; Latency: %0.0f usec. \r", + (float) usec / 1000000, + (float) latency * (negative?-1:1)); +} + +/* Someone requested that the latency is shown */ +static void sigusr1_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { + + if (!stream) + return; + + pa_operation_unref(pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL)); +} + +static void time_event_callback(pa_mainloop_api*m, pa_time_event *e, const struct timeval *tv, void *userdata) { + struct timeval next; + + if (stream && pa_stream_get_state(stream) == PA_STREAM_READY) { + pa_operation *o; + if (!(o = pa_stream_update_timing_info(stream, stream_update_timing_callback, NULL))) + fprintf(stderr, "pa_stream_update_timing_info() failed: %s\n", pa_strerror(pa_context_errno(context))); + else + pa_operation_unref(o); + } + + pa_gettimeofday(&next); + pa_timeval_add(&next, TIME_EVENT_USEC); + + m->time_restart(e, &next); +} + +static void help(const char *argv0) { + + printf("%s [options]\n\n" + " -h, --help Show this help\n" + " --version Show version\n\n" + " -r, --record Create a connection for recording\n" + " -p, --playback Create a connection for playback\n\n" + " -v, --verbose Enable verbose operations\n\n" + " -s, --server=SERVER The name of the server to connect to\n" + " -d, --device=DEVICE The name of the sink/source to connect to\n" + " -n, --client-name=NAME How to call this client on the server\n" + " --stream-name=NAME How to call this stream on the server\n" + " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n" + " --rate=SAMPLERATE The sample rate in Hz (defaults to 44100)\n" + " --format=SAMPLEFORMAT The sample type, one of s16le, s16be, u8, float32le,\n" + " float32be, ulaw, alaw (defaults to s16ne)\n" + " --channels=CHANNELS The number of channels, 1 for mono, 2 for stereo\n" + " (defaults to 2)\n" + " --channel-map=CHANNELMAP Channel map to use instead of the default\n" + " --fix-format Take the sample format from the sink the stream is\n" + " being connected to.\n" + " --fix-rate Take the sampling rate from the sink the stream is\n" + " being connected to.\n" + " --fix-channels Take the number of channels and the channel map\n" + " from the sink the stream is being connected to.\n" + " --no-remix Don't upmix or downmix channels.\n" + " --no-remap Map channels by index instead of name.\n" + , + argv0); +} + +enum { + ARG_VERSION = 256, + ARG_STREAM_NAME, + ARG_VOLUME, + ARG_SAMPLERATE, + ARG_SAMPLEFORMAT, + ARG_CHANNELS, + ARG_CHANNELMAP, + ARG_FIX_FORMAT, + ARG_FIX_RATE, + ARG_FIX_CHANNELS, + ARG_NO_REMAP, + ARG_NO_REMIX +}; + +int main(int argc, char *argv[]) { + pa_mainloop* m = NULL; + int ret = 1, r, c; + char *bn, *server = NULL; + pa_time_event *time_event = NULL; + + static const struct option long_options[] = { + {"record", 0, NULL, 'r'}, + {"playback", 0, NULL, 'p'}, + {"device", 1, NULL, 'd'}, + {"server", 1, NULL, 's'}, + {"client-name", 1, NULL, 'n'}, + {"stream-name", 1, NULL, ARG_STREAM_NAME}, + {"version", 0, NULL, ARG_VERSION}, + {"help", 0, NULL, 'h'}, + {"verbose", 0, NULL, 'v'}, + {"volume", 1, NULL, ARG_VOLUME}, + {"rate", 1, NULL, ARG_SAMPLERATE}, + {"format", 1, NULL, ARG_SAMPLEFORMAT}, + {"channels", 1, NULL, ARG_CHANNELS}, + {"channel-map", 1, NULL, ARG_CHANNELMAP}, + {"fix-format", 0, NULL, ARG_FIX_FORMAT}, + {"fix-rate", 0, NULL, ARG_FIX_RATE}, + {"fix-channels",0, NULL, ARG_FIX_CHANNELS}, + {"no-remap", 0, NULL, ARG_NO_REMAP}, + {"no-remix", 0, NULL, ARG_NO_REMIX}, + {NULL, 0, NULL, 0} + }; + + if (!(bn = strrchr(argv[0], '/'))) + bn = argv[0]; + else + bn++; + + if (strstr(bn, "rec") || strstr(bn, "mon")) + mode = RECORD; + else if (strstr(bn, "cat") || strstr(bn, "play")) + mode = PLAYBACK; + + while ((c = getopt_long(argc, argv, "rpd:s:n:hv", long_options, NULL)) != -1) { + + switch (c) { + case 'h' : + help(bn); + ret = 0; + goto quit; + + case ARG_VERSION: + printf("pacat "PACKAGE_VERSION"\nCompiled with libpulse %s\nLinked with libpulse %s\n", pa_get_headers_version(), pa_get_library_version()); + ret = 0; + goto quit; + + case 'r': + mode = RECORD; + break; + + case 'p': + mode = PLAYBACK; + break; + + case 'd': + pa_xfree(device); + device = pa_xstrdup(optarg); + break; + + case 's': + pa_xfree(server); + server = pa_xstrdup(optarg); + break; + + case 'n': + pa_xfree(client_name); + client_name = pa_xstrdup(optarg); + break; + + case ARG_STREAM_NAME: + pa_xfree(stream_name); + stream_name = pa_xstrdup(optarg); + break; + + case 'v': + verbose = 1; + break; + + case ARG_VOLUME: { + int v = atoi(optarg); + volume = v < 0 ? 0 : v; + break; + } + + case ARG_CHANNELS: + sample_spec.channels = atoi(optarg); + break; + + case ARG_SAMPLEFORMAT: + sample_spec.format = pa_parse_sample_format(optarg); + break; + + case ARG_SAMPLERATE: + sample_spec.rate = atoi(optarg); + break; + + case ARG_CHANNELMAP: + if (!pa_channel_map_parse(&channel_map, optarg)) { + fprintf(stderr, "Invalid channel map\n"); + goto quit; + } + + channel_map_set = 1; + break; + + case ARG_FIX_CHANNELS: + flags |= PA_STREAM_FIX_CHANNELS; + break; + + case ARG_FIX_RATE: + flags |= PA_STREAM_FIX_RATE; + break; + + case ARG_FIX_FORMAT: + flags |= PA_STREAM_FIX_FORMAT; + break; + + case ARG_NO_REMIX: + flags |= PA_STREAM_NO_REMIX_CHANNELS; + break; + + case ARG_NO_REMAP: + flags |= PA_STREAM_NO_REMAP_CHANNELS; + break; + + default: + goto quit; + } + } + + if (!pa_sample_spec_valid(&sample_spec)) { + fprintf(stderr, "Invalid sample specification\n"); + goto quit; + } + + if (channel_map_set && channel_map.channels != sample_spec.channels) { + fprintf(stderr, "Channel map doesn't match sample specification\n"); + goto quit; + } + + if (verbose) { + char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; + pa_sample_spec_snprint(t, sizeof(t), &sample_spec); + fprintf(stderr, "Opening a %s stream with sample specification '%s'.\n", mode == RECORD ? "recording" : "playback", t); + } + + if (!(optind >= argc)) { + if (optind+1 == argc) { + int fd; + + if ((fd = open(argv[optind], mode == PLAYBACK ? O_RDONLY : O_WRONLY|O_TRUNC|O_CREAT, 0666)) < 0) { + fprintf(stderr, "open(): %s\n", strerror(errno)); + goto quit; + } + + if (dup2(fd, mode == PLAYBACK ? 0 : 1) < 0) { + fprintf(stderr, "dup2(): %s\n", strerror(errno)); + goto quit; + } + + close(fd); + + if (!stream_name) + stream_name = pa_xstrdup(argv[optind]); + + } else { + fprintf(stderr, "Too many arguments.\n"); + goto quit; + } + } + + if (!client_name) + client_name = pa_xstrdup(bn); + + if (!stream_name) + stream_name = pa_xstrdup(client_name); + + /* Set up a new main loop */ + if (!(m = pa_mainloop_new())) { + fprintf(stderr, "pa_mainloop_new() failed.\n"); + goto quit; + } + + mainloop_api = pa_mainloop_get_api(m); + + r = pa_signal_init(mainloop_api); + assert(r == 0); + pa_signal_new(SIGINT, exit_signal_callback, NULL); + pa_signal_new(SIGTERM, exit_signal_callback, NULL); +#ifdef SIGUSR1 + pa_signal_new(SIGUSR1, sigusr1_signal_callback, NULL); +#endif +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + if (!(stdio_event = mainloop_api->io_new(mainloop_api, + mode == PLAYBACK ? STDIN_FILENO : STDOUT_FILENO, + mode == PLAYBACK ? PA_IO_EVENT_INPUT : PA_IO_EVENT_OUTPUT, + mode == PLAYBACK ? stdin_callback : stdout_callback, NULL))) { + fprintf(stderr, "io_new() failed.\n"); + goto quit; + } + + /* Create a new connection context */ + if (!(context = pa_context_new(mainloop_api, client_name))) { + fprintf(stderr, "pa_context_new() failed.\n"); + goto quit; + } + + pa_context_set_state_callback(context, context_state_callback, NULL); + + /* Connect the context */ + pa_context_connect(context, server, 0, NULL); + + if (verbose) { + struct timeval tv; + + pa_gettimeofday(&tv); + pa_timeval_add(&tv, TIME_EVENT_USEC); + + if (!(time_event = mainloop_api->time_new(mainloop_api, &tv, time_event_callback, NULL))) { + fprintf(stderr, "time_new() failed.\n"); + goto quit; + } + } + + /* Run the main loop */ + if (pa_mainloop_run(m, &ret) < 0) { + fprintf(stderr, "pa_mainloop_run() failed.\n"); + goto quit; + } + +quit: + if (stream) + pa_stream_unref(stream); + + if (context) + pa_context_unref(context); + + if (stdio_event) { + assert(mainloop_api); + mainloop_api->io_free(stdio_event); + } + + if (time_event) { + assert(mainloop_api); + mainloop_api->time_free(time_event); + } + + if (m) { + pa_signal_done(); + pa_mainloop_free(m); + } + + pa_xfree(buffer); + + pa_xfree(server); + pa_xfree(device); + pa_xfree(client_name); + pa_xfree(stream_name); + + return ret; +} diff --git a/src/utils/pacmd.c b/src/utils/pacmd.c new file mode 100644 index 00000000..daa6a96e --- /dev/null +++ b/src/utils/pacmd.c @@ -0,0 +1,188 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <assert.h> +#include <signal.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <sys/un.h> + +#include <pulse/error.h> +#include <pulse/util.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/pid.h> + +int main(PA_GCC_UNUSED int argc, PA_GCC_UNUSED char*argv[]) { + pid_t pid ; + int fd = -1; + int ret = 1, i; + struct sockaddr_un sa; + char ibuf[256], obuf[256]; + size_t ibuf_index, ibuf_length, obuf_index, obuf_length; + fd_set ifds, ofds; + + if (pa_pid_file_check_running(&pid, "pulseaudio") < 0) { + pa_log("no PulseAudio daemon running"); + goto fail; + } + + if ((fd = socket(PF_UNIX, SOCK_STREAM, 0)) < 0) { + pa_log("socket(PF_UNIX, SOCK_STREAM, 0): %s", strerror(errno)); + goto fail; + } + + memset(&sa, 0, sizeof(sa)); + sa.sun_family = AF_UNIX; + pa_runtime_path("cli", sa.sun_path, sizeof(sa.sun_path)); + + for (i = 0; i < 5; i++) { + int r; + + if ((r = connect(fd, (struct sockaddr*) &sa, sizeof(sa))) < 0 && (errno != ECONNREFUSED && errno != ENOENT)) { + pa_log("connect(): %s", strerror(errno)); + goto fail; + } + + if (r >= 0) + break; + + if (pa_pid_file_kill(SIGUSR2, NULL, "pulseaudio") < 0) { + pa_log("failed to kill PulseAudio daemon."); + goto fail; + } + + pa_msleep(300); + } + + if (i >= 5) { + pa_log("daemon not responding."); + goto fail; + } + + ibuf_index = ibuf_length = obuf_index = obuf_length = 0; + + + FD_ZERO(&ifds); + FD_SET(0, &ifds); + FD_SET(fd, &ifds); + + FD_ZERO(&ofds); + + for (;;) { + if (select(FD_SETSIZE, &ifds, &ofds, NULL, NULL) < 0) { + pa_log("select(): %s", strerror(errno)); + goto fail; + } + + if (FD_ISSET(0, &ifds)) { + ssize_t r; + assert(!ibuf_length); + + if ((r = read(0, ibuf, sizeof(ibuf))) <= 0) { + if (r == 0) + break; + + pa_log("read(): %s", strerror(errno)); + goto fail; + } + + ibuf_length = (size_t) r; + ibuf_index = 0; + } + + if (FD_ISSET(fd, &ifds)) { + ssize_t r; + assert(!obuf_length); + + if ((r = read(fd, obuf, sizeof(obuf))) <= 0) { + if (r == 0) + break; + + pa_log("read(): %s", strerror(errno)); + goto fail; + } + + obuf_length = (size_t) r; + obuf_index = 0; + } + + if (FD_ISSET(1, &ofds)) { + ssize_t r; + assert(obuf_length); + + if ((r = write(1, obuf + obuf_index, obuf_length)) < 0) { + pa_log("write(): %s", strerror(errno)); + goto fail; + } + + obuf_length -= (size_t) r; + obuf_index += obuf_index; + + } + + if (FD_ISSET(fd, &ofds)) { + ssize_t r; + assert(ibuf_length); + + if ((r = write(fd, ibuf + ibuf_index, ibuf_length)) < 0) { + pa_log("write(): %s", strerror(errno)); + goto fail; + } + + ibuf_length -= (size_t) r; + ibuf_index += obuf_index; + + } + + FD_ZERO(&ifds); + FD_ZERO(&ofds); + + if (obuf_length <= 0) + FD_SET(fd, &ifds); + else + FD_SET(1, &ofds); + + if (ibuf_length <= 0) + FD_SET(0, &ifds); + else + FD_SET(fd, &ofds); + } + + + ret = 0; + +fail: + if (fd >= 0) + close(fd); + + return ret; +} diff --git a/src/utils/pactl.c b/src/utils/pactl.c new file mode 100644 index 00000000..4381d9d2 --- /dev/null +++ b/src/utils/pactl.c @@ -0,0 +1,930 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <signal.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <getopt.h> + +#include <sndfile.h> + +#include <pulse/pulseaudio.h> +#include <pulsecore/core-util.h> + +#if PA_API_VERSION < 10 +#error Invalid PulseAudio API version +#endif + +#define BUFSIZE 1024 + +static pa_context *context = NULL; +static pa_mainloop_api *mainloop_api = NULL; + +static char *device = NULL, *sample_name = NULL, *sink_name = NULL, *source_name = NULL, *module_name = NULL, *module_args = NULL; +static uint32_t sink_input_idx = PA_INVALID_INDEX, source_output_idx = PA_INVALID_INDEX; +static uint32_t module_index; +static int suspend; + +static SNDFILE *sndfile = NULL; +static pa_stream *sample_stream = NULL; +static pa_sample_spec sample_spec; +static size_t sample_length = 0; + +static int actions = 1; + +static int nl = 0; + +static enum { + NONE, + EXIT, + STAT, + UPLOAD_SAMPLE, + PLAY_SAMPLE, + REMOVE_SAMPLE, + LIST, + MOVE_SINK_INPUT, + MOVE_SOURCE_OUTPUT, + LOAD_MODULE, + UNLOAD_MODULE, + SUSPEND_SINK, + SUSPEND_SOURCE, +} action = NONE; + +static void quit(int ret) { + assert(mainloop_api); + mainloop_api->quit(mainloop_api, ret); +} + + +static void context_drain_complete(pa_context *c, void *userdata) { + pa_context_disconnect(c); +} + +static void drain(void) { + pa_operation *o; + if (!(o = pa_context_drain(context, context_drain_complete, NULL))) + pa_context_disconnect(context); + else + pa_operation_unref(o); +} + + +static void complete_action(void) { + assert(actions > 0); + + if (!(--actions)) + drain(); +} + +static void stat_callback(pa_context *c, const pa_stat_info *i, void *userdata) { + char s[128]; + if (!i) { + fprintf(stderr, "Failed to get statistics: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + pa_bytes_snprint(s, sizeof(s), i->memblock_total_size); + printf("Currently in use: %u blocks containing %s bytes total.\n", i->memblock_total, s); + + pa_bytes_snprint(s, sizeof(s), i->memblock_allocated_size); + printf("Allocated during whole lifetime: %u blocks containing %s bytes total.\n", i->memblock_allocated, s); + + pa_bytes_snprint(s, sizeof(s), i->scache_size); + printf("Sample cache size: %s\n", s); + + complete_action(); +} + +static void get_server_info_callback(pa_context *c, const pa_server_info *i, void *useerdata) { + char s[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + if (!i) { + fprintf(stderr, "Failed to get server information: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec); + + printf("User name: %s\n" + "Host Name: %s\n" + "Server Name: %s\n" + "Server Version: %s\n" + "Default Sample Specification: %s\n" + "Default Sink: %s\n" + "Default Source: %s\n" + "Cookie: %08x\n", + i->user_name, + i->host_name, + i->server_name, + i->server_version, + s, + i->default_sink_name, + i->default_source_name, + i->cookie); + + complete_action(); +} + +static void get_sink_info_callback(pa_context *c, const pa_sink_info *i, int is_last, void *userdata) { + char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + if (is_last < 0) { + fprintf(stderr, "Failed to get sink information: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (is_last) { + complete_action(); + return; + } + + assert(i); + + if (nl) + printf("\n"); + nl = 1; + + printf("*** Sink #%u ***\n" + "Name: %s\n" + "Driver: %s\n" + "Description: %s\n" + "Sample Specification: %s\n" + "Channel Map: %s\n" + "Owner Module: %u\n" + "Volume: %s\n" + "Monitor Source: %u\n" + "Latency: %0.0f usec\n" + "Flags: %s%s%s\n", + i->index, + i->name, + i->driver, + i->description, + pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + i->owner_module, + i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume), + i->monitor_source, + (double) i->latency, + i->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + i->flags & PA_SINK_LATENCY ? "LATENCY " : "", + i->flags & PA_SINK_HARDWARE ? "HARDWARE" : ""); + +} + +static void get_source_info_callback(pa_context *c, const pa_source_info *i, int is_last, void *userdata) { + char s[PA_SAMPLE_SPEC_SNPRINT_MAX], t[32], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + if (is_last < 0) { + fprintf(stderr, "Failed to get source information: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (is_last) { + complete_action(); + return; + } + + assert(i); + + if (nl) + printf("\n"); + nl = 1; + + snprintf(t, sizeof(t), "%u", i->monitor_of_sink); + + printf("*** Source #%u ***\n" + "Name: %s\n" + "Driver: %s\n" + "Description: %s\n" + "Sample Specification: %s\n" + "Channel Map: %s\n" + "Owner Module: %u\n" + "Volume: %s\n" + "Monitor of Sink: %s\n" + "Latency: %0.0f usec\n" + "Flags: %s%s%s\n", + i->index, + i->name, + i->driver, + i->description, + pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + i->owner_module, + i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume), + i->monitor_of_sink != PA_INVALID_INDEX ? t : "no", + (double) i->latency, + i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", + i->flags & PA_SOURCE_HARDWARE ? "HARDWARE" : ""); + +} + +static void get_module_info_callback(pa_context *c, const pa_module_info *i, int is_last, void *userdata) { + char t[32]; + + if (is_last < 0) { + fprintf(stderr, "Failed to get module information: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (is_last) { + complete_action(); + return; + } + + assert(i); + + if (nl) + printf("\n"); + nl = 1; + + snprintf(t, sizeof(t), "%u", i->n_used); + + printf("*** Module #%u ***\n" + "Name: %s\n" + "Argument: %s\n" + "Usage counter: %s\n" + "Auto unload: %s\n", + i->index, + i->name, + i->argument ? i->argument : "", + i->n_used != PA_INVALID_INDEX ? t : "n/a", + i->auto_unload ? "yes" : "no"); +} + +static void get_client_info_callback(pa_context *c, const pa_client_info *i, int is_last, void *userdata) { + char t[32]; + + if (is_last < 0) { + fprintf(stderr, "Failed to get client information: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (is_last) { + complete_action(); + return; + } + + assert(i); + + if (nl) + printf("\n"); + nl = 1; + + snprintf(t, sizeof(t), "%u", i->owner_module); + + printf("*** Client #%u ***\n" + "Name: %s\n" + "Driver: %s\n" + "Owner Module: %s\n", + i->index, + i->name, + i->driver, + i->owner_module != PA_INVALID_INDEX ? t : "n/a"); +} + +static void get_sink_input_info_callback(pa_context *c, const pa_sink_input_info *i, int is_last, void *userdata) { + char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + if (is_last < 0) { + fprintf(stderr, "Failed to get sink input information: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (is_last) { + complete_action(); + return; + } + + assert(i); + + if (nl) + printf("\n"); + nl = 1; + + snprintf(t, sizeof(t), "%u", i->owner_module); + snprintf(k, sizeof(k), "%u", i->client); + + printf("*** Sink Input #%u ***\n" + "Name: %s\n" + "Driver: %s\n" + "Owner Module: %s\n" + "Client: %s\n" + "Sink: %u\n" + "Sample Specification: %s\n" + "Channel Map: %s\n" + "Volume: %s\n" + "Buffer Latency: %0.0f usec\n" + "Sink Latency: %0.0f usec\n" + "Resample method: %s\n", + i->index, + i->name, + i->driver, + i->owner_module != PA_INVALID_INDEX ? t : "n/a", + i->client != PA_INVALID_INDEX ? k : "n/a", + i->sink, + pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume), + (double) i->buffer_usec, + (double) i->sink_usec, + i->resample_method ? i->resample_method : "n/a"); +} + + +static void get_source_output_info_callback(pa_context *c, const pa_source_output_info *i, int is_last, void *userdata) { + char t[32], k[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + if (is_last < 0) { + fprintf(stderr, "Failed to get source output information: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (is_last) { + complete_action(); + return; + } + + assert(i); + + if (nl) + printf("\n"); + nl = 1; + + + snprintf(t, sizeof(t), "%u", i->owner_module); + snprintf(k, sizeof(k), "%u", i->client); + + printf("*** Source Output #%u ***\n" + "Name: %s\n" + "Driver: %s\n" + "Owner Module: %s\n" + "Client: %s\n" + "Source: %u\n" + "Sample Specification: %s\n" + "Channel Map: %s\n" + "Buffer Latency: %0.0f usec\n" + "Source Latency: %0.0f usec\n" + "Resample method: %s\n", + i->index, + i->name, + i->driver, + i->owner_module != PA_INVALID_INDEX ? t : "n/a", + i->client != PA_INVALID_INDEX ? k : "n/a", + i->source, + pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + (double) i->buffer_usec, + (double) i->source_usec, + i->resample_method ? i->resample_method : "n/a"); +} + +static void get_sample_info_callback(pa_context *c, const pa_sample_info *i, int is_last, void *userdata) { + char t[32], s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + + if (is_last < 0) { + fprintf(stderr, "Failed to get sample information: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (is_last) { + complete_action(); + return; + } + + assert(i); + + if (nl) + printf("\n"); + nl = 1; + + + pa_bytes_snprint(t, sizeof(t), i->bytes); + + printf("*** Sample #%u ***\n" + "Name: %s\n" + "Volume: %s\n" + "Sample Specification: %s\n" + "Channel Map: %s\n" + "Duration: %0.1fs\n" + "Size: %s\n" + "Lazy: %s\n" + "Filename: %s\n", + i->index, + i->name, + pa_cvolume_snprint(cv, sizeof(cv), &i->volume), + pa_sample_spec_valid(&i->sample_spec) ? pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec) : "n/a", + pa_sample_spec_valid(&i->sample_spec) ? pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map) : "n/a", + (double) i->duration/1000000, + t, + i->lazy ? "yes" : "no", + i->filename ? i->filename : "n/a"); +} + +static void get_autoload_info_callback(pa_context *c, const pa_autoload_info *i, int is_last, void *userdata) { + if (is_last < 0) { + fprintf(stderr, "Failed to get autoload information: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (is_last) { + complete_action(); + return; + } + + assert(i); + + if (nl) + printf("\n"); + nl = 1; + + printf("*** Autoload Entry #%u ***\n" + "Name: %s\n" + "Type: %s\n" + "Module: %s\n" + "Argument: %s\n", + i->index, + i->name, + i->type == PA_AUTOLOAD_SINK ? "sink" : "source", + i->module, + i->argument); +} + +static void simple_callback(pa_context *c, int success, void *userdata) { + if (!success) { + fprintf(stderr, "Failure: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + complete_action(); +} + +static void index_callback(pa_context *c, uint32_t idx, void *userdata) { + if (idx == PA_INVALID_INDEX) { + fprintf(stderr, "Failure: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + printf("%u\n", idx); + + complete_action(); +} + +static void stream_state_callback(pa_stream *s, void *userdata) { + assert(s); + + switch (pa_stream_get_state(s)) { + case PA_STREAM_CREATING: + case PA_STREAM_READY: + break; + + case PA_STREAM_TERMINATED: + drain(); + break; + + case PA_STREAM_FAILED: + default: + fprintf(stderr, "Failed to upload sample: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + quit(1); + } +} + +static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { + sf_count_t l; + float *d; + assert(s && length && sndfile); + + d = pa_xmalloc(length); + + assert(sample_length >= length); + l = length/pa_frame_size(&sample_spec); + + if ((sf_readf_float(sndfile, d, l)) != l) { + pa_xfree(d); + fprintf(stderr, "Premature end of file\n"); + quit(1); + } + + pa_stream_write(s, d, length, pa_xfree, 0, PA_SEEK_RELATIVE); + + sample_length -= length; + + if (sample_length <= 0) { + pa_stream_set_write_callback(sample_stream, NULL, NULL); + pa_stream_finish_upload(sample_stream); + } +} + +static void context_state_callback(pa_context *c, void *userdata) { + assert(c); + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + switch (action) { + case STAT: + actions = 2; + pa_operation_unref(pa_context_stat(c, stat_callback, NULL)); + pa_operation_unref(pa_context_get_server_info(c, get_server_info_callback, NULL)); + break; + + case PLAY_SAMPLE: + pa_operation_unref(pa_context_play_sample(c, sample_name, device, PA_VOLUME_NORM, simple_callback, NULL)); + break; + + case REMOVE_SAMPLE: + pa_operation_unref(pa_context_remove_sample(c, sample_name, simple_callback, NULL)); + break; + + case UPLOAD_SAMPLE: + sample_stream = pa_stream_new(c, sample_name, &sample_spec, NULL); + assert(sample_stream); + + pa_stream_set_state_callback(sample_stream, stream_state_callback, NULL); + pa_stream_set_write_callback(sample_stream, stream_write_callback, NULL); + pa_stream_connect_upload(sample_stream, sample_length); + break; + + case EXIT: + pa_operation_unref(pa_context_exit_daemon(c, NULL, NULL)); + drain(); + + case LIST: + actions = 8; + pa_operation_unref(pa_context_get_module_info_list(c, get_module_info_callback, NULL)); + pa_operation_unref(pa_context_get_sink_info_list(c, get_sink_info_callback, NULL)); + pa_operation_unref(pa_context_get_source_info_list(c, get_source_info_callback, NULL)); + pa_operation_unref(pa_context_get_sink_input_info_list(c, get_sink_input_info_callback, NULL)); + pa_operation_unref(pa_context_get_source_output_info_list(c, get_source_output_info_callback, NULL)); + pa_operation_unref(pa_context_get_client_info_list(c, get_client_info_callback, NULL)); + pa_operation_unref(pa_context_get_sample_info_list(c, get_sample_info_callback, NULL)); + pa_operation_unref(pa_context_get_autoload_info_list(c, get_autoload_info_callback, NULL)); + break; + + case MOVE_SINK_INPUT: + pa_operation_unref(pa_context_move_sink_input_by_name(c, sink_input_idx, sink_name, simple_callback, NULL)); + break; + + case MOVE_SOURCE_OUTPUT: + pa_operation_unref(pa_context_move_source_output_by_name(c, source_output_idx, source_name, simple_callback, NULL)); + break; + + case LOAD_MODULE: + pa_operation_unref(pa_context_load_module(c, module_name, module_args, index_callback, NULL)); + break; + + case UNLOAD_MODULE: + pa_operation_unref(pa_context_unload_module(c, module_index, simple_callback, NULL)); + break; + + case SUSPEND_SINK: + if (sink_name) + pa_operation_unref(pa_context_suspend_sink_by_name(c, sink_name, suspend, simple_callback, NULL)); + else + pa_operation_unref(pa_context_suspend_sink_by_index(c, PA_INVALID_INDEX, suspend, simple_callback, NULL)); + break; + + case SUSPEND_SOURCE: + if (source_name) + pa_operation_unref(pa_context_suspend_source_by_name(c, source_name, suspend, simple_callback, NULL)); + else + pa_operation_unref(pa_context_suspend_source_by_index(c, PA_INVALID_INDEX, suspend, simple_callback, NULL)); + break; + + default: + assert(0); + } + break; + + case PA_CONTEXT_TERMINATED: + quit(0); + break; + + case PA_CONTEXT_FAILED: + default: + fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + } +} + +static void exit_signal_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) { + fprintf(stderr, "Got SIGINT, exiting.\n"); + quit(0); +} + +static void help(const char *argv0) { + + printf("%s [options] stat\n" + "%s [options] list\n" + "%s [options] exit\n" + "%s [options] upload-sample FILENAME [NAME]\n" + "%s [options] play-sample NAME [SINK]\n" + "%s [options] remove-sample NAME\n" + "%s [options] move-sink-input ID SINK\n" + "%s [options] move-source-output ID SOURCE\n" + "%s [options] load-module NAME [ARGS ...]\n" + "%s [options] unload-module ID\n" + "%s [options] suspend-sink [SINK] 1|0\n" + "%s [options] suspend-source [SOURCE] 1|0\n\n" + " -h, --help Show this help\n" + " --version Show version\n\n" + " -s, --server=SERVER The name of the server to connect to\n" + " -n, --client-name=NAME How to call this client on the server\n", + argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0, argv0); +} + +enum { ARG_VERSION = 256 }; + +int main(int argc, char *argv[]) { + pa_mainloop* m = NULL; + char tmp[PATH_MAX]; + int ret = 1, r, c; + char *server = NULL, *client_name = NULL, *bn; + + static const struct option long_options[] = { + {"server", 1, NULL, 's'}, + {"client-name", 1, NULL, 'n'}, + {"version", 0, NULL, ARG_VERSION}, + {"help", 0, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if (!(bn = strrchr(argv[0], '/'))) + bn = argv[0]; + else + bn++; + + while ((c = getopt_long(argc, argv, "s:n:h", long_options, NULL)) != -1) { + switch (c) { + case 'h' : + help(bn); + ret = 0; + goto quit; + + case ARG_VERSION: + printf("pactl "PACKAGE_VERSION"\nCompiled with libpulse %s\nLinked with libpulse %s\n", pa_get_headers_version(), pa_get_library_version()); + ret = 0; + goto quit; + + case 's': + pa_xfree(server); + server = pa_xstrdup(optarg); + break; + + case 'n': + pa_xfree(client_name); + client_name = pa_xstrdup(optarg); + break; + + default: + goto quit; + } + } + + if (!client_name) + client_name = pa_xstrdup(bn); + + if (optind < argc) { + if (!strcmp(argv[optind], "stat")) + action = STAT; + else if (!strcmp(argv[optind], "exit")) + action = EXIT; + else if (!strcmp(argv[optind], "list")) + action = LIST; + else if (!strcmp(argv[optind], "upload-sample")) { + struct SF_INFO sfinfo; + action = UPLOAD_SAMPLE; + + if (optind+1 >= argc) { + fprintf(stderr, "Please specify a sample file to load\n"); + goto quit; + } + + if (optind+2 < argc) + sample_name = pa_xstrdup(argv[optind+2]); + else { + char *f = strrchr(argv[optind+1], '/'); + size_t n; + if (f) + f++; + else + f = argv[optind]; + + n = strcspn(f, "."); + strncpy(tmp, f, n); + tmp[n] = 0; + sample_name = pa_xstrdup(tmp); + } + + memset(&sfinfo, 0, sizeof(sfinfo)); + if (!(sndfile = sf_open(argv[optind+1], SFM_READ, &sfinfo))) { + fprintf(stderr, "Failed to open sound file.\n"); + goto quit; + } + + sample_spec.format = PA_SAMPLE_FLOAT32; + sample_spec.rate = sfinfo.samplerate; + sample_spec.channels = sfinfo.channels; + + sample_length = sfinfo.frames*pa_frame_size(&sample_spec); + } else if (!strcmp(argv[optind], "play-sample")) { + action = PLAY_SAMPLE; + if (argc != optind+2 && argc != optind+3) { + fprintf(stderr, "You have to specify a sample name to play\n"); + goto quit; + } + + sample_name = pa_xstrdup(argv[optind+1]); + + if (optind+2 < argc) + device = pa_xstrdup(argv[optind+2]); + + } else if (!strcmp(argv[optind], "remove-sample")) { + action = REMOVE_SAMPLE; + if (argc != optind+2) { + fprintf(stderr, "You have to specify a sample name to remove\n"); + goto quit; + } + + sample_name = pa_xstrdup(argv[optind+1]); + } else if (!strcmp(argv[optind], "move-sink-input")) { + action = MOVE_SINK_INPUT; + if (argc != optind+3) { + fprintf(stderr, "You have to specify a sink input index and a sink\n"); + goto quit; + } + + sink_input_idx = atoi(argv[optind+1]); + sink_name = pa_xstrdup(argv[optind+2]); + } else if (!strcmp(argv[optind], "move-source-output")) { + action = MOVE_SOURCE_OUTPUT; + if (argc != optind+3) { + fprintf(stderr, "You have to specify a source output index and a source\n"); + goto quit; + } + + source_output_idx = atoi(argv[optind+1]); + source_name = pa_xstrdup(argv[optind+2]); + } else if (!strcmp(argv[optind], "load-module")) { + int i; + size_t n = 0; + char *p; + + action = LOAD_MODULE; + + if (argc <= optind+1) { + fprintf(stderr, "You have to specify a module name and arguments.\n"); + goto quit; + } + + module_name = argv[optind+1]; + + for (i = optind+2; i < argc; i++) + n += strlen(argv[i])+1; + + if (n > 0) { + p = module_args = pa_xnew0(char, n); + + for (i = optind+2; i < argc; i++) + p += sprintf(p, "%s%s", p == module_args ? "" : " ", argv[i]); + } + + } else if (!strcmp(argv[optind], "unload-module")) { + action = UNLOAD_MODULE; + + if (argc != optind+2) { + fprintf(stderr, "You have to specify a module index\n"); + goto quit; + } + + module_index = atoi(argv[optind+1]); + + } else if (!strcmp(argv[optind], "suspend-sink")) { + action = SUSPEND_SINK; + + if (argc > optind+3 || optind+1 >= argc) { + fprintf(stderr, "You may not specify more than one sink. You have to specify at least one boolean value.\n"); + goto quit; + } + + suspend = pa_parse_boolean(argv[argc-1]); + + if (argc > optind+2) + sink_name = pa_xstrdup(argv[optind+1]); + + } else if (!strcmp(argv[optind], "suspend-source")) { + action = SUSPEND_SOURCE; + + if (argc > optind+3 || optind+1 >= argc) { + fprintf(stderr, "You may not specify more than one source. You have to specify at least one boolean value.\n"); + goto quit; + } + + suspend = pa_parse_boolean(argv[argc-1]); + + if (argc > optind+2) + source_name = pa_xstrdup(argv[optind+1]); + } + } + + if (action == NONE) { + fprintf(stderr, "No valid command specified.\n"); + goto quit; + } + + if (!(m = pa_mainloop_new())) { + fprintf(stderr, "pa_mainloop_new() failed.\n"); + goto quit; + } + + mainloop_api = pa_mainloop_get_api(m); + + r = pa_signal_init(mainloop_api); + assert(r == 0); + pa_signal_new(SIGINT, exit_signal_callback, NULL); +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + if (!(context = pa_context_new(mainloop_api, client_name))) { + fprintf(stderr, "pa_context_new() failed.\n"); + goto quit; + } + + pa_context_set_state_callback(context, context_state_callback, NULL); + pa_context_connect(context, server, 0, NULL); + + if (pa_mainloop_run(m, &ret) < 0) { + fprintf(stderr, "pa_mainloop_run() failed.\n"); + goto quit; + } + +quit: + if (sample_stream) + pa_stream_unref(sample_stream); + + if (context) + pa_context_unref(context); + + if (m) { + pa_signal_done(); + pa_mainloop_free(m); + } + + if (sndfile) + sf_close(sndfile); + + pa_xfree(server); + pa_xfree(device); + pa_xfree(sample_name); + pa_xfree(sink_name); + pa_xfree(source_name); + pa_xfree(module_args); + pa_xfree(client_name); + + return ret; +} diff --git a/src/utils/padsp b/src/utils/padsp new file mode 100755 index 00000000..c70c3af7 --- /dev/null +++ b/src/utils/padsp @@ -0,0 +1,88 @@ +#!/bin/sh + +# $Id$ +# +# This file is part of PulseAudio. +# +# Copyright 2006 Lennart Poettering +# Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB +# +# 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. + +while getopts 'hs:n:m:MSDd' param ; do + case $param in + s) + PULSE_SERVER="$OPTARG" + export PULSE_SERVER + ;; + n) + PADSP_CLIENT_NAME="$OPTARG" + export PADSP_CLIENT_NAME + ;; + m) + PADSP_STREAM_NAME="$OPTARG" + export PADSP_STREAM_NAME + ;; + M) + PADSP_NO_MIXER=1 + export PADSP_NO_MIXER + ;; + S) + PADSP_NO_SNDSTAT=1 + export PADSP_NO_SNDSTAT + ;; + D) + PADSP_NO_DSP=1 + export PADSP_NO_DSP + ;; + d) + if [ x"$PADSP_DEBUG" = x ]; then + PADSP_DEBUG=1 + else + PADSP_DEBUG=$(( $PADSP_DEBUG + 1 )) + fi + export PADSP_DEBUG + ;; + *) + echo "$0 - redirect OSS audio devices to PulseAudio" + echo " " + echo "$0 [options] application [arguments]" + echo " " + echo "options:" + echo " -h show brief help" + echo " -s <host>[:<port>] contact a specific PulseAudio server" + echo " -n <name> client name to report to the server" + echo " -m <name> stream name to report to the server" + echo " -M disable /dev/mixer emulation" + echo " -S disable /dev/sndstat emulation" + echo " -D disable /dev/dsp emulation" + echo " -d enable debug output" + exit 0 + ;; + esac +done + +shift $(( $OPTIND - 1 )) + +if [ x"$LD_PRELOAD" = x ] ; then + LD_PRELOAD="libpulsedsp.so" +else + LD_PRELOAD="$LD_PRELOAD libpulsedsp.so" +fi + +export LD_PRELOAD + +exec "$@" diff --git a/src/utils/padsp.c b/src/utils/padsp.c new file mode 100644 index 00000000..cb57ff8a --- /dev/null +++ b/src/utils/padsp.c @@ -0,0 +1,2664 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2006 Lennart Poettering + Copyright 2006-2007 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 + +#ifdef _FILE_OFFSET_BITS +#undef _FILE_OFFSET_BITS +#endif + +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE 1 +#endif + +#include <sys/soundcard.h> +#include <sys/ioctl.h> +#include <pthread.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <dlfcn.h> +#include <errno.h> +#include <fcntl.h> +#include <string.h> +#include <stdarg.h> +#include <stdio.h> +#include <signal.h> + +#ifdef __linux__ +#include <linux/sockios.h> +#endif + +#include <pulse/pulseaudio.h> +#include <pulsecore/llist.h> +#include <pulsecore/gccmacro.h> + +/* On some systems SIOCINQ isn't defined, but FIONREAD is just an alias */ +#if !defined(SIOCINQ) && defined(FIONREAD) +# define SIOCINQ FIONREAD +#endif + +/* make sure gcc doesn't redefine open and friends as macros */ +#undef open +#undef open64 + +typedef enum { + FD_INFO_MIXER, + FD_INFO_STREAM, +} fd_info_type_t; + +typedef struct fd_info fd_info; + +struct fd_info { + pthread_mutex_t mutex; + int ref; + int unusable; + + fd_info_type_t type; + int app_fd, thread_fd; + + pa_sample_spec sample_spec; + size_t fragment_size; + unsigned n_fragments; + + pa_threaded_mainloop *mainloop; + pa_context *context; + pa_stream *play_stream; + pa_stream *rec_stream; + int play_precork; + int rec_precork; + + pa_io_event *io_event; + pa_io_event_flags_t io_flags; + + void *buf; + size_t rec_offset; + + int operation_success; + + pa_cvolume sink_volume, source_volume; + uint32_t sink_index, source_index; + int volume_modify_count; + + int optr_n_blocks; + + PA_LLIST_FIELDS(fd_info); +}; + +static int dsp_drain(fd_info *i); +static void fd_info_remove_from_list(fd_info *i); + +static pthread_mutex_t fd_infos_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_mutex_t func_mutex = PTHREAD_MUTEX_INITIALIZER; + +static PA_LLIST_HEAD(fd_info, fd_infos) = NULL; + +static int (*_ioctl)(int, int, void*) = NULL; +static int (*_close)(int) = NULL; +static int (*_open)(const char *, int, mode_t) = NULL; +static FILE* (*_fopen)(const char *path, const char *mode) = NULL; +static int (*_stat)(const char *, struct stat *) = NULL; +#ifdef _STAT_VER +static int (*___xstat)(int, const char *, struct stat *) = NULL; +#endif +#ifdef HAVE_OPEN64 +static int (*_open64)(const char *, int, mode_t) = NULL; +static FILE* (*_fopen64)(const char *path, const char *mode) = NULL; +static int (*_stat64)(const char *, struct stat64 *) = NULL; +#ifdef _STAT_VER +static int (*___xstat64)(int, const char *, struct stat64 *) = NULL; +#endif +#endif +static int (*_fclose)(FILE *f) = NULL; +static int (*_access)(const char *, int) = NULL; + +/* dlsym() violates ISO C, so confide the breakage into this function to + * avoid warnings. */ +typedef void (*fnptr)(void); +static inline fnptr dlsym_fn(void *handle, const char *symbol) { + return (fnptr) (long) dlsym(handle, symbol); +} + +#define LOAD_IOCTL_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_ioctl) \ + _ioctl = (int (*)(int, int, void*)) dlsym_fn(RTLD_NEXT, "ioctl"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_OPEN_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_open) \ + _open = (int (*)(const char *, int, mode_t)) dlsym_fn(RTLD_NEXT, "open"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_OPEN64_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_open64) \ + _open64 = (int (*)(const char *, int, mode_t)) dlsym_fn(RTLD_NEXT, "open64"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_CLOSE_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_close) \ + _close = (int (*)(int)) dlsym_fn(RTLD_NEXT, "close"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_ACCESS_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_access) \ + _access = (int (*)(const char*, int)) dlsym_fn(RTLD_NEXT, "access"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_STAT_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_stat) \ + _stat = (int (*)(const char *, struct stat *)) dlsym_fn(RTLD_NEXT, "stat"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_STAT64_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_stat64) \ + _stat64 = (int (*)(const char *, struct stat64 *)) dlsym_fn(RTLD_NEXT, "stat64"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_XSTAT_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!___xstat) \ + ___xstat = (int (*)(int, const char *, struct stat *)) dlsym_fn(RTLD_NEXT, "__xstat"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_XSTAT64_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!___xstat64) \ + ___xstat64 = (int (*)(int, const char *, struct stat64 *)) dlsym_fn(RTLD_NEXT, "__xstat64"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_FOPEN_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_fopen) \ + _fopen = (FILE* (*)(const char *, const char*)) dlsym_fn(RTLD_NEXT, "fopen"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_FOPEN64_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_fopen64) \ + _fopen64 = (FILE* (*)(const char *, const char*)) dlsym_fn(RTLD_NEXT, "fopen64"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define LOAD_FCLOSE_FUNC() \ +do { \ + pthread_mutex_lock(&func_mutex); \ + if (!_fclose) \ + _fclose = (int (*)(FILE *)) dlsym_fn(RTLD_NEXT, "fclose"); \ + pthread_mutex_unlock(&func_mutex); \ +} while(0) + +#define CONTEXT_CHECK_DEAD_GOTO(i, label) do { \ +if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY) { \ + debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ + goto label; \ +} \ +} while(0) + +#define PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, label) do { \ +if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY || \ + !(i)->play_stream || pa_stream_get_state((i)->play_stream) != PA_STREAM_READY) { \ + debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ + goto label; \ +} \ +} while(0) + +#define RECORD_STREAM_CHECK_DEAD_GOTO(i, label) do { \ +if (!(i)->context || pa_context_get_state((i)->context) != PA_CONTEXT_READY || \ + !(i)->rec_stream || pa_stream_get_state((i)->rec_stream) != PA_STREAM_READY) { \ + debug(DEBUG_LEVEL_NORMAL, __FILE__": Not connected: %s\n", (i)->context ? pa_strerror(pa_context_errno((i)->context)) : "NULL"); \ + goto label; \ +} \ +} while(0) + +static void debug(int level, const char *format, ...) PA_GCC_PRINTF_ATTR(2,3); + +#define DEBUG_LEVEL_ALWAYS 0 +#define DEBUG_LEVEL_NORMAL 1 +#define DEBUG_LEVEL_VERBOSE 2 + +static void debug(int level, const char *format, ...) { + va_list ap; + const char *dlevel_s; + int dlevel; + + dlevel_s = getenv("PADSP_DEBUG"); + if (!dlevel_s) + return; + + dlevel = atoi(dlevel_s); + + if (dlevel < level) + return; + + va_start(ap, format); + vfprintf(stderr, format, ap); + va_end(ap); +} + +static int padsp_disabled(void) { + static int *sym; + static int sym_resolved = 0; + + /* If the current process has a symbol __padsp_disabled__ we use + * it to detect whether we should enable our stuff or not. A + * program needs to be compiled with -rdynamic for this to work! + * The symbol must be an int containing a three bit bitmask: bit 1 + * -> disable /dev/dsp emulation, bit 2 -> disable /dev/sndstat + * emulation, bit 3 -> disable /dev/mixer emulation. Hence a value + * of 7 disables padsp entirely. */ + + pthread_mutex_lock(&func_mutex); + if (!sym_resolved) { + sym = (int*) dlsym(RTLD_DEFAULT, "__padsp_disabled__"); + sym_resolved = 1; + + } + pthread_mutex_unlock(&func_mutex); + + if (!sym) + return 0; + + return *sym; +} + +static int dsp_cloak_enable(void) { + if (padsp_disabled() & 1) + return 0; + + if (getenv("PADSP_NO_DSP")) + return 0; + + return 1; +} + +static int sndstat_cloak_enable(void) { + if (padsp_disabled() & 2) + return 0; + + if (getenv("PADSP_NO_SNDSTAT")) + return 0; + + return 1; +} + +static int mixer_cloak_enable(void) { + if (padsp_disabled() & 4) + return 0; + + if (getenv("PADSP_NO_MIXER")) + return 0; + + return 1; +} +static pthread_key_t recursion_key; + +static void recursion_key_alloc(void) { + pthread_key_create(&recursion_key, NULL); +} + +static int function_enter(void) { + /* Avoid recursive calls */ + static pthread_once_t recursion_key_once = PTHREAD_ONCE_INIT; + pthread_once(&recursion_key_once, recursion_key_alloc); + + if (pthread_getspecific(recursion_key)) + return 0; + + pthread_setspecific(recursion_key, (void*) 1); + return 1; +} + +static void function_exit(void) { + pthread_setspecific(recursion_key, NULL); +} + +static void fd_info_free(fd_info *i) { + assert(i); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": freeing fd info (fd=%i)\n", i->app_fd); + + dsp_drain(i); + + if (i->mainloop) + pa_threaded_mainloop_stop(i->mainloop); + + if (i->play_stream) { + pa_stream_disconnect(i->play_stream); + pa_stream_unref(i->play_stream); + } + + if (i->rec_stream) { + pa_stream_disconnect(i->rec_stream); + pa_stream_unref(i->rec_stream); + } + + if (i->context) { + pa_context_disconnect(i->context); + pa_context_unref(i->context); + } + + if (i->mainloop) + pa_threaded_mainloop_free(i->mainloop); + + if (i->app_fd >= 0) { + LOAD_CLOSE_FUNC(); + _close(i->app_fd); + } + + if (i->thread_fd >= 0) { + LOAD_CLOSE_FUNC(); + _close(i->thread_fd); + } + + free(i->buf); + + pthread_mutex_destroy(&i->mutex); + free(i); +} + +static fd_info *fd_info_ref(fd_info *i) { + assert(i); + + pthread_mutex_lock(&i->mutex); + assert(i->ref >= 1); + i->ref++; + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": ref++, now %i\n", i->ref); + pthread_mutex_unlock(&i->mutex); + + return i; +} + +static void fd_info_unref(fd_info *i) { + int r; + pthread_mutex_lock(&i->mutex); + assert(i->ref >= 1); + r = --i->ref; + debug(DEBUG_LEVEL_VERBOSE, __FILE__": ref--, now %i\n", i->ref); + pthread_mutex_unlock(&i->mutex); + + if (r <= 0) + fd_info_free(i); +} + +static void context_state_cb(pa_context *c, void *userdata) { + fd_info *i = userdata; + assert(c); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_READY: + case PA_CONTEXT_TERMINATED: + case PA_CONTEXT_FAILED: + pa_threaded_mainloop_signal(i->mainloop, 0); + break; + + case PA_CONTEXT_UNCONNECTED: + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + } +} + +static void reset_params(fd_info *i) { + assert(i); + + i->sample_spec.format = PA_SAMPLE_U8; + i->sample_spec.channels = 1; + i->sample_spec.rate = 8000; + i->fragment_size = 0; + i->n_fragments = 0; +} + +static const char *client_name(char *buf, size_t n) { + char p[PATH_MAX]; + const char *e; + + if ((e = getenv("PADSP_CLIENT_NAME"))) + return e; + + if (pa_get_binary_name(p, sizeof(p))) + snprintf(buf, n, "OSS Emulation[%s]", p); + else + snprintf(buf, n, "OSS"); + + return buf; +} + +static const char *stream_name(void) { + const char *e; + + if ((e = getenv("PADSP_STREAM_NAME"))) + return e; + + return "Audio Stream"; +} + +static void atfork_prepare(void) { + fd_info *i; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_prepare() enter\n"); + + function_enter(); + + pthread_mutex_lock(&fd_infos_mutex); + + for (i = fd_infos; i; i = i->next) { + pthread_mutex_lock(&i->mutex); + pa_threaded_mainloop_lock(i->mainloop); + } + + pthread_mutex_lock(&func_mutex); + + + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_prepare() exit\n"); +} + +static void atfork_parent(void) { + fd_info *i; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_parent() enter\n"); + + pthread_mutex_unlock(&func_mutex); + + for (i = fd_infos; i; i = i->next) { + pa_threaded_mainloop_unlock(i->mainloop); + pthread_mutex_unlock(&i->mutex); + } + + pthread_mutex_unlock(&fd_infos_mutex); + + function_exit(); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_parent() exit\n"); +} + +static void atfork_child(void) { + fd_info *i; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_child() enter\n"); + + /* We do only the bare minimum to get all fds closed */ + pthread_mutex_init(&func_mutex, NULL); + pthread_mutex_init(&fd_infos_mutex, NULL); + + for (i = fd_infos; i; i = i->next) { + pthread_mutex_init(&i->mutex, NULL); + + if (i->context) { + pa_context_disconnect(i->context); + pa_context_unref(i->context); + i->context = NULL; + } + + if (i->play_stream) { + pa_stream_unref(i->play_stream); + i->play_stream = NULL; + } + + if (i->rec_stream) { + pa_stream_unref(i->rec_stream); + i->rec_stream = NULL; + } + + if (i->app_fd >= 0) { + close(i->app_fd); + i->app_fd = -1; + } + + if (i->thread_fd >= 0) { + close(i->thread_fd); + i->thread_fd = -1; + } + + i->unusable = 1; + } + + function_exit(); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": atfork_child() exit\n"); +} + +static void install_atfork(void) { + pthread_atfork(atfork_prepare, atfork_parent, atfork_child); +} + +static void stream_success_cb(pa_stream *s, int success, void *userdata) { + fd_info *i = userdata; + + assert(s); + assert(i); + + i->operation_success = success; + pa_threaded_mainloop_signal(i->mainloop, 0); +} + +static void context_success_cb(pa_context *c, int success, void *userdata) { + fd_info *i = userdata; + + assert(c); + assert(i); + + i->operation_success = success; + pa_threaded_mainloop_signal(i->mainloop, 0); +} + +static fd_info* fd_info_new(fd_info_type_t type, int *_errno) { + fd_info *i; + int sfds[2] = { -1, -1 }; + char name[64]; + static pthread_once_t install_atfork_once = PTHREAD_ONCE_INIT; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": fd_info_new()\n"); + + signal(SIGPIPE, SIG_IGN); /* Yes, ugly as hell */ + + pthread_once(&install_atfork_once, install_atfork); + + if (!(i = malloc(sizeof(fd_info)))) { + *_errno = ENOMEM; + goto fail; + } + + i->app_fd = i->thread_fd = -1; + i->type = type; + + i->mainloop = NULL; + i->context = NULL; + i->play_stream = NULL; + i->rec_stream = NULL; + i->play_precork = 0; + i->rec_precork = 0; + i->io_event = NULL; + i->io_flags = 0; + pthread_mutex_init(&i->mutex, NULL); + i->ref = 1; + i->buf = NULL; + i->rec_offset = 0; + i->unusable = 0; + pa_cvolume_reset(&i->sink_volume, 2); + pa_cvolume_reset(&i->source_volume, 2); + i->volume_modify_count = 0; + i->sink_index = (uint32_t) -1; + i->source_index = (uint32_t) -1; + i->optr_n_blocks = 0; + PA_LLIST_INIT(fd_info, i); + + reset_params(i); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sfds) < 0) { + *_errno = errno; + debug(DEBUG_LEVEL_NORMAL, __FILE__": socket() failed: %s\n", strerror(errno)); + goto fail; + } + + i->app_fd = sfds[0]; + i->thread_fd = sfds[1]; + + if (!(i->mainloop = pa_threaded_mainloop_new())) { + *_errno = EIO; + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_threaded_mainloop_new() failed\n"); + goto fail; + } + + if (!(i->context = pa_context_new(pa_threaded_mainloop_get_api(i->mainloop), client_name(name, sizeof(name))))) { + *_errno = EIO; + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_context_new() failed\n"); + goto fail; + } + + pa_context_set_state_callback(i->context, context_state_cb, i); + + if (pa_context_connect(i->context, NULL, 0, NULL) < 0) { + *_errno = ECONNREFUSED; + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + pa_threaded_mainloop_lock(i->mainloop); + + if (pa_threaded_mainloop_start(i->mainloop) < 0) { + *_errno = EIO; + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_threaded_mainloop_start() failed\n"); + goto unlock_and_fail; + } + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait(i->mainloop); + + if (pa_context_get_state(i->context) != PA_CONTEXT_READY) { + *_errno = ECONNREFUSED; + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_context_connect() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + goto unlock_and_fail; + } + + pa_threaded_mainloop_unlock(i->mainloop); + return i; + +unlock_and_fail: + + pa_threaded_mainloop_unlock(i->mainloop); + +fail: + + if (i) + fd_info_unref(i); + + return NULL; +} + +static void fd_info_add_to_list(fd_info *i) { + assert(i); + + pthread_mutex_lock(&fd_infos_mutex); + PA_LLIST_PREPEND(fd_info, fd_infos, i); + pthread_mutex_unlock(&fd_infos_mutex); + + fd_info_ref(i); +} + +static void fd_info_remove_from_list(fd_info *i) { + assert(i); + + pthread_mutex_lock(&fd_infos_mutex); + PA_LLIST_REMOVE(fd_info, fd_infos, i); + pthread_mutex_unlock(&fd_infos_mutex); + + fd_info_unref(i); +} + +static fd_info* fd_info_find(int fd) { + fd_info *i; + + pthread_mutex_lock(&fd_infos_mutex); + + for (i = fd_infos; i; i = i->next) + if (i->app_fd == fd && !i->unusable) { + fd_info_ref(i); + break; + } + + pthread_mutex_unlock(&fd_infos_mutex); + + return i; +} + +static void fix_metrics(fd_info *i) { + size_t fs; + char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + fs = pa_frame_size(&i->sample_spec); + + /* Don't fix things more than necessary */ + if ((i->fragment_size % fs) == 0 && + i->n_fragments >= 2 && + i->fragment_size > 0) + return; + + i->fragment_size = (i->fragment_size/fs)*fs; + + /* Number of fragments set? */ + if (i->n_fragments < 2) { + if (i->fragment_size > 0) { + i->n_fragments = pa_bytes_per_second(&i->sample_spec) / 2 / i->fragment_size; + if (i->n_fragments < 2) + i->n_fragments = 2; + } else + i->n_fragments = 12; + } + + /* Fragment size set? */ + if (i->fragment_size <= 0) { + i->fragment_size = pa_bytes_per_second(&i->sample_spec) / 2 / i->n_fragments; + if (i->fragment_size < 1024) + i->fragment_size = 1024; + } + + debug(DEBUG_LEVEL_NORMAL, __FILE__": sample spec: %s\n", pa_sample_spec_snprint(t, sizeof(t), &i->sample_spec)); + debug(DEBUG_LEVEL_NORMAL, __FILE__": fixated metrics to %i fragments, %li bytes each.\n", i->n_fragments, (long)i->fragment_size); +} + +static void stream_request_cb(pa_stream *s, size_t length, void *userdata) { + fd_info *i = userdata; + assert(s); + + if (i->io_event) { + pa_mainloop_api *api; + size_t n; + + api = pa_threaded_mainloop_get_api(i->mainloop); + + if (s == i->play_stream) { + n = pa_stream_writable_size(i->play_stream); + if (n == (size_t)-1) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_writable_size(): %s\n", + pa_strerror(pa_context_errno(i->context))); + } + + if (n >= i->fragment_size) + i->io_flags |= PA_IO_EVENT_INPUT; + else + i->io_flags &= ~PA_IO_EVENT_INPUT; + } + + if (s == i->rec_stream) { + n = pa_stream_readable_size(i->rec_stream); + if (n == (size_t)-1) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_readable_size(): %s\n", + pa_strerror(pa_context_errno(i->context))); + } + + if (n >= i->fragment_size) + i->io_flags |= PA_IO_EVENT_OUTPUT; + else + i->io_flags &= ~PA_IO_EVENT_OUTPUT; + } + + api->io_enable(i->io_event, i->io_flags); + } +} + +static void stream_latency_update_cb(pa_stream *s, void *userdata) { + fd_info *i = userdata; + assert(s); + + pa_threaded_mainloop_signal(i->mainloop, 0); +} + +static void fd_info_shutdown(fd_info *i) { + assert(i); + + if (i->io_event) { + pa_mainloop_api *api; + api = pa_threaded_mainloop_get_api(i->mainloop); + api->io_free(i->io_event); + i->io_event = NULL; + i->io_flags = 0; + } + + if (i->thread_fd >= 0) { + close(i->thread_fd); + i->thread_fd = -1; + } +} + +static int fd_info_copy_data(fd_info *i, int force) { + size_t n; + + if (!i->play_stream && !i->rec_stream) + return -1; + + if ((i->play_stream) && (pa_stream_get_state(i->play_stream) == PA_STREAM_READY)) { + n = pa_stream_writable_size(i->play_stream); + + if (n == (size_t)-1) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_writable_size(): %s\n", + pa_strerror(pa_context_errno(i->context))); + return -1; + } + + while (n >= i->fragment_size || force) { + ssize_t r; + + if (!i->buf) { + if (!(i->buf = malloc(i->fragment_size))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": malloc() failed.\n"); + return -1; + } + } + + if ((r = read(i->thread_fd, i->buf, i->fragment_size)) <= 0) { + + if (errno == EAGAIN) + break; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": read(): %s\n", r == 0 ? "EOF" : strerror(errno)); + return -1; + } + + if (pa_stream_write(i->play_stream, i->buf, r, free, 0, PA_SEEK_RELATIVE) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_write(): %s\n", pa_strerror(pa_context_errno(i->context))); + return -1; + } + + i->buf = NULL; + + assert(n >= (size_t) r); + n -= r; + } + + if (n >= i->fragment_size) + i->io_flags |= PA_IO_EVENT_INPUT; + else + i->io_flags &= ~PA_IO_EVENT_INPUT; + } + + if ((i->rec_stream) && (pa_stream_get_state(i->rec_stream) == PA_STREAM_READY)) { + n = pa_stream_readable_size(i->rec_stream); + + if (n == (size_t)-1) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_readable_size(): %s\n", + pa_strerror(pa_context_errno(i->context))); + return -1; + } + + while (n >= i->fragment_size || force) { + ssize_t r; + const void *data; + const char *buf; + size_t len; + + if (pa_stream_peek(i->rec_stream, &data, &len) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_peek(): %s\n", pa_strerror(pa_context_errno(i->context))); + return -1; + } + + if (!data) + break; + + buf = (const char*)data + i->rec_offset; + + if ((r = write(i->thread_fd, buf, len - i->rec_offset)) <= 0) { + + if (errno == EAGAIN) + break; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": write(): %s\n", strerror(errno)); + return -1; + } + + assert((size_t)r <= len - i->rec_offset); + i->rec_offset += r; + + if (i->rec_offset == len) { + if (pa_stream_drop(i->rec_stream) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_drop(): %s\n", pa_strerror(pa_context_errno(i->context))); + return -1; + } + i->rec_offset = 0; + } + + assert(n >= (size_t) r); + n -= r; + } + + if (n >= i->fragment_size) + i->io_flags |= PA_IO_EVENT_OUTPUT; + else + i->io_flags &= ~PA_IO_EVENT_OUTPUT; + } + + if (i->io_event) { + pa_mainloop_api *api; + + api = pa_threaded_mainloop_get_api(i->mainloop); + api->io_enable(i->io_event, i->io_flags); + } + + return 0; +} + +static void stream_state_cb(pa_stream *s, void * userdata) { + fd_info *i = userdata; + assert(s); + + switch (pa_stream_get_state(s)) { + + case PA_STREAM_READY: + debug(DEBUG_LEVEL_NORMAL, __FILE__": stream established.\n"); + break; + + case PA_STREAM_FAILED: + if (s == i->play_stream) { + debug(DEBUG_LEVEL_NORMAL, + __FILE__": pa_stream_connect_playback() failed: %s\n", + pa_strerror(pa_context_errno(i->context))); + pa_stream_unref(i->play_stream); + i->play_stream = NULL; + } else if (s == i->rec_stream) { + debug(DEBUG_LEVEL_NORMAL, + __FILE__": pa_stream_connect_record() failed: %s\n", + pa_strerror(pa_context_errno(i->context))); + pa_stream_unref(i->rec_stream); + i->rec_stream = NULL; + } + fd_info_shutdown(i); + break; + + case PA_STREAM_TERMINATED: + case PA_STREAM_UNCONNECTED: + case PA_STREAM_CREATING: + break; + } +} + +static int create_playback_stream(fd_info *i) { + pa_buffer_attr attr; + int n, flags; + + assert(i); + + fix_metrics(i); + + if (!(i->play_stream = pa_stream_new(i->context, stream_name(), &i->sample_spec, NULL))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + pa_stream_set_state_callback(i->play_stream, stream_state_cb, i); + pa_stream_set_write_callback(i->play_stream, stream_request_cb, i); + pa_stream_set_latency_update_callback(i->play_stream, stream_latency_update_cb, i); + + memset(&attr, 0, sizeof(attr)); + attr.maxlength = i->fragment_size * (i->n_fragments+1); + attr.tlength = i->fragment_size * i->n_fragments; + attr.prebuf = i->fragment_size; + attr.minreq = i->fragment_size; + + flags = PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE; + if (i->play_precork) { + flags |= PA_STREAM_START_CORKED; + debug(DEBUG_LEVEL_NORMAL, __FILE__": creating stream corked\n"); + } + if (pa_stream_connect_playback(i->play_stream, NULL, &attr, flags, NULL, NULL) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_connect_playback() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + n = i->fragment_size; + setsockopt(i->app_fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)); + n = i->fragment_size; + setsockopt(i->thread_fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)); + + return 0; + +fail: + return -1; +} + +static int create_record_stream(fd_info *i) { + pa_buffer_attr attr; + int n, flags; + + assert(i); + + fix_metrics(i); + + if (!(i->rec_stream = pa_stream_new(i->context, stream_name(), &i->sample_spec, NULL))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_new() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + pa_stream_set_state_callback(i->rec_stream, stream_state_cb, i); + pa_stream_set_read_callback(i->rec_stream, stream_request_cb, i); + pa_stream_set_latency_update_callback(i->rec_stream, stream_latency_update_cb, i); + + memset(&attr, 0, sizeof(attr)); + attr.maxlength = i->fragment_size * (i->n_fragments+1); + attr.fragsize = i->fragment_size; + + flags = PA_STREAM_INTERPOLATE_TIMING|PA_STREAM_AUTO_TIMING_UPDATE; + if (i->rec_precork) { + flags |= PA_STREAM_START_CORKED; + debug(DEBUG_LEVEL_NORMAL, __FILE__": creating stream corked\n"); + } + if (pa_stream_connect_record(i->rec_stream, NULL, &attr, flags) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_connect_record() failed: %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + n = i->fragment_size; + setsockopt(i->app_fd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n)); + n = i->fragment_size; + setsockopt(i->thread_fd, SOL_SOCKET, SO_SNDBUF, &n, sizeof(n)); + + return 0; + +fail: + return -1; +} + +static void free_streams(fd_info *i) { + assert(i); + + if (i->play_stream) { + pa_stream_disconnect(i->play_stream); + pa_stream_unref(i->play_stream); + i->play_stream = NULL; + i->io_flags |= PA_IO_EVENT_INPUT; + } + + if (i->rec_stream) { + pa_stream_disconnect(i->rec_stream); + pa_stream_unref(i->rec_stream); + i->rec_stream = NULL; + i->io_flags |= PA_IO_EVENT_OUTPUT; + } + + if (i->io_event) { + pa_mainloop_api *api; + + api = pa_threaded_mainloop_get_api(i->mainloop); + api->io_enable(i->io_event, i->io_flags); + } +} + +static void io_event_cb(pa_mainloop_api *api, pa_io_event *e, int fd, pa_io_event_flags_t flags, void *userdata) { + fd_info *i = userdata; + + pa_threaded_mainloop_signal(i->mainloop, 0); + + if (flags & PA_IO_EVENT_INPUT) { + + if (!i->play_stream) { + if (create_playback_stream(i) < 0) + goto fail; + } else { + if (fd_info_copy_data(i, 0) < 0) + goto fail; + } + + } else if (flags & PA_IO_EVENT_OUTPUT) { + + if (!i->rec_stream) { + if (create_record_stream(i) < 0) + goto fail; + } else { + if (fd_info_copy_data(i, 0) < 0) + goto fail; + } + + } else if (flags & (PA_IO_EVENT_HANGUP|PA_IO_EVENT_ERROR)) + goto fail; + + return; + +fail: + /* We can't do anything better than removing the event source */ + fd_info_shutdown(i); +} + +static int dsp_open(int flags, int *_errno) { + fd_info *i; + pa_mainloop_api *api; + int ret; + int f; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open()\n"); + + if (!(i = fd_info_new(FD_INFO_STREAM, _errno))) + return -1; + + if ((flags & O_NONBLOCK) == O_NONBLOCK) { + if ((f = fcntl(i->app_fd, F_GETFL)) >= 0) + fcntl(i->app_fd, F_SETFL, f|O_NONBLOCK); + } + if ((f = fcntl(i->thread_fd, F_GETFL)) >= 0) + fcntl(i->thread_fd, F_SETFL, f|O_NONBLOCK); + + fcntl(i->app_fd, F_SETFD, FD_CLOEXEC); + fcntl(i->thread_fd, F_SETFD, FD_CLOEXEC); + + pa_threaded_mainloop_lock(i->mainloop); + api = pa_threaded_mainloop_get_api(i->mainloop); + + switch (flags & O_ACCMODE) { + case O_RDONLY: + i->io_flags = PA_IO_EVENT_OUTPUT; + shutdown(i->thread_fd, SHUT_RD); + shutdown(i->app_fd, SHUT_WR); + break; + case O_WRONLY: + i->io_flags = PA_IO_EVENT_INPUT; + shutdown(i->thread_fd, SHUT_WR); + shutdown(i->app_fd, SHUT_RD); + break; + case O_RDWR: + i->io_flags = PA_IO_EVENT_INPUT | PA_IO_EVENT_OUTPUT; + break; + default: + return -1; + } + + if (!(i->io_event = api->io_new(api, i->thread_fd, i->io_flags, io_event_cb, i))) + goto fail; + + pa_threaded_mainloop_unlock(i->mainloop); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open() succeeded, fd=%i\n", i->app_fd); + + fd_info_add_to_list(i); + ret = i->app_fd; + fd_info_unref(i); + + return ret; + +fail: + pa_threaded_mainloop_unlock(i->mainloop); + + if (i) + fd_info_unref(i); + + *_errno = EIO; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": dsp_open() failed\n"); + + return -1; +} + +static void sink_info_cb(pa_context *context, const pa_sink_info *si, int eol, void *userdata) { + fd_info *i = userdata; + + if (!si && eol < 0) { + i->operation_success = 0; + pa_threaded_mainloop_signal(i->mainloop, 0); + return; + } + + if (eol) + return; + + if (!pa_cvolume_equal(&i->sink_volume, &si->volume)) + i->volume_modify_count++; + + i->sink_volume = si->volume; + i->sink_index = si->index; + + i->operation_success = 1; + pa_threaded_mainloop_signal(i->mainloop, 0); +} + +static void source_info_cb(pa_context *context, const pa_source_info *si, int eol, void *userdata) { + fd_info *i = userdata; + + if (!si && eol < 0) { + i->operation_success = 0; + pa_threaded_mainloop_signal(i->mainloop, 0); + return; + } + + if (eol) + return; + + if (!pa_cvolume_equal(&i->source_volume, &si->volume)) + i->volume_modify_count++; + + i->source_volume = si->volume; + i->source_index = si->index; + + i->operation_success = 1; + pa_threaded_mainloop_signal(i->mainloop, 0); +} + +static void subscribe_cb(pa_context *context, pa_subscription_event_type_t t, uint32_t idx, void *userdata) { + fd_info *i = userdata; + pa_operation *o = NULL; + + if (i->sink_index != idx) + return; + + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) != PA_SUBSCRIPTION_EVENT_CHANGE) + return; + + if (!(o = pa_context_get_sink_info_by_index(i->context, i->sink_index, sink_info_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context))); + return; + } + + pa_operation_unref(o); +} + +static int mixer_open(int flags, int *_errno) { + fd_info *i; + pa_operation *o = NULL; + int ret; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open()\n"); + + if (!(i = fd_info_new(FD_INFO_MIXER, _errno))) + return -1; + + pa_threaded_mainloop_lock(i->mainloop); + + pa_context_set_subscribe_callback(i->context, subscribe_cb, i); + + if (!(o = pa_context_subscribe(i->context, PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE, context_success_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context))); + *_errno = EIO; + goto fail; + } + + i->operation_success = 0; + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait(i->mainloop); + CONTEXT_CHECK_DEAD_GOTO(i, fail); + } + + pa_operation_unref(o); + o = NULL; + + if (!i->operation_success) { + debug(DEBUG_LEVEL_NORMAL, __FILE__":Failed to subscribe to events: %s", pa_strerror(pa_context_errno(i->context))); + *_errno = EIO; + goto fail; + } + + /* Get sink info */ + + if (!(o = pa_context_get_sink_info_by_name(i->context, NULL, sink_info_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context))); + *_errno = EIO; + goto fail; + } + + i->operation_success = 0; + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait(i->mainloop); + CONTEXT_CHECK_DEAD_GOTO(i, fail); + } + + pa_operation_unref(o); + o = NULL; + + if (!i->operation_success) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get sink info: %s", pa_strerror(pa_context_errno(i->context))); + *_errno = EIO; + goto fail; + } + + /* Get source info */ + + if (!(o = pa_context_get_source_info_by_name(i->context, NULL, source_info_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get source info: %s", pa_strerror(pa_context_errno(i->context))); + *_errno = EIO; + goto fail; + } + + i->operation_success = 0; + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + pa_threaded_mainloop_wait(i->mainloop); + CONTEXT_CHECK_DEAD_GOTO(i, fail); + } + + pa_operation_unref(o); + o = NULL; + + if (!i->operation_success) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to get source info: %s", pa_strerror(pa_context_errno(i->context))); + *_errno = EIO; + goto fail; + } + + pa_threaded_mainloop_unlock(i->mainloop); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open() succeeded, fd=%i\n", i->app_fd); + + fd_info_add_to_list(i); + ret = i->app_fd; + fd_info_unref(i); + + return ret; + +fail: + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(i->mainloop); + + if (i) + fd_info_unref(i); + + *_errno = EIO; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": mixer_open() failed\n"); + + return -1; +} + +static int sndstat_open(int flags, int *_errno) { + static const char sndstat[] = + "Sound Driver:3.8.1a-980706 (PulseAudio Virtual OSS)\n" + "Kernel: POSIX\n" + "Config options: 0\n" + "\n" + "Installed drivers:\n" + "Type 255: PulseAudio Virtual OSS\n" + "\n" + "Card config:\n" + "PulseAudio Virtual OSS\n" + "\n" + "Audio devices:\n" + "0: PulseAudio Virtual OSS\n" + "\n" + "Synth devices: NOT ENABLED IN CONFIG\n" + "\n" + "Midi devices:\n" + "\n" + "Timers:\n" + "\n" + "Mixers:\n" + "0: PulseAudio Virtual OSS\n"; + + char fn[] = "/tmp/padsp-sndstat-XXXXXX"; + mode_t u; + int fd = -1; + int e; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": sndstat_open()\n"); + + if (flags != O_RDONLY +#ifdef O_LARGEFILE + && flags != (O_RDONLY|O_LARGEFILE) +#endif + ) { + *_errno = EACCES; + debug(DEBUG_LEVEL_NORMAL, __FILE__": bad access!\n"); + goto fail; + } + + u = umask(0077); + fd = mkstemp(fn); + e = errno; + umask(u); + + if (fd < 0) { + *_errno = e; + debug(DEBUG_LEVEL_NORMAL, __FILE__": mkstemp() failed: %s\n", strerror(errno)); + goto fail; + } + + unlink(fn); + + if (write(fd, sndstat, sizeof(sndstat) -1) != sizeof(sndstat)-1) { + *_errno = errno; + debug(DEBUG_LEVEL_NORMAL, __FILE__": write() failed: %s\n", strerror(errno)); + goto fail; + } + + if (lseek(fd, SEEK_SET, 0) < 0) { + *_errno = errno; + debug(DEBUG_LEVEL_NORMAL, __FILE__": lseek() failed: %s\n", strerror(errno)); + goto fail; + } + + return fd; + +fail: + if (fd >= 0) + close(fd); + return -1; +} + +static int real_open(const char *filename, int flags, mode_t mode) { + int r, _errno = 0; + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": open(%s)\n", filename?filename:"NULL"); + + if (!function_enter()) { + LOAD_OPEN_FUNC(); + return _open(filename, flags, mode); + } + + if (filename && dsp_cloak_enable() && (strcmp(filename, "/dev/dsp") == 0 || strcmp(filename, "/dev/adsp") == 0)) + r = dsp_open(flags, &_errno); + else if (filename && mixer_cloak_enable() && strcmp(filename, "/dev/mixer") == 0) + r = mixer_open(flags, &_errno); + else if (filename && sndstat_cloak_enable() && strcmp(filename, "/dev/sndstat") == 0) + r = sndstat_open(flags, &_errno); + else { + function_exit(); + LOAD_OPEN_FUNC(); + return _open(filename, flags, mode); + } + + function_exit(); + + if (_errno) + errno = _errno; + + return r; +} + +int open(const char *filename, int flags, ...) { + va_list args; + mode_t mode = 0; + + if (flags & O_CREAT) { + va_start(args, flags); + if (sizeof(mode_t) < sizeof(int)) + mode = va_arg(args, int); + else + mode = va_arg(args, mode_t); + va_end(args); + } + + return real_open(filename, flags, mode); +} + +static int mixer_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) { + int ret = -1; + + switch (request) { + case SOUND_MIXER_READ_DEVMASK : + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_DEVMASK\n"); + + *(int*) argp = SOUND_MASK_PCM | SOUND_MASK_IGAIN; + break; + + case SOUND_MIXER_READ_RECMASK : + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_RECMASK\n"); + + *(int*) argp = SOUND_MASK_IGAIN; + break; + + case SOUND_MIXER_READ_STEREODEVS: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_STEREODEVS\n"); + + pa_threaded_mainloop_lock(i->mainloop); + *(int*) argp = 0; + if (i->sink_volume.channels > 1) + *(int*) argp |= SOUND_MASK_PCM; + if (i->source_volume.channels > 1) + *(int*) argp |= SOUND_MASK_IGAIN; + pa_threaded_mainloop_unlock(i->mainloop); + + break; + + case SOUND_MIXER_READ_RECSRC: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_RECSRC\n"); + + *(int*) argp = SOUND_MASK_IGAIN; + break; + + case SOUND_MIXER_WRITE_RECSRC: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_WRITE_RECSRC\n"); + break; + + case SOUND_MIXER_READ_CAPS: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_CAPS\n"); + + *(int*) argp = 0; + break; + + case SOUND_MIXER_READ_PCM: + case SOUND_MIXER_READ_IGAIN: { + pa_cvolume *v; + + if (request == SOUND_MIXER_READ_PCM) + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_PCM\n"); + else + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_READ_IGAIN\n"); + + pa_threaded_mainloop_lock(i->mainloop); + + if (request == SOUND_MIXER_READ_PCM) + v = &i->sink_volume; + else + v = &i->source_volume; + + *(int*) argp = + ((v->values[0]*100/PA_VOLUME_NORM)) | + ((v->values[v->channels > 1 ? 1 : 0]*100/PA_VOLUME_NORM) << 8); + + pa_threaded_mainloop_unlock(i->mainloop); + + break; + } + + case SOUND_MIXER_WRITE_PCM: + case SOUND_MIXER_WRITE_IGAIN: { + pa_cvolume v, *pv; + + if (request == SOUND_MIXER_WRITE_PCM) + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_WRITE_PCM\n"); + else + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_WRITE_IGAIN\n"); + + pa_threaded_mainloop_lock(i->mainloop); + + if (request == SOUND_MIXER_WRITE_PCM) { + v = i->sink_volume; + pv = &i->sink_volume; + } else { + v = i->source_volume; + pv = &i->source_volume; + } + + pv->values[0] = ((*(int*) argp & 0xFF)*PA_VOLUME_NORM)/100; + pv->values[1] = ((*(int*) argp >> 8)*PA_VOLUME_NORM)/100; + + if (!pa_cvolume_equal(pv, &v)) { + pa_operation *o; + + if (request == SOUND_MIXER_WRITE_PCM) + o = pa_context_set_sink_volume_by_index(i->context, i->sink_index, pv, context_success_cb, i); + else + o = pa_context_set_source_volume_by_index(i->context, i->source_index, pv, context_success_cb, i); + + if (!o) + debug(DEBUG_LEVEL_NORMAL, __FILE__":Failed set volume: %s", pa_strerror(pa_context_errno(i->context))); + else { + + i->operation_success = 0; + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + CONTEXT_CHECK_DEAD_GOTO(i, exit_loop); + + pa_threaded_mainloop_wait(i->mainloop); + } + exit_loop: + + if (!i->operation_success) + debug(DEBUG_LEVEL_NORMAL, __FILE__": Failed to set volume: %s\n", pa_strerror(pa_context_errno(i->context))); + + pa_operation_unref(o); + } + + /* We don't wait for completion here */ + i->volume_modify_count++; + } + + pa_threaded_mainloop_unlock(i->mainloop); + + break; + } + + case SOUND_MIXER_INFO: { + mixer_info *mi = argp; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_MIXER_INFO\n"); + + memset(mi, 0, sizeof(mixer_info)); + strncpy(mi->id, "PULSEAUDIO", sizeof(mi->id)); + strncpy(mi->name, "PulseAudio Virtual OSS", sizeof(mi->name)); + pa_threaded_mainloop_lock(i->mainloop); + mi->modify_counter = i->volume_modify_count; + pa_threaded_mainloop_unlock(i->mainloop); + break; + } + + default: + debug(DEBUG_LEVEL_NORMAL, __FILE__": unknown ioctl 0x%08lx\n", request); + + *_errno = EINVAL; + goto fail; + } + + ret = 0; + +fail: + + return ret; +} + +static int map_format(int *fmt, pa_sample_spec *ss) { + + switch (*fmt) { + case AFMT_MU_LAW: + ss->format = PA_SAMPLE_ULAW; + break; + + case AFMT_A_LAW: + ss->format = PA_SAMPLE_ALAW; + break; + + case AFMT_S8: + *fmt = AFMT_U8; + /* fall through */ + case AFMT_U8: + ss->format = PA_SAMPLE_U8; + break; + + case AFMT_U16_BE: + *fmt = AFMT_S16_BE; + /* fall through */ + case AFMT_S16_BE: + ss->format = PA_SAMPLE_S16BE; + break; + + case AFMT_U16_LE: + *fmt = AFMT_S16_LE; + /* fall through */ + case AFMT_S16_LE: + ss->format = PA_SAMPLE_S16LE; + break; + + default: + ss->format = PA_SAMPLE_S16NE; + *fmt = AFMT_S16_NE; + break; + } + + return 0; +} + +static int map_format_back(pa_sample_format_t format) { + switch (format) { + case PA_SAMPLE_S16LE: return AFMT_S16_LE; + case PA_SAMPLE_S16BE: return AFMT_S16_BE; + case PA_SAMPLE_ULAW: return AFMT_MU_LAW; + case PA_SAMPLE_ALAW: return AFMT_A_LAW; + case PA_SAMPLE_U8: return AFMT_U8; + default: + abort(); + } +} + +static int dsp_flush_fd(int fd) { +#ifdef SIOCINQ + int l; + + if (ioctl(fd, SIOCINQ, &l) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ: %s\n", strerror(errno)); + return -1; + } + + while (l > 0) { + char buf[1024]; + size_t k; + + k = (size_t) l > sizeof(buf) ? sizeof(buf) : (size_t) l; + if (read(fd, buf, k) < 0) + debug(DEBUG_LEVEL_NORMAL, __FILE__": read(): %s\n", strerror(errno)); + l -= k; + } + + return 0; +#else +# warning "Your platform does not support SIOCINQ, something might not work as intended." + return 0; +#endif +} + +static int dsp_flush_socket(fd_info *i) { + int res = 0; + + if ((i->thread_fd < 0) && (i->app_fd < 0)) + return -1; + + if (i->thread_fd >= 0) + res = dsp_flush_fd(i->thread_fd); + + if (res < 0) + return res; + + if (i->app_fd >= 0) + res = dsp_flush_fd(i->app_fd); + + if (res < 0) + return res; + + return 0; +} + +static int dsp_empty_socket(fd_info *i) { +#ifdef SIOCINQ + int ret = -1; + + /* Empty the socket */ + for (;;) { + int l; + + if (i->thread_fd < 0) + break; + + if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ: %s\n", strerror(errno)); + break; + } + + if (!l) { + ret = 0; + break; + } + + pa_threaded_mainloop_wait(i->mainloop); + } + + return ret; +#else +# warning "Your platform does not support SIOCINQ, something might not work as intended." + return 0; +#endif +} + +static int dsp_drain(fd_info *i) { + pa_operation *o = NULL; + int r = -1; + + if (!i->mainloop) + return 0; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": Draining.\n"); + + pa_threaded_mainloop_lock(i->mainloop); + + if (dsp_empty_socket(i) < 0) + goto fail; + + if (!i->play_stream) + goto fail; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": Really draining.\n"); + + if (!(o = pa_stream_drain(i->play_stream, stream_success_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_drain(): %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + i->operation_success = 0; + while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, fail); + + pa_threaded_mainloop_wait(i->mainloop); + } + + if (!i->operation_success) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_drain() 2: %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + r = 0; + +fail: + + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(i->mainloop); + + return 0; +} + +static int dsp_trigger(fd_info *i) { + pa_operation *o = NULL; + int r = -1; + + if (!i->play_stream) + return 0; + + pa_threaded_mainloop_lock(i->mainloop); + + if (dsp_empty_socket(i) < 0) + goto fail; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": Triggering.\n"); + + if (!(o = pa_stream_trigger(i->play_stream, stream_success_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + i->operation_success = 0; + while (!pa_operation_get_state(o) != PA_OPERATION_DONE) { + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, fail); + + pa_threaded_mainloop_wait(i->mainloop); + } + + if (!i->operation_success) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_trigger(): %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + r = 0; + +fail: + + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(i->mainloop); + + return 0; +} + +static int dsp_cork(fd_info *i, pa_stream *s, int b) { + pa_operation *o = NULL; + int r = -1; + + pa_threaded_mainloop_lock(i->mainloop); + + if (!(o = pa_stream_cork(s, b, stream_success_cb, i))) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_cork(): %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + i->operation_success = 0; + while (!pa_operation_get_state(o) != PA_OPERATION_DONE) { + if (s == i->play_stream) + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, fail); + else if (s == i->rec_stream) + RECORD_STREAM_CHECK_DEAD_GOTO(i, fail); + + pa_threaded_mainloop_wait(i->mainloop); + } + + if (!i->operation_success) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_cork(): %s\n", pa_strerror(pa_context_errno(i->context))); + goto fail; + } + + r = 0; + +fail: + + if (o) + pa_operation_unref(o); + + pa_threaded_mainloop_unlock(i->mainloop); + + return 0; +} + +static int dsp_ioctl(fd_info *i, unsigned long request, void*argp, int *_errno) { + int ret = -1; + + if (i->thread_fd == -1) { + /* + * We've encountered some fatal error and are just waiting + * for a close. + */ + debug(DEBUG_LEVEL_NORMAL, __FILE__": got ioctl 0x%08lx in fatal error state\n", request); + *_errno = EIO; + return -1; + } + + switch (request) { + case SNDCTL_DSP_SETFMT: { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETFMT: %i\n", *(int*) argp); + + pa_threaded_mainloop_lock(i->mainloop); + + if (*(int*) argp == AFMT_QUERY) + *(int*) argp = map_format_back(i->sample_spec.format); + else { + map_format((int*) argp, &i->sample_spec); + free_streams(i); + } + + pa_threaded_mainloop_unlock(i->mainloop); + break; + } + + case SNDCTL_DSP_SPEED: { + pa_sample_spec ss; + int valid; + char t[256]; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SPEED: %i\n", *(int*) argp); + + pa_threaded_mainloop_lock(i->mainloop); + + ss = i->sample_spec; + ss.rate = *(int*) argp; + + if ((valid = pa_sample_spec_valid(&ss))) { + i->sample_spec = ss; + free_streams(i); + } + + debug(DEBUG_LEVEL_NORMAL, __FILE__": ss: %s\n", pa_sample_spec_snprint(t, sizeof(t), &i->sample_spec)); + + pa_threaded_mainloop_unlock(i->mainloop); + + if (!valid) { + *_errno = EINVAL; + goto fail; + } + + break; + } + + case SNDCTL_DSP_STEREO: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_STEREO: %i\n", *(int*) argp); + + pa_threaded_mainloop_lock(i->mainloop); + + i->sample_spec.channels = *(int*) argp ? 2 : 1; + free_streams(i); + + pa_threaded_mainloop_unlock(i->mainloop); + return 0; + + case SNDCTL_DSP_CHANNELS: { + pa_sample_spec ss; + int valid; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_CHANNELS: %i\n", *(int*) argp); + + pa_threaded_mainloop_lock(i->mainloop); + + ss = i->sample_spec; + ss.channels = *(int*) argp; + + if ((valid = pa_sample_spec_valid(&ss))) { + i->sample_spec = ss; + free_streams(i); + } + + pa_threaded_mainloop_unlock(i->mainloop); + + if (!valid) { + *_errno = EINVAL; + goto fail; + } + + break; + } + + case SNDCTL_DSP_GETBLKSIZE: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETBLKSIZE\n"); + + pa_threaded_mainloop_lock(i->mainloop); + + fix_metrics(i); + *(int*) argp = i->fragment_size; + + pa_threaded_mainloop_unlock(i->mainloop); + + break; + + case SNDCTL_DSP_SETFRAGMENT: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETFRAGMENT: 0x%08x\n", *(int*) argp); + + pa_threaded_mainloop_lock(i->mainloop); + + i->fragment_size = 1 << ((*(int*) argp) & 31); + i->n_fragments = (*(int*) argp) >> 16; + + /* 0x7FFF means that we can set whatever we like */ + if (i->n_fragments == 0x7FFF) + i->n_fragments = 12; + + free_streams(i); + + pa_threaded_mainloop_unlock(i->mainloop); + + break; + + case SNDCTL_DSP_GETCAPS: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_CAPS\n"); + + *(int*) argp = DSP_CAP_DUPLEX | DSP_CAP_TRIGGER +#ifdef DSP_CAP_MULTI + | DSP_CAP_MULTI +#endif + ; + break; + + case SNDCTL_DSP_GETODELAY: { + int l; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETODELAY\n"); + + pa_threaded_mainloop_lock(i->mainloop); + + *(int*) argp = 0; + + for (;;) { + pa_usec_t usec; + + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, exit_loop); + + if (pa_stream_get_latency(i->play_stream, &usec, NULL) >= 0) { + *(int*) argp = pa_usec_to_bytes(usec, &i->sample_spec); + break; + } + + if (pa_context_errno(i->context) != PA_ERR_NODATA) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_get_latency(): %s\n", pa_strerror(pa_context_errno(i->context))); + break; + } + + pa_threaded_mainloop_wait(i->mainloop); + } + + exit_loop: + +#ifdef SIOCINQ + if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno)); + else + *(int*) argp += l; +#else +# warning "Your platform does not support SIOCINQ, something might not work as intended." +#endif + + pa_threaded_mainloop_unlock(i->mainloop); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": ODELAY: %i\n", *(int*) argp); + + break; + } + + case SNDCTL_DSP_RESET: { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_RESET\n"); + + pa_threaded_mainloop_lock(i->mainloop); + + free_streams(i); + dsp_flush_socket(i); + + i->optr_n_blocks = 0; + + pa_threaded_mainloop_unlock(i->mainloop); + break; + } + + case SNDCTL_DSP_GETFMTS: { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETFMTS\n"); + + *(int*) argp = AFMT_MU_LAW|AFMT_A_LAW|AFMT_U8|AFMT_S16_LE|AFMT_S16_BE; + break; + } + + case SNDCTL_DSP_POST: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_POST\n"); + + if (dsp_trigger(i) < 0) + *_errno = EIO; + break; + + case SNDCTL_DSP_GETTRIGGER: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETTRIGGER\n"); + + *(int*) argp = 0; + if (!i->play_precork) + *(int*) argp |= PCM_ENABLE_OUTPUT; + if (!i->rec_precork) + *(int*) argp |= PCM_ENABLE_INPUT; + + break; + + case SNDCTL_DSP_SETTRIGGER: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETTRIGGER: 0x%08x\n", *(int*) argp); + + if (!i->io_event) { + *_errno = EIO; + break; + } + + i->play_precork = !((*(int*) argp) & PCM_ENABLE_OUTPUT); + + if (i->play_stream) { + if (dsp_cork(i, i->play_stream, !((*(int*) argp) & PCM_ENABLE_OUTPUT)) < 0) + *_errno = EIO; + if (dsp_trigger(i) < 0) + *_errno = EIO; + } + + i->rec_precork = !((*(int*) argp) & PCM_ENABLE_INPUT); + + if (i->rec_stream) { + if (dsp_cork(i, i->rec_stream, !((*(int*) argp) & PCM_ENABLE_INPUT)) < 0) + *_errno = EIO; + } + + break; + + case SNDCTL_DSP_SYNC: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SYNC\n"); + + if (dsp_drain(i) < 0) + *_errno = EIO; + + break; + + case SNDCTL_DSP_GETOSPACE: + case SNDCTL_DSP_GETISPACE: { + audio_buf_info *bi = (audio_buf_info*) argp; + int l = 0; + size_t k = 0; + + if (request == SNDCTL_DSP_GETOSPACE) + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETOSPACE\n"); + else + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETISPACE\n"); + + pa_threaded_mainloop_lock(i->mainloop); + + fix_metrics(i); + + if (request == SNDCTL_DSP_GETOSPACE) { + if (i->play_stream) { + if ((k = pa_stream_writable_size(i->play_stream)) == (size_t) -1) + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_writable_size(): %s\n", pa_strerror(pa_context_errno(i->context))); + } else + k = i->fragment_size * i->n_fragments; + +#ifdef SIOCINQ + if (ioctl(i->thread_fd, SIOCINQ, &l) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno)); + l = 0; + } +#else +# warning "Your platform does not dsp_flush_fd, something might not work as intended." +#endif + + bi->bytes = k > (size_t) l ? k - l : 0; + } else { + if (i->rec_stream) { + if ((k = pa_stream_readable_size(i->rec_stream)) == (size_t) -1) + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_readable_size(): %s\n", pa_strerror(pa_context_errno(i->context))); + } else + k = 0; + +#ifdef SIOCINQ + if (ioctl(i->app_fd, SIOCINQ, &l) < 0) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": SIOCINQ failed: %s\n", strerror(errno)); + l = 0; + } +#else +# warning "Your platform does not dsp_flush_fd, something might not work as intended." +#endif + bi->bytes = k + l; + } + + bi->fragsize = i->fragment_size; + bi->fragstotal = i->n_fragments; + bi->fragments = bi->bytes / bi->fragsize; + + pa_threaded_mainloop_unlock(i->mainloop); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": fragsize=%i, fragstotal=%i, bytes=%i, fragments=%i\n", bi->fragsize, bi->fragstotal, bi->bytes, bi->fragments); + + break; + } + + case SOUND_PCM_READ_RATE: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_RATE\n"); + + pa_threaded_mainloop_lock(i->mainloop); + *(int*) argp = i->sample_spec.rate; + pa_threaded_mainloop_unlock(i->mainloop); + break; + + case SOUND_PCM_READ_CHANNELS: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_CHANNELS\n"); + + pa_threaded_mainloop_lock(i->mainloop); + *(int*) argp = i->sample_spec.channels; + pa_threaded_mainloop_unlock(i->mainloop); + break; + + case SOUND_PCM_READ_BITS: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SOUND_PCM_READ_BITS\n"); + + pa_threaded_mainloop_lock(i->mainloop); + *(int*) argp = pa_sample_size(&i->sample_spec)*8; + pa_threaded_mainloop_unlock(i->mainloop); + break; + + case SNDCTL_DSP_GETOPTR: { + count_info *info; + + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_GETOPTR\n"); + + info = (count_info*) argp; + memset(info, 0, sizeof(*info)); + + pa_threaded_mainloop_lock(i->mainloop); + + for (;;) { + pa_usec_t usec; + + PLAYBACK_STREAM_CHECK_DEAD_GOTO(i, exit_loop); + + if (pa_stream_get_time(i->play_stream, &usec) >= 0) { + size_t k = pa_usec_to_bytes(usec, &i->sample_spec); + int m; + + info->bytes = (int) k; + m = k / i->fragment_size; + info->blocks = m - i->optr_n_blocks; + i->optr_n_blocks = m; + + break; + } + + if (pa_context_errno(i->context) != PA_ERR_NODATA) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": pa_stream_get_latency(): %s\n", pa_strerror(pa_context_errno(i->context))); + break; + } + + pa_threaded_mainloop_wait(i->mainloop); + } + + pa_threaded_mainloop_unlock(i->mainloop); + + debug(DEBUG_LEVEL_NORMAL, __FILE__": GETOPTR bytes=%i, blocks=%i, ptr=%i\n", info->bytes, info->blocks, info->ptr); + + break; + } + + case SNDCTL_DSP_GETIPTR: + debug(DEBUG_LEVEL_NORMAL, __FILE__": invalid ioctl SNDCTL_DSP_GETIPTR\n"); + goto inval; + + case SNDCTL_DSP_SETDUPLEX: + debug(DEBUG_LEVEL_NORMAL, __FILE__": SNDCTL_DSP_SETDUPLEX\n"); + /* this is a no-op */ + break; + + default: + /* Mixer ioctls are valid on /dev/dsp aswell */ + return mixer_ioctl(i, request, argp, _errno); + +inval: + *_errno = EINVAL; + goto fail; + } + + ret = 0; + +fail: + + return ret; +} + +int ioctl(int fd, unsigned long request, ...) { + fd_info *i; + va_list args; + void *argp; + int r, _errno = 0; + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": ioctl()\n"); + + va_start(args, request); + argp = va_arg(args, void *); + va_end(args); + + if (!function_enter()) { + LOAD_IOCTL_FUNC(); + return _ioctl(fd, request, argp); + } + + if (!(i = fd_info_find(fd))) { + function_exit(); + LOAD_IOCTL_FUNC(); + return _ioctl(fd, request, argp); + } + + if (i->type == FD_INFO_MIXER) + r = mixer_ioctl(i, request, argp, &_errno); + else + r = dsp_ioctl(i, request, argp, &_errno); + + fd_info_unref(i); + + if (_errno) + errno = _errno; + + function_exit(); + + return r; +} + +int close(int fd) { + fd_info *i; + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": close()\n"); + + if (!function_enter()) { + LOAD_CLOSE_FUNC(); + return _close(fd); + } + + if (!(i = fd_info_find(fd))) { + function_exit(); + LOAD_CLOSE_FUNC(); + return _close(fd); + } + + fd_info_remove_from_list(i); + fd_info_unref(i); + + function_exit(); + + return 0; +} + +int access(const char *pathname, int mode) { + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": access(%s)\n", pathname?pathname:"NULL"); + + if (!pathname || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + LOAD_ACCESS_FUNC(); + return _access(pathname, mode); + } + + if (mode & (W_OK | X_OK)) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": access(%s, %x) = EACCESS\n", pathname, mode); + errno = EACCES; + return -1; + } + + debug(DEBUG_LEVEL_NORMAL, __FILE__": access(%s, %x) = OK\n", pathname, mode); + + return 0; +} + +int stat(const char *pathname, struct stat *buf) { +#ifdef HAVE_OPEN64 + struct stat64 parent; +#else + struct stat parent; +#endif + int ret; + + if (!pathname || + !buf || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + debug(DEBUG_LEVEL_VERBOSE, __FILE__": stat(%s)\n", pathname?pathname:"NULL"); + LOAD_STAT_FUNC(); + return _stat(pathname, buf); + } + + debug(DEBUG_LEVEL_NORMAL, __FILE__": stat(%s)\n", pathname); + +#ifdef _STAT_VER +#ifdef HAVE_OPEN64 + ret = __xstat64(_STAT_VER, "/dev", &parent); +#else + ret = __xstat(_STAT_VER, "/dev", &parent); +#endif +#else +#ifdef HAVE_OPEN64 + ret = stat64("/dev", &parent); +#else + ret = stat("/dev", &parent); +#endif +#endif + + if (ret) { + debug(DEBUG_LEVEL_NORMAL, __FILE__": unable to stat \"/dev\"\n"); + return -1; + } + + buf->st_dev = parent.st_dev; + buf->st_ino = 0xDEADBEEF; /* FIXME: Can we do this in a safe way? */ + buf->st_mode = S_IFCHR | S_IRUSR | S_IWUSR; + buf->st_nlink = 1; + buf->st_uid = getuid(); + buf->st_gid = getgid(); + buf->st_rdev = 0x0E03; /* FIXME: Linux specific */ + buf->st_size = 0; + buf->st_atime = 1181557705; + buf->st_mtime = 1181557705; + buf->st_ctime = 1181557705; + buf->st_blksize = 1; + buf->st_blocks = 0; + + return 0; +} + +#ifdef HAVE_OPEN64 + +int stat64(const char *pathname, struct stat64 *buf) { + struct stat oldbuf; + int ret; + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": stat64(%s)\n", pathname?pathname:"NULL"); + + if (!pathname || + !buf || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + LOAD_STAT64_FUNC(); + return _stat64(pathname, buf); + } + + ret = stat(pathname, &oldbuf); + if (ret) + return ret; + + buf->st_dev = oldbuf.st_dev; + buf->st_ino = oldbuf.st_ino; + buf->st_mode = oldbuf.st_mode; + buf->st_nlink = oldbuf.st_nlink; + buf->st_uid = oldbuf.st_uid; + buf->st_gid = oldbuf.st_gid; + buf->st_rdev = oldbuf.st_rdev; + buf->st_size = oldbuf.st_size; + buf->st_atime = oldbuf.st_atime; + buf->st_mtime = oldbuf.st_mtime; + buf->st_ctime = oldbuf.st_ctime; + buf->st_blksize = oldbuf.st_blksize; + buf->st_blocks = oldbuf.st_blocks; + + return 0; +} + +int open64(const char *filename, int flags, ...) { + va_list args; + mode_t mode = 0; + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": open64(%s)\n", filename?filename:"NULL"); + + if (flags & O_CREAT) { + va_start(args, flags); + if (sizeof(mode_t) < sizeof(int)) + mode = va_arg(args, int); + else + mode = va_arg(args, mode_t); + va_end(args); + } + + if (!filename || + ( strcmp(filename, "/dev/dsp") != 0 && + strcmp(filename, "/dev/adsp") != 0 && + strcmp(filename, "/dev/sndstat") != 0 && + strcmp(filename, "/dev/mixer") != 0 )) { + LOAD_OPEN64_FUNC(); + return _open64(filename, flags, mode); + } + + return real_open(filename, flags, mode); +} + +#endif + +#ifdef _STAT_VER + +int __xstat(int ver, const char *pathname, struct stat *buf) { + debug(DEBUG_LEVEL_VERBOSE, __FILE__": __xstat(%s)\n", pathname?pathname:"NULL"); + + if (!pathname || + !buf || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + LOAD_XSTAT_FUNC(); + return ___xstat(ver, pathname, buf); + } + + if (ver != _STAT_VER) { + errno = EINVAL; + return -1; + } + + return stat(pathname, buf); +} + +#ifdef HAVE_OPEN64 + +int __xstat64(int ver, const char *pathname, struct stat64 *buf) { + debug(DEBUG_LEVEL_VERBOSE, __FILE__": __xstat64(%s)\n", pathname?pathname:"NULL"); + + if (!pathname || + !buf || + ( strcmp(pathname, "/dev/dsp") != 0 && + strcmp(pathname, "/dev/adsp") != 0 && + strcmp(pathname, "/dev/sndstat") != 0 && + strcmp(pathname, "/dev/mixer") != 0 )) { + LOAD_XSTAT64_FUNC(); + return ___xstat64(ver, pathname, buf); + } + + if (ver != _STAT_VER) { + errno = EINVAL; + return -1; + } + + return stat64(pathname, buf); +} + +#endif + +#endif + +FILE* fopen(const char *filename, const char *mode) { + FILE *f = NULL; + int fd; + mode_t m; + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": fopen(%s)\n", filename?filename:"NULL"); + + if (!filename || + !mode || + ( strcmp(filename, "/dev/dsp") != 0 && + strcmp(filename, "/dev/adsp") != 0 && + strcmp(filename, "/dev/sndstat") != 0 && + strcmp(filename, "/dev/mixer") != 0 )) { + LOAD_FOPEN_FUNC(); + return _fopen(filename, mode); + } + + switch (mode[0]) { + case 'r': + m = O_RDONLY; + break; + case 'w': + case 'a': + m = O_WRONLY; + break; + default: + errno = EINVAL; + return NULL; + } + + if ((((mode[1] == 'b') || (mode[1] == 't')) && (mode[2] == '+')) || (mode[1] == '+')) + m = O_RDWR; + + if ((fd = real_open(filename, m, 0)) < 0) + return NULL; + + if (!(f = fdopen(fd, mode))) { + close(fd); + return NULL; + } + + return f; +} + +#ifdef HAVE_OPEN64 + +FILE *fopen64(const char *filename, const char *mode) { + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": fopen64(%s)\n", filename?filename:"NULL"); + + if (!filename || + !mode || + ( strcmp(filename, "/dev/dsp") != 0 && + strcmp(filename, "/dev/adsp") != 0 && + strcmp(filename, "/dev/sndstat") != 0 && + strcmp(filename, "/dev/mixer") != 0 )) { + LOAD_FOPEN64_FUNC(); + return _fopen64(filename, mode); + } + + return fopen(filename, mode); +} + +#endif + +int fclose(FILE *f) { + fd_info *i; + + debug(DEBUG_LEVEL_VERBOSE, __FILE__": fclose()\n"); + + if (!function_enter()) { + LOAD_FCLOSE_FUNC(); + return _fclose(f); + } + + if (!(i = fd_info_find(fileno(f)))) { + function_exit(); + LOAD_FCLOSE_FUNC(); + return _fclose(f); + } + + fd_info_remove_from_list(i); + + /* Dirty trick to avoid that the fd is not freed twice, once by us + * and once by the real fclose() */ + i->app_fd = -1; + + fd_info_unref(i); + + function_exit(); + + LOAD_FCLOSE_FUNC(); + return _fclose(f); +} diff --git a/src/utils/paplay.c b/src/utils/paplay.c new file mode 100644 index 00000000..fddbb18c --- /dev/null +++ b/src/utils/paplay.c @@ -0,0 +1,435 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 Lennart Poettering + Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB + + 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 <signal.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <getopt.h> +#include <locale.h> + +#include <sndfile.h> + +#include <pulse/pulseaudio.h> + +#if PA_API_VERSION < 9 +#error Invalid PulseAudio API version +#endif + +static pa_context *context = NULL; +static pa_stream *stream = NULL; +static pa_mainloop_api *mainloop_api = NULL; + +static char *stream_name = NULL, *client_name = NULL, *device = NULL; + +static int verbose = 0; +static pa_volume_t volume = PA_VOLUME_NORM; + +static SNDFILE* sndfile = NULL; + +static pa_sample_spec sample_spec = { 0, 0, 0 }; +static pa_channel_map channel_map; +static int channel_map_set = 0; + +static sf_count_t (*readf_function)(SNDFILE *_sndfile, void *ptr, sf_count_t frames) = NULL; + +/* A shortcut for terminating the application */ +static void quit(int ret) { + assert(mainloop_api); + mainloop_api->quit(mainloop_api, ret); +} + +/* Connection draining complete */ +static void context_drain_complete(pa_context*c, void *userdata) { + pa_context_disconnect(c); +} + +/* Stream draining complete */ +static void stream_drain_complete(pa_stream*s, int success, void *userdata) { + pa_operation *o; + + if (!success) { + fprintf(stderr, "Failed to drain stream: %s\n", pa_strerror(pa_context_errno(context))); + quit(1); + } + + if (verbose) + fprintf(stderr, "Playback stream drained.\n"); + + pa_stream_disconnect(stream); + pa_stream_unref(stream); + stream = NULL; + + if (!(o = pa_context_drain(context, context_drain_complete, NULL))) + pa_context_disconnect(context); + else { + pa_operation_unref(o); + + if (verbose) + fprintf(stderr, "Draining connection to server.\n"); + } +} + +/* This is called whenever new data may be written to the stream */ +static void stream_write_callback(pa_stream *s, size_t length, void *userdata) { + sf_count_t bytes; + void *data; + assert(s && length); + + if (!sndfile) + return; + + data = pa_xmalloc(length); + + if (readf_function) { + size_t k = pa_frame_size(&sample_spec); + + if ((bytes = readf_function(sndfile, data, length/k)) > 0) + bytes *= k; + + } else + bytes = sf_read_raw(sndfile, data, length); + + if (bytes > 0) + pa_stream_write(s, data, bytes, pa_xfree, 0, PA_SEEK_RELATIVE); + else + pa_xfree(data); + + if (bytes < (sf_count_t) length) { + sf_close(sndfile); + sndfile = NULL; + pa_operation_unref(pa_stream_drain(s, stream_drain_complete, NULL)); + } +} + +/* This routine is called whenever the stream state changes */ +static void stream_state_callback(pa_stream *s, void *userdata) { + assert(s); + + switch (pa_stream_get_state(s)) { + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_READY: + if (verbose) + fprintf(stderr, "Stream successfully created\n"); + break; + + case PA_STREAM_FAILED: + default: + fprintf(stderr, "Stream errror: %s\n", pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + quit(1); + } +} + +/* This is called whenever the context status changes */ +static void context_state_callback(pa_context *c, void *userdata) { + assert(c); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: { + pa_cvolume cv; + + assert(c && !stream); + + if (verbose) + fprintf(stderr, "Connection established.\n"); + + stream = pa_stream_new(c, stream_name, &sample_spec, channel_map_set ? &channel_map : NULL); + assert(stream); + + pa_stream_set_state_callback(stream, stream_state_callback, NULL); + pa_stream_set_write_callback(stream, stream_write_callback, NULL); + pa_stream_connect_playback(stream, device, NULL, 0, pa_cvolume_set(&cv, sample_spec.channels, volume), NULL); + + break; + } + + case PA_CONTEXT_TERMINATED: + quit(0); + break; + + case PA_CONTEXT_FAILED: + default: + fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + } +} + +/* UNIX signal to quit recieved */ +static void exit_signal_callback(pa_mainloop_api*m, pa_signal_event *e, int sig, void *userdata) { + if (verbose) + fprintf(stderr, "Got SIGINT, exiting.\n"); + quit(0); + +} + +static void help(const char *argv0) { + + printf("%s [options] [FILE]\n\n" + " -h, --help Show this help\n" + " --version Show version\n\n" + " -v, --verbose Enable verbose operation\n\n" + " -s, --server=SERVER The name of the server to connect to\n" + " -d, --device=DEVICE The name of the sink to connect to\n" + " -n, --client-name=NAME How to call this client on the server\n" + " --stream-name=NAME How to call this stream on the server\n" + " --volume=VOLUME Specify the initial (linear) volume in range 0...65536\n" + " --channel-map=CHANNELMAP Set the channel map to the use\n", + argv0); +} + +enum { + ARG_VERSION = 256, + ARG_STREAM_NAME, + ARG_VOLUME, + ARG_CHANNELMAP +}; + +int main(int argc, char *argv[]) { + pa_mainloop* m = NULL; + int ret = 1, r, c; + char *bn, *server = NULL; + const char *filename; + SF_INFO sfinfo; + + static const struct option long_options[] = { + {"device", 1, NULL, 'd'}, + {"server", 1, NULL, 's'}, + {"client-name", 1, NULL, 'n'}, + {"stream-name", 1, NULL, ARG_STREAM_NAME}, + {"version", 0, NULL, ARG_VERSION}, + {"help", 0, NULL, 'h'}, + {"verbose", 0, NULL, 'v'}, + {"volume", 1, NULL, ARG_VOLUME}, + {"channel-map", 1, NULL, ARG_CHANNELMAP}, + {NULL, 0, NULL, 0} + }; + + setlocale(LC_ALL, ""); + + if (!(bn = strrchr(argv[0], '/'))) + bn = argv[0]; + else + bn++; + + while ((c = getopt_long(argc, argv, "d:s:n:hv", long_options, NULL)) != -1) { + + switch (c) { + case 'h' : + help(bn); + ret = 0; + goto quit; + + case ARG_VERSION: + printf("paplay "PACKAGE_VERSION"\nCompiled with libpulse %s\nLinked with libpulse %s\n", pa_get_headers_version(), pa_get_library_version()); + ret = 0; + goto quit; + + case 'd': + pa_xfree(device); + device = pa_xstrdup(optarg); + break; + + case 's': + pa_xfree(server); + server = pa_xstrdup(optarg); + break; + + case 'n': + pa_xfree(client_name); + client_name = pa_xstrdup(optarg); + break; + + case ARG_STREAM_NAME: + pa_xfree(stream_name); + stream_name = pa_xstrdup(optarg); + break; + + case 'v': + verbose = 1; + break; + + case ARG_VOLUME: { + int v = atoi(optarg); + volume = v < 0 ? 0 : v; + break; + } + + case ARG_CHANNELMAP: + if (!pa_channel_map_parse(&channel_map, optarg)) { + fprintf(stderr, "Invalid channel map\n"); + goto quit; + } + + channel_map_set = 1; + break; + + default: + goto quit; + } + } + + filename = optind < argc ? argv[optind] : "STDIN"; + + memset(&sfinfo, 0, sizeof(sfinfo)); + + if (optind < argc) + sndfile = sf_open(filename, SFM_READ, &sfinfo); + else + sndfile = sf_open_fd(STDIN_FILENO, SFM_READ, &sfinfo, 0); + + if (!sndfile) { + fprintf(stderr, "Failed to open file '%s'\n", filename); + goto quit; + } + + sample_spec.rate = sfinfo.samplerate; + sample_spec.channels = sfinfo.channels; + + readf_function = NULL; + + switch (sfinfo.format & 0xFF) { + case SF_FORMAT_PCM_16: + case SF_FORMAT_PCM_U8: + case SF_FORMAT_PCM_S8: + sample_spec.format = PA_SAMPLE_S16NE; + readf_function = (sf_count_t (*)(SNDFILE *_sndfile, void *ptr, sf_count_t frames)) sf_readf_short; + break; + + case SF_FORMAT_ULAW: + sample_spec.format = PA_SAMPLE_ULAW; + break; + + case SF_FORMAT_ALAW: + sample_spec.format = PA_SAMPLE_ALAW; + break; + + case SF_FORMAT_FLOAT: + case SF_FORMAT_DOUBLE: + default: + sample_spec.format = PA_SAMPLE_FLOAT32NE; + readf_function = (sf_count_t (*)(SNDFILE *_sndfile, void *ptr, sf_count_t frames)) sf_readf_float; + break; + } + + assert(pa_sample_spec_valid(&sample_spec)); + + if (channel_map_set && channel_map.channels != sample_spec.channels) { + fprintf(stderr, "Channel map doesn't match file.\n"); + goto quit; + } + + if (!client_name) { + client_name = pa_locale_to_utf8(bn); + if (!client_name) + client_name = pa_utf8_filter(bn); + } + + if (!stream_name) { + const char *n; + + n = sf_get_string(sndfile, SF_STR_TITLE); + + if (!n) + n = filename; + + stream_name = pa_locale_to_utf8(n); + if (!stream_name) + stream_name = pa_utf8_filter(n); + } + + if (verbose) { + char t[PA_SAMPLE_SPEC_SNPRINT_MAX]; + pa_sample_spec_snprint(t, sizeof(t), &sample_spec); + fprintf(stderr, "Using sample spec '%s'\n", t); + } + + /* Set up a new main loop */ + if (!(m = pa_mainloop_new())) { + fprintf(stderr, "pa_mainloop_new() failed.\n"); + goto quit; + } + + mainloop_api = pa_mainloop_get_api(m); + + r = pa_signal_init(mainloop_api); + assert(r == 0); + pa_signal_new(SIGINT, exit_signal_callback, NULL); +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + /* Create a new connection context */ + if (!(context = pa_context_new(mainloop_api, client_name))) { + fprintf(stderr, "pa_context_new() failed.\n"); + goto quit; + } + + pa_context_set_state_callback(context, context_state_callback, NULL); + + /* Connect the context */ + pa_context_connect(context, server, 0, NULL); + + /* Run the main loop */ + if (pa_mainloop_run(m, &ret) < 0) { + fprintf(stderr, "pa_mainloop_run() failed.\n"); + goto quit; + } + +quit: + if (stream) + pa_stream_unref(stream); + + if (context) + pa_context_unref(context); + + if (m) { + pa_signal_done(); + pa_mainloop_free(m); + } + + pa_xfree(server); + pa_xfree(device); + pa_xfree(client_name); + pa_xfree(stream_name); + + if (sndfile) + sf_close(sndfile); + + return ret; +} diff --git a/src/utils/pasuspender.c b/src/utils/pasuspender.c new file mode 100644 index 00000000..05d96a68 --- /dev/null +++ b/src/utils/pasuspender.c @@ -0,0 +1,318 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <sys/types.h> +#include <sys/wait.h> + +#include <signal.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <limits.h> +#include <getopt.h> + +#include <sndfile.h> + +#ifdef __linux__ +#include <sys/prctl.h> +#endif + +#include <pulse/pulseaudio.h> +#include <pulsecore/macro.h> + +#if PA_API_VERSION < 10 +#error Invalid PulseAudio API version +#endif + +#define BUFSIZE 1024 + +static pa_context *context = NULL; +static pa_mainloop_api *mainloop_api = NULL; +static char **child_argv = NULL; +static int child_argc = 0; +static pid_t child_pid = (pid_t) -1; +static int child_ret = 0; +static int dead = 1; + +static void quit(int ret) { + pa_assert(mainloop_api); + mainloop_api->quit(mainloop_api, ret); +} + + +static void context_drain_complete(pa_context *c, void *userdata) { + pa_context_disconnect(c); +} + +static void drain(void) { + pa_operation *o; + + if (!(o = pa_context_drain(context, context_drain_complete, NULL))) + pa_context_disconnect(context); + else + pa_operation_unref(o); +} + +static void start_child(void) { + + if ((child_pid = fork()) < 0) { + + fprintf(stderr, "fork(): %s\n", strerror(errno)); + quit(1); + + } else if (child_pid == 0) { + /* Child */ + +#ifdef __linux__ + prctl(PR_SET_PDEATHSIG, SIGTERM, 0, 0, 0); +#endif + + if (execvp(child_argv[0], child_argv) < 0) + fprintf(stderr, "execvp(): %s\n", strerror(errno)); + + _exit(1); + + } else { + + /* parent */ + dead = 0; + } +} + +static void suspend_complete(pa_context *c, int success, void *userdata) { + static int n = 0; + + n++; + + if (!success) { + fprintf(stderr, "Failure to suspend: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (n >= 2) + start_child(); +} + +static void resume_complete(pa_context *c, int success, void *userdata) { + static int n = 0; + + n++; + + if (!success) { + fprintf(stderr, "Failure to resume: %s\n", pa_strerror(pa_context_errno(c))); + quit(1); + return; + } + + if (n >= 2) + drain(); /* drain and quit */ +} + +static void context_state_callback(pa_context *c, void *userdata) { + pa_assert(c); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + if (pa_context_is_local(c)) { + pa_operation_unref(pa_context_suspend_sink_by_index(c, PA_INVALID_INDEX, 1, suspend_complete, NULL)); + pa_operation_unref(pa_context_suspend_source_by_index(c, PA_INVALID_INDEX, 1, suspend_complete, NULL)); + } else { + fprintf(stderr, "WARNING: Sound server is not local, not suspending.\n"); + start_child(); + } + + break; + + case PA_CONTEXT_TERMINATED: + quit(0); + break; + + case PA_CONTEXT_FAILED: + default: + fprintf(stderr, "Connection failure: %s\n", pa_strerror(pa_context_errno(c))); + + pa_context_unref(context); + context = NULL; + + if (child_pid == (pid_t) -1) + /* not started yet, then we do it now */ + start_child(); + else if (dead) + /* already started, and dead, so let's quit */ + quit(1); + + break; + } +} + +static void sigint_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) { + fprintf(stderr, "Got SIGINT, exiting.\n"); + quit(0); +} + +static void sigchld_callback(pa_mainloop_api *m, pa_signal_event *e, int sig, void *userdata) { + int status = 0; + pid_t p; + + p = waitpid(-1, &status, WNOHANG); + + if (p != child_pid) + return; + + dead = 1; + + if (WIFEXITED(status)) + child_ret = WEXITSTATUS(status); + else if (WIFSIGNALED(status)) { + fprintf(stderr, "WARNING: Child process terminated by signal %u\n", WTERMSIG(status)); + child_ret = 1; + } + + if (context) { + if (pa_context_is_local(context)) { + /* A context is around, so let's resume */ + pa_operation_unref(pa_context_suspend_sink_by_index(context, PA_INVALID_INDEX, 0, resume_complete, NULL)); + pa_operation_unref(pa_context_suspend_source_by_index(context, PA_INVALID_INDEX, 0, resume_complete, NULL)); + } else + drain(); + } else + /* Hmm, no context here, so let's terminate right away */ + quit(0); +} + +static void help(const char *argv0) { + + printf("%s [options] ... \n\n" + " -h, --help Show this help\n" + " --version Show version\n" + " -s, --server=SERVER The name of the server to connect to\n\n", + argv0); +} + +enum { + ARG_VERSION = 256 +}; + +int main(int argc, char *argv[]) { + pa_mainloop* m = NULL; + int c, ret = 1; + char *server = NULL, *bn; + + static const struct option long_options[] = { + {"server", 1, NULL, 's'}, + {"version", 0, NULL, ARG_VERSION}, + {"help", 0, NULL, 'h'}, + {NULL, 0, NULL, 0} + }; + + if (!(bn = strrchr(argv[0], '/'))) + bn = argv[0]; + else + bn++; + + while ((c = getopt_long(argc, argv, "s:h", long_options, NULL)) != -1) { + switch (c) { + case 'h' : + help(bn); + ret = 0; + goto quit; + + case ARG_VERSION: + printf("pasuspender "PACKAGE_VERSION"\nCompiled with libpulse %s\nLinked with libpulse %s\n", pa_get_headers_version(), pa_get_library_version()); + ret = 0; + goto quit; + + case 's': + pa_xfree(server); + server = pa_xstrdup(optarg); + break; + + default: + goto quit; + } + } + + child_argv = argv + optind; + child_argc = argc - optind; + + if (child_argc <= 0) { + help(bn); + ret = 0; + goto quit; + } + + if (!(m = pa_mainloop_new())) { + fprintf(stderr, "pa_mainloop_new() failed.\n"); + goto quit; + } + + pa_assert_se(mainloop_api = pa_mainloop_get_api(m)); + pa_assert_se(pa_signal_init(mainloop_api) == 0); + pa_signal_new(SIGINT, sigint_callback, NULL); + pa_signal_new(SIGCHLD, sigchld_callback, NULL); +#ifdef SIGPIPE + signal(SIGPIPE, SIG_IGN); +#endif + + if (!(context = pa_context_new(mainloop_api, bn))) { + fprintf(stderr, "pa_context_new() failed.\n"); + goto quit; + } + + pa_context_set_state_callback(context, context_state_callback, NULL); + pa_context_connect(context, server, PA_CONTEXT_NOAUTOSPAWN, NULL); + + if (pa_mainloop_run(m, &ret) < 0) { + fprintf(stderr, "pa_mainloop_run() failed.\n"); + goto quit; + } + +quit: + if (context) + pa_context_unref(context); + + if (m) { + pa_signal_done(); + pa_mainloop_free(m); + } + + pa_xfree(server); + + if (!dead) + kill(child_pid, SIGTERM); + + return ret == 0 ? child_ret : ret; +} diff --git a/src/utils/pax11publish.c b/src/utils/pax11publish.c new file mode 100644 index 00000000..9a50f8ef --- /dev/null +++ b/src/utils/pax11publish.c @@ -0,0 +1,222 @@ +/* $Id$ */ + +/*** + This file is part of PulseAudio. + + Copyright 2004-2006 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 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 <getopt.h> +#include <string.h> +#include <assert.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> + +#include <pulse/util.h> + +#include <pulsecore/core-util.h> +#include <pulsecore/log.h> +#include <pulsecore/authkey.h> +#include <pulsecore/native-common.h> +#include <pulsecore/x11prop.h> + +#include "../pulse/client-conf.h" + +int main(int argc, char *argv[]) { + const char *dname = NULL, *sink = NULL, *source = NULL, *server = NULL, *cookie_file = PA_NATIVE_COOKIE_FILE; + int c, ret = 1; + Display *d = NULL; + enum { DUMP, EXPORT, IMPORT, REMOVE } mode = DUMP; + + while ((c = getopt(argc, argv, "deiD:S:O:I:c:hr")) != -1) { + switch (c) { + case 'D' : + dname = optarg; + break; + case 'h': + printf("%s [-D display] [-S server] [-O sink] [-I source] [-c file] [-d|-e|-i|-r]\n\n" + " -d Show current PulseAudio data attached to X11 display (default)\n" + " -e Export local PulseAudio data to X11 display\n" + " -i Import PulseAudio data from X11 display to local environment variables and cookie file.\n" + " -r Remove PulseAudio data from X11 display\n", + pa_path_get_filename(argv[0])); + ret = 0; + goto finish; + case 'd': + mode = DUMP; + break; + case 'e': + mode = EXPORT; + break; + case 'i': + mode = IMPORT; + break; + case 'r': + mode = REMOVE; + break; + case 'c': + cookie_file = optarg; + break; + case 'I': + source = optarg; + break; + case 'O': + sink = optarg; + break; + case 'S': + server = optarg; + break; + default: + fprintf(stderr, "Failed to parse command line.\n"); + goto finish; + } + } + + if (!(d = XOpenDisplay(dname))) { + pa_log("XOpenDisplay() failed"); + goto finish; + } + + switch (mode) { + case DUMP: { + char t[1024]; + if (pa_x11_get_prop(d, "PULSE_SERVER", t, sizeof(t))) + printf("Server: %s\n", t); + if (pa_x11_get_prop(d, "PULSE_SOURCE", t, sizeof(t))) + printf("Source: %s\n", t); + if (pa_x11_get_prop(d, "PULSE_SINK", t, sizeof(t))) + printf("Sink: %s\n", t); + if (pa_x11_get_prop(d, "PULSE_COOKIE", t, sizeof(t))) + printf("Cookie: %s\n", t); + + break; + } + + case IMPORT: { + char t[1024]; + if (pa_x11_get_prop(d, "PULSE_SERVER", t, sizeof(t))) + printf("PULSE_SERVER='%s'\nexport PULSE_SERVER\n", t); + if (pa_x11_get_prop(d, "PULSE_SOURCE", t, sizeof(t))) + printf("PULSE_SOURCE='%s'\nexport PULSE_SOURCE\n", t); + if (pa_x11_get_prop(d, "PULSE_SINK", t, sizeof(t))) + printf("PULSE_SINK='%s'\nexport PULSE_SINK\n", t); + + if (pa_x11_get_prop(d, "PULSE_COOKIE", t, sizeof(t))) { + uint8_t cookie[PA_NATIVE_COOKIE_LENGTH]; + size_t l; + if ((l = pa_parsehex(t, cookie, sizeof(cookie))) != sizeof(cookie)) { + fprintf(stderr, "Failed to parse cookie data\n"); + goto finish; + } + + if (pa_authkey_save(cookie_file, cookie, l) < 0) { + fprintf(stderr, "Failed to save cookie data\n"); + goto finish; + } + } + + break; + } + + case EXPORT: { + pa_client_conf *conf = pa_client_conf_new(); + uint8_t cookie[PA_NATIVE_COOKIE_LENGTH]; + char hx[PA_NATIVE_COOKIE_LENGTH*2+1]; + assert(conf); + + if (pa_client_conf_load(conf, NULL) < 0) { + fprintf(stderr, "Failed to load client configuration file.\n"); + goto finish; + } + + if (pa_client_conf_env(conf) < 0) { + fprintf(stderr, "Failed to read environment configuration data.\n"); + goto finish; + } + + pa_x11_del_prop(d, "PULSE_SERVER"); + pa_x11_del_prop(d, "PULSE_SINK"); + pa_x11_del_prop(d, "PULSE_SOURCE"); + pa_x11_del_prop(d, "PULSE_ID"); + pa_x11_del_prop(d, "PULSE_COOKIE"); + + if (server) + pa_x11_set_prop(d, "PULSE_SERVER", server); + else if (conf->default_server) + pa_x11_set_prop(d, "PULSE_SERVER", conf->default_server); + else { + char hn[256]; + if (!pa_get_fqdn(hn, sizeof(hn))) { + fprintf(stderr, "Failed to get FQDN.\n"); + goto finish; + } + + pa_x11_set_prop(d, "PULSE_SERVER", hn); + } + + if (sink) + pa_x11_set_prop(d, "PULSE_SINK", sink); + else if (conf->default_sink) + pa_x11_set_prop(d, "PULSE_SINK", conf->default_sink); + + if (source) + pa_x11_set_prop(d, "PULSE_SOURCE", source); + if (conf->default_source) + pa_x11_set_prop(d, "PULSE_SOURCE", conf->default_source); + + pa_client_conf_free(conf); + + if (pa_authkey_load_auto(cookie_file, cookie, sizeof(cookie)) < 0) { + fprintf(stderr, "Failed to load cookie data\n"); + goto finish; + } + + pa_x11_set_prop(d, "PULSE_COOKIE", pa_hexstr(cookie, sizeof(cookie), hx, sizeof(hx))); + break; + } + + case REMOVE: + pa_x11_del_prop(d, "PULSE_SERVER"); + pa_x11_del_prop(d, "PULSE_SINK"); + pa_x11_del_prop(d, "PULSE_SOURCE"); + pa_x11_del_prop(d, "PULSE_ID"); + pa_x11_del_prop(d, "PULSE_COOKIE"); + break; + + default: + fprintf(stderr, "No yet implemented.\n"); + goto finish; + } + + ret = 0; + +finish: + + if (d) { + XSync(d, False); + XCloseDisplay(d); + } + + return ret; +} |