diff options
Diffstat (limited to 'src')
33 files changed, 759 insertions, 261 deletions
diff --git a/src/daemon/main.c b/src/daemon/main.c index d25647cb..4c4a9052 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -334,27 +334,38 @@ static void set_all_rlimits(const pa_daemon_conf *conf) { #endif #ifdef HAVE_DBUS -static void register_org_pulseaudio(pa_core *c) -{ +static pa_dbus_connection *register_dbus(pa_core *c) { DBusError error; pa_dbus_connection *conn; dbus_error_init(&error); + if (!(conn = pa_dbus_bus_get(c, pa_in_system_mode() ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION, &error)) || dbus_error_is_set(&error)) { - pa_log_warn("Unable to contact DBUS: %s: %s", error.name, error.message); - goto finish_dbus; + pa_log_warn("Unable to contact D-Bus: %s: %s", error.name, error.message); + goto fail; } - if (dbus_bus_request_name (pa_dbus_connection_get(conn), "org.pulseaudio.Server", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) + if (dbus_bus_request_name(pa_dbus_connection_get(conn), "org.pulseaudio.Server", DBUS_NAME_FLAG_DO_NOT_QUEUE, &error) == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { pa_log_debug("Got org.pulseaudio.Server!"); - else if (dbus_error_is_set(&error)) - pa_log_warn("Unable to get org.pulseaudio.Server: %s: %s", error.name, error.message); + return conn; + } + + if (dbus_error_is_set(&error)) + pa_log_warn("Failed to acquire org.pulseaudio.Server: %s: %s", error.name, error.message); + else + pa_log_warn("D-Bus name org.pulseaudio.Server already taken. Weird shit!"); + + /* PA cannot be started twice by the same user and hence we can + * ignore mostly the case that org.pulseaudio.Server is already + * taken. */ + +fail: -finish_dbus: if (conn) pa_dbus_connection_unref(conn); dbus_error_free(&error); + return NULL; } #endif @@ -380,6 +391,9 @@ int main(int argc, char *argv[]) { #endif int autospawn_fd = -1; pa_bool_t autospawn_locked = FALSE; +#ifdef HAVE_DBUS + pa_dbus_connection *dbus = NULL; +#endif pa_log_set_ident("pulseaudio"); pa_log_set_level(PA_LOG_INFO); @@ -1026,7 +1040,7 @@ int main(int argc, char *argv[]) { #endif #ifdef HAVE_DBUS - register_org_pulseaudio(c); + dbus = register_dbus(c); #endif pa_log_info(_("Daemon startup complete.")); @@ -1038,6 +1052,10 @@ int main(int argc, char *argv[]) { pa_log_info(_("Daemon shutdown initiated.")); finish: +#ifdef HAVE_DBUS + if (dbus) + pa_dbus_connection_unref(dbus); +#endif if (autospawn_fd >= 0) { if (autospawn_locked) diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index f9fb0335..c18c34ef 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -1728,7 +1728,7 @@ pa_sink *pa_alsa_sink_new(pa_module *m, pa_modargs *ma, const char*driver, pa_ca if (setup_mixer(u, ignore_dB) < 0) goto fail; - pa_alsa_dump(u->pcm_handle); + pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index 9c36211b..9cbd79fa 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -1579,7 +1579,7 @@ pa_source *pa_alsa_source_new(pa_module *m, pa_modargs *ma, const char*driver, p if (setup_mixer(u, ignore_dB) < 0) goto fail; - pa_alsa_dump(u->pcm_handle); + pa_alsa_dump(PA_LOG_DEBUG, u->pcm_handle); if (!(u->thread = pa_thread_new(thread_func, u))) { pa_log("Failed to create thread."); diff --git a/src/modules/alsa/alsa-util.c b/src/modules/alsa/alsa-util.c index 870cf0f1..fbf88b08 100644 --- a/src/modules/alsa/alsa-util.c +++ b/src/modules/alsa/alsa-util.c @@ -1297,7 +1297,7 @@ int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel return 0; } -void pa_alsa_dump(snd_pcm_t *pcm) { +void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm) { int err; snd_output_t *out; @@ -1306,11 +1306,11 @@ void pa_alsa_dump(snd_pcm_t *pcm) { pa_assert_se(snd_output_buffer_open(&out) == 0); if ((err = snd_pcm_dump(pcm, out)) < 0) - pa_log_debug("snd_pcm_dump(): %s", snd_strerror(err)); + pa_logl(level, "snd_pcm_dump(): %s", snd_strerror(err)); else { char *s = NULL; snd_output_buffer_string(out, &s); - pa_log_debug("snd_pcm_dump():\n%s", pa_strnull(s)); + pa_logl(level, "snd_pcm_dump():\n%s", pa_strnull(s)); } pa_assert_se(snd_output_close(out) == 0); @@ -1612,6 +1612,7 @@ snd_pcm_sframes_t pa_alsa_safe_avail(snd_pcm_t *pcm, size_t hwbuf_size, const pa (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC), pa_strnull(dn)); pa_xfree(dn); + pa_alsa_dump(PA_LOG_ERROR, pcm); } PA_ONCE_END; /* Mhmm, let's try not to fail completely */ @@ -1653,6 +1654,7 @@ int pa_alsa_safe_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delay, size_t hwbuf_si (unsigned long) (pa_bytes_to_usec(abs_k, ss) / PA_USEC_PER_MSEC), pa_strnull(dn)); pa_xfree(dn); + pa_alsa_dump(PA_LOG_ERROR, pcm); } PA_ONCE_END; /* Mhmm, let's try not to fail completely */ @@ -1698,6 +1700,7 @@ int pa_alsa_safe_mmap_begin(snd_pcm_t *pcm, const snd_pcm_channel_area_t **areas (unsigned long) (pa_bytes_to_usec(k, ss) / PA_USEC_PER_MSEC), pa_strnull(dn)); pa_xfree(dn); + pa_alsa_dump(PA_LOG_ERROR, pcm); } PA_ONCE_END; return r; diff --git a/src/modules/alsa/alsa-util.h b/src/modules/alsa/alsa-util.h index 94f27d14..9fce6daf 100644 --- a/src/modules/alsa/alsa-util.h +++ b/src/modules/alsa/alsa-util.h @@ -33,6 +33,7 @@ #include <pulsecore/rtpoll.h> #include <pulsecore/core.h> +#include <pulsecore/log.h> typedef struct pa_alsa_fdlist pa_alsa_fdlist; @@ -114,7 +115,7 @@ int pa_alsa_probe_profiles( int pa_alsa_calc_mixer_map(snd_mixer_elem_t *elem, const pa_channel_map *channel_map, snd_mixer_selem_channel_id_t mixer_map[], pa_bool_t playback); -void pa_alsa_dump(snd_pcm_t *pcm); +void pa_alsa_dump(pa_log_level_t level, snd_pcm_t *pcm); void pa_alsa_dump_status(snd_pcm_t *pcm); void pa_alsa_redirect_errors_inc(void); diff --git a/src/modules/bluetooth/bluetooth-util.c b/src/modules/bluetooth/bluetooth-util.c index 6e4344f7..5c7681d4 100644 --- a/src/modules/bluetooth/bluetooth-util.c +++ b/src/modules/bluetooth/bluetooth-util.c @@ -773,6 +773,8 @@ void pa_bluetooth_discovery_unref(pa_bluetooth_discovery *y) { if (y->core) pa_shared_remove(y->core, "bluetooth-discovery"); + + pa_xfree(y); } void pa_bluetooth_discovery_sync(pa_bluetooth_discovery *y) { diff --git a/src/modules/bluetooth/module-bluetooth-device.c b/src/modules/bluetooth/module-bluetooth-device.c index 4613172e..90f64861 100644 --- a/src/modules/bluetooth/module-bluetooth-device.c +++ b/src/modules/bluetooth/module-bluetooth-device.c @@ -166,10 +166,14 @@ struct userdata { pa_modargs *modargs; - int stream_write_type, stream_read_type; + int stream_write_type; int service_write_type, service_read_type; }; +#define FIXED_LATENCY_PLAYBACK_A2DP (25*PA_USEC_PER_MSEC) +#define FIXED_LATENCY_PLAYBACK_HSP (125*PA_USEC_PER_MSEC) +#define FIXED_LATENCY_RECORD_HSP (25*PA_USEC_PER_MSEC) + #ifdef NOKIA #define USE_SCO_OVER_PCM(u) (u->profile == PROFILE_HSP && (u->hsp.sco_sink && u->hsp.sco_source)) #endif @@ -275,6 +279,7 @@ static ssize_t service_expect(struct userdata*u, bt_audio_msg_header_t *rsp, siz return 0; } +/* Run from main thread */ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capabilities_rsp *rsp) { uint16_t bytes_left; const codec_capabilities_t *codec; @@ -335,6 +340,7 @@ static int parse_caps(struct userdata *u, uint8_t seid, const struct bt_get_capa return 0; } +/* Run from main thread */ static int get_caps(struct userdata *u, uint8_t seid) { union { struct bt_get_capabilities_req getcaps_req; @@ -374,6 +380,7 @@ static int get_caps(struct userdata *u, uint8_t seid) { return get_caps(u, ret); } +/* Run from main thread */ static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) { switch (freq) { @@ -419,6 +426,7 @@ static uint8_t a2dp_default_bitpool(uint8_t freq, uint8_t mode) { } } +/* Run from main thread */ static int setup_a2dp(struct userdata *u) { sbc_capabilities_t *cap; int i; @@ -526,6 +534,7 @@ static int setup_a2dp(struct userdata *u) { return 0; } +/* Run from main thread */ static void setup_sbc(struct a2dp_info *a2dp) { sbc_capabilities_t *active_capabilities; @@ -617,6 +626,7 @@ static void setup_sbc(struct a2dp_info *a2dp) { a2dp->frame_length = sbc_get_frame_length(&a2dp->sbc); } +/* Run from main thread */ static int set_conf(struct userdata *u) { union { struct bt_open_req open_req; @@ -705,6 +715,7 @@ static int start_stream_fd(struct userdata *u) { uint8_t buf[BT_SUGGESTED_BUFFER_SIZE]; } msg; struct pollfd *pollfd; + int one; pa_assert(u); pa_assert(u->rtpoll); @@ -733,13 +744,29 @@ static int start_stream_fd(struct userdata *u) { pa_make_fd_nonblock(u->stream_fd); pa_make_socket_low_delay(u->stream_fd); + one = 1; + if (setsockopt(u->stream_fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) + pa_log_warn("Failed to enable SO_TIMESTAMP: %s", pa_cstrerror(errno)); + + pa_log_debug("Stream properly set up, we're ready to roll!"); + u->rtpoll_item = pa_rtpoll_item_new(u->rtpoll, PA_RTPOLL_NEVER, 1); pollfd = pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL); pollfd->fd = u->stream_fd; pollfd->events = pollfd->revents = 0; - u->read_index = 0; - u->write_index = 0; + u->read_index = u->write_index = 0; + u->started_at = 0; + + if (u->source) + u->read_smoother = pa_smoother_new( + PA_USEC_PER_SEC, + PA_USEC_PER_SEC*2, + TRUE, + TRUE, + 10, + pa_rtclock_usec(), + TRUE); return 0; } @@ -775,9 +802,15 @@ static int stop_stream_fd(struct userdata *u) { pa_close(u->stream_fd); u->stream_fd = -1; + if (u->read_smoother) { + pa_smoother_free(u->read_smoother); + u->read_smoother = NULL; + } + return r; } +/* Run from IO thread */ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SINK(o)->userdata; pa_bool_t failed = FALSE; @@ -785,7 +818,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse pa_assert(u->sink == PA_SINK(o)); - pa_log_debug("got message: %d", code); switch (code) { case PA_SINK_MESSAGE_SET_STATE: @@ -813,8 +845,6 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse if (!u->source || u->source->state == PA_SOURCE_SUSPENDED) if (start_stream_fd(u) < 0) failed = TRUE; - - u->started_at = pa_rtclock_usec(); break; case PA_SINK_UNLINKED: @@ -825,7 +855,24 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse break; case PA_SINK_MESSAGE_GET_LATENCY: { - *((pa_usec_t*) data) = 0; + + if (u->read_smoother) { + pa_usec_t wi, ri; + + ri = pa_smoother_get(u->read_smoother, pa_rtclock_usec()); + wi = pa_bytes_to_usec(u->write_index + u->block_size, &u->sample_spec); + + *((pa_usec_t*) data) = wi > ri ? wi - ri : 0; + } else { + pa_usec_t ri, wi; + + ri = pa_rtclock_usec() - u->started_at; + wi = pa_bytes_to_usec(u->write_index, &u->sample_spec); + + *((pa_usec_t*) data) = wi > ri ? wi - ri : 0; + } + + *((pa_usec_t*) data) += u->sink->fixed_latency; return 0; } } @@ -835,6 +882,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void *data, int64_t offse return (r < 0 || !failed) ? r : -1; } +/* Run from IO thread */ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t offset, pa_memchunk *chunk) { struct userdata *u = PA_SOURCE(o)->userdata; pa_bool_t failed = FALSE; @@ -842,7 +890,6 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off pa_assert(u->source == PA_SOURCE(o)); - pa_log_debug("got message: %d", code); switch (code) { case PA_SOURCE_MESSAGE_SET_STATE: @@ -856,7 +903,8 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off if (!u->sink || u->sink->state == PA_SINK_SUSPENDED) stop_stream_fd(u); - pa_smoother_pause(u->read_smoother, pa_rtclock_usec()); + if (u->read_smoother) + pa_smoother_pause(u->read_smoother, pa_rtclock_usec()); break; case PA_SOURCE_IDLE: @@ -869,7 +917,8 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off if (start_stream_fd(u) < 0) failed = TRUE; - pa_smoother_resume(u->read_smoother, pa_rtclock_usec(), TRUE); + /* We don't resume the smoother here. Instead we + * wait until the first packet arrives */ break; case PA_SOURCE_UNLINKED: @@ -880,7 +929,12 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off break; case PA_SOURCE_MESSAGE_GET_LATENCY: { - *((pa_usec_t*) data) = 0; + pa_usec_t wi, ri; + + wi = pa_smoother_get(u->read_smoother, pa_rtclock_usec()); + ri = pa_bytes_to_usec(u->read_index, &u->sample_spec); + + *((pa_usec_t*) data) = (wi > ri ? wi - ri : 0) + u->source->fixed_latency; return 0; } @@ -891,6 +945,7 @@ static int source_process_msg(pa_msgobject *o, int code, void *data, int64_t off return (r < 0 || !failed) ? r : -1; } +/* Run from IO thread */ static int hsp_process_render(struct userdata *u) { int ret = 0; @@ -947,12 +1002,14 @@ static int hsp_process_render(struct userdata *u) { pa_memblock_unref(u->write_memchunk.memblock); pa_memchunk_reset(&u->write_memchunk); + ret = 1; break; } return ret; } +/* Run from IO thread */ static int hsp_process_push(struct userdata *u) { int ret = 0; pa_memchunk memchunk; @@ -960,6 +1017,7 @@ static int hsp_process_push(struct userdata *u) { pa_assert(u); pa_assert(u->profile == PROFILE_HSP); pa_assert(u->source); + pa_assert(u->read_smoother); memchunk.memblock = pa_memblock_new(u->core->mempool, u->block_size); memchunk.index = memchunk.length = 0; @@ -967,9 +1025,26 @@ static int hsp_process_push(struct userdata *u) { for (;;) { ssize_t l; void *p; + struct msghdr m; + struct cmsghdr *cm; + uint8_t aux[1024]; + struct iovec iov; + pa_bool_t found_tstamp = FALSE; + pa_usec_t tstamp; + + memset(&m, 0, sizeof(m)); + memset(&aux, 0, sizeof(aux)); + memset(&iov, 0, sizeof(iov)); + + m.msg_iov = &iov; + m.msg_iovlen = 1; + m.msg_control = aux; + m.msg_controllen = sizeof(aux); p = pa_memblock_acquire(memchunk.memblock); - l = pa_read(u->stream_fd, p, pa_memblock_get_length(memchunk.memblock), &u->stream_read_type); + iov.iov_base = p; + iov.iov_len = pa_memblock_get_length(memchunk.memblock); + l = recvmsg(u->stream_fd, &m, 0); pa_memblock_release(memchunk.memblock); if (l <= 0) { @@ -992,7 +1067,26 @@ static int hsp_process_push(struct userdata *u) { memchunk.length = (size_t) l; u->read_index += (uint64_t) l; + for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) + if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) { + struct timeval *tv = (struct timeval*) CMSG_DATA(cm); + pa_rtclock_from_wallclock(tv); + tstamp = pa_timeval_load(tv); + found_tstamp = TRUE; + break; + } + + if (!found_tstamp) { + pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); + tstamp = pa_rtclock_usec(); + } + + pa_smoother_put(u->read_smoother, tstamp, pa_bytes_to_usec(u->read_index, &u->sample_spec)); + pa_smoother_resume(u->read_smoother, tstamp, TRUE); + pa_source_post(u->source, &memchunk); + + ret = 1; break; } @@ -1001,6 +1095,7 @@ static int hsp_process_push(struct userdata *u) { return ret; } +/* Run from IO thread */ static void a2dp_prepare_buffer(struct userdata *u) { pa_assert(u); @@ -1012,6 +1107,7 @@ static void a2dp_prepare_buffer(struct userdata *u) { u->a2dp.buffer = pa_xmalloc(u->a2dp.buffer_size); } +/* Run from IO thread */ static int a2dp_process_render(struct userdata *u) { struct a2dp_info *a2dp; struct rtp_header *header; @@ -1095,7 +1191,7 @@ static int a2dp_process_render(struct userdata *u) { header->v = 2; header->pt = 1; header->sequence_number = htons(a2dp->seq_num++); - header->timestamp = htonl(u->write_index / pa_frame_size(&u->sink->sample_spec)); + header->timestamp = htonl(u->write_index / pa_frame_size(&u->sample_spec)); header->ssrc = htonl(1); payload->frame_count = frame_count; @@ -1137,6 +1233,8 @@ static int a2dp_process_render(struct userdata *u) { pa_memblock_unref(u->write_memchunk.memblock); pa_memchunk_reset(&u->write_memchunk); + ret = 1; + break; } @@ -1145,7 +1243,8 @@ static int a2dp_process_render(struct userdata *u) { static void thread_func(void *userdata) { struct userdata *u = userdata; - pa_bool_t do_write = FALSE, writable = FALSE; + unsigned do_write = 0; + pa_bool_t writable = FALSE; pa_assert(u); @@ -1160,8 +1259,6 @@ static void thread_func(void *userdata) { pa_thread_mq_install(&u->thread_mq); pa_rtpoll_install(u->rtpoll); - pa_smoother_set_time_offset(u->read_smoother, pa_rtclock_usec()); - for (;;) { struct pollfd *pollfd; int ret; @@ -1171,13 +1268,20 @@ static void thread_func(void *userdata) { if (u->source && PA_SOURCE_IS_LINKED(u->source->thread_info.state)) { + /* We should send two blocks to the device before we expect + * a response. */ + + if (u->write_index == 0 && u->read_index <= 0) + do_write = 2; + if (pollfd && (pollfd->revents & POLLIN)) { + int n_read; - if (hsp_process_push(u) < 0) + if ((n_read = hsp_process_push(u)) < 0) goto fail; /* We just read something, so we are supposed to write something, too */ - do_write = TRUE; + do_write += n_read; } } @@ -1190,7 +1294,7 @@ static void thread_func(void *userdata) { if (pollfd->revents & POLLOUT) writable = TRUE; - if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && !do_write && writable) { + if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0 && writable) { pa_usec_t time_passed; uint64_t should_have_written; @@ -1198,36 +1302,37 @@ static void thread_func(void *userdata) { * to. So let's do things by time */ time_passed = pa_rtclock_usec() - u->started_at; - should_have_written = pa_usec_to_bytes(time_passed, &u->sink->sample_spec); + should_have_written = pa_usec_to_bytes(time_passed, &u->sample_spec); - do_write = u->write_index <= should_have_written ; -/* pa_log_debug("Time has come: %s", pa_yes_no(do_write)); */ + do_write = u->write_index <= should_have_written; } - if (writable && do_write) { - if (u->write_index == 0) + if (writable && do_write > 0) { + int n_written; + + if (u->write_index <= 0) u->started_at = pa_rtclock_usec(); if (u->profile == PROFILE_A2DP) { - if (a2dp_process_render(u) < 0) + if ((n_written = a2dp_process_render(u)) < 0) goto fail; } else { - if (hsp_process_render(u) < 0) + if ((n_written = hsp_process_render(u)) < 0) goto fail; } - do_write = FALSE; + do_write -= n_written; writable = FALSE; } - if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && !do_write) { + if ((!u->source || !PA_SOURCE_IS_LINKED(u->source->thread_info.state)) && do_write <= 0) { pa_usec_t time_passed, next_write_at, sleep_for; /* Hmm, there is no input stream we could synchronize * to. So let's estimate when we need to wake up the latest */ time_passed = pa_rtclock_usec() - u->started_at; - next_write_at = pa_bytes_to_usec(u->write_index, &u->sink->sample_spec); + next_write_at = pa_bytes_to_usec(u->write_index, &u->sample_spec); sleep_for = time_passed < next_write_at ? next_write_at - time_passed : 0; /* pa_log("Sleeping for %lu; time passed %lu, next write at %lu", (unsigned long) sleep_for, (unsigned long) time_passed, (unsigned long)next_write_at); */ @@ -1255,7 +1360,11 @@ static void thread_func(void *userdata) { pollfd = u->rtpoll_item ? pa_rtpoll_item_get_pollfd(u->rtpoll_item, NULL) : NULL; if (pollfd && (pollfd->revents & ~(POLLOUT|POLLIN))) { - pa_log_error("FD error."); + pa_log_info("FD error: %s%s%s%s", + pollfd->revents & POLLERR ? "POLLERR " :"", + pollfd->revents & POLLHUP ? "POLLHUP " :"", + pollfd->revents & POLLPRI ? "POLLPRI " :"", + pollfd->revents & POLLNVAL ? "POLLNVAL " :""); goto fail; } } @@ -1270,6 +1379,7 @@ finish: pa_log_debug("IO thread shutting down"); } +/* Run from main thread */ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *userdata) { DBusError err; struct userdata *u; @@ -1302,12 +1412,12 @@ static DBusHandlerResult filter_cb(DBusConnection *bus, DBusMessage *m, void *us if (u->profile == PROFILE_HSP) { if (u->sink && dbus_message_is_signal(m, "org.bluez.Headset", "SpeakerGainChanged")) { - pa_cvolume_set(&v, u->sink->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); pa_sink_volume_changed(u->sink, &v); } else if (u->source && dbus_message_is_signal(m, "org.bluez.Headset", "MicrophoneGainChanged")) { - pa_cvolume_set(&v, u->sink->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + pa_cvolume_set(&v, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); pa_source_volume_changed(u->source, &v); } } @@ -1319,6 +1429,7 @@ fail: return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } +/* Run from main thread */ static void sink_set_volume_cb(pa_sink *s) { struct userdata *u = s->userdata; DBusMessage *m; @@ -1334,7 +1445,7 @@ static void sink_set_volume_cb(pa_sink *s) { if (gain > 15) gain = 15; - pa_cvolume_set(&s->virtual_volume, u->sink->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetSpeakerGain")); pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID)); @@ -1342,6 +1453,7 @@ static void sink_set_volume_cb(pa_sink *s) { dbus_message_unref(m); } +/* Run from main thread */ static void source_set_volume_cb(pa_source *s) { struct userdata *u = s->userdata; DBusMessage *m; @@ -1357,7 +1469,7 @@ static void source_set_volume_cb(pa_source *s) { if (gain > 15) gain = 15; - pa_cvolume_set(&s->virtual_volume, u->source->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); + pa_cvolume_set(&s->virtual_volume, u->sample_spec.channels, (pa_volume_t) (gain * PA_VOLUME_NORM / 15)); pa_assert_se(m = dbus_message_new_method_call("org.bluez", u->path, "org.bluez.Headset", "SetMicrophoneGain")); pa_assert_se(dbus_message_append_args(m, DBUS_TYPE_UINT16, &gain, DBUS_TYPE_INVALID)); @@ -1365,6 +1477,7 @@ static void source_set_volume_cb(pa_source *s) { dbus_message_unref(m); } +/* Run from main thread */ static char *get_name(const char *type, pa_modargs *ma, const char *device_id, pa_bool_t *namereg_fail) { char *t; const char *n; @@ -1451,6 +1564,7 @@ static pa_hook_result_t source_state_changed_cb(pa_core *c, pa_source *s, struct #endif +/* Run from main thread */ static int add_sink(struct userdata *u) { #ifdef NOKIA @@ -1492,6 +1606,11 @@ static int add_sink(struct userdata *u) { u->sink->userdata = u; u->sink->parent.process_msg = sink_process_msg; + + pa_sink_set_max_request(u->sink, u->block_size); + u->sink->fixed_latency = + (u->profile == PROFILE_A2DP ? FIXED_LATENCY_PLAYBACK_A2DP : FIXED_LATENCY_PLAYBACK_HSP) + + pa_bytes_to_usec(u->block_size, &u->sample_spec); } if (u->profile == PROFILE_HSP) { @@ -1502,12 +1621,13 @@ static int add_sink(struct userdata *u) { return 0; } +/* Run from main thread */ static int add_source(struct userdata *u) { #ifdef NOKIA if (USE_SCO_OVER_PCM(u)) { u->source = u->hsp.sco_source; - pa_proplist_sets(u->source->proplist, "bluetooth.protocol", "sco"); + pa_proplist_sets(u->source->proplist, "bluetooth.protocol", "hsp"); if (!u->hsp.source_state_changed_slot) u->hsp.source_state_changed_slot = pa_hook_connect(&u->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) source_state_changed_cb, u); @@ -1523,7 +1643,7 @@ static int add_source(struct userdata *u) { data.driver = __FILE__; data.module = u->module; pa_source_new_data_set_sample_spec(&data, &u->sample_spec); - pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "sco"); + pa_proplist_sets(data.proplist, "bluetooth.protocol", u->profile == PROFILE_A2DP ? "a2dp" : "hsp"); data.card = u->card; data.name = get_name("source", u->modargs, u->address, &b); data.namereg_fail = b; @@ -1538,6 +1658,10 @@ static int add_source(struct userdata *u) { u->source->userdata = u; u->source->parent.process_msg = source_process_msg; + + u->source->fixed_latency = + (/* u->profile == PROFILE_A2DP ? FIXED_LATENCY_RECORD_A2DP : */ FIXED_LATENCY_RECORD_HSP) + + pa_bytes_to_usec(u->block_size, &u->sample_spec); } if (u->profile == PROFILE_HSP) { @@ -1549,6 +1673,7 @@ static int add_source(struct userdata *u) { return 0; } +/* Run from main thread */ static void shutdown_bt(struct userdata *u) { pa_assert(u); @@ -1557,12 +1682,12 @@ static void shutdown_bt(struct userdata *u) { u->stream_fd = -1; u->stream_write_type = 0; - u->stream_read_type = 0; } if (u->service_fd >= 0) { pa_close(u->service_fd); u->service_fd = -1; + u->service_write_type = u->service_write_type = 0; } if (u->write_memchunk.memblock) { @@ -1571,12 +1696,13 @@ static void shutdown_bt(struct userdata *u) { } } +/* Run from main thread */ static int init_bt(struct userdata *u) { pa_assert(u); shutdown_bt(u); - u->stream_write_type = u->stream_read_type = 0; + u->stream_write_type = 0; u->service_write_type = u->service_write_type = 0; if ((u->service_fd = bt_audio_service_open()) < 0) { @@ -1589,6 +1715,7 @@ static int init_bt(struct userdata *u) { return 0; } +/* Run from main thread */ static int setup_bt(struct userdata *u) { pa_assert(u); @@ -1614,6 +1741,7 @@ static int setup_bt(struct userdata *u) { return 0; } +/* Run from main thread */ static int init_profile(struct userdata *u) { int r = 0; pa_assert(u); @@ -1634,6 +1762,7 @@ static int init_profile(struct userdata *u) { return r; } +/* Run from main thread */ static void stop_thread(struct userdata *u) { pa_assert(u); @@ -1674,8 +1803,14 @@ static void stop_thread(struct userdata *u) { pa_rtpoll_free(u->rtpoll); u->rtpoll = NULL; } + + if (u->read_smoother) { + pa_smoother_free(u->read_smoother); + u->read_smoother = NULL; + } } +/* Run from main thread */ static int start_thread(struct userdata *u) { pa_assert(u); pa_assert(!u->thread); @@ -1724,6 +1859,7 @@ static int start_thread(struct userdata *u) { return 0; } +/* Run from main thread */ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { struct userdata *u; enum profile *d; @@ -1797,6 +1933,7 @@ static int card_set_profile(pa_card *c, pa_card_profile *new_profile) { return 0; } +/* Run from main thread */ static int add_card(struct userdata *u, const char *default_profile, const pa_bluetooth_device *device) { pa_card_new_data data; pa_bool_t b; @@ -1845,7 +1982,7 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl } if (pa_bluetooth_uuid_has(device->uuids, HSP_HS_UUID) || - pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) { + pa_bluetooth_uuid_has(device->uuids, HFP_HS_UUID)) { p = pa_card_profile_new("hsp", _("Telephony Duplex (HSP/HFP)"), sizeof(enum profile)); p->priority = 20; p->n_sinks = 1; @@ -1890,6 +2027,7 @@ static int add_card(struct userdata *u, const char *default_profile, const pa_bl return 0; } +/* Run from main thread */ static const pa_bluetooth_device* find_device(struct userdata *u, const char *address, const char *path) { const pa_bluetooth_device *d = NULL; @@ -1926,6 +2064,7 @@ static const pa_bluetooth_device* find_device(struct userdata *u, const char *ad return d; } +/* Run from main thread */ static int setup_dbus(struct userdata *u) { DBusError err; @@ -1965,14 +2104,6 @@ int pa__init(pa_module* m) { u->core = m->core; u->service_fd = -1; u->stream_fd = -1; - u->read_smoother = pa_smoother_new( - PA_USEC_PER_SEC, - PA_USEC_PER_SEC*2, - TRUE, - TRUE, - 10, - 0, - FALSE); u->sample_spec = m->core->default_sample_spec; u->modargs = ma; diff --git a/src/modules/module-null-sink.c b/src/modules/module-null-sink.c index 129bc1c3..d9bab6bd 100644 --- a/src/modules/module-null-sink.c +++ b/src/modules/module-null-sink.c @@ -119,6 +119,7 @@ static int sink_process_msg( static void sink_update_requested_latency_cb(pa_sink *s) { struct userdata *u; + size_t nbytes; pa_sink_assert_ref(s); pa_assert_se(u = s->userdata); @@ -127,6 +128,10 @@ static void sink_update_requested_latency_cb(pa_sink *s) { if (u->block_usec == (pa_usec_t) -1) u->block_usec = s->thread_info.max_latency; + + nbytes = pa_usec_to_bytes(u->block_usec, &s->sample_spec); + pa_sink_set_max_rewind_within_thread(s, nbytes); + pa_sink_set_max_request_within_thread(s, nbytes); } static void process_rewind(struct userdata *u, pa_usec_t now) { @@ -284,7 +289,7 @@ int pa__init(pa_module*m) { pa_proplist_sets(data.proplist, PA_PROP_DEVICE_DESCRIPTION, pa_modargs_get_value(ma, "description", "Null Output")); pa_proplist_sets(data.proplist, PA_PROP_DEVICE_CLASS, "abstract"); - u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY); + u->sink = pa_sink_new(m->core, &data, PA_SINK_LATENCY|PA_SINK_DYNAMIC_LATENCY); pa_sink_new_data_done(&data); if (!u->sink) { diff --git a/src/modules/reserve-wrap.c b/src/modules/reserve-wrap.c index 1927342b..02ff29be 100644 --- a/src/modules/reserve-wrap.c +++ b/src/modules/reserve-wrap.c @@ -23,6 +23,8 @@ #include <config.h> #endif +#include <errno.h> + #include <pulse/xmalloc.h> #include <pulse/i18n.h> @@ -127,8 +129,13 @@ pa_reserve_wrapper* pa_reserve_wrapper_get(pa_core *c, const char *device_name) request_cb, NULL)) < 0) { - pa_log_error("Failed to acquire reservation lock on device '%s': %s", device_name, pa_cstrerror(-k)); - goto fail; + if (k == -EBUSY) { + pa_log_error("Device '%s' already locked.", device_name); + goto fail; + } else { + pa_log_warn("Failed to acquire reservation lock on device '%s': %s", device_name, pa_cstrerror(-k)); + return r; + } } pa_log_debug("Successfully acquired reservation lock on device '%s'", device_name); diff --git a/src/modules/rtp/module-rtp-recv.c b/src/modules/rtp/module-rtp-recv.c index e7749cdd..c61d2d8b 100644 --- a/src/modules/rtp/module-rtp-recv.c +++ b/src/modules/rtp/module-rtp-recv.c @@ -52,6 +52,8 @@ #include <pulsecore/rtclock.h> #include <pulsecore/atomic.h> #include <pulsecore/time-smoother.h> +#include <pulsecore/socket-util.h> +#include <pulsecore/once.h> #include "module-rtp-recv-symdef.h" @@ -165,7 +167,7 @@ static void sink_input_process_rewind_cb(pa_sink_input *i, size_t nbytes) { pa_memblockq_rewind(s->memblockq, nbytes); } -/* Called from thread context */ +/* Called from I/O thread context */ static void sink_input_update_max_rewind_cb(pa_sink_input *i, size_t nbytes) { struct session *s; @@ -184,11 +186,24 @@ static void sink_input_kill(pa_sink_input* i) { session_free(s); } +/* Called from IO context */ +static void sink_input_suspend_within_thread(pa_sink_input* i, pa_bool_t b) { + struct session *s; + pa_sink_input_assert_ref(i); + pa_assert_se(s = i->userdata); + + if (b) { + pa_smoother_pause(s->smoother, pa_rtclock_usec()); + pa_memblockq_flush_read(s->memblockq); + } else + s->first_packet = FALSE; +} + /* Called from I/O thread context */ static int rtpoll_work_cb(pa_rtpoll_item *i) { pa_memchunk chunk; int64_t k, j, delta; - struct timeval now; + struct timeval now = { 0, 0 }; struct session *s; struct pollfd *p; @@ -206,10 +221,11 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { p->revents = 0; - if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->module->core->mempool) < 0) + if (pa_rtp_recv(&s->rtp_context, &chunk, s->userdata->module->core->mempool, &now) < 0) return 0; - if (s->sdp_info.payload != s->rtp_context.payload) { + if (s->sdp_info.payload != s->rtp_context.payload || + !PA_SINK_IS_OPENED(s->sink_input->sink->thread_info.state)) { pa_memblock_unref(chunk.memblock); return 0; } @@ -240,10 +256,19 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { pa_memblockq_seek(s->memblockq, delta * (int64_t) s->rtp_context.frame_size, PA_SEEK_RELATIVE, TRUE); - pa_rtclock_get(&now); + if (now.tv_sec == 0) { + PA_ONCE_BEGIN { + pa_log_warn("Using artificial time instead of timestamp"); + } PA_ONCE_END; + pa_rtclock_get(&now); + } else + pa_rtclock_from_wallclock(&now); pa_smoother_put(s->smoother, pa_timeval_load(&now), pa_bytes_to_usec((uint64_t) pa_memblockq_get_write_index(s->memblockq), &s->sink_input->sample_spec)); + /* Tell the smoother that we are rolling now, in case it is still paused */ + pa_smoother_resume(s->smoother, pa_timeval_load(&now), TRUE); + if (pa_memblockq_push(s->memblockq, &chunk) < 0) { pa_log_warn("Queue overrun"); pa_memblockq_seek(s->memblockq, (int64_t) chunk.length, PA_SEEK_RELATIVE, TRUE); @@ -262,14 +287,14 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { pa_usec_t wi, ri, render_delay, sink_delay = 0, latency, fix; unsigned fix_samples; - pa_log("Updating sample rate"); + pa_log_debug("Updating sample rate"); wi = pa_smoother_get(s->smoother, pa_timeval_load(&now)); ri = pa_bytes_to_usec((uint64_t) pa_memblockq_get_read_index(s->memblockq), &s->sink_input->sample_spec); - if (PA_MSGOBJECT(s->sink_input->sink)->process_msg(PA_MSGOBJECT(s->sink_input->sink), PA_SINK_MESSAGE_GET_LATENCY, &sink_delay, 0, NULL) < 0) - sink_delay = 0; + pa_log_debug("wi=%lu ri=%lu", (unsigned long) wi, (unsigned long) ri); + sink_delay = pa_sink_get_latency_within_thread(s->sink_input->sink); render_delay = pa_bytes_to_usec(pa_memblockq_get_length(s->sink_input->thread_info.render_memblockq), &s->sink_input->sink->sample_spec); if (ri > render_delay+sink_delay) @@ -294,14 +319,20 @@ static int rtpoll_work_cb(pa_rtpoll_item *i) { fix_samples = (unsigned) (fix * (pa_usec_t) s->sink_input->thread_info.sample_spec.rate / (pa_usec_t) RATE_UPDATE_INTERVAL); /* Check if deviation is in bounds */ - if (fix_samples > s->sink_input->sample_spec.rate*.20) + if (fix_samples > s->sink_input->sample_spec.rate*.50) pa_log_debug("Hmmm, rate fix is too large (%lu Hz), not applying.", (unsigned long) fix_samples); + else { + /* Fix up rate */ + if (latency < s->intended_latency) + s->sink_input->sample_spec.rate -= fix_samples; + else + s->sink_input->sample_spec.rate += fix_samples; + + if (s->sink_input->sample_spec.rate > PA_RATE_MAX) + s->sink_input->sample_spec.rate = PA_RATE_MAX; + } - /* Fix up rate */ - if (latency < s->intended_latency) - s->sink_input->sample_spec.rate -= fix_samples; - else - s->sink_input->sample_spec.rate += fix_samples; + pa_assert(pa_sample_spec_valid(&s->sink_input->sample_spec)); pa_resampler_set_input_rate(s->sink_input->thread_info.resampler, s->sink_input->sample_spec.rate); @@ -362,6 +393,14 @@ static int mcast_socket(const struct sockaddr* sa, socklen_t salen) { goto fail; } + pa_make_udp_socket_low_delay(fd); + + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)) < 0) { + pa_log("SO_TIMESTAMP failed: %s", pa_cstrerror(errno)); + goto fail; + } + one = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) { pa_log("SO_REUSEADDR failed: %s", pa_cstrerror(errno)); @@ -437,7 +476,7 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in TRUE, 10, pa_timeval_load(&now), - FALSE); + TRUE); s->last_rate_update = pa_timeval_load(&now); pa_atomic_store(&s->timestamp, (int) now.tv_sec); @@ -478,6 +517,7 @@ static struct session *session_new(struct userdata *u, const pa_sdp_info *sdp_in s->sink_input->kill = sink_input_kill; s->sink_input->attach = sink_input_attach; s->sink_input->detach = sink_input_detach; + s->sink_input->suspend_within_thread = sink_input_suspend_within_thread; pa_sink_input_get_silence(s->sink_input, &silence); @@ -654,8 +694,7 @@ int pa__init(pa_module*m) { if ((fd = mcast_socket(sa, salen)) < 0) goto fail; - u = pa_xnew(struct userdata, 1); - m->userdata = u; + m->userdata = u = pa_xnew(struct userdata, 1); u->module = m; u->sink_name = pa_xstrdup(pa_modargs_get_value(ma, "sink", NULL)); diff --git a/src/modules/rtp/module-rtp-send.c b/src/modules/rtp/module-rtp-send.c index 722d12bd..cdd2c57d 100644 --- a/src/modules/rtp/module-rtp-send.c +++ b/src/modules/rtp/module-rtp-send.c @@ -347,10 +347,10 @@ int pa__init(pa_module*m) { o->push = source_output_push; o->kill = source_output_kill; - u = pa_xnew(struct userdata, 1); - m->userdata = u; - o->userdata = u; + pa_log_info("Configured source latency of %lu ms.", + pa_source_output_set_requested_latency(o, pa_bytes_to_usec(mtu, &o->sample_spec)) / PA_USEC_PER_MSEC); + m->userdata = o->userdata = u = pa_xnew(struct userdata, 1); u->module = m; u->source_output = o; diff --git a/src/modules/rtp/rtp.c b/src/modules/rtp/rtp.c index 7537c1f5..6706a10f 100644 --- a/src/modules/rtp/rtp.c +++ b/src/modules/rtp/rtp.c @@ -162,13 +162,16 @@ pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame return c; } -int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { +int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool, struct timeval *tstamp) { int size; struct msghdr m; + struct cmsghdr *cm; struct iovec iov; uint32_t header; unsigned cc; ssize_t r; + uint8_t aux[1024]; + pa_bool_t found_tstamp = FALSE; pa_assert(c); pa_assert(chunk); @@ -208,8 +211,8 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { m.msg_namelen = 0; m.msg_iov = &iov; m.msg_iovlen = 1; - m.msg_control = NULL; - m.msg_controllen = 0; + m.msg_control = aux; + m.msg_controllen = sizeof(aux); m.msg_flags = 0; r = recvmsg(c->fd, &m, 0); @@ -275,6 +278,18 @@ int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool) { pa_memchunk_reset(&c->memchunk); } + for (cm = CMSG_FIRSTHDR(&m); cm; cm = CMSG_NXTHDR(&m, cm)) { + if (cm->cmsg_level == SOL_SOCKET && cm->cmsg_type == SO_TIMESTAMP) + memcpy(tstamp, CMSG_DATA(cm), sizeof(struct timeval)); + found_tstamp = TRUE; + break; + } + + if (!found_tstamp) { + pa_log_warn("Couldn't find SO_TIMESTAMP data in auxiliary recvmsg() data!"); + memset(tstamp, 0, sizeof(tstamp)); + } + return 0; fail: diff --git a/src/modules/rtp/rtp.h b/src/modules/rtp/rtp.h index eff5e75b..b197e82f 100644 --- a/src/modules/rtp/rtp.h +++ b/src/modules/rtp/rtp.h @@ -43,7 +43,7 @@ pa_rtp_context* pa_rtp_context_init_send(pa_rtp_context *c, int fd, uint32_t ssr int pa_rtp_send(pa_rtp_context *c, size_t size, pa_memblockq *q); pa_rtp_context* pa_rtp_context_init_recv(pa_rtp_context *c, int fd, size_t frame_size); -int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool); +int pa_rtp_recv(pa_rtp_context *c, pa_memchunk *chunk, pa_mempool *pool, struct timeval *tstamp); void pa_rtp_context_destroy(pa_rtp_context *c); diff --git a/src/pulse/stream.h b/src/pulse/stream.h index 8e99a753..49c132a2 100644 --- a/src/pulse/stream.h +++ b/src/pulse/stream.h @@ -424,7 +424,9 @@ int pa_stream_disconnect(pa_stream *s); * 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.*/ + * 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. */ int pa_stream_write( pa_stream *p /**< The stream to use */, const void *data /**< The data to write */, @@ -519,7 +521,16 @@ void pa_stream_set_event_callback(pa_stream *p, pa_stream_event_cb_t cb, void *u * pa_stream_set_moved_callback() as well. \since 0.9.15 */ void pa_stream_set_buffer_attr_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. */ +/** Pause (or resume) playback of this stream temporarily. Available + * on both playback and recording streams. If b is 1 the stream is + * paused. If b is 0 the stream is resumed. The pause/resume operation + * is executed as quickly as possible. If a cork is very quickly + * followed by an uncork or the other way round this might not + * actually have any effect on the stream that is output. You can use + * pa_stream_is_corked() to find out whether the stream is currently + * paused or not. Normally a stream will be created in uncorked + * state. If you pass PA_STREAM_START_CORKED as flag during connection + * of the stream it will be created in corked state. */ 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 @@ -542,37 +553,63 @@ pa_operation* pa_stream_set_name(pa_stream *s, const char *name, pa_stream_succe /** 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_MONOTONIC. This may be + * 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_MONOTONIC. 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'. */ + * able to deal with time going 'backwards'. + * + * The time interpolator activated by PA_STREAM_INTERPOLATE_TIMING + * favours 'smooth' time graphs over accurate ones to improve the + * smoothness of UI operations that are tied to the audio clock. If + * accuracy is more important to you you might need to estimate your + * timing based on the data from pa_stream_get_timing_info() yourself + * or not work with interpolated timing at all and instead always + * query on the server side for the most up to date timing with + * pa_stream_update_timing_info(). + * + * If no timing information has been + * recieved yet this call will return PA_ERR_NODATA. For more details + * see pa_stream_get_timing_info(). */ 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. */ + * 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. + * + * If no timing information has been recieved yet this call will + * return PA_ERR_NODATA. For more details see + * pa_stream_get_timing_info() and pa_stream_get_time(). */ 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. */ + * be requested using pa_stream_update_timing_info(). + * + * If no timing information has been received before (i.e. by + * requesting pa_stream_update_timing_info() or by using + * PA_STREAM_AUTO_TIMING_UPDATE), 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. */ const pa_timing_info* pa_stream_get_timing_info(pa_stream *s); /** Return a pointer to the stream's sample specification. */ diff --git a/src/pulse/volume.c b/src/pulse/volume.c index c865058d..ad3b3a49 100644 --- a/src/pulse/volume.c +++ b/src/pulse/volume.c @@ -126,7 +126,7 @@ pa_volume_t pa_sw_volume_from_dB(double dB) { if (isinf(dB) < 0 || dB <= -USER_DECIBEL_RANGE) return PA_VOLUME_MUTED; - return (pa_volume_t) lrint((dB/USER_DECIBEL_RANGE+1)*PA_VOLUME_NORM); + return (pa_volume_t) lrint(ceil((dB/USER_DECIBEL_RANGE+1.0)*PA_VOLUME_NORM)); } double pa_sw_volume_to_dB(pa_volume_t v) { @@ -138,19 +138,19 @@ double pa_sw_volume_to_dB(pa_volume_t v) { pa_volume_t pa_sw_volume_from_linear(double v) { - if (v <= 0) + if (v <= 0.0) return PA_VOLUME_MUTED; if (v > .999 && v < 1.001) return PA_VOLUME_NORM; - return pa_sw_volume_from_dB(20*log10(v)); + return pa_sw_volume_from_dB(20.0*log10(v)); } double pa_sw_volume_to_linear(pa_volume_t v) { if (v == PA_VOLUME_MUTED) - return 0; + return 0.0; return pow(10.0, pa_sw_volume_to_dB(v)/20.0); } diff --git a/src/pulsecore/cli-text.c b/src/pulsecore/cli-text.c index 324f83c8..b0911ef1 100644 --- a/src/pulsecore/cli-text.c +++ b/src/pulsecore/cli-text.c @@ -288,7 +288,11 @@ char *pa_sink_list_to_string(pa_core *c) { (double) pa_sink_get_requested_latency(sink) / (double) PA_USEC_PER_MSEC, (double) min_latency / PA_USEC_PER_MSEC, (double) max_latency / PA_USEC_PER_MSEC); - } + } else + pa_strbuf_printf( + s, + "\tfixed latency: %0.2f ms\n", + (double) pa_sink_get_requested_latency(sink) / PA_USEC_PER_MSEC); if (sink->card) pa_strbuf_printf(s, "\tcard: %u <%s>\n", sink->card->index, sink->card->name); @@ -382,7 +386,11 @@ char *pa_source_list_to_string(pa_core *c) { (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC, (double) min_latency / PA_USEC_PER_MSEC, (double) max_latency / PA_USEC_PER_MSEC); - } + } else + pa_strbuf_printf( + s, + "\tfixed latency: %0.2f ms\n", + (double) pa_source_get_requested_latency(source) / PA_USEC_PER_MSEC); if (source->monitor_of) pa_strbuf_printf(s, "\tmonitor_of: %u\n", source->monitor_of->index); diff --git a/src/pulsecore/dbus-shared.c b/src/pulsecore/dbus-shared.c index b52c14cb..9d9445b6 100644 --- a/src/pulsecore/dbus-shared.c +++ b/src/pulsecore/dbus-shared.c @@ -41,7 +41,7 @@ struct pa_dbus_connection { const char *property_name; }; -static pa_dbus_connection* pa_dbus_connection_new(pa_core *c, pa_dbus_wrap_connection *conn, const char *name) { +static pa_dbus_connection* dbus_connection_new(pa_core *c, pa_dbus_wrap_connection *conn, const char *name) { pa_dbus_connection *pconn; pconn = pa_xnew(pa_dbus_connection, 1); @@ -73,9 +73,7 @@ pa_dbus_connection* pa_dbus_bus_get(pa_core *c, DBusBusType type, DBusError *err if (!(conn = pa_dbus_wrap_connection_new(c->mainloop, type, error))) return NULL; - pconn = pa_dbus_connection_new(c, conn, prop_name[type]); - - return pconn; + return dbus_connection_new(c, conn, prop_name[type]); } DBusConnection* pa_dbus_connection_get(pa_dbus_connection *c){ @@ -93,7 +91,8 @@ void pa_dbus_connection_unref(pa_dbus_connection *c) { if (PA_REFCNT_DEC(c) > 0) return; - /* already disconnected, just free */ + pa_dbus_wrap_connection_free(c->connection); + pa_shared_remove(c->core, c->property_name); pa_xfree(c); } @@ -106,6 +105,3 @@ pa_dbus_connection* pa_dbus_connection_ref(pa_dbus_connection *c) { return c; } - - - diff --git a/src/pulsecore/dbus-util.c b/src/pulsecore/dbus-util.c index d712bff3..ece36def 100644 --- a/src/pulsecore/dbus-util.c +++ b/src/pulsecore/dbus-util.c @@ -28,6 +28,8 @@ #include <pulse/xmalloc.h> #include <pulse/timeval.h> + +#include <pulsecore/core-util.h> #include <pulsecore/log.h> #include "dbus-util.h" @@ -244,7 +246,8 @@ static void wakeup_main(void *userdata) { pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *m, DBusBusType type, DBusError *error) { DBusConnection *conn; - pa_dbus_wrap_connection *pconn = NULL; + pa_dbus_wrap_connection *pconn; + char *id; pa_assert(type == DBUS_BUS_SYSTEM || type == DBUS_BUS_SESSION || type == DBUS_BUS_STARTER); @@ -263,6 +266,13 @@ pa_dbus_wrap_connection* pa_dbus_wrap_connection_new(pa_mainloop_api *m, DBusBus pconn->dispatch_event = pconn->mainloop->defer_new(pconn->mainloop, dispatch_cb, conn); + pa_log_debug("Successfully connected to D-Bus %s bus %s as %s", + type == DBUS_BUS_SYSTEM ? "system" : (type == DBUS_BUS_SESSION ? "session" : "starter"), + pa_strnull((id = dbus_connection_get_server_id(conn))), + pa_strnull(dbus_bus_get_unique_name(conn))); + + dbus_free(id); + return pconn; } @@ -273,7 +283,8 @@ void pa_dbus_wrap_connection_free(pa_dbus_wrap_connection* c) { dbus_connection_close(c->connection); /* must process remaining messages, bit of a kludge to handle * both unload and shutdown */ - while (dbus_connection_read_write_dispatch(c->connection, -1)); + while (dbus_connection_read_write_dispatch(c->connection, -1)) + ; } c->mainloop->defer_free(c->dispatch_event); @@ -369,8 +380,10 @@ pa_dbus_pending *pa_dbus_pending_new( void pa_dbus_pending_free(pa_dbus_pending *p) { pa_assert(p); - if (p->pending) - dbus_pending_call_cancel(p->pending); /* p->pending is freed by cancel() */ + if (p->pending) { + dbus_pending_call_cancel(p->pending); + dbus_pending_call_unref(p->pending); + } if (p->message) dbus_message_unref(p->message); diff --git a/src/pulsecore/log.h b/src/pulsecore/log.h index 8628bf40..2f379f68 100644 --- a/src/pulsecore/log.h +++ b/src/pulsecore/log.h @@ -113,6 +113,7 @@ void pa_log_levelv( #define pa_log_notice(...) pa_log_level_meta(PA_LOG_NOTICE, __FILE__, __LINE__, __func__, __VA_ARGS__) #define pa_log_warn(...) pa_log_level_meta(PA_LOG_WARN, __FILE__, __LINE__, __func__, __VA_ARGS__) #define pa_log_error(...) pa_log_level_meta(PA_LOG_ERROR, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define pa_logl(level, ...) pa_log_level_meta(level, __FILE__, __LINE__, __func__, __VA_ARGS__) #else diff --git a/src/pulsecore/memblockq.c b/src/pulsecore/memblockq.c index d12d13a8..77f9efc9 100644 --- a/src/pulsecore/memblockq.c +++ b/src/pulsecore/memblockq.c @@ -90,8 +90,8 @@ pa_memblockq* pa_memblockq_new( pa_memblockq_set_maxlength(bq, maxlength); pa_memblockq_set_tlength(bq, tlength); - pa_memblockq_set_prebuf(bq, prebuf); pa_memblockq_set_minreq(bq, minreq); + pa_memblockq_set_prebuf(bq, prebuf); pa_memblockq_set_maxrewind(bq, maxrewind); pa_log_debug("memblockq sanitized: maxlength=%lu, tlength=%lu, base=%lu, prebuf=%lu, minreq=%lu maxrewind=%lu", @@ -784,16 +784,13 @@ void pa_memblockq_set_maxlength(pa_memblockq *bq, size_t maxlength) { if (bq->tlength > bq->maxlength) pa_memblockq_set_tlength(bq, bq->maxlength); - - if (bq->prebuf > bq->maxlength) - pa_memblockq_set_prebuf(bq, bq->maxlength); } void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) { size_t old_tlength; pa_assert(bq); - if (tlength <= 0) + if (tlength <= 0 || tlength == (size_t) -1) tlength = bq->maxlength; old_tlength = bq->tlength; @@ -802,49 +799,46 @@ void pa_memblockq_set_tlength(pa_memblockq *bq, size_t tlength) { if (bq->tlength > bq->maxlength) bq->tlength = bq->maxlength; - if (bq->prebuf > bq->tlength) - pa_memblockq_set_prebuf(bq, bq->tlength); - if (bq->minreq > bq->tlength) pa_memblockq_set_minreq(bq, bq->tlength); + if (bq->prebuf > bq->tlength+bq->base-bq->minreq) + pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq); + bq->missing += (int64_t) bq->tlength - (int64_t) old_tlength; } +void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) { + pa_assert(bq); + + bq->minreq = (minreq/bq->base)*bq->base; + + if (bq->minreq > bq->tlength) + bq->minreq = bq->tlength; + + if (bq->minreq < bq->base) + bq->minreq = bq->base; + + if (bq->prebuf > bq->tlength+bq->base-bq->minreq) + pa_memblockq_set_prebuf(bq, bq->tlength+bq->base-bq->minreq); +} + void pa_memblockq_set_prebuf(pa_memblockq *bq, size_t prebuf) { pa_assert(bq); if (prebuf == (size_t) -1) - prebuf = bq->tlength; + prebuf = bq->tlength+bq->base-bq->minreq; bq->prebuf = ((prebuf+bq->base-1)/bq->base)*bq->base; if (prebuf > 0 && bq->prebuf < bq->base) bq->prebuf = bq->base; - if (bq->prebuf > bq->tlength) - bq->prebuf = bq->tlength; + if (bq->prebuf > bq->tlength+bq->base-bq->minreq) + bq->prebuf = bq->tlength+bq->base-bq->minreq; if (bq->prebuf <= 0 || pa_memblockq_get_length(bq) >= bq->prebuf) bq->in_prebuf = FALSE; - - if (bq->minreq > bq->prebuf) - pa_memblockq_set_minreq(bq, bq->prebuf); -} - -void pa_memblockq_set_minreq(pa_memblockq *bq, size_t minreq) { - pa_assert(bq); - - bq->minreq = (minreq/bq->base)*bq->base; - - if (bq->minreq > bq->tlength) - bq->minreq = bq->tlength; - - if (bq->minreq > bq->prebuf) - bq->minreq = bq->prebuf; - - if (bq->minreq < bq->base) - bq->minreq = bq->base; } void pa_memblockq_set_maxrewind(pa_memblockq *bq, size_t maxrewind) { diff --git a/src/pulsecore/protocol-native.c b/src/pulsecore/protocol-native.c index 59e5d80e..7c2183d8 100644 --- a/src/pulsecore/protocol-native.c +++ b/src/pulsecore/protocol-native.c @@ -850,7 +850,7 @@ static int playback_stream_process_msg(pa_msgobject *o, int code, void*userdata, /* Called from main context */ static void fix_playback_buffer_attr(playback_stream *s) { - size_t frame_size; + size_t frame_size, max_prebuf; pa_usec_t orig_tlength_usec, tlength_usec, orig_minreq_usec, minreq_usec, sink_usec; pa_assert(s); @@ -976,8 +976,11 @@ static void fix_playback_buffer_attr(playback_stream *s) { if (s->buffer_attr.tlength <= s->buffer_attr.minreq) s->buffer_attr.tlength = s->buffer_attr.minreq*2 + (uint32_t) frame_size; - if (s->buffer_attr.prebuf == (uint32_t) -1 || s->buffer_attr.prebuf > s->buffer_attr.tlength) - s->buffer_attr.prebuf = s->buffer_attr.tlength; + max_prebuf = s->buffer_attr.tlength + (uint32_t)frame_size - s->buffer_attr.minreq; + + if (s->buffer_attr.prebuf == (uint32_t) -1 || + s->buffer_attr.prebuf > max_prebuf) + s->buffer_attr.prebuf = max_prebuf; } /* Called from main context */ @@ -1465,7 +1468,8 @@ static int sink_input_pop_cb(pa_sink_input *i, size_t nbytes, pa_memchunk *chunk if (pa_memblockq_is_readable(s->memblockq)) s->is_underrun = FALSE; else { - pa_log_debug("Underrun on '%s', %lu bytes in queue.", pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)), (unsigned long) pa_memblockq_get_length(s->memblockq)); + if (!s->is_underrun) + pa_log_debug("Underrun on '%s', %lu bytes in queue.", pa_strnull(pa_proplist_gets(i->proplist, PA_PROP_MEDIA_NAME)), (unsigned long) pa_memblockq_get_length(s->memblockq)); if (s->drain_request && pa_sink_input_safe_to_remove(i)) { s->drain_request = FALSE; @@ -4241,7 +4245,7 @@ static void pstream_memblock_callback(pa_pstream *p, uint32_t channel, int64_t o pa_native_connection_assert_ref(c); if (!(stream = OUTPUT_STREAM(pa_idxset_get_by_index(c->output_streams, channel)))) { - pa_log("client sent block for invalid stream."); + pa_log_debug("Client sent block for invalid stream."); /* Ignoring */ return; } diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c index 1fdb3fa6..b1b9fb56 100644 --- a/src/pulsecore/sink-input.c +++ b/src/pulsecore/sink-input.c @@ -117,6 +117,7 @@ static void reset_callbacks(pa_sink_input *i) { i->attach = NULL; i->detach = NULL; i->suspend = NULL; + i->suspend_within_thread = NULL; i->moving = NULL; i->kill = NULL; i->get_latency = NULL; @@ -287,6 +288,7 @@ int pa_sink_input_new( i->volume_factor = data->volume_factor; pa_cvolume_init(&i->soft_volume); + memset(i->relative_volume, 0, sizeof(i->relative_volume)); i->save_volume = data->save_volume; i->save_sink = data->save_sink; i->save_muted = data->save_muted; @@ -333,8 +335,8 @@ int pa_sink_input_new( 0, &i->sink->silence); - pa_assert_se(pa_idxset_put(core->sink_inputs, pa_sink_input_ref(i), &i->index) == 0); - pa_assert_se(pa_idxset_put(i->sink->inputs, i, NULL) == 0); + pa_assert_se(pa_idxset_put(core->sink_inputs, i, &i->index) == 0); + pa_assert_se(pa_idxset_put(i->sink->inputs, pa_sink_input_ref(i), NULL) == 0); if (i->client) pa_assert_se(pa_idxset_put(i->client->sink_inputs, i, NULL) >= 0); @@ -529,7 +531,7 @@ void pa_sink_input_put(pa_sink_input *i) { pa_sink_update_flat_volume(i->sink, &new_volume); pa_sink_set_volume(i->sink, &new_volume, FALSE, FALSE); } else - pa_sw_cvolume_multiply(&i->soft_volume, &i->virtual_volume, &i->volume_factor); + pa_sink_input_set_relative_volume(i, &i->virtual_volume); i->thread_info.soft_volume = i->soft_volume; i->thread_info.muted = i->muted; @@ -822,6 +824,9 @@ void pa_sink_input_update_max_request(pa_sink_input *i, size_t nbytes /* in the pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa_usec_t usec) { pa_sink_input_assert_ref(i); + if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) + usec = i->sink->fixed_latency; + if (usec != (pa_usec_t) -1) usec = PA_CLAMP(usec, i->sink->thread_info.min_latency, i->sink->thread_info.max_latency); @@ -833,8 +838,6 @@ pa_usec_t pa_sink_input_set_requested_latency_within_thread(pa_sink_input *i, pa /* Called from main context */ pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) { - pa_usec_t min_latency, max_latency; - pa_sink_input_assert_ref(i); if (PA_SINK_INPUT_IS_LINKED(i->state) && i->sink) { @@ -846,10 +849,14 @@ pa_usec_t pa_sink_input_set_requested_latency(pa_sink_input *i, pa_usec_t usec) * we have to touch the thread info data directly */ if (i->sink) { - pa_sink_get_latency_range(i->sink, &min_latency, &max_latency); + if (!(i->sink->flags & PA_SINK_DYNAMIC_LATENCY)) + usec = i->sink->fixed_latency; - if (usec != (pa_usec_t) -1) + if (usec != (pa_usec_t) -1) { + pa_usec_t min_latency, max_latency; + pa_sink_get_latency_range(i->sink, &min_latency, &max_latency); usec = PA_CLAMP(usec, min_latency, max_latency); + } } i->thread_info.requested_sink_latency = usec; @@ -900,11 +907,12 @@ void pa_sink_input_set_volume(pa_sink_input *i, const pa_cvolume *volume, pa_boo /* OK, we are in normal volume mode. The volume only affects * ourselves */ - pa_sw_cvolume_multiply(&i->soft_volume, volume, &i->volume_factor); + pa_sink_input_set_relative_volume(i, volume); /* Hooks have the ability to play games with i->soft_volume */ pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); + /* Copy the new soft_volume to the thread_info struct */ pa_assert_se(pa_asyncmsgq_send(i->sink->asyncmsgq, PA_MSGOBJECT(i), PA_SINK_INPUT_MESSAGE_SET_SOFT_VOLUME, NULL, 0, NULL) == 0); } @@ -922,24 +930,50 @@ const pa_cvolume *pa_sink_input_get_volume(pa_sink_input *i) { /* Called from main context */ pa_cvolume *pa_sink_input_get_relative_volume(pa_sink_input *i, pa_cvolume *v) { + unsigned c; + pa_sink_input_assert_ref(i); pa_assert(v); pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); - *v = i->virtual_volume; - /* This always returns a relative volume, even in flat volume mode */ - if (i->sink->flags & PA_SINK_FLAT_VOLUME) { - pa_cvolume sv; + v->channels = i->sample_spec.channels; + + for (c = 0; c < v->channels; c++) + v->values[c] = pa_sw_volume_from_linear(i->relative_volume[c]); + + return v; +} + +/* Called from main context */ +void pa_sink_input_set_relative_volume(pa_sink_input *i, const pa_cvolume *v) { + unsigned c; + pa_cvolume _v; + + pa_sink_input_assert_ref(i); + pa_assert(PA_SINK_INPUT_IS_LINKED(i->state)); + pa_assert(!v || pa_cvolume_compatible(v, &i->sample_spec)); - sv = *pa_sink_get_volume(i->sink, FALSE); + if (!v) + v = pa_cvolume_reset(&_v, i->sample_spec.channels); - pa_sw_cvolume_divide(v, v, - pa_cvolume_remap(&sv, &i->sink->channel_map, &i->channel_map)); + /* This basically calculates: + * + * i->relative_volume := v + * i->soft_volume := i->relative_volume * i->volume_factor */ + + i->soft_volume.channels = i->sample_spec.channels; + + for (c = 0; c < i->sample_spec.channels; c++) { + i->relative_volume[c] = pa_sw_volume_to_linear(v->values[c]); + + i->soft_volume.values[c] = pa_sw_volume_from_linear( + i->relative_volume[c] * + pa_sw_volume_to_linear(i->volume_factor.values[c])); } - return v; + /* We don't copy the data to the thread_info data. That's left for someone else to do */ } /* Called from main context */ @@ -1109,9 +1143,11 @@ int pa_sink_input_start_move(pa_sink_input *i) { if (i->sink->flags & PA_SINK_FLAT_VOLUME) { pa_cvolume new_volume; - /* Make the absolute volume relative */ - i->virtual_volume = i->soft_volume; - i->soft_volume = i->volume_factor; + /* Make the virtual volume relative */ + pa_sink_input_get_relative_volume(i, &i->virtual_volume); + + /* And reset the the relative volume */ + pa_sink_input_set_relative_volume(i, NULL); /* We might need to update the sink's volume if we are in flat * volume mode. */ @@ -1124,6 +1160,8 @@ int pa_sink_input_start_move(pa_sink_input *i) { pa_sink_update_status(i->sink); i->sink = NULL; + pa_sink_input_unref(i); + return 0; } @@ -1171,7 +1209,7 @@ int pa_sink_input_finish_move(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { i->sink = dest; i->save_sink = save; - pa_idxset_put(dest->inputs, i, NULL); + pa_idxset_put(dest->inputs, pa_sink_input_ref(i), NULL); if (pa_sink_input_get_state(i) == PA_SINK_INPUT_CORKED) i->sink->n_corked++; @@ -1236,11 +1274,19 @@ int pa_sink_input_move_to(pa_sink_input *i, pa_sink *dest, pa_bool_t save) { if (!pa_sink_input_may_move_to(i, dest)) return -PA_ERR_NOTSUPPORTED; - if ((r = pa_sink_input_start_move(i)) < 0) + pa_sink_input_ref(i); + + if ((r = pa_sink_input_start_move(i)) < 0) { + pa_sink_input_unref(i); return r; + } - if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) + if ((r = pa_sink_input_finish_move(i, dest, save)) < 0) { + pa_sink_input_unref(i); return r; + } + + pa_sink_input_unref(i); return 0; } diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h index 0dd5e9aa..96ad2baf 100644 --- a/src/pulsecore/sink-input.h +++ b/src/pulsecore/sink-input.h @@ -91,7 +91,10 @@ struct pa_sink_input { pa_sink_input *sync_prev, *sync_next; - pa_cvolume virtual_volume, soft_volume, volume_factor; + pa_cvolume virtual_volume; /* The volume clients are informed about */ + pa_cvolume volume_factor; /* An internally used volume factor that can be used by modules to apply effects and suchlike without having that visible to the outside */ + double relative_volume[PA_CHANNELS_MAX]; /* The calculated volume relative to the sink volume as linear factors. */ + pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through. Usually calculated as relative_volume * volume_factor */ pa_bool_t muted:1; /* if TRUE then the source we are connected to and/or the volume @@ -148,6 +151,10 @@ struct pa_sink_input { * to suspends or resumes. Called from main context */ void (*suspend) (pa_sink_input *i, pa_bool_t b); /* may be NULL */ + /* If non-NULL called whenever the sink this input is attached + * to suspends or resumes. Called from IO context */ + void (*suspend_within_thread) (pa_sink_input *i, pa_bool_t b); /* may be NULL */ + /* If non-NULL called whenever the sink input is moved to a new * sink. Called from main context after the sink input has been * detached from the old sink and before it has been attached to @@ -345,4 +352,7 @@ pa_bool_t pa_sink_input_safe_to_remove(pa_sink_input *i); pa_memchunk* pa_sink_input_get_silence(pa_sink_input *i, pa_memchunk *ret); +/* To be used by sink.c only */ +void pa_sink_input_set_relative_volume(pa_sink_input *i, const pa_cvolume *v); + #endif diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c index a0f0ea7e..93800d14 100644 --- a/src/pulsecore/sink.c +++ b/src/pulsecore/sink.c @@ -50,6 +50,7 @@ #define MIX_BUFFER_LENGTH (PA_PAGE_SIZE) #define ABSOLUTE_MIN_LATENCY (500) #define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC) +#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC) static PA_DEFINE_CHECK_TYPE(pa_sink, pa_msgobject); @@ -208,6 +209,8 @@ pa_sink* pa_sink_new( s->muted = data->muted; s->refresh_volume = s->refresh_muted = FALSE; + s->fixed_latency = flags & PA_SINK_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; + reset_callbacks(s); s->userdata = NULL; @@ -363,8 +366,13 @@ void pa_sink_put(pa_sink* s) { if (s->flags & PA_SINK_LATENCY) s->monitor_source->flags |= PA_SOURCE_LATENCY; - if (s->flags & PA_SINK_DYNAMIC_LATENCY) + if (s->flags & PA_SINK_DYNAMIC_LATENCY) { s->monitor_source->flags |= PA_SOURCE_DYNAMIC_LATENCY; + s->fixed_latency = 0; + } else if (s->fixed_latency <= 0) + s->fixed_latency = DEFAULT_FIXED_LATENCY; + + s->monitor_source->fixed_latency = s->fixed_latency; pa_assert_se(sink_set_state(s, PA_SINK_IDLE) == 0); @@ -515,8 +523,12 @@ pa_queue *pa_sink_move_all_start(pa_sink *s) { for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = n) { n = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx)); + pa_sink_input_ref(i); + if (pa_sink_input_start_move(i) >= 0) - pa_queue_push(q, pa_sink_input_ref(i)); + pa_queue_push(q, i); + else + pa_sink_input_unref(i); } return q; @@ -984,6 +996,46 @@ pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) { return usec; } +static void compute_new_soft_volume(pa_sink_input *i, const pa_cvolume *new_volume) { + unsigned c; + + pa_sink_input_assert_ref(i); + pa_assert(new_volume->channels == i->sample_spec.channels); + + /* + * This basically calculates: + * + * i->relative_volume := i->virtual_volume / new_volume + * i->soft_volume := i->relative_volume * i->volume_factor + */ + + /* The new sink volume passed in here must already be remapped to + * the sink input's channel map! */ + + i->soft_volume.channels = i->sample_spec.channels; + + for (c = 0; c < i->sample_spec.channels; c++) + + if (new_volume->values[c] <= PA_VOLUME_MUTED) + /* We leave i->relative_volume untouched */ + i->soft_volume.values[c] = PA_VOLUME_MUTED; + else { + i->relative_volume[c] = + pa_sw_volume_to_linear(i->virtual_volume.values[c]) / + pa_sw_volume_to_linear(new_volume->values[c]); + + i->soft_volume.values[c] = pa_sw_volume_from_linear( + i->relative_volume[c] * + pa_sw_volume_to_linear(i->volume_factor.values[c])); + } + + /* Hooks have the ability to play games with i->soft_volume */ + pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); + + /* We don't copy the soft_volume to the thread_info data + * here. That must be done by the caller */ +} + /* Called from main thread */ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) { pa_sink_input *i; @@ -998,7 +1050,7 @@ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) { * might need to fix up the sink volume accordingly. Please note * that we don't actually update the sinks volume here, we only * return how it needs to be updated. The caller should then call - * pa_sink_set_flat_volume().*/ + * pa_sink_set_volume().*/ if (pa_idxset_isempty(s->inputs)) { /* In the special case that we have no sink input we leave the @@ -1030,26 +1082,22 @@ void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume) { remapped_new_volume = *new_volume; pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map); - pa_sw_cvolume_divide(&i->soft_volume, &i->virtual_volume, &remapped_new_volume); - pa_sw_cvolume_multiply(&i->soft_volume, &i->soft_volume, &i->volume_factor); - - /* Hooks have the ability to play games with i->soft_volume */ - pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_INPUT_SET_VOLUME], i); + compute_new_soft_volume(i, &remapped_new_volume); - /* We don't issue PA_SINK_INPUT_MESSAGE_SET_VOLUME because - * we want the update to have atomically with the sink - * volume update, hence we do it within the - * pa_sink_set_flat_volume() call below*/ + /* We don't copy soft_volume to the thread_info data here + * (i.e. issue PA_SINK_INPUT_MESSAGE_SET_VOLUME) because we + * want the update to be atomically with the sink volume + * update, hence we do it within the pa_sink_set_volume() call + * below */ } } /* Called from main thread */ -void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume) { +void pa_sink_propagate_flat_volume(pa_sink *s) { pa_sink_input *i; uint32_t idx; pa_sink_assert_ref(s); - pa_assert(old_volume); pa_assert(PA_SINK_IS_LINKED(s->state)); pa_assert(s->flags & PA_SINK_FLAT_VOLUME); @@ -1058,39 +1106,43 @@ void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume) { * sink input volumes accordingly */ for (i = PA_SINK_INPUT(pa_idxset_first(s->inputs, &idx)); i; i = PA_SINK_INPUT(pa_idxset_next(s->inputs, &idx))) { - pa_cvolume remapped_old_volume, remapped_new_volume, fixed_volume; + pa_cvolume sink_volume, new_virtual_volume; unsigned c; - remapped_new_volume = s->virtual_volume; - pa_cvolume_remap(&remapped_new_volume, &s->channel_map, &i->channel_map); + /* This basically calculates i->virtual_volume := i->relative_volume * s->virtual_volume */ - remapped_old_volume = *old_volume; - pa_cvolume_remap(&remapped_old_volume, &s->channel_map, &i->channel_map); + sink_volume = s->virtual_volume; + pa_cvolume_remap(&sink_volume, &s->channel_map, &i->channel_map); for (c = 0; c < i->sample_spec.channels; c++) + new_virtual_volume.values[c] = pa_sw_volume_from_linear( + i->relative_volume[c] * + pa_sw_volume_to_linear(sink_volume.values[c])); - if (remapped_old_volume.values[c] == PA_VOLUME_MUTED) - fixed_volume.values[c] = PA_VOLUME_MUTED; - else - fixed_volume.values[c] = (pa_volume_t) - ((uint64_t) i->virtual_volume.values[c] * - (uint64_t) remapped_new_volume.values[c] / - (uint64_t) remapped_old_volume.values[c]); + new_virtual_volume.channels = i->sample_spec.channels; - fixed_volume.channels = i->virtual_volume.channels; + if (!pa_cvolume_equal(&new_virtual_volume, &i->virtual_volume)) { + i->virtual_volume = new_virtual_volume; - if (!pa_cvolume_equal(&fixed_volume, &i->virtual_volume)) { - i->virtual_volume = fixed_volume; + /* Hmm, the soft volume might no longer actually match + * what has been chosen as new virtual volume here, + * especially when the old volume was + * PA_VOLUME_MUTED. Hence let's recalculate the soft + * volumes here. */ + compute_new_soft_volume(i, &sink_volume); /* The virtual volume changed, let's tell people so */ pa_subscription_post(i->core, PA_SUBSCRIPTION_EVENT_SINK_INPUT|PA_SUBSCRIPTION_EVENT_CHANGE, i->index); } } + + /* If the soft_volume of any of the sink inputs got changed, let's + * make sure the thread copies are synced up. */ + pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SYNC_VOLUMES, NULL, 0, NULL) == 0); } /* Called from main thread */ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg) { - pa_cvolume old_virtual_volume; pa_bool_t virtual_volume_changed; pa_sink_assert_ref(s); @@ -1099,14 +1151,13 @@ void pa_sink_set_volume(pa_sink *s, const pa_cvolume *volume, pa_bool_t propagat pa_assert(pa_cvolume_valid(volume)); pa_assert(pa_cvolume_compatible(volume, &s->sample_spec)); - old_virtual_volume = s->virtual_volume; + virtual_volume_changed = !pa_cvolume_equal(volume, &s->virtual_volume); s->virtual_volume = *volume; - virtual_volume_changed = !pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume); /* Propagate this volume change back to the inputs */ if (virtual_volume_changed) if (propagate && (s->flags & PA_SINK_FLAT_VOLUME)) - pa_sink_propagate_flat_volume(s, &old_virtual_volume); + pa_sink_propagate_flat_volume(s); if (s->set_volume) { /* If we have a function set_volume(), then we do not apply a @@ -1157,7 +1208,7 @@ const pa_cvolume *pa_sink_get_volume(pa_sink *s, pa_bool_t force_refresh) { if (!pa_cvolume_equal(&old_virtual_volume, &s->virtual_volume)) { if (s->flags & PA_SINK_FLAT_VOLUME) - pa_sink_propagate_flat_volume(s, &old_virtual_volume); + pa_sink_propagate_flat_volume(s); pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_CHANGE, s->index); } @@ -1565,9 +1616,13 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse pa_sink_request_rewind(s, (size_t) -1); } - if (s->flags & PA_SINK_FLAT_VOLUME) - sync_input_volumes_within_thread(s); + if (!(s->flags & PA_SINK_FLAT_VOLUME)) + return 0; + + /* Fall through ... */ + case PA_SINK_MESSAGE_SYNC_VOLUMES: + sync_input_volumes_within_thread(s); return 0; case PA_SINK_MESSAGE_GET_VOLUME: @@ -1585,7 +1640,11 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse case PA_SINK_MESSAGE_GET_MUTE: return 0; - case PA_SINK_MESSAGE_SET_STATE: + case PA_SINK_MESSAGE_SET_STATE: { + + pa_bool_t suspend_change = + (s->thread_info.state == PA_SINK_SUSPENDED && PA_SINK_IS_OPENED(PA_PTR_TO_UINT(userdata))) || + (PA_SINK_IS_OPENED(s->thread_info.state) && PA_PTR_TO_UINT(userdata) == PA_SINK_SUSPENDED); s->thread_info.state = PA_PTR_TO_UINT(userdata); @@ -1594,7 +1653,17 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse s->thread_info.rewind_requested = FALSE; } + if (suspend_change) { + pa_sink_input *i; + void *state = NULL; + + while ((i = pa_hashmap_iterate(s->thread_info.inputs, &state, NULL))) + if (i->suspend_within_thread) + i->suspend_within_thread(i, s->thread_info.state == PA_SINK_SUSPENDED); + } + return 0; + } case PA_SINK_MESSAGE_DETACH: @@ -1763,6 +1832,9 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { pa_sink_assert_ref(s); + if (!(s->flags & PA_SINK_DYNAMIC_LATENCY)) + return PA_CLAMP(s->fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); + if (s->thread_info.requested_latency_valid) return s->thread_info.requested_latency; @@ -1778,13 +1850,8 @@ pa_usec_t pa_sink_get_requested_latency_within_thread(pa_sink *s) { (result == (pa_usec_t) -1 || result > monitor_latency)) result = monitor_latency; - if (result != (pa_usec_t) -1) { - if (result > s->thread_info.max_latency) - result = s->thread_info.max_latency; - - if (result < s->thread_info.min_latency) - result = s->thread_info.min_latency; - } + if (result != (pa_usec_t) -1) + result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); s->thread_info.requested_latency = result; s->thread_info.requested_latency_valid = TRUE; @@ -1873,6 +1940,9 @@ void pa_sink_invalidate_requested_latency(pa_sink *s) { pa_sink_assert_ref(s); + if (!(s->flags & PA_SINK_DYNAMIC_LATENCY)) + return; + s->thread_info.requested_latency_valid = FALSE; if (PA_SINK_IS_LINKED(s->thread_info.state)) { diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h index 634bf3ef..cb4697f9 100644 --- a/src/pulsecore/sink.h +++ b/src/pulsecore/sink.h @@ -74,7 +74,8 @@ struct pa_sink { pa_volume_t base_volume; /* shall be constant */ unsigned n_volume_steps; /* shall be constant */ - pa_cvolume virtual_volume, soft_volume; + pa_cvolume virtual_volume; /* The volume clients are informed about */ + pa_cvolume soft_volume; /* The internal software volume we apply to all PCM data while it passes through */ pa_bool_t muted:1; pa_bool_t refresh_volume:1; @@ -85,6 +86,8 @@ struct pa_sink { pa_memchunk silence; + pa_usec_t fixed_latency; /* for sinks with PA_SINK_DYNAMIC_LATENCY this is 0 */ + /* Called when the main loop requests a state change. Called from * main loop context. If returns -1 the state change will be * inhibited */ @@ -159,6 +162,7 @@ typedef enum pa_sink_message { PA_SINK_MESSAGE_REMOVE_INPUT, PA_SINK_MESSAGE_GET_VOLUME, PA_SINK_MESSAGE_SET_VOLUME, + PA_SINK_MESSAGE_SYNC_VOLUMES, PA_SINK_MESSAGE_GET_MUTE, PA_SINK_MESSAGE_SET_MUTE, PA_SINK_MESSAGE_GET_LATENCY, @@ -249,7 +253,7 @@ int pa_sink_suspend(pa_sink *s, pa_bool_t suspend); int pa_sink_suspend_all(pa_core *c, pa_bool_t suspend); void pa_sink_update_flat_volume(pa_sink *s, pa_cvolume *new_volume); -void pa_sink_propagate_flat_volume(pa_sink *s, const pa_cvolume *old_volume); +void pa_sink_propagate_flat_volume(pa_sink *s); void pa_sink_set_volume(pa_sink *sink, const pa_cvolume *volume, pa_bool_t propagate, pa_bool_t sendmsg); const pa_cvolume *pa_sink_get_volume(pa_sink *sink, pa_bool_t force_refresh); diff --git a/src/pulsecore/socket-server.c b/src/pulsecore/socket-server.c index 8147b27f..e660700c 100644 --- a/src/pulsecore/socket-server.c +++ b/src/pulsecore/socket-server.c @@ -536,6 +536,7 @@ char *pa_socket_server_get_address(pa_socket_server *s, char *c, size_t l) { return NULL; pa_snprintf(c, l, "{%s}unix:%s", id, s->filename); + pa_xfree(id); return c; } diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c index 1c37be93..3ee26735 100644 --- a/src/pulsecore/source-output.c +++ b/src/pulsecore/source-output.c @@ -87,6 +87,7 @@ static void reset_callbacks(pa_source_output *o) { o->attach = NULL; o->detach = NULL; o->suspend = NULL; + o->suspend_within_thread = NULL; o->moving = NULL; o->kill = NULL; o->get_latency = NULL; @@ -519,6 +520,9 @@ void pa_source_output_update_max_rewind(pa_source_output *o, size_t nbytes /* i pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output *o, pa_usec_t usec) { pa_source_output_assert_ref(o); + if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) + usec = o->source->fixed_latency; + if (usec != (pa_usec_t) -1) usec = PA_CLAMP(usec, o->source->thread_info.min_latency, o->source->thread_info.max_latency); @@ -530,8 +534,6 @@ pa_usec_t pa_source_output_set_requested_latency_within_thread(pa_source_output /* Called from main context */ pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t usec) { - pa_usec_t min_latency, max_latency; - pa_source_output_assert_ref(o); if (PA_SOURCE_OUTPUT_IS_LINKED(o->state) && o->source) { @@ -543,10 +545,14 @@ pa_usec_t pa_source_output_set_requested_latency(pa_source_output *o, pa_usec_t * have to touch the thread info data directly */ if (o->source) { - pa_source_get_latency_range(o->source, &min_latency, &max_latency); + if (!(o->source->flags & PA_SOURCE_DYNAMIC_LATENCY)) + usec = o->source->fixed_latency; - if (usec != (pa_usec_t) -1) + if (usec != (pa_usec_t) -1) { + pa_usec_t min_latency, max_latency; + pa_source_get_latency_range(o->source, &min_latency, &max_latency); usec = PA_CLAMP(usec, min_latency, max_latency); + } } o->thread_info.requested_source_latency = usec; @@ -704,6 +710,8 @@ int pa_source_output_start_move(pa_source_output *o) { pa_source_update_status(o->source); o->source = NULL; + pa_source_output_unref(o); + return 0; } @@ -751,7 +759,7 @@ int pa_source_output_finish_move(pa_source_output *o, pa_source *dest, pa_bool_t o->source = dest; o->save_source = save; - pa_idxset_put(o->source->outputs, o, NULL); + pa_idxset_put(o->source->outputs, pa_source_output_ref(o), NULL); if (pa_source_output_get_state(o) == PA_SOURCE_OUTPUT_CORKED) o->source->n_corked++; @@ -803,11 +811,19 @@ int pa_source_output_move_to(pa_source_output *o, pa_source *dest, pa_bool_t sav if (!pa_source_output_may_move_to(o, dest)) return -PA_ERR_NOTSUPPORTED; - if ((r = pa_source_output_start_move(o)) < 0) + pa_source_output_ref(o); + + if ((r = pa_source_output_start_move(o)) < 0) { + pa_source_output_unref(o); return r; + } - if ((r = pa_source_output_finish_move(o, dest, save)) < 0) + if ((r = pa_source_output_finish_move(o, dest, save)) < 0) { + pa_source_output_unref(o); return r; + } + + pa_source_output_unref(o); return 0; } diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h index 9f5f7744..9824e160 100644 --- a/src/pulsecore/source-output.h +++ b/src/pulsecore/source-output.h @@ -120,6 +120,10 @@ struct pa_source_output { * to suspends or resumes. Called from main context */ void (*suspend) (pa_source_output *o, pa_bool_t b); /* may be NULL */ + /* If non-NULL called whenever the source this output is attached + * to suspends or resumes. Called from IO context */ + void (*suspend_within_thread) (pa_source_output *o, pa_bool_t b); /* may be NULL */ + /* If non-NULL called whenever the source output is moved to a new * source. Called from main context after the stream was detached * from the old source and before it is attached to the new diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c index 252e23ab..21902509 100644 --- a/src/pulsecore/source.c +++ b/src/pulsecore/source.c @@ -43,6 +43,7 @@ #define ABSOLUTE_MIN_LATENCY (500) #define ABSOLUTE_MAX_LATENCY (10*PA_USEC_PER_SEC) +#define DEFAULT_FIXED_LATENCY (250*PA_USEC_PER_MSEC) static PA_DEFINE_CHECK_TYPE(pa_source, pa_msgobject); @@ -199,6 +200,8 @@ pa_source* pa_source_new( s->muted = data->muted; s->refresh_volume = s->refresh_muted = FALSE; + s->fixed_latency = flags & PA_SOURCE_DYNAMIC_LATENCY ? 0 : DEFAULT_FIXED_LATENCY; + reset_callbacks(s); s->userdata = NULL; @@ -303,8 +306,7 @@ void pa_source_put(pa_source *s) { /* The following fields must be initialized properly when calling _put() */ pa_assert(s->asyncmsgq); pa_assert(s->rtpoll); - pa_assert(!s->thread_info.min_latency || !s->thread_info.max_latency || - s->thread_info.min_latency <= s->thread_info.max_latency); + pa_assert(s->thread_info.min_latency <= s->thread_info.max_latency); if (!(s->flags & PA_SOURCE_HW_VOLUME_CTRL)) { s->flags |= PA_SOURCE_DECIBEL_VOLUME; @@ -316,6 +318,11 @@ void pa_source_put(pa_source *s) { if (s->flags & PA_SOURCE_DECIBEL_VOLUME) s->n_volume_steps = PA_VOLUME_NORM+1; + if (s->flags & PA_SOURCE_DYNAMIC_LATENCY) + s->fixed_latency = 0; + else if (s->fixed_latency <= 0) + s->fixed_latency = DEFAULT_FIXED_LATENCY; + pa_assert_se(source_set_state(s, PA_SOURCE_IDLE) == 0); pa_subscription_post(s->core, PA_SUBSCRIPTION_EVENT_SOURCE | PA_SUBSCRIPTION_EVENT_NEW, s->index); @@ -466,8 +473,12 @@ pa_queue *pa_source_move_all_start(pa_source *s) { for (o = PA_SOURCE_OUTPUT(pa_idxset_first(s->outputs, &idx)); o; o = n) { n = PA_SOURCE_OUTPUT(pa_idxset_next(s->outputs, &idx)); + pa_source_output_ref(o); + if (pa_source_output_start_move(o) >= 0) - pa_queue_push(q, pa_source_output_ref(o)); + pa_queue_push(q, o); + else + pa_source_output_unref(o); } return q; @@ -933,9 +944,26 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_ case PA_SOURCE_MESSAGE_GET_MUTE: return 0; - case PA_SOURCE_MESSAGE_SET_STATE: + case PA_SOURCE_MESSAGE_SET_STATE: { + + pa_bool_t suspend_change = + (s->thread_info.state == PA_SOURCE_SUSPENDED && PA_SOURCE_IS_OPENED(PA_PTR_TO_UINT(userdata))) || + (PA_SOURCE_IS_OPENED(s->thread_info.state) && PA_PTR_TO_UINT(userdata) == PA_SOURCE_SUSPENDED); + s->thread_info.state = PA_PTR_TO_UINT(userdata); + + if (suspend_change) { + pa_source_output *o; + void *state = NULL; + + while ((o = pa_hashmap_iterate(s->thread_info.outputs, &state, NULL))) + if (o->suspend_within_thread) + o->suspend_within_thread(o, s->thread_info.state == PA_SOURCE_SUSPENDED); + } + + return 0; + } case PA_SOURCE_MESSAGE_DETACH: @@ -1075,6 +1103,9 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { pa_source_assert_ref(s); + if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + return PA_CLAMP(s->fixed_latency, s->thread_info.min_latency, s->thread_info.max_latency); + if (s->thread_info.requested_latency_valid) return s->thread_info.requested_latency; @@ -1084,13 +1115,8 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { (result == (pa_usec_t) -1 || result > o->thread_info.requested_source_latency)) result = o->thread_info.requested_source_latency; - if (result != (pa_usec_t) -1) { - if (s->thread_info.max_latency > 0 && result > s->thread_info.max_latency) - result = s->thread_info.max_latency; - - if (s->thread_info.min_latency > 0 && result < s->thread_info.min_latency) - result = s->thread_info.min_latency; - } + if (result != (pa_usec_t) -1) + result = PA_CLAMP(result, s->thread_info.min_latency, s->thread_info.max_latency); s->thread_info.requested_latency = result; s->thread_info.requested_latency_valid = TRUE; @@ -1100,7 +1126,7 @@ pa_usec_t pa_source_get_requested_latency_within_thread(pa_source *s) { /* Called from main thread */ pa_usec_t pa_source_get_requested_latency(pa_source *s) { - pa_usec_t usec; + pa_usec_t usec = 0; pa_source_assert_ref(s); pa_assert(PA_SOURCE_IS_LINKED(s->state)); @@ -1148,6 +1174,9 @@ void pa_source_invalidate_requested_latency(pa_source *s) { pa_source_assert_ref(s); + if (!(s->flags & PA_SOURCE_DYNAMIC_LATENCY)) + return; + s->thread_info.requested_latency_valid = FALSE; if (PA_SOURCE_IS_LINKED(s->thread_info.state)) { @@ -1217,7 +1246,7 @@ void pa_source_get_latency_range(pa_source *s, pa_usec_t *min_latency, pa_usec_t } } -/* Called from IO thread, and from main thread before pa_sink_put() is called */ +/* Called from IO thread, and from main thread before pa_source_put() is called */ void pa_source_set_latency_range_within_thread(pa_source *s, pa_usec_t min_latency, pa_usec_t max_latency) { void *state = NULL; diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h index 652783ef..b502c228 100644 --- a/src/pulsecore/source.h +++ b/src/pulsecore/source.h @@ -87,6 +87,8 @@ struct pa_source { pa_memchunk silence; + pa_usec_t fixed_latency; /* for sources with PA_SOURCE_DYNAMIC_LATENCY this is 0 */ + /* Called when the main loop requests a state change. Called from * main loop context. If returns -1 the state change will be * inhibited */ diff --git a/src/pulsecore/time-smoother.c b/src/pulsecore/time-smoother.c index 55ac8687..9d5a0705 100644 --- a/src/pulsecore/time-smoother.c +++ b/src/pulsecore/time-smoother.c @@ -291,7 +291,8 @@ static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) { pa_assert(s); pa_assert(y); - if (!s->smoothing || x >= s->px) { + if (x >= s->px) { + /* Linear interpolation right from px */ int64_t t; /* The requested point is right of the point where we wanted @@ -307,7 +308,22 @@ static void estimate(pa_smoother *s, pa_usec_t x, pa_usec_t *y, double *deriv) { if (deriv) *deriv = s->dp; + } else if (x <= s->ex) { + /* Linear interpolation left from ex */ + int64_t t; + + t = (int64_t) s->ey - (int64_t) llrint(s->de * (double) (s->ex - x)); + + if (t < 0) + t = 0; + + *y = (pa_usec_t) t; + + if (deriv) + *deriv = s->de; + } else { + /* Spline interpolation between ex and px */ double tx, ty; /* Ok, we're not yet on track, thus let's interpolate, and @@ -381,7 +397,9 @@ void pa_smoother_put(pa_smoother *s, pa_usec_t x, pa_usec_t y) { s->abc_valid = FALSE; -/* pa_log_debug("put(%llu | %llu) = %llu", (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */ +#ifdef DEBUG_DATA + pa_log_debug("%p, put(%llu | %llu) = %llu", s, (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); +#endif } pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) { @@ -412,7 +430,9 @@ pa_usec_t pa_smoother_get(pa_smoother *s, pa_usec_t x) { s->last_y = y; } -/* pa_log_debug("get(%llu | %llu) = %llu", (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); */ +#ifdef DEBUG_DATA + pa_log_debug("%p, get(%llu | %llu) = %llu", s, (unsigned long long) (x + s->time_offset), (unsigned long long) x, (unsigned long long) y); +#endif return y; } @@ -422,7 +442,9 @@ void pa_smoother_set_time_offset(pa_smoother *s, pa_usec_t offset) { s->time_offset = offset; -/* pa_log_debug("offset(%llu)", (unsigned long long) offset); */ +#ifdef DEBUG_DATA + pa_log_debug("offset(%llu)", (unsigned long long) offset); +#endif } void pa_smoother_pause(pa_smoother *s, pa_usec_t x) { @@ -431,7 +453,9 @@ void pa_smoother_pause(pa_smoother *s, pa_usec_t x) { if (s->paused) return; -/* pa_log_debug("pause(%llu)", (unsigned long long) x); */ +#ifdef DEBUG_DATA + pa_log_debug("pause(%llu)", (unsigned long long) x); +#endif s->paused = TRUE; s->pause_time = x; @@ -446,7 +470,9 @@ void pa_smoother_resume(pa_smoother *s, pa_usec_t x, pa_bool_t fix_now) { if (x < s->pause_time) x = s->pause_time; -/* pa_log_debug("resume(%llu)", (unsigned long long) x); */ +#ifdef DEBUG_DATA + pa_log_debug("resume(%llu)", (unsigned long long) x); +#endif s->paused = FALSE; s->time_offset += x - s->pause_time; @@ -481,7 +507,9 @@ pa_usec_t pa_smoother_translate(pa_smoother *s, pa_usec_t x, pa_usec_t y_delay) if (s->dp > nde) nde = s->dp; -/* pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / nde), nde); */ +#ifdef DEBUG_DATA + pa_log_debug("translate(%llu) = %llu (%0.2f)", (unsigned long long) y_delay, (unsigned long long) ((double) y_delay / nde), nde); +#endif return (pa_usec_t) llrint((double) y_delay / nde); } diff --git a/src/tests/interpol-test.c b/src/tests/interpol-test.c index c103a493..0c906d3e 100644 --- a/src/tests/interpol-test.c +++ b/src/tests/interpol-test.c @@ -37,6 +37,7 @@ #include <pulsecore/thread.h> #define INTERPOLATE +//#define CORK static pa_context *context = NULL; static pa_stream *stream = NULL; @@ -125,7 +126,9 @@ int main(int argc, char *argv[]) { int k, r; struct timeval start, last_info = { 0, 0 }; pa_usec_t old_t = 0, old_rtc = 0; +#ifdef CORK pa_bool_t corked = FALSE; +#endif pa_log_set_level(PA_LOG_DEBUG); @@ -150,7 +153,12 @@ int main(int argc, char *argv[]) { r = pa_threaded_mainloop_start(m); assert(r >= 0); - for (k = 0; k < 20000; k++) { +/* #ifdef CORK */ + for (k = 0; k < 20000; k++) +/* #else */ +/* for (k = 0; k < 2000; k++) */ +/* #endif */ + { pa_bool_t success = FALSE, changed = FALSE; pa_usec_t t, rtc; struct timeval now, tv; @@ -179,8 +187,9 @@ int main(int argc, char *argv[]) { pa_gettimeofday(&now); if (success) { +#ifdef CORK pa_bool_t cork_now; - +#endif rtc = pa_timeval_diff(&now, &start); printf("%i\t%llu\t%llu\t%llu\t%llu\t%lli\t%u\t%u\n", k, (unsigned long long) rtc, @@ -195,6 +204,7 @@ int main(int argc, char *argv[]) { old_t = t; old_rtc = rtc; +#ifdef CORK cork_now = (rtc / (2*PA_USEC_PER_SEC)) % 2 == 1; if (corked != cork_now) { @@ -206,6 +216,7 @@ int main(int argc, char *argv[]) { corked = cork_now; } +#endif } /* Spin loop, ugly but normal usleep() is just too badly grained */ diff --git a/src/tests/voltest.c b/src/tests/voltest.c index 0c6d2ea6..2dcfa53c 100644 --- a/src/tests/voltest.c +++ b/src/tests/voltest.c @@ -9,6 +9,9 @@ int main(int argc, char *argv[]) { float b; pa_channel_map map; + printf("Attenuation of sample 1 against 32767: %g dB\n", 20.0*log10(1.0/32767.0)); + printf("Smallest possible attenutation > 0 applied to 32767: %li\n", lrint(32767.0*pa_sw_volume_to_linear(1))); + for (v = PA_VOLUME_MUTED; v <= PA_VOLUME_NORM*2; v += 256) { double dB = pa_sw_volume_to_dB(v); |