diff options
Diffstat (limited to 'src/pulse')
55 files changed, 14525 insertions, 0 deletions
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 |