diff options
Diffstat (limited to 'src/pulse')
-rw-r--r-- | src/pulse/client-conf.c | 23 | ||||
-rw-r--r-- | src/pulse/client.conf.in | 2 | ||||
-rw-r--r-- | src/pulse/context.c | 76 | ||||
-rw-r--r-- | src/pulse/internal.h | 5 | ||||
-rw-r--r-- | src/pulse/introspect.c | 132 | ||||
-rw-r--r-- | src/pulse/mainloop.c | 13 | ||||
-rw-r--r-- | src/pulse/operation.h | 6 | ||||
-rw-r--r-- | src/pulse/simple.c | 63 | ||||
-rw-r--r-- | src/pulse/stream.c | 162 | ||||
-rw-r--r-- | src/pulse/stream.h | 85 | ||||
-rw-r--r-- | src/pulse/thread-mainloop.c | 13 | ||||
-rw-r--r-- | src/pulse/thread-mainloop.h | 18 | ||||
-rw-r--r-- | src/pulse/volume.c | 5 |
13 files changed, 413 insertions, 190 deletions
diff --git a/src/pulse/client-conf.c b/src/pulse/client-conf.c index 8eab1094..62c06f6a 100644 --- a/src/pulse/client-conf.c +++ b/src/pulse/client-conf.c @@ -94,17 +94,18 @@ int pa_client_conf_load(pa_client_conf *c, const char *filename) { /* Prepare the configuration parse table */ pa_config_item table[] = { - { "daemon-binary", pa_config_parse_string, &c->daemon_binary, NULL }, - { "extra-arguments", pa_config_parse_string, &c->extra_arguments, NULL }, - { "default-sink", pa_config_parse_string, &c->default_sink, NULL }, - { "default-source", pa_config_parse_string, &c->default_source, NULL }, - { "default-server", pa_config_parse_string, &c->default_server, NULL }, - { "default-dbus-server", pa_config_parse_string, &c->default_dbus_server, NULL }, - { "autospawn", pa_config_parse_bool, &c->autospawn, NULL }, - { "cookie-file", pa_config_parse_string, &c->cookie_file, NULL }, - { "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL }, - { "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL }, - { NULL, NULL, NULL, NULL }, + { "daemon-binary", pa_config_parse_string, &c->daemon_binary, NULL }, + { "extra-arguments", pa_config_parse_string, &c->extra_arguments, NULL }, + { "default-sink", pa_config_parse_string, &c->default_sink, NULL }, + { "default-source", pa_config_parse_string, &c->default_source, NULL }, + { "default-server", pa_config_parse_string, &c->default_server, NULL }, + { "default-dbus-server", pa_config_parse_string, &c->default_dbus_server, NULL }, + { "autospawn", pa_config_parse_bool, &c->autospawn, NULL }, + { "cookie-file", pa_config_parse_string, &c->cookie_file, NULL }, + { "disable-shm", pa_config_parse_bool, &c->disable_shm, NULL }, + { "enable-shm", pa_config_parse_not_bool, &c->disable_shm, NULL }, + { "shm-size-bytes", pa_config_parse_size, &c->shm_size, NULL }, + { NULL, NULL, NULL, NULL }, }; if (filename) { diff --git a/src/pulse/client.conf.in b/src/pulse/client.conf.in index 3340b0b2..e03096e0 100644 --- a/src/pulse/client.conf.in +++ b/src/pulse/client.conf.in @@ -30,5 +30,5 @@ ; cookie-file = -; disable-shm = no +; enable-shm = yes ; shm-size-bytes = 0 # setting this 0 will use the system-default, usually 64 MiB diff --git a/src/pulse/context.c b/src/pulse/context.c index 505e758a..894ab2e0 100644 --- a/src/pulse/context.c +++ b/src/pulse/context.c @@ -668,11 +668,24 @@ static pa_strlist *prepend_per_user(pa_strlist *l) { static int context_autospawn(pa_context *c) { pid_t pid; int status, r; - - pa_log_debug("Trying to autospawn..."); + struct sigaction sa; pa_context_ref(c); + if (sigaction(SIGCHLD, NULL, &sa) < 0) { + pa_log_debug("sigaction() failed: %s", pa_cstrerror(errno)); + pa_context_fail(c, PA_ERR_INTERNAL); + goto fail; + } + + if ((sa.sa_flags & SA_NOCLDWAIT) || sa.sa_handler == SIG_IGN) { + pa_log_debug("Process disabled waitpid(), cannot autospawn."); + pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); + goto fail; + } + + pa_log_debug("Trying to autospawn..."); + if (c->spawn_api.prefork) c->spawn_api.prefork(); @@ -688,23 +701,23 @@ static int context_autospawn(pa_context *c) { /* Child */ const char *state = NULL; -#define MAX_ARGS 64 - const char * argv[MAX_ARGS+1]; - int n; + const char * argv[32]; + unsigned n = 0; if (c->spawn_api.atfork) c->spawn_api.atfork(); + /* We leave most of the cleaning up of the process environment + * to the executable. We only clean up the file descriptors to + * make sure the executable can actually be loaded + * correctly. */ pa_close_all(-1); /* Setup argv */ - - n = 0; - argv[n++] = c->conf->daemon_binary; argv[n++] = "--start"; - while (n < MAX_ARGS) { + while (n < PA_ELEMENTSOF(argv)-1) { char *a; if (!(a = pa_split_spaces(c->conf->extra_arguments, &state))) @@ -714,10 +727,10 @@ static int context_autospawn(pa_context *c) { } argv[n++] = NULL; + pa_assert(n <= PA_ELEMENTSOF(argv)); execv(argv[0], (char * const *) argv); _exit(1); -#undef MAX_ARGS } /* Parent */ @@ -730,9 +743,16 @@ static int context_autospawn(pa_context *c) { } while (r < 0 && errno == EINTR); if (r < 0) { - pa_log(_("waitpid(): %s"), pa_cstrerror(errno)); - pa_context_fail(c, PA_ERR_INTERNAL); - goto fail; + + if (errno != ESRCH) { + pa_log(_("waitpid(): %s"), pa_cstrerror(errno)); + pa_context_fail(c, PA_ERR_INTERNAL); + goto fail; + } + + /* hmm, something already reaped our child, so we assume + * startup worked, even if we cannot know */ + } else if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { pa_context_fail(c, PA_ERR_CONNECTIONREFUSED); goto fail; @@ -761,22 +781,33 @@ static void track_pulseaudio_on_dbus(pa_context *c, DBusBusType type, pa_dbus_wr pa_assert(conn); dbus_error_init(&error); + if (!(*conn = pa_dbus_wrap_connection_new(c->mainloop, c->use_rtclock, type, &error)) || dbus_error_is_set(&error)) { pa_log_warn("Unable to contact DBUS: %s: %s", error.name, error.message); - goto finish; + goto fail; } if (!dbus_connection_add_filter(pa_dbus_wrap_connection_get(*conn), filter_cb, c, NULL)) { pa_log_warn("Failed to add filter function"); - goto finish; + goto fail; } if (pa_dbus_add_matches( pa_dbus_wrap_connection_get(*conn), &error, - "type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='NameOwnerChanged',arg0='org.pulseaudio.Server',arg1=''", NULL) < 0) + "type='signal',sender='" DBUS_SERVICE_DBUS "',interface='" DBUS_INTERFACE_DBUS "',member='NameOwnerChanged',arg0='org.pulseaudio.Server',arg1=''", NULL) < 0) { + pa_log_warn("Unable to track org.pulseaudio.Server: %s: %s", error.name, error.message); + goto fail; + } + + return; + +fail: + if (*conn) { + pa_dbus_wrap_connection_free(*conn); + *conn = NULL; + } - finish: dbus_error_free(&error); } #endif @@ -861,7 +892,7 @@ static void on_connection(pa_socket_client *client, pa_iochannel*io, void *userd c->client = NULL; if (!io) { - /* Try the item in the list */ + /* Try the next item in the list */ if (saved_errno == ECONNREFUSED || saved_errno == ETIMEDOUT || saved_errno == EHOSTUNREACH) { @@ -897,7 +928,7 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *message, vo /* FIXME: We probably should check if this is actually the NameOwnerChanged we were looking for */ is_session = c->session_bus && bus == pa_dbus_wrap_connection_get(c->session_bus); - pa_log_debug("Rock!! PulseAudio is back on %s bus", is_session ? "session" : "system"); + pa_log_debug("Rock!! PulseAudio might be back on %s bus", is_session ? "session" : "system"); if (is_session) /* The user instance via PF_LOCAL */ @@ -937,7 +968,7 @@ int pa_context_connect( pa_context_ref(c); - c->no_fail = flags & PA_CONTEXT_NOFAIL; + c->no_fail = !!(flags & PA_CONTEXT_NOFAIL); c->server_specified = !!server; pa_assert(!c->server_list); @@ -954,10 +985,7 @@ int pa_context_connect( /* Follow the X display */ if ((d = getenv("DISPLAY"))) { - char *e; - d = pa_xstrdup(d); - if ((e = strchr(d, ':'))) - *e = 0; + d = pa_xstrndup(d, strcspn(d, ":")); if (*d) c->server_list = pa_strlist_prepend(c->server_list, d); diff --git a/src/pulse/internal.h b/src/pulse/internal.h index ec2da85b..e069c9e9 100644 --- a/src/pulse/internal.h +++ b/src/pulse/internal.h @@ -151,6 +151,11 @@ struct pa_stream { uint32_t device_index; char *device_name; + /* playback */ + pa_memblock *write_memblock; + void *write_data; + + /* recording */ pa_memchunk peek_memchunk; void *peek_data; pa_memblockq *record_memblockq; diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c index ab67f596..27a587cb 100644 --- a/src/pulse/introspect.c +++ b/src/pulse/introspect.c @@ -201,42 +201,44 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, u goto finish; } - if (i.n_ports > 0) { - i.ports = pa_xnew(pa_sink_port_info*, i.n_ports+1); - i.ports[0] = pa_xnew(pa_sink_port_info, i.n_ports); - - for (j = 0; j < i.n_ports; j++) { - if (pa_tagstruct_gets(t, &i.ports[0][j].name) < 0 || - pa_tagstruct_gets(t, &i.ports[0][j].description) < 0 || - pa_tagstruct_getu32(t, &i.ports[0][j].priority) < 0) { - - pa_context_fail(o->context, PA_ERR_PROTOCOL); - pa_xfree(i.ports); - pa_xfree(i.ports[0]); - pa_proplist_free(i.proplist); - goto finish; + if (o->context->version >= 16) { + if (i.n_ports > 0) { + i.ports = pa_xnew(pa_sink_port_info*, i.n_ports+1); + i.ports[0] = pa_xnew(pa_sink_port_info, i.n_ports); + + for (j = 0; j < i.n_ports; j++) { + if (pa_tagstruct_gets(t, &i.ports[0][j].name) < 0 || + pa_tagstruct_gets(t, &i.ports[0][j].description) < 0 || + pa_tagstruct_getu32(t, &i.ports[0][j].priority) < 0) { + + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + pa_proplist_free(i.proplist); + goto finish; + } + + i.ports[j] = &i.ports[0][j]; } - i.ports[j] = &i.ports[0][j]; + i.ports[j] = NULL; } - i.ports[j] = NULL; - } - - if (pa_tagstruct_gets(t, &ap) < 0) { - pa_context_fail(o->context, PA_ERR_PROTOCOL); - pa_xfree(i.ports[0]); - pa_xfree(i.ports); - pa_proplist_free(i.proplist); - goto finish; - } + if (pa_tagstruct_gets(t, &ap) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + pa_proplist_free(i.proplist); + goto finish; + } - if (ap) { - for (j = 0; j < i.n_ports; j++) - if (pa_streq(i.ports[j]->name, ap)) { - i.active_port = i.ports[j]; - break; - } + if (ap) { + for (j = 0; j < i.n_ports; j++) + if (pa_streq(i.ports[j]->name, ap)) { + i.active_port = i.ports[j]; + break; + } + } } i.mute = (int) mute; @@ -248,6 +250,10 @@ static void context_get_sink_info_callback(pa_pdispatch *pd, uint32_t command, u cb(o->context, &i, 0, o->userdata); } + if (i.ports) { + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + } pa_proplist_free(i.proplist); } } @@ -428,42 +434,44 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command, goto finish; } - if (i.n_ports > 0) { - i.ports = pa_xnew(pa_source_port_info*, i.n_ports+1); - i.ports[0] = pa_xnew(pa_source_port_info, i.n_ports); + if (o->context->version >= 16) { + if (i.n_ports > 0) { + i.ports = pa_xnew(pa_source_port_info*, i.n_ports+1); + i.ports[0] = pa_xnew(pa_source_port_info, i.n_ports); - for (j = 0; j < i.n_ports; j++) { - if (pa_tagstruct_gets(t, &i.ports[0][j].name) < 0 || - pa_tagstruct_gets(t, &i.ports[0][j].description) < 0 || - pa_tagstruct_getu32(t, &i.ports[0][j].priority) < 0) { + for (j = 0; j < i.n_ports; j++) { + if (pa_tagstruct_gets(t, &i.ports[0][j].name) < 0 || + pa_tagstruct_gets(t, &i.ports[0][j].description) < 0 || + pa_tagstruct_getu32(t, &i.ports[0][j].priority) < 0) { - pa_context_fail(o->context, PA_ERR_PROTOCOL); - pa_xfree(i.ports[0]); - pa_xfree(i.ports); - pa_proplist_free(i.proplist); - goto finish; + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + pa_proplist_free(i.proplist); + goto finish; + } + + i.ports[j] = &i.ports[0][j]; } - i.ports[j] = &i.ports[0][j]; + i.ports[j] = NULL; } - i.ports[j] = NULL; - } - - if (pa_tagstruct_gets(t, &ap) < 0) { - pa_context_fail(o->context, PA_ERR_PROTOCOL); - pa_xfree(i.ports[0]); - pa_xfree(i.ports); - pa_proplist_free(i.proplist); - goto finish; - } + if (pa_tagstruct_gets(t, &ap) < 0) { + pa_context_fail(o->context, PA_ERR_PROTOCOL); + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + pa_proplist_free(i.proplist); + goto finish; + } - if (ap) { - for (j = 0; j < i.n_ports; j++) - if (pa_streq(i.ports[j]->name, ap)) { - i.active_port = i.ports[j]; - break; - } + if (ap) { + for (j = 0; j < i.n_ports; j++) + if (pa_streq(i.ports[j]->name, ap)) { + i.active_port = i.ports[j]; + break; + } + } } i.mute = (int) mute; @@ -475,6 +483,10 @@ static void context_get_source_info_callback(pa_pdispatch *pd, uint32_t command, cb(o->context, &i, 0, o->userdata); } + if (i.ports) { + pa_xfree(i.ports[0]); + pa_xfree(i.ports); + } pa_proplist_free(i.proplist); } } diff --git a/src/pulse/mainloop.c b/src/pulse/mainloop.c index c418d108..93a4742d 100644 --- a/src/pulse/mainloop.c +++ b/src/pulse/mainloop.c @@ -765,23 +765,22 @@ static pa_time_event* find_next_time_event(pa_mainloop *m) { static int calc_next_timeout(pa_mainloop *m) { pa_time_event *t; - pa_usec_t usec; + pa_usec_t clock_now; if (!m->n_enabled_time_events) return -1; - t = find_next_time_event(m); - pa_assert(t); + pa_assert_se(t = find_next_time_event(m)); - if (t->time == 0) + if (t->time <= 0) return 0; - usec = t->time - pa_rtclock_now(); + clock_now = pa_rtclock_now(); - if (usec <= 0) + if (t->time <= clock_now) return 0; - return (int) (usec / 1000); /* in milliseconds */ + return (int) ((t->time - clock_now) / 1000); /* in milliseconds */ } static int dispatch_timeout(pa_mainloop *m) { diff --git a/src/pulse/operation.h b/src/pulse/operation.h index 7b0dabdd..b6b5691d 100644 --- a/src/pulse/operation.h +++ b/src/pulse/operation.h @@ -40,7 +40,11 @@ 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. */ +/** Cancel the operation. Beware! This will not necessarily cancel the + * execution of the operation on the server side. However it will make + * sure that the callback associated with this operation will not be + * called anymore, effectively disabling the operation from the client + * side's view. */ void pa_operation_cancel(pa_operation *o); /** Return the current status of the operation */ diff --git a/src/pulse/simple.c b/src/pulse/simple.c index f4481fc3..9ed7a653 100644 --- a/src/pulse/simple.c +++ b/src/pulse/simple.c @@ -70,8 +70,8 @@ struct pa_simple { #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_IS_GOOD(pa_context_get_state((p)->context)) || \ + !(p)->stream || !PA_STREAM_IS_GOOD(pa_stream_get_state((p)->stream))) { \ 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) \ @@ -157,12 +157,8 @@ pa_simple* pa_simple_new( 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 = pa_xnew0(pa_simple, 1); p->direction = dir; - p->read_data = NULL; - p->read_index = p->read_length = 0; if (!(p->mainloop = pa_threaded_mainloop_new())) goto fail; @@ -182,12 +178,21 @@ pa_simple* pa_simple_new( if (pa_threaded_mainloop_start(p->mainloop) < 0) goto unlock_and_fail; - /* Wait until the context is ready */ - pa_threaded_mainloop_wait(p->mainloop); + for (;;) { + pa_context_state_t state; - if (pa_context_get_state(p->context) != PA_CONTEXT_READY) { - error = pa_context_errno(p->context); - goto unlock_and_fail; + state = pa_context_get_state(p->context); + + if (state == PA_CONTEXT_READY) + break; + + if (!PA_CONTEXT_IS_GOOD(state)) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + /* Wait until the context is ready */ + pa_threaded_mainloop_wait(p->mainloop); } if (!(p->stream = pa_stream_new(p->context, stream_name, ss, map))) { @@ -216,13 +221,21 @@ pa_simple* pa_simple_new( goto unlock_and_fail; } - /* Wait until the stream is ready */ - pa_threaded_mainloop_wait(p->mainloop); + for (;;) { + pa_stream_state_t state; - /* 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; + state = pa_stream_get_state(p->stream); + + if (state == PA_STREAM_READY) + break; + + if (!PA_STREAM_IS_GOOD(state)) { + error = pa_context_errno(p->context); + goto unlock_and_fail; + } + + /* Wait until the stream is ready */ + pa_threaded_mainloop_wait(p->mainloop); } pa_threaded_mainloop_unlock(p->mainloop); @@ -248,8 +261,10 @@ void pa_simple_free(pa_simple *s) { if (s->stream) pa_stream_unref(s->stream); - if (s->context) + if (s->context) { + pa_context_disconnect(s->context); pa_context_unref(s->context); + } if (s->mainloop) pa_threaded_mainloop_free(s->mainloop); @@ -261,7 +276,8 @@ 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); + CHECK_VALIDITY_RETURN_ANY(rerror, data, PA_ERR_INVALID, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, length > 0, PA_ERR_INVALID, -1); pa_threaded_mainloop_lock(p->mainloop); @@ -300,7 +316,8 @@ 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); + CHECK_VALIDITY_RETURN_ANY(rerror, data, PA_ERR_INVALID, -1); + CHECK_VALIDITY_RETURN_ANY(rerror, length > 0, PA_ERR_INVALID, -1); pa_threaded_mainloop_lock(p->mainloop); @@ -375,7 +392,7 @@ int pa_simple_drain(pa_simple *p, int *rerror) { CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail); p->operation_success = 0; - while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { pa_threaded_mainloop_wait(p->mainloop); CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); } @@ -411,7 +428,7 @@ int pa_simple_flush(pa_simple *p, int *rerror) { CHECK_SUCCESS_GOTO(p, rerror, o, unlock_and_fail); p->operation_success = 0; - while (pa_operation_get_state(o) != PA_OPERATION_DONE) { + while (pa_operation_get_state(o) == PA_OPERATION_RUNNING) { pa_threaded_mainloop_wait(p->mainloop); CHECK_DEAD_GOTO(p, rerror, unlock_and_fail); } diff --git a/src/pulse/stream.c b/src/pulse/stream.c index 40556329..2bc2b1e4 100644 --- a/src/pulse/stream.c +++ b/src/pulse/stream.c @@ -144,12 +144,13 @@ pa_stream *pa_stream_new_with_proplist( s->suspended = FALSE; s->corked = FALSE; + s->write_memblock = NULL; + s->write_data = NULL; + pa_memchunk_reset(&s->peek_memchunk); s->peek_data = NULL; - s->record_memblockq = NULL; - memset(&s->timing_info, 0, sizeof(s->timing_info)); s->timing_info_valid = FALSE; @@ -221,6 +222,11 @@ static void stream_free(pa_stream *s) { stream_unlink(s); + if (s->write_memblock) { + pa_memblock_release(s->write_memblock); + pa_memblock_unref(s->write_data); + } + if (s->peek_memchunk.memblock) { if (s->peek_data) pa_memblock_release(s->peek_memchunk.memblock); @@ -821,7 +827,7 @@ static void create_stream_complete(pa_stream *s) { if (s->flags & PA_STREAM_AUTO_TIMING_UPDATE) { s->auto_timing_interval_usec = AUTO_TIMING_INTERVAL_START_USEC; pa_assert(!s->auto_timing_update_event); - s->auto_timing_update_event = pa_context_rttime_new(s->context, pa_rtclock_now() + s->auto_timing_interval_usec, &auto_timing_update_callback, s); + s->auto_timing_update_event = pa_context_rttime_new(s->context, pa_rtclock_now() + s->auto_timing_interval_usec, &auto_timing_update_callback, s); request_auto_timing_update(s, TRUE); } @@ -861,7 +867,7 @@ static void automatic_buffer_attr(pa_stream *s, pa_buffer_attr *attr, const pa_s void pa_create_stream_callback(pa_pdispatch *pd, uint32_t command, uint32_t tag, pa_tagstruct *t, void *userdata) { pa_stream *s = userdata; - uint32_t requested_bytes; + uint32_t requested_bytes = 0; pa_assert(pd); pa_assert(s); @@ -1166,7 +1172,7 @@ int pa_stream_connect_playback( const char *dev, const pa_buffer_attr *attr, pa_stream_flags_t flags, - pa_cvolume *volume, + const pa_cvolume *volume, pa_stream *sync_stream) { pa_assert(s); @@ -1187,20 +1193,71 @@ int pa_stream_connect_record( return create_stream(PA_STREAM_RECORD, s, dev, attr, flags, NULL, NULL); } +int pa_stream_begin_write( + pa_stream *s, + void **data, + size_t *nbytes) { + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + 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, data, PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, nbytes && *nbytes != 0, PA_ERR_INVALID); + + if (*nbytes != (size_t) -1) { + size_t m, fs; + + m = pa_mempool_block_size_max(s->context->mempool); + fs = pa_frame_size(&s->sample_spec); + + m = (m / fs) * fs; + if (*nbytes > m) + *nbytes = m; + } + + if (!s->write_memblock) { + s->write_memblock = pa_memblock_new(s->context->mempool, *nbytes); + s->write_data = pa_memblock_acquire(s->write_memblock); + } + + *data = s->write_data; + *nbytes = pa_memblock_get_length(s->write_memblock); + + return 0; +} + +int pa_stream_cancel_write( + pa_stream *s) { + + pa_assert(s); + pa_assert(PA_REFCNT_VALUE(s) >= 1); + + PA_CHECK_VALIDITY(s->context, !pa_detect_fork(), PA_ERR_FORKED); + 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, s->write_memblock, PA_ERR_BADSTATE); + + pa_assert(s->write_data); + + pa_memblock_release(s->write_memblock); + pa_memblock_unref(s->write_memblock); + s->write_memblock = NULL; + s->write_data = NULL; + + return 0; +} + int pa_stream_write( pa_stream *s, const void *data, size_t length, - void (*free_cb)(void *p), + pa_free_cb_t free_cb, int64_t offset, pa_seek_mode_t seek) { - pa_memchunk chunk; - pa_seek_mode_t t_seek; - int64_t t_offset; - size_t t_length; - const void *t_data; - pa_assert(s); pa_assert(PA_REFCNT_VALUE(s) >= 1); pa_assert(data); @@ -1210,46 +1267,71 @@ int pa_stream_write( 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); + PA_CHECK_VALIDITY(s->context, + !s->write_memblock || + ((data >= s->write_data) && + ((const char*) data + length <= (const char*) s->write_data + pa_memblock_get_length(s->write_memblock))), + PA_ERR_INVALID); + PA_CHECK_VALIDITY(s->context, !free_cb || !s->write_memblock, PA_ERR_INVALID); - if (length <= 0) - return 0; + if (s->write_memblock) { + pa_memchunk chunk; - t_seek = seek; - t_offset = offset; - t_length = length; - t_data = data; + /* pa_stream_write_begin() was called before */ - while (t_length > 0) { + pa_memblock_release(s->write_memblock); - chunk.index = 0; + chunk.memblock = s->write_memblock; + chunk.index = (const char *) data - (const char *) s->write_data; + chunk.length = length; - if (free_cb && !pa_pstream_get_shm(s->context->pstream)) { - chunk.memblock = pa_memblock_new_user(s->context->mempool, (void*) t_data, t_length, free_cb, 1); - chunk.length = t_length; - } else { - void *d; + s->write_memblock = NULL; + s->write_data = NULL; - chunk.length = PA_MIN(t_length, pa_mempool_block_size_max(s->context->mempool)); - chunk.memblock = pa_memblock_new(s->context->mempool, chunk.length); + pa_pstream_send_memblock(s->context->pstream, s->channel, offset, seek, &chunk); + pa_memblock_unref(chunk.memblock); - d = pa_memblock_acquire(chunk.memblock); - memcpy(d, t_data, chunk.length); - pa_memblock_release(chunk.memblock); - } + } else { + pa_seek_mode_t t_seek = seek; + int64_t t_offset = offset; + size_t t_length = length; + const void *t_data = data; - pa_pstream_send_memblock(s->context->pstream, s->channel, t_offset, t_seek, &chunk); + /* pa_stream_write_begin() was not called before */ - t_offset = 0; - t_seek = PA_SEEK_RELATIVE; + while (t_length > 0) { + pa_memchunk chunk; - t_data = (const uint8_t*) t_data + chunk.length; - t_length -= chunk.length; + chunk.index = 0; - pa_memblock_unref(chunk.memblock); - } + if (free_cb && !pa_pstream_get_shm(s->context->pstream)) { + chunk.memblock = pa_memblock_new_user(s->context->mempool, (void*) t_data, t_length, free_cb, 1); + chunk.length = t_length; + } else { + void *d; + + chunk.length = PA_MIN(t_length, pa_mempool_block_size_max(s->context->mempool)); + chunk.memblock = pa_memblock_new(s->context->mempool, chunk.length); + + d = pa_memblock_acquire(chunk.memblock); + memcpy(d, t_data, chunk.length); + pa_memblock_release(chunk.memblock); + } - if (free_cb && pa_pstream_get_shm(s->context->pstream)) - free_cb((void*) data); + pa_pstream_send_memblock(s->context->pstream, s->channel, t_offset, t_seek, &chunk); + + t_offset = 0; + t_seek = PA_SEEK_RELATIVE; + + t_data = (const uint8_t*) t_data + chunk.length; + t_length -= chunk.length; + + pa_memblock_unref(chunk.memblock); + } + + if (free_cb && pa_pstream_get_shm(s->context->pstream)) + free_cb((void*) data); + } /* This is obviously wrong since we ignore the seeking index . But * that's OK, the server side applies the same error */ diff --git a/src/pulse/stream.h b/src/pulse/stream.h index 49c132a2..8a08421f 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -405,7 +405,7 @@ int pa_stream_connect_playback( 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 */, + const 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 */ @@ -418,15 +418,71 @@ int pa_stream_connect_record( /** 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 +/** Prepare writing data to the server (for playback streams). This + * function may be used to optimize the number of memory copies when + * doing playback ("zero-copy"). It is recommended to call this + * function before each call to pa_stream_write(). Pass in the address + * to a pointer and an address of the number of bytes you want to + * write. On return the two values will contain a pointer where you + * can place the data to write and the maximum number of bytes you can + * write. On return *nbytes can be smaller or have the same value as + * you passed in. You need to be able to handle both cases. Accessing + * memory beyond the returned *nbytes value is invalid. Acessing the + * memory returned after the following pa_stream_write() or + * pa_stream_cancel_write() is invalid. On invocation only *nbytes + * needs to be initialized, on return both *data and *nbytes will be + * valid. If you place (size_t) -1 in *nbytes on invocation the memory + * size will be chosen automatically (which is recommended to + * do). After placing your data in the memory area returned call + * pa_stream_write() with data set to an address within this memory + * area and an nbytes value that is smaller or equal to what was + * returned by this function to actually execute the write. An + * invocation of pa_stream_write() should follow "quickly" on + * pa_stream_begin_write(). It is not recommended letting an unbounded + * amount of time pass after calling pa_stream_begin_write() and + * before calling pa_stream_write(). If you want to cancel a + * previously called pa_stream_begin_write() without calling + * pa_stream_write() use pa_stream_cancel_write(). Calling + * pa_stream_begin_write() twice without calling pa_stream_write() or + * pa_stream_cancel_write() in between will return exactly the same + * pointer/nbytes values.\since 0.9.16 */ +int pa_stream_begin_write( + pa_stream *p, + void **data, + size_t *nbytes); + +/** Reverses the effect of pa_stream_begin_write() dropping all data + * that has already been placed in the memory area returned by + * pa_stream_begin_write(). Only valid to call if + * pa_stream_begin_write() was called before and neither + * pa_stream_cancel_write() nor pa_stream_write() have been called + * yet. Accessing the memory previously returned by + * pa_stream_begin_write() after this call is invalid. Any further + * explicit freeing of the memory area is not necessary. \since + * 0.9.16 */ +int pa_stream_cancel_write( + pa_stream *p); + +/** Write some data to the server (for playback streams), 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 may 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. Afte ther write call succeeded * the write index will be a the position after where this chunk of - * data has been written to. */ + * data has been written to. + * + * As an optimization for avoiding needless memory copies you may call + * pa_stream_begin_write() before this call and then place your audio + * data directly in the memory area returned by that call. Then, pass + * a pointer to that memory area to pa_stream_write(). After the + * invocation of pa_stream_write() the memory area may no longer be + * accessed. Any further explicit freeing of the memory area is not + * necessary. It is OK to write the memory area returned by + * pa_stream_begin_write() only partially with this call, skipping + * bytes both at the end and at the beginning of the reserved memory + * area.*/ int pa_stream_write( pa_stream *p /**< The stream to use */, const void *data /**< The data to write */, @@ -435,11 +491,12 @@ int pa_stream_write( 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 */ +/** Read the next fragment from the buffer (for recording streams). + * data will point to the actual data and nbytes will contain the size + * of the data in bytes (which can be less or more than a complete + * fragment). Use pa_stream_drop() to actually remove the data from + * the buffer. If no data is available this will return a NULL + * pointer */ int pa_stream_peek( pa_stream *p /**< The stream to use */, const void **data /**< Pointer to pointer that will point to data */, @@ -455,7 +512,9 @@ size_t pa_stream_writable_size(pa_stream *p); /** Return the number of bytes that may be read using pa_stream_peek()*/ size_t pa_stream_readable_size(pa_stream *p); -/** Drain a playback stream. Use this for notification when the buffer is empty */ +/** Drain a playback stream. Use this for notification when the buffer + * is empty. Please note that only one drain operation per stream may + * be issued at a time. */ 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 diff --git a/src/pulse/thread-mainloop.c b/src/pulse/thread-mainloop.c index 6916d867..a2b98ce1 100644 --- a/src/pulse/thread-mainloop.c +++ b/src/pulse/thread-mainloop.c @@ -51,7 +51,7 @@ struct pa_threaded_mainloop { pa_mainloop *real_mainloop; - int n_waiting; + int n_waiting, n_waiting_for_accept; pa_thread* thread; pa_mutex* mutex; @@ -190,8 +190,12 @@ void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept) { pa_cond_signal(m->cond, 1); - if (wait_for_accept && m->n_waiting > 0) - pa_cond_wait(m->accept_cond, m->mutex); + if (wait_for_accept) { + m->n_waiting_for_accept ++; + + while (m->n_waiting_for_accept > 0) + pa_cond_wait(m->accept_cond, m->mutex); + } } void pa_threaded_mainloop_wait(pa_threaded_mainloop *m) { @@ -214,6 +218,9 @@ void pa_threaded_mainloop_accept(pa_threaded_mainloop *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_assert(m->n_waiting_for_accept > 0); + m->n_waiting_for_accept --; + pa_cond_signal(m->accept_cond, 0); } diff --git a/src/pulse/thread-mainloop.h b/src/pulse/thread-mainloop.h index 8eddce4c..e847070d 100644 --- a/src/pulse/thread-mainloop.h +++ b/src/pulse/thread-mainloop.h @@ -137,15 +137,19 @@ PA_C_DECL_BEGIN * 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. + * 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. Also the underlying OS synchronization primitives + * are usually not free of spurious wake-ups, so a + * pa_threaded_mainloop_wait() must be called within a loop even if + * you have only one thread waiting. * * 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 + * As you can see, 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. @@ -274,7 +278,9 @@ void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m); * 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. */ + * will be acquired again. This function may spuriously wake up even + * without _signal() being called. You need to make sure to handle + * that! */ void pa_threaded_mainloop_wait(pa_threaded_mainloop *m); /** Signal all threads waiting for a signalling event in diff --git a/src/pulse/volume.c b/src/pulse/volume.c index 42cde5b9..c23f360b 100644 --- a/src/pulse/volume.c +++ b/src/pulse/volume.c @@ -205,9 +205,12 @@ pa_volume_t pa_sw_volume_from_linear(double v) { * * http://www.robotplanet.dk/audio/audio_gui_design/ * http://lists.linuxaudio.org/pipermail/linux-audio-dev/2009-May/thread.html#23151 + * + * We make sure that the conversion to linear and back yields the + * same volume value! That's why we need the lround() below! */ - return (pa_volume_t) (cbrt(v) * PA_VOLUME_NORM); + return (pa_volume_t) lround(cbrt(v) * PA_VOLUME_NORM); } double pa_sw_volume_to_linear(pa_volume_t v) { |