[pulseaudio-discuss] [PATCH 3/3] core: prefer available devices during default source/sink selection

2018-04-09 Thread Georg Chini
Because ports with disabled jack detection are set to availability
unknown by default, available ports should be preferred over ports
with unknown availability.
---
 src/pulsecore/core.c | 20 ++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index acd47cbb..8c074afe 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -325,7 +325,15 @@ static int compare_sinks(pa_sink *a, pa_sink *b) {
 
 core = a->core;
 
-/* Available sinks always beat unavailable sinks. */
+/* Available sinks always beat sinks with unknown availability. */
+if ((!a->active_port || a->active_port->available <= PA_AVAILABLE_UNKNOWN)
+&& b->active_port && b->active_port->available == PA_AVAILABLE_YES)
+return -1;
+if ((!b->active_port || b->active_port->available <= PA_AVAILABLE_UNKNOWN)
+&& a->active_port && a->active_port->available == PA_AVAILABLE_YES)
+return 1;
+
+/* Possibly available sinks always beat unavailable sinks. */
 if (a->active_port && a->active_port->available == PA_AVAILABLE_NO
 && (!b->active_port || b->active_port->available != 
PA_AVAILABLE_NO))
 return -1;
@@ -402,7 +410,15 @@ static int compare_sources(pa_source *a, pa_source *b) {
 
 core = a->core;
 
-/* Available sources always beat unavailable sources. */
+/* Available sources always beat sources with unknown availability. */
+if ((!a->active_port || a->active_port->available <= PA_AVAILABLE_UNKNOWN)
+&& b->active_port && b->active_port->available == PA_AVAILABLE_YES)
+return -1;
+if ((!b->active_port || b->active_port->available <= PA_AVAILABLE_UNKNOWN)
+&& a->active_port && a->active_port->available == PA_AVAILABLE_YES)
+return 1;
+
+/* Possibly available sources always beat unavailable sources. */
 if (a->active_port && a->active_port->available == PA_AVAILABLE_NO
 && (!b->active_port || b->active_port->available != 
PA_AVAILABLE_NO))
 return -1;
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 1/3] card: add infrastructure to enable/disable jack detection

2018-04-09 Thread Georg Chini
This patch adds a card message handler which will be used to enable
or disable jack detection on a per port basis. Only the necessary
variables and functions are added without changing functionality.
---
 src/pulsecore/card.c| 129 +++-
 src/pulsecore/card.h|   4 ++
 src/pulsecore/device-port.h |   2 +
 3 files changed, 133 insertions(+), 2 deletions(-)

diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c
index bc5b75b0..e91c034f 100644
--- a/src/pulsecore/card.c
+++ b/src/pulsecore/card.c
@@ -27,11 +27,13 @@
 
 #include 
 #include 
+#include 
 
 #include 
 #include 
 #include 
 #include 
+#include 
 #include 
 
 #include "card.h"
@@ -86,6 +88,7 @@ pa_card_new_data* pa_card_new_data_init(pa_card_new_data 
*data) {
 data->proplist = pa_proplist_new();
 data->profiles = pa_hashmap_new_full(pa_idxset_string_hash_func, 
pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_card_profile_free);
 data->ports = pa_hashmap_new_full(pa_idxset_string_hash_func, 
pa_idxset_string_compare_func, NULL, (pa_free_cb_t) pa_device_port_unref);
+data->jack_detection = true;
 return data;
 }
 
@@ -120,9 +123,115 @@ void pa_card_new_data_done(pa_card_new_data *data) {
 pa_xfree(data->name);
 }
 
+static int card_message_handler(const char *object_path, const char *message, 
char *message_parameters, char **response, void *userdata) {
+pa_card *c;
+char *port_name;
+bool jack_detection;
+void *state = NULL;
+void *state2;
+pa_device_port *port = NULL;
+
+pa_assert(c = (pa_card *) userdata);
+pa_assert(message);
+pa_assert(response);
+
+/* Get the first argument of the message */
+if (pa_message_param_read_string(message_parameters, _name, false, 
) <= 0)
+return -PA_ERR_INVALID;
+
+/* If the port argument is not "all", retrieve the port */
+if (!pa_streq(port_name, "all")) {
+if (!(port = pa_hashmap_get(c->ports, port_name)))
+return -PA_ERR_INVALID;
+}
+
+if (pa_streq(message, "set-jack-detection")) {
+
+/* Get the requested detection state */
+if (pa_message_param_read_bool(message_parameters, _detection, 
) <= 0)
+return -PA_ERR_INVALID;
+
+if (!port) {
+c->jack_detection = jack_detection;
+
+PA_HASHMAP_FOREACH(port, c->ports, state2)
+port->jack_detection = c->jack_detection;
+
+} else
+port->jack_detection = jack_detection;
+
+return PA_OK;
+
+} else if (pa_streq(message, "get-jack-detection")) {
+pa_message_param *param;
+
+param = pa_message_param_new();
+if (!port) {
+pa_message_param_begin_list(param);
+pa_message_param_write_string(param, c->name, false);
+pa_message_param_write_bool(param, c->jack_detection);
+pa_message_param_end_list(param);
+
+PA_HASHMAP_FOREACH(port, c->ports, state2) {
+pa_message_param_begin_list(param);
+pa_message_param_write_string(param, port->name, false);
+pa_message_param_write_bool(param, port->jack_detection);
+pa_message_param_end_list(param);
+}
+
+} else {
+
+pa_message_param_begin_list(param);
+pa_message_param_write_string(param, port->name, false);
+pa_message_param_write_bool(param, port->jack_detection);
+pa_message_param_end_list(param);
+}
+
+*response = pa_message_param_to_string(param);
+return PA_OK;
+
+} else if (pa_streq(message, "set-port-state")) {
+
+/* Not implemented because jack_detection is still unused
+ * and manually setting a port state would require to disable
+ * jack detection */
+return -PA_ERR_NOTIMPLEMENTED;
+
+} else if (pa_streq(message, "get-port-state")) {
+pa_message_param *param;
+uint64_t current_state;
+
+param = pa_message_param_new();
+if (!port) {
+PA_HASHMAP_FOREACH(port, c->ports, state2) {
+pa_message_param_begin_list(param);
+pa_message_param_write_string(param, port->name, false);
+current_state = port->available;
+pa_message_param_write_uint64(param, current_state);
+pa_message_param_end_list(param);
+}
+
+} else {
+
+pa_message_param_begin_list(param);
+pa_message_param_write_string(param, port->name, false);
+current_state = port->available;
+pa_message_param_write_uint64(param, current_state);
+pa_message_param_end_list(param);
+}
+
+*response = pa_message_param_to_string(param);
+return PA_OK;
+
+}
+
+return -PA_ERR_NOTIMPLEMENTED;
+}
+
 pa_card *pa_card_new(pa_core *core, pa_card_new_data *data) {
 pa_card *c;
-const char *name;
+const 

[pulseaudio-discuss] [PATCH 0/3] card: Add messages to control jack detection

2018-04-09 Thread Georg Chini
This patch set uses the message API to implement messages that can
control jack detection and port availability. Jack detection can be
switched on and off for individual ports or whole cards. The current
port state can be queried.

Additionally, a port can be forced to some state. This implies
switching off jack detection for that port.

Because the set depends on the not yet reviewed messaging patches
it only implements the basic functionality and does not provide
pactl commands.

Georg Chini (3):
  card: add infrastructure to enable/disable jack detection
  device-port: Add messages to enable/disable jack detection
  core: prefer available devices during default source/sink selection

 doc/messaging_api.txt|  22 
 src/modules/alsa/alsa-ucm.c  |   2 +-
 src/modules/alsa/module-alsa-card.c  |   4 +-
 src/modules/bluetooth/module-bluez5-device.c |   4 +-
 src/pulsecore/card.c | 169 ++-
 src/pulsecore/card.h |   4 +
 src/pulsecore/core.c |  20 +++-
 src/pulsecore/device-port.c  |  15 ++-
 src/pulsecore/device-port.h  |   5 +-
 9 files changed, 234 insertions(+), 11 deletions(-)

-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 2/3] device-port: Add messages to enable/disable jack detection

2018-04-09 Thread Georg Chini
With this patch, messages can be sent to the cards to enable/disable
jack detection for the whole card or single ports, manually set a port
state and to retrieve the current state of jack detection and port
availability.
---
 doc/messaging_api.txt| 22 
 src/modules/alsa/alsa-ucm.c  |  2 +-
 src/modules/alsa/module-alsa-card.c  |  4 +--
 src/modules/bluetooth/module-bluez5-device.c |  4 +--
 src/pulsecore/card.c | 52 
 src/pulsecore/device-port.c  | 15 +++-
 src/pulsecore/device-port.h  |  3 +-
 7 files changed, 89 insertions(+), 13 deletions(-)

diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt
index 57a92f58..b3dfede0 100644
--- a/doc/messaging_api.txt
+++ b/doc/messaging_api.txt
@@ -70,3 +70,25 @@ Object path: /core
 Message: list-handlers
 Parameters: None
 Return value: {{{Handler name} {Description}} ...}
+
+Object path: /cards/
+Message: set-jack-detection
+Parameters: {port_name|all}{0|1}
+Return value: None
+
+Object path: /cards/
+Message: get-jack-detection
+Parameters: {port_name|all}
+Return value: {{port_name}{0|1}} ...
+If "all" is specified, the first value returned is the setting
+for the card.
+
+Object path: /cards/
+Message: set-port-state
+Parameters: {port_name|all}{availability}
+Return value: None
+
+Object path: /cards/
+Message: get-port-state
+Parameters: {port_name|all}
+Return value: {{port_name}{availability}} ...
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index b42c0407..03073a53 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -1880,7 +1880,7 @@ static void ucm_port_update_available(struct ucm_port 
*port) {
 }
 }
 
-pa_device_port_set_available(port->core_port, available);
+pa_device_port_set_available(port->core_port, available, false);
 }
 
 #else /* HAVE_ALSA_UCM */
diff --git a/src/modules/alsa/module-alsa-card.c 
b/src/modules/alsa/module-alsa-card.c
index 385d61d2..104bb05d 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -421,10 +421,10 @@ static int report_jack_state(snd_mixer_elem_t *melem, 
unsigned int mask) {
 
 for (tp = tports; tp->port; tp++)
 if (tp->avail != PA_AVAILABLE_NO)
-   pa_device_port_set_available(tp->port, tp->avail);
+   pa_device_port_set_available(tp->port, tp->avail, false);
 for (tp = tports; tp->port; tp++)
 if (tp->avail == PA_AVAILABLE_NO)
-   pa_device_port_set_available(tp->port, tp->avail);
+   pa_device_port_set_available(tp->port, tp->avail, false);
 
 for (tp = tports; tp->port; tp++) {
 pa_alsa_port_data *data;
diff --git a/src/modules/bluetooth/module-bluez5-device.c 
b/src/modules/bluetooth/module-bluez5-device.c
index 935ee2ce..560de934 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -2253,9 +2253,9 @@ static void handle_transport_state_change(struct userdata 
*u, struct pa_bluetoot
 
 /* Update port availability */
 pa_assert_se(port = pa_hashmap_get(u->card->ports, u->output_port_name));
-pa_device_port_set_available(port, get_port_availability(u, 
PA_DIRECTION_OUTPUT));
+pa_device_port_set_available(port, get_port_availability(u, 
PA_DIRECTION_OUTPUT), false);
 pa_assert_se(port = pa_hashmap_get(u->card->ports, u->input_port_name));
-pa_device_port_set_available(port, get_port_availability(u, 
PA_DIRECTION_INPUT));
+pa_device_port_set_available(port, get_port_availability(u, 
PA_DIRECTION_INPUT), false);
 
 /* Acquire or release transport as needed */
 acquire = (t->state == PA_BLUETOOTH_TRANSPORT_STATE_PLAYING && u->profile 
== t->profile);
diff --git a/src/pulsecore/card.c b/src/pulsecore/card.c
index e91c034f..e056711c 100644
--- a/src/pulsecore/card.c
+++ b/src/pulsecore/card.c
@@ -127,6 +127,7 @@ static int card_message_handler(const char *object_path, 
const char *message, ch
 pa_card *c;
 char *port_name;
 bool jack_detection;
+uint64_t port_state;
 void *state = NULL;
 void *state2;
 pa_device_port *port = NULL;
@@ -154,11 +155,29 @@ static int card_message_handler(const char *object_path, 
const char *message, ch
 if (!port) {
 c->jack_detection = jack_detection;
 
-PA_HASHMAP_FOREACH(port, c->ports, state2)
+PA_HASHMAP_FOREACH(port, c->ports, state2) {
+pa_available_t avail = PA_AVAILABLE_UNKNOWN;
+
+/* If jack detection was enabled, set the port state
+ * to the hardware state. */
+if (c->jack_detection)
+avail = port->hw_available;
+
 port->jack_detection = c->jack_detection;
+pa_device_port_set_available(port, avail, true);
+}
+
+} else {
+pa_available_t 

[pulseaudio-discuss] [PATCH 0/3] core: Add signal sending/receiving

2018-04-09 Thread Georg Chini
This patch set adds signal sending capability to the core. This is
used to send signals when message handlers are added/removed or
when the description changes.

This patch set depends on the messaging patch set.

Georg Chini (3):
  protocol-native: Add signal receiving capability
  pactl: Add signal receiver
  message-handler: Send signal on handler events

 PROTOCOL|  6 
 man/pactl.1.xml.in  |  2 +-
 src/map-file|  2 ++
 src/pulse/def.h |  6 
 src/pulse/internal.h|  2 ++
 src/pulse/subscribe.c   | 71 +
 src/pulse/subscribe.h   |  9 ++
 src/pulsecore/core.h|  1 +
 src/pulsecore/message-handler.c | 41 
 src/pulsecore/message-handler.h | 13 
 src/pulsecore/native-common.h   |  3 ++
 src/pulsecore/pdispatch.c   |  1 +
 src/pulsecore/protocol-native.c | 56 
 src/utils/pactl.c   | 20 
 14 files changed, 226 insertions(+), 7 deletions(-)

-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 2/3] pactl: Add signal receiver

2018-04-09 Thread Georg Chini
---
 man/pactl.1.xml.in |  2 +-
 src/utils/pactl.c  | 20 
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in
index 66c0bda9..5279791b 100644
--- a/man/pactl.1.xml.in
+++ b/man/pactl.1.xml.in
@@ -254,7 +254,7 @@ License along with PulseAudio; if not, see 
.
 
 
   subscribe
-  Subscribe to events, pactl does not exit by itself, but 
keeps waiting for new events.
+  Subscribe to events and signals, pactl does not exit by 
itself, but keeps waiting for new events.
 
 
   
diff --git a/src/utils/pactl.c b/src/utils/pactl.c
index 1f13e60c..87827d38 100644
--- a/src/utils/pactl.c
+++ b/src/utils/pactl.c
@@ -1251,6 +1251,17 @@ static void context_subscribe_callback(pa_context *c, 
pa_subscription_event_type
 fflush(stdout);
 }
 
+static void context_signal_callback(pa_context *c, const char 
*signal_object_path, const char *signal, char *signal_parameters, void 
*userdata) {
+pa_assert(c);
+
+printf(_("Signal '%s' from %s\n"),
+   signal,
+   signal_object_path);
+if (signal_parameters)
+printf(_("Signal parameters: '%s'\n"), signal_parameters);
+fflush(stdout);
+}
+
 static void context_state_callback(pa_context *c, void *userdata) {
 pa_operation *o = NULL;
 
@@ -1498,6 +1509,15 @@ static void context_state_callback(pa_context *c, void 
*userdata) {
  PA_SUBSCRIPTION_MASK_CARD,
  NULL,
  NULL);
+if (o) {
+pa_operation_unref(o);
+actions++;
+}
+
+pa_context_set_signal_callback(c, context_signal_callback, 
NULL);
+
+o = pa_context_subscribe_signals(c, (uint64_t) -1, NULL, 
NULL);
+
 break;
 
 default:
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 1/3] protocol-native: Add signal receiving capability

2018-04-09 Thread Georg Chini
This patch extends the client subscription API, so that signals sent from
PulseAudio can be processed. Within PulseAudio, a signal can be emitted
using pa_signal_post(). The interface can be used to notify the client of
events that are not covered by the subscription API (for example a button
press event on a bluetooth headset).

Setting up signal notification is very similar to using subscriptions.
First the client needs to subscribe with pa_context_subscribe_signals()
and then sets up a signal handler using pa_context_set_signal_callback().

The signal handler will receive three arguments in addition to the usual
context and userdata:
object_path - string that specifies the origin of the signal
signal - string that specifies the type of the signal
signal_parameters - optional string for additional information
---
 PROTOCOL|  6 
 src/map-file|  2 ++
 src/pulse/def.h |  6 
 src/pulse/internal.h|  2 ++
 src/pulse/subscribe.c   | 71 +
 src/pulse/subscribe.h   |  9 ++
 src/pulsecore/core.h|  1 +
 src/pulsecore/message-handler.c | 16 ++
 src/pulsecore/message-handler.h | 13 
 src/pulsecore/native-common.h   |  3 ++
 src/pulsecore/pdispatch.c   |  1 +
 src/pulsecore/protocol-native.c | 56 
 12 files changed, 180 insertions(+), 6 deletions(-)

diff --git a/PROTOCOL b/PROTOCOL
index f693cd3d..d20f48c6 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -434,6 +434,12 @@ parameters:
 
 The command returns a string.
 
+Added signal sending capability and command PA_COMMAND_SUBSCRIBE_SIGNALS.
+This command allows clients to subscribe to signals that PulseAudio sends.
+
+parameters:
+uint64_t facility_mask - subscription mask
+
  If you just changed the protocol, read this
 ## module-tunnel depends on the sink/source/sink-input/source-input protocol
 ## internals, so if you changed these, you might have broken module-tunnel.
diff --git a/src/map-file b/src/map-file
index 88d892ef..e3f86bf9 100644
--- a/src/map-file
+++ b/src/map-file
@@ -94,6 +94,7 @@ pa_context_set_default_sink;
 pa_context_set_default_source;
 pa_context_set_event_callback;
 pa_context_set_name;
+pa_context_set_signal_callback;
 pa_context_set_sink_input_mute;
 pa_context_set_sink_input_volume;
 pa_context_set_sink_mute_by_index;
@@ -114,6 +115,7 @@ pa_context_set_state_callback;
 pa_context_set_subscribe_callback;
 pa_context_stat;
 pa_context_subscribe;
+pa_context_subscribe_signals;
 pa_context_suspend_sink_by_index;
 pa_context_suspend_sink_by_name;
 pa_context_suspend_source_by_index;
diff --git a/src/pulse/def.h b/src/pulse/def.h
index 100df5b5..964fa9fa 100644
--- a/src/pulse/def.h
+++ b/src/pulse/def.h
@@ -615,6 +615,11 @@ typedef enum pa_subscription_event_type {
 PA_SUBSCRIPTION_EVENT_REMOVE = 0x0020U,
 /**< An object was removed */
 
+/** \cond fulldocs */
+PA_SUBSCRIPTION_EVENT_SIGNAL = 0x0040U,
+/** A signal was issued. Only used internally. */
+/** \endcond */
+
 PA_SUBSCRIPTION_EVENT_TYPE_MASK = 0x0030U
 /**< A mask to extract the event operation from an event value */
 
@@ -650,6 +655,7 @@ typedef enum pa_subscription_event_type {
 #define PA_SUBSCRIPTION_EVENT_NEW PA_SUBSCRIPTION_EVENT_NEW
 #define PA_SUBSCRIPTION_EVENT_CHANGE PA_SUBSCRIPTION_EVENT_CHANGE
 #define PA_SUBSCRIPTION_EVENT_REMOVE PA_SUBSCRIPTION_EVENT_REMOVE
+#define PA_SUBSCRIPTION_EVENT_SIGNAL PA_SUBSCRIPTION_EVENT_SIGNAL
 #define PA_SUBSCRIPTION_EVENT_TYPE_MASK PA_SUBSCRIPTION_EVENT_TYPE_MASK
 /** \endcond */
 
diff --git a/src/pulse/internal.h b/src/pulse/internal.h
index 0d18aa71..a1b4957e 100644
--- a/src/pulse/internal.h
+++ b/src/pulse/internal.h
@@ -95,6 +95,8 @@ struct pa_context {
 void *subscribe_userdata;
 pa_context_event_cb_t event_callback;
 void *event_userdata;
+pa_context_signal_cb_t signal_callback;
+void *signal_userdata;
 
 pa_mempool *mempool;
 
diff --git a/src/pulse/subscribe.c b/src/pulse/subscribe.c
index e7cce2e3..fa5567fb 100644
--- a/src/pulse/subscribe.c
+++ b/src/pulse/subscribe.c
@@ -32,7 +32,6 @@
 void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t command, uint32_t 
tag, pa_tagstruct *t, void *userdata) {
 pa_context *c = userdata;
 pa_subscription_event_type_t e;
-uint32_t idx;
 
 pa_assert(pd);
 pa_assert(command == PA_COMMAND_SUBSCRIBE_EVENT);
@@ -42,15 +41,44 @@ void pa_command_subscribe_event(pa_pdispatch *pd, uint32_t 
command, uint32_t tag
 
 pa_context_ref(c);
 
-if (pa_tagstruct_getu32(t, ) < 0 ||
-pa_tagstruct_getu32(t, ) < 0 ||
-!pa_tagstruct_eof(t)) {
+if (pa_tagstruct_getu32(t, ) < 0) {
 pa_context_fail(c, PA_ERR_PROTOCOL);
 goto finish;
 }
 
-if (c->subscribe_callback)
-c->subscribe_callback(c, e, idx, c->subscribe_userdata);
+if (e != PA_SUBSCRIPTION_EVENT_SIGNAL) {
+uint32_t 

[pulseaudio-discuss] [PATCH 3/3] message-handler: Send signal on handler events

2018-04-09 Thread Georg Chini
---
 src/pulsecore/message-handler.c | 25 +
 1 file changed, 25 insertions(+)

diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c
index 75310667..a983e136 100644
--- a/src/pulsecore/message-handler.c
+++ b/src/pulsecore/message-handler.c
@@ -23,6 +23,7 @@
 #include 
 
 #include 
+#include 
 
 #include 
 #include 
@@ -64,6 +65,7 @@ static bool object_path_is_valid(const char *test_string) {
 /* Register message handler for the specified object. object_path must be a 
unique name starting with "/". */
 void pa_message_handler_register(pa_core *c, const char *object_path, const 
char *description, pa_message_handler_cb_t cb, void *userdata) {
 struct pa_message_handler *handler;
+char *sig_param;
 
 pa_assert(c);
 pa_assert(object_path);
@@ -80,11 +82,17 @@ void pa_message_handler_register(pa_core *c, const char 
*object_path, const char
 handler->description = pa_xstrdup(description);
 
 pa_assert_se(pa_hashmap_put(c->message_handlers, handler->object_path, 
handler) == 0);
+
+/* Notify clients that a handler was added. */
+sig_param = pa_sprintf_malloc("{%s}", object_path);
+pa_signal_post(c, "message-api", 1, "handler-added", sig_param);
+pa_xfree(sig_param);
 }
 
 /* Unregister a message handler */
 void pa_message_handler_unregister(pa_core *c, const char *object_path) {
 struct pa_message_handler *handler;
+char *sig_param;
 
 pa_assert(c);
 pa_assert(object_path);
@@ -94,6 +102,11 @@ void pa_message_handler_unregister(pa_core *c, const char 
*object_path) {
 pa_xfree(handler->object_path);
 pa_xfree(handler->description);
 pa_xfree(handler);
+
+/* Notify clients that a handler was removed. */
+sig_param = pa_sprintf_malloc("{%s}", object_path);
+pa_signal_post(c, "message-api", 1, "handler-removed", sig_param);
+pa_xfree(sig_param);
 }
 
 /* Send a message to an object identified by object_path */
@@ -134,6 +147,8 @@ int pa_message_handler_send_message(pa_core *c, const char 
*object_path, const c
 /* Set handler description */
 int pa_message_handler_set_description(pa_core *c, const char *object_path, 
const char *description) {
 struct pa_message_handler *handler;
+char *sig_param;
+pa_message_param *param;
 
 pa_assert(c);
 pa_assert(object_path);
@@ -141,9 +156,19 @@ int pa_message_handler_set_description(pa_core *c, const 
char *object_path, cons
 if (!(handler = pa_hashmap_get(c->message_handlers, object_path)))
 return -PA_ERR_NOENTITY;
 
+param = pa_message_param_new();
+pa_message_param_write_string(param, object_path, false);
+pa_message_param_write_string(param, handler->description, true);
+pa_message_param_write_string(param, description, true);
+sig_param = pa_message_param_to_string(param);
+
 pa_xfree(handler->description);
 handler->description = pa_xstrdup(description);
 
+/* Notify clients that a handler description changed. */
+pa_signal_post(c, "message-api", 1, "handler-changed", sig_param);
+pa_xfree(sig_param);
+
 return PA_OK;
 }
 
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 7/8] message-params: Add read/write functions for various simple data types

2018-04-09 Thread Georg Chini
See doc/messaging_api.txt for the added functions. All read functions
return 1 on success, 0 if end of string is found and -1 on parse error.
Additionally, for the numeric/boolean read functions, 2 is returned if
the list element is empty.
---
 doc/messaging_api.txt  |  15 +++-
 src/map-file   |   9 +++
 src/pulse/message-params.c | 185 +++--
 src/pulse/message-params.h |  35 +
 4 files changed, 236 insertions(+), 8 deletions(-)

diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt
index 0e6be53f..d080b783 100644
--- a/doc/messaging_api.txt
+++ b/doc/messaging_api.txt
@@ -27,6 +27,11 @@ the structure
 pa_message_param_begin_list() - starts a list
 pa_message_param_end_list() - ends a list
 pa_message_param_write_string() - writes a string to a pa_message_param 
structure
+pa_message_param_write_double() - writes a double to a pa_message_param 
structure
+pa_message_param_write_int64() - writes an integer to a pa_message_param 
structure
+pa_message_param_write_uint64() - writes an unsigned to a pa_message_param 
structure
+pa_message_param_write_bool() - writes a boolean to a pa_message_param 
structure
+pa_message_param_write_raw() - writes raw a string to a pa_message_param 
structure
 
 For string parameters that contain curly braces, those braces must be escaped
 by adding a "\" before them. This however means that a trailing backslash would
@@ -44,9 +49,15 @@ Other strings can be passed without modification.
 For reading, the following functions are available:
 pa_message_param_split_list() - parse message parameter string
 pa_message_param_read_string() - read a string from a parameter list
+pa_message_param_read_double() - read a double from a parameter list
+pa_message_param_read_int64() - read an integer from a parameter list
+pa_message_param_read_uint64() - read an unsigned from a parameter list
+pa_message_param_read_bool() - read a boolean from a parameter list
 
-pa_message_param_read_string() also reverts the changes that
-pa_message_param_write_string() might have introduced.
+All read functions return 1 on success, 0 if end of string is found and -1 on
+parse error. Additionally, for the numeric/boolean read functions, 2 is 
returned
+if the list element is empty. Also pa_message_param_read_string() reverts the
+changes that pa_message_param_write_string() might have introduced.
 
 Reference:
 
diff --git a/src/map-file b/src/map-file
index 372d190d..ab8c21c6 100644
--- a/src/map-file
+++ b/src/map-file
@@ -228,10 +228,19 @@ pa_mainloop_wakeup;
 pa_message_param_begin_list;
 pa_message_param_end_list;
 pa_message_param_new;
+pa_message_param_read_bool;
+pa_message_param_read_double;
+pa_message_param_read_int64;
 pa_message_param_read_string;
+pa_message_param_read_uint64;
 pa_message_param_split_list;
 pa_message_param_to_string;
+pa_message_param_write_bool;
+pa_message_param_write_double;
+pa_message_param_write_int64;
+pa_message_param_write_raw;
 pa_message_param_write_string;
+pa_message_param_write_uint64;
 pa_msleep;
 pa_operation_cancel;
 pa_operation_get_state;
diff --git a/src/pulse/message-params.c b/src/pulse/message-params.c
index d68ec59d..93972399 100644
--- a/src/pulse/message-params.c
+++ b/src/pulse/message-params.c
@@ -23,6 +23,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 
 #include 
@@ -84,7 +85,7 @@ int pa_message_param_split_list(char *c, char **result, bool 
*is_unpacked, void
 
 /* Empty or no string */
 if (!current || *current == 0)
-return 0;
+return PA_PARAM_LIST_END;
 
 /* Find opening brace */
 while (*current != 0) {
@@ -101,7 +102,7 @@ int pa_message_param_split_list(char *c, char **result, 
bool *is_unpacked, void
 
 /* unexpected closing brace, parse error */
 if (*current == '}' && !found_backslash)
-return -1;
+return PA_PARAM_PARSE_ERROR;
 
 found_backslash = false;
 current++;
@@ -109,7 +110,7 @@ int pa_message_param_split_list(char *c, char **result, 
bool *is_unpacked, void
 
 /* No opening brace found, end of string */
 if (*current == 0)
-return 0;
+return PA_PARAM_LIST_END;
 
 if (is_unpacked)
 *is_unpacked = true;
@@ -140,7 +141,7 @@ int pa_message_param_split_list(char *c, char **result, 
bool *is_unpacked, void
 /* Parse error, closing brace missing */
 if (open_braces != 0) {
 *result = NULL;
-return -1;
+return PA_PARAM_PARSE_ERROR;
 }
 
 /* Replace } with 0 */
@@ -148,7 +149,7 @@ int pa_message_param_split_list(char *c, char **result, 
bool *is_unpacked, void
 
 *state = current + 1;
 
-return 1;
+return PA_PARAM_OK;
 }
 
 /* Read a string from the parameter list. The state pointer is
@@ -165,7 +166,7 @@ int pa_message_param_read_string(char *c, char **result, 
bool allocate, void **s
 
 *result = NULL;
 
-if ((err = pa_message_param_split_list(c, _pos, _unpacked, 
state)) 

[pulseaudio-discuss] [PATCH 6/8] message-params: Allow parameter strings to contain escaped curly braces

2018-04-09 Thread Georg Chini
The patch adds the possibility to escape curly braces within parameter strings
and introduces several new functions that can be used for writing parameters.

For writing, the structure pa_message_param, which is a wrapper for pa_strbuf
has been created. Following new write functions are available:

pa_message_param_new() - creates a new pa_message_param structure
pa_message_param_to_string() - converts a pa_message_param to string and frees
the structure
pa_message_param_begin_list() - starts a list
pa_message_param_end_list() - ends a list
pa_message_param_write_string() - writes a string to a pa_message_param 
structure

For string parameters that contain curly braces, those braces must be escaped
by adding a "\" before them. This however means that a trailing backslash would
falsely escape the closing bracket. To avoid this, single quotes must be added
at start and end of the string. The function pa_message_param_write_string()
has a parameter do_escape. If true, the necessary escaping is added. Escaping
is only needed if a string might fulfill one of the following conditions:

- It contains curly braces
- It contains a trailing "\"
- It is enclosed in single quotes

Other strings can be passed without modification.

For reading, pa_message_param_read_string() reverts the changes that
pa_message_param_write_string() might have introduced.

The patch also adds more restrictions on the object path name. Now only
alphanumeric characters and one of "_", ".", "-" and "/" are allowed.
The path name may not end with a /. If the user specifies a trailing / when
sending a message, it will be removed.
---
 doc/messaging_api.txt   |  34 +++-
 src/Makefile.am |   3 +-
 src/map-file|   5 ++
 src/pulse/message-params.c  | 181 ++--
 src/pulse/message-params.h  |  23 -
 src/pulsecore/core.c|  20 ++---
 src/pulsecore/message-handler.c |  53 +++-
 src/utils/pactl.c   |   4 +-
 8 files changed, 279 insertions(+), 44 deletions(-)

diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt
index 431a5df2..0e6be53f 100644
--- a/doc/messaging_api.txt
+++ b/doc/messaging_api.txt
@@ -14,10 +14,42 @@ look like that:
 {{Integer} {{1st float} {2nd float} ...}}{...}
 Any characters that are not enclosed in curly braces are ignored (all 
characters
 between { and {, between } and } and between } and {). The same syntax is used
-to specify message parameters. The following reference lists available 
messages,
+to specify message parameters. The reference further down lists available 
messages,
 their parameters and return values. If a return value is enclosed in {}, this
 means that multiple elements of the same type may be returned.
 
+There are several functions that simplify reading and writing message parameter
+strings. For writing, the structure pa_message_param can be used. Following
+functions are available:
+pa_message_param_new() - creates a new pa_message_param structure
+pa_message_param_to_string() - converts a pa_message_param to string and frees
+the structure
+pa_message_param_begin_list() - starts a list
+pa_message_param_end_list() - ends a list
+pa_message_param_write_string() - writes a string to a pa_message_param 
structure
+
+For string parameters that contain curly braces, those braces must be escaped
+by adding a "\" before them. This however means that a trailing backslash would
+falsely escape the closing bracket. To avoid this, single quotes must be added
+at start and end of the string. The function pa_message_param_write_string()
+has a parameter do_escape. If true, the necessary escaping is added. Escaping
+is only needed if a string might fulfill one of the following conditions:
+
+- It contains curly braces
+- It contains a trailing "\"
+- It is enclosed in single quotes
+
+Other strings can be passed without modification.
+
+For reading, the following functions are available:
+pa_message_param_split_list() - parse message parameter string
+pa_message_param_read_string() - read a string from a parameter list
+
+pa_message_param_read_string() also reverts the changes that
+pa_message_param_write_string() might have introduced.
+
+Reference:
+
 Object path: /core
 Message: list-handlers
 Parameters: None
diff --git a/src/Makefile.am b/src/Makefile.am
index ccdad8ff..72d8cf22 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -675,6 +675,7 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = \
pulse/timeval.c pulse/timeval.h \
pulse/rtclock.c pulse/rtclock.h \
pulse/volume.c pulse/volume.h \
+   pulse/message-params.c pulse/message-params.h \
pulsecore/atomic.h \
pulsecore/authkey.c pulsecore/authkey.h \
pulsecore/conf-parser.c pulsecore/conf-parser.h \
@@ -884,6 +885,7 @@ libpulse_la_SOURCES = \
pulse/mainloop-api.c pulse/mainloop-api.h \

[pulseaudio-discuss] [PATCH 2/8] protocol-native: add message sending capability

2018-04-09 Thread Georg Chini
This patch adds the PA_COMMAND_SEND_OBJECT_MESSAGE command to protocol-native
so that clients can use the messaging feature introduced in the previous patch.

Sending messages can in effect replace the extension system for modules. The
approach is more flexible than the extension interface because a generic string
format is used to exchange information. Furthermore the messaging system can be
used for any object, not only for modules, and is easier to implement than
extensions.
---
 PROTOCOL| 14 +
 configure.ac|  2 +-
 src/map-file|  1 +
 src/pulse/introspect.c  | 64 +
 src/pulse/introspect.h  | 16 +++
 src/pulsecore/native-common.h   |  3 ++
 src/pulsecore/pdispatch.c   |  3 ++
 src/pulsecore/protocol-native.c | 52 +
 8 files changed, 154 insertions(+), 1 deletion(-)

diff --git a/PROTOCOL b/PROTOCOL
index 546998b7..f693cd3d 100644
--- a/PROTOCOL
+++ b/PROTOCOL
@@ -420,6 +420,20 @@ memfd support only to 10.0+ clients.
 
 Check commit 451d1d676237c81 for further details.
 
+## v33, implemented by >= 13.0
+
+Added new command for communication with objects.
+
+PA_COMMAND_SEND_OBJECT_MESSAGE:
+sends a message to an object identified by an object path
+
+parameters:
+string object_path - unique path identifying the object
+string message - message command
+string message_parameters - additional parameters if required
+
+The command returns a string.
+
  If you just changed the protocol, read this
 ## module-tunnel depends on the sink/source/sink-input/source-input protocol
 ## internals, so if you changed these, you might have broken module-tunnel.
diff --git a/configure.ac b/configure.ac
index 57cb3a92..5537d921 100644
--- a/configure.ac
+++ b/configure.ac
@@ -40,7 +40,7 @@ AC_SUBST(PA_MINOR, pa_minor)
 AC_SUBST(PA_MAJORMINOR, pa_major.pa_minor)
 
 AC_SUBST(PA_API_VERSION, 12)
-AC_SUBST(PA_PROTOCOL_VERSION, 32)
+AC_SUBST(PA_PROTOCOL_VERSION, 33)
 
 # The stable ABI for client applications, for the version info x:y:z
 # always will hold y=z
diff --git a/src/map-file b/src/map-file
index 9b6cba22..4d747f19 100644
--- a/src/map-file
+++ b/src/map-file
@@ -87,6 +87,7 @@ pa_context_remove_autoload_by_name;
 pa_context_remove_sample;
 pa_context_rttime_new;
 pa_context_rttime_restart;
+pa_context_send_message_to_object;
 pa_context_set_card_profile_by_index;
 pa_context_set_card_profile_by_name;
 pa_context_set_default_sink;
diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c
index 510d784a..76bfee41 100644
--- a/src/pulse/introspect.c
+++ b/src/pulse/introspect.c
@@ -2184,3 +2184,67 @@ pa_operation* 
pa_context_suspend_source_by_index(pa_context *c, uint32_t idx, in
 
 return o;
 }
+
+/** Object response string processing **/
+
+static void context_string_callback(pa_pdispatch *pd, uint32_t command, 
uint32_t tag, pa_tagstruct *t, void *userdata) {
+pa_operation *o = userdata;
+const char *response;
+int success = 1;
+
+pa_assert(pd);
+pa_assert(o);
+pa_assert(PA_REFCNT_VALUE(o) >= 1);
+
+if (!o->context)
+goto finish;
+
+if (command != PA_COMMAND_REPLY) {
+if (pa_context_handle_error(o->context, command, t, false) < 0)
+goto finish;
+
+success = 0;
+response = "";
+} else if (pa_tagstruct_gets(t, )  < 0 ||
+   !pa_tagstruct_eof(t)) {
+pa_context_fail(o->context, PA_ERR_PROTOCOL);
+goto finish;
+}
+
+if (!response)
+response = "";
+
+if (o->callback) {
+pa_context_string_cb_t cb = (pa_context_string_cb_t) o->callback;
+cb(o->context, success, response, o->userdata);
+}
+
+finish:
+pa_operation_done(o);
+pa_operation_unref(o);
+}
+
+pa_operation* pa_context_send_message_to_object(pa_context *c, const char 
*object_path, const char *message, const char *message_parameters, 
pa_context_string_cb_t cb, void *userdata) {
+pa_operation *o;
+pa_tagstruct *t;
+uint32_t tag;
+
+pa_assert(c);
+pa_assert(PA_REFCNT_VALUE(c) >= 1);
+
+PA_CHECK_VALIDITY_RETURN_NULL(c, !pa_detect_fork(), PA_ERR_FORKED);
+PA_CHECK_VALIDITY_RETURN_NULL(c, c->state == PA_CONTEXT_READY, 
PA_ERR_BADSTATE);
+
+o = pa_operation_new(c, NULL, (pa_operation_cb_t) cb, userdata);
+
+t = pa_tagstruct_command(c, PA_COMMAND_SEND_OBJECT_MESSAGE, );
+
+pa_tagstruct_puts(t, object_path);
+pa_tagstruct_puts(t, message);
+pa_tagstruct_puts(t, message_parameters);
+
+pa_pstream_send_tagstruct(c->pstream, t);
+pa_pdispatch_register_reply(c->pdispatch, tag, DEFAULT_TIMEOUT, 
context_string_callback, pa_operation_ref(o), (pa_free_cb_t) 
pa_operation_unref);
+
+return o;
+}
diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
index 43389b73..7e47e740 100644
--- a/src/pulse/introspect.h
+++ b/src/pulse/introspect.h
@@ -204,6 +204,12 @@
  * Server 

[pulseaudio-discuss] [PATCH 8/8] message-params: Add read functions for arrays

2018-04-09 Thread Georg Chini
---
 doc/messaging_api.txt  |   5 ++
 src/map-file   |   4 +
 src/pulse/message-params.c | 202 +
 src/pulse/message-params.h |  12 +++
 4 files changed, 223 insertions(+)

diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt
index d080b783..57a92f58 100644
--- a/doc/messaging_api.txt
+++ b/doc/messaging_api.txt
@@ -53,6 +53,11 @@ pa_message_param_read_double() - read a double from a 
parameter list
 pa_message_param_read_int64() - read an integer from a parameter list
 pa_message_param_read_uint64() - read an unsigned from a parameter list
 pa_message_param_read_bool() - read a boolean from a parameter list
+pa_message_param_read_double_array() - read an array of double from list
+pa_message_param_read_int64_array() - read an array of int64 from list
+pa_message_param_read_uint64_array() - read an array of uint64 from list
+pa_message_param_read_string_array() - read an array of strings from a list
+
 
 All read functions return 1 on success, 0 if end of string is found and -1 on
 parse error. Additionally, for the numeric/boolean read functions, 2 is 
returned
diff --git a/src/map-file b/src/map-file
index ab8c21c6..88d892ef 100644
--- a/src/map-file
+++ b/src/map-file
@@ -230,9 +230,13 @@ pa_message_param_end_list;
 pa_message_param_new;
 pa_message_param_read_bool;
 pa_message_param_read_double;
+pa_message_param_read_double_array;
 pa_message_param_read_int64;
+pa_message_param_read_int64_array;
 pa_message_param_read_string;
+pa_message_param_read_string_array;
 pa_message_param_read_uint64;
+pa_message_param_read_uint64_array;
 pa_message_param_split_list;
 pa_message_param_to_string;
 pa_message_param_write_bool;
diff --git a/src/pulse/message-params.c b/src/pulse/message-params.c
index 93972399..b9846863 100644
--- a/src/pulse/message-params.c
+++ b/src/pulse/message-params.c
@@ -38,6 +38,8 @@ struct pa_message_param {
 pa_strbuf *buffer;
 };
 
+/* Helper functions */
+
 /* Remove escaping from a string */
 static char *unescape(char *value) {
 char *tmp;
@@ -62,6 +64,59 @@ static char *unescape(char *value) {
 return value;
 }
 
+/* Count number of top level elements in parameter list */
+static int count_elements(const char *c) {
+const char *s;
+uint32_t element_count;
+bool found_element, found_backslash;
+int open_braces;
+
+if (!c || *c == 0)
+return PA_PARAM_LIST_END;
+
+element_count = 0;
+open_braces = 0;
+found_element = false;
+found_backslash = false;
+s = c;
+
+/* Count elements in list */
+while (*s != 0) {
+
+/* Skip escaped curly braces. */
+if (*s == '\\') {
+found_backslash = true;
+s++;
+continue;
+}
+
+if (*s == '{' && !found_backslash) {
+found_element = true;
+open_braces++;
+}
+if (*s == '}' && !found_backslash)
+open_braces--;
+
+/* unexpected closing brace, parse error */
+if (open_braces < 0)
+return PA_PARAM_PARSE_ERROR;
+
+if (open_braces == 0 && found_element) {
+element_count++;
+found_element = false;
+}
+
+found_backslash = false;
+s++;
+}
+
+/* missing closing brace, parse error */
+if (open_braces > 0)
+return PA_PARAM_PARSE_ERROR;
+
+return element_count;
+}
+
 /* Read functions */
 
 /* Split the specified string into elements. An element is defined as
@@ -300,6 +355,153 @@ int pa_message_param_read_bool(char *c, bool *result, 
void **state) {
 return PA_PARAM_OK;
 }
 
+/* Converts a parameter list to a string array. Escaping is removed
+ * from a string if the string does not contain a list. If allocate
+ * is true, new strings will be allocated, otherwise pointers to
+ * sub-strings within c will be returned. */
+int pa_message_param_read_string_array(char *c, char ***parameter_list, bool 
allocate) {
+char *start_pos;
+void *state = NULL;
+uint32_t element_count, i;
+bool is_unpacked;
+int err;
+
+pa_assert(parameter_list);
+
+*parameter_list = NULL;
+
+/* Count elements, return if no element was found or parse error. */
+if ((element_count = count_elements(c)) <= 0)
+return element_count;
+
+/* Allocate array */
+*parameter_list = pa_xmalloc0(element_count * sizeof(char *));
+
+i = 0;
+while ((err = pa_message_param_split_list(c, _pos, _unpacked, 
)) > 0) {
+(*parameter_list)[i] = start_pos;
+if (is_unpacked)
+(*parameter_list)[i] = unescape(start_pos);
+
+/* If new strings are allocated, they must be freed by the caller */
+if (allocate)
+(*parameter_list)[i] = pa_xstrdup((*parameter_list)[i]);
+
+i++;
+}
+
+if (err < 0) {
+if (allocate) {
+for (i = 0; i < element_count; i++)
+pa_xfree((*parameter_list)[i]);
+}
+

[pulseaudio-discuss] [PATCH 4/8] core: add message handler

2018-04-09 Thread Georg Chini
This patch adds a small message handler to the core which enables
clients to list available handlers via the list-handlers message.
Command: pacmd send-message /core list-handlers
pactl can be used with the same parameters.

The patch also introduces a convention for the return string.
It consists of a list of elements where curly braces are used
to separate elements. Each element can itself contain further
elements. For example consider a message that returns multiple
elements which each contain an integer and an array of float.
A response string would look like that:
{{Integer} {{1st float} {2nd float} ...}}{...}
---
 doc/messaging_api.txt   | 21 +--
 src/pulsecore/core.c| 45 +
 src/pulsecore/message-handler.c | 24 ++
 3 files changed, 84 insertions(+), 6 deletions(-)

diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt
index cbc06e75..431a5df2 100644
--- a/doc/messaging_api.txt
+++ b/doc/messaging_api.txt
@@ -6,10 +6,19 @@ PA_COMMAND_SEND_OBJECT_MESSAGE. A message consists at least 
of an object path
 and a message command, both specified as strings. Additional parameters can
 be specified using a single string, but are not mandatory. The message handler
 returns an error number as defined in def.h and also returns a string in
-the "response" variable. The following reference lists available messages,
-their parameters and return values.
+the "response" variable. If the string is not empty it consists of elements.
+Curly braces are used to separate elements. Each element can itself contain
+further elements. For example consider a message that returns multiple elements
+which each contain an integer and an array of float. A response string would
+look like that:
+{{Integer} {{1st float} {2nd float} ...}}{...}
+Any characters that are not enclosed in curly braces are ignored (all 
characters
+between { and {, between } and } and between } and {). The same syntax is used
+to specify message parameters. The following reference lists available 
messages,
+their parameters and return values. If a return value is enclosed in {}, this
+means that multiple elements of the same type may be returned.
 
-Recipient:
-Message:
-Parameters:
-Return value:
+Object path: /core
+Message: list-handlers
+Parameters: None
+Return value: {{{Handler name} {Description}} ...}
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index f4723728..10cd6f2f 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -33,11 +33,13 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
 #include 
 #include 
+#include 
 
 #include "core.h"
 
@@ -61,6 +63,45 @@ static int core_process_msg(pa_msgobject *o, int code, void 
*userdata, int64_t o
 
 static void core_free(pa_object *o);
 
+/* Returns a list of handlers. */
+static char *message_handler_list(pa_core *c) {
+pa_strbuf *buf;
+void *state = NULL;
+struct pa_message_handler *handler;
+
+buf = pa_strbuf_new();
+
+pa_strbuf_putc(buf, '{');
+PA_HASHMAP_FOREACH(handler, c->message_handlers, state) {
+pa_strbuf_putc(buf, '{');
+
+pa_strbuf_printf(buf, "{%s} {", handler->object_path);
+if (handler->description)
+pa_strbuf_puts(buf, handler->description);
+
+pa_strbuf_puts(buf, "}}");
+}
+pa_strbuf_putc(buf, '}');
+
+return pa_strbuf_to_string_free(buf);
+}
+
+static int core_message_handler(const char *object_path, const char *message, 
const char *message_parameters, char **response, void *userdata) {
+pa_core *c;
+
+pa_assert(c = (pa_core *) userdata);
+pa_assert(message);
+pa_assert(response);
+pa_assert(pa_safe_streq(object_path, "/core"));
+
+if (pa_streq(message, "list-handlers")) {
+*response = message_handler_list(c);
+return PA_OK;
+}
+
+return -PA_ERR_NOTIMPLEMENTED;
+}
+
 pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool enable_memfd, 
size_t shm_size) {
 pa_core* c;
 pa_mempool *pool;
@@ -105,6 +146,8 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool 
enable_memfd, size_t
 c->shared = pa_hashmap_new(pa_idxset_string_hash_func, 
pa_idxset_string_compare_func);
 c->message_handlers = pa_hashmap_new(pa_idxset_string_hash_func, 
pa_idxset_string_compare_func);
 
+pa_message_handler_register(c, "/core", "Core message handler", 
core_message_handler, (void *) c);
+
 c->default_source = NULL;
 c->default_sink = NULL;
 
@@ -205,6 +248,8 @@ static void core_free(pa_object *o) {
 pa_assert(pa_hashmap_isempty(c->shared));
 pa_hashmap_free(c->shared);
 
+pa_message_handler_unregister(c, "/core");
+
 pa_assert(pa_hashmap_isempty(c->message_handlers));
 pa_hashmap_free(c->message_handlers);
 
diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c
index 7555a18f..18c62fc2 100644
--- a/src/pulsecore/message-handler.c
+++ 

[pulseaudio-discuss] [PATCH 0/8] core: Add message sending/receiving

2018-04-09 Thread Georg Chini
This is a re-base of the messaging patches. It does not contain
changes. The first two patches have already been reviewed, I
nevertheless included them for completeness.

Georg Chini (8):
  core: add simple message interface
  protocol-native: add message sending capability
  pactl, pacmd, cli-command: Add send-message command
  core: add message handler
  pactl: Implement list message-handlers
  message-params: Allow parameter strings to contain escaped curly
braces
  message-params: Add read/write functions for various simple data types
  message-params: Add read functions for arrays

 PROTOCOL |  14 +
 configure.ac |   2 +-
 doc/messaging_api.txt|  72 +
 man/pactl.1.xml.in   |   9 +-
 man/pulse-cli-syntax.5.xml.in|   7 +
 shell-completion/bash/pulseaudio |   7 +-
 shell-completion/zsh/_pulseaudio |   3 +
 src/Makefile.am  |   3 +
 src/map-file |  21 ++
 src/pulse/introspect.c   |  71 +
 src/pulse/introspect.h   |  17 +
 src/pulse/message-params.c   | 654 +++
 src/pulse/message-params.h   | 109 +++
 src/pulsecore/cli-command.c  |  44 +++
 src/pulsecore/core.c |  49 +++
 src/pulsecore/core.h |   2 +-
 src/pulsecore/message-handler.c  | 148 +
 src/pulsecore/message-handler.h  |  50 +++
 src/pulsecore/native-common.h|   3 +
 src/pulsecore/pdispatch.c|   3 +
 src/pulsecore/protocol-native.c  |  52 
 src/utils/pacmd.c|   1 +
 src/utils/pactl.c| 106 ++-
 23 files changed, 1438 insertions(+), 9 deletions(-)
 create mode 100644 doc/messaging_api.txt
 create mode 100644 src/pulse/message-params.c
 create mode 100644 src/pulse/message-params.h
 create mode 100644 src/pulsecore/message-handler.c
 create mode 100644 src/pulsecore/message-handler.h

-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 5/8] pactl: Implement list message-handlers

2018-04-09 Thread Georg Chini
For better readability, "pactl list message-handlers" is introduced which
prints a formatted output of "pactl send-message /core list-handlers".

The patch also adds the functions pa_message_param_split_list() and
pa_message_param_read_string() for easy parsing of the message response
string. Because the function needs to modify the parameter string,
the message handler and the pa_context_string_callback function now
receive a char* instead of a const char* as parameter argument.
---
 man/pactl.1.xml.in   |   2 +-
 shell-completion/bash/pulseaudio |   2 +-
 shell-completion/zsh/_pulseaudio |   1 +
 src/Makefile.am  |   1 +
 src/map-file |   2 +
 src/pulse/introspect.c   |  11 +++-
 src/pulse/introspect.h   |   2 +-
 src/pulse/message-params.c   | 116 +++
 src/pulse/message-params.h   |  41 ++
 src/pulsecore/core.c |   2 +-
 src/pulsecore/message-handler.c  |   9 ++-
 src/pulsecore/message-handler.h  |   2 +-
 src/utils/pactl.c|  65 +-
 13 files changed, 245 insertions(+), 11 deletions(-)
 create mode 100644 src/pulse/message-params.c
 create mode 100644 src/pulse/message-params.h

diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in
index 4052fae3..66c0bda9 100644
--- a/man/pactl.1.xml.in
+++ b/man/pactl.1.xml.in
@@ -76,7 +76,7 @@ License along with PulseAudio; if not, see 
.
 
   list [short] [TYPE]
   Dump all currently loaded modules, available sinks, sources, 
streams, etc.  TYPE must be one of:
-  modules, sinks, sources, sink-inputs, source-outputs, clients, samples, 
cards.  If not specified, all info is listed.  If
+  modules, sinks, sources, sink-inputs, source-outputs, clients, samples, 
cards, message-handlers.  If not specified, all info is listed.  If
   short is given, output is in a tabular format, for easy parsing by 
scripts.
 
 
diff --git a/shell-completion/bash/pulseaudio b/shell-completion/bash/pulseaudio
index 797ec067..24d2aa1c 100644
--- a/shell-completion/bash/pulseaudio
+++ b/shell-completion/bash/pulseaudio
@@ -113,7 +113,7 @@ _pactl() {
 local comps
 local flags='-h --help --version -s --server= --client-name='
 local list_types='short sinks sources sink-inputs source-outputs cards
-modules samples clients'
+modules samples clients message-handlers'
 local commands=(stat info list exit upload-sample play-sample remove-sample
 load-module unload-module move-sink-input 
move-source-output
 suspend-sink suspend-source set-card-profile set-sink-port
diff --git a/shell-completion/zsh/_pulseaudio b/shell-completion/zsh/_pulseaudio
index a2817ebb..c24d0387 100644
--- a/shell-completion/zsh/_pulseaudio
+++ b/shell-completion/zsh/_pulseaudio
@@ -285,6 +285,7 @@ _pactl_completion() {
 'clients: list connected clients'
 'samples: list samples'
 'cards: list available cards'
+'message-handlers: list available message-handlers'
 )
 
 if ((CURRENT == 2)); then
diff --git a/src/Makefile.am b/src/Makefile.am
index 27e5f875..ccdad8ff 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -896,6 +896,7 @@ libpulse_la_SOURCES = \
pulse/timeval.c pulse/timeval.h \
pulse/utf8.c pulse/utf8.h \
pulse/util.c pulse/util.h \
+   pulse/message-params.c pulse/message-params.h \
pulse/volume.c pulse/volume.h \
pulse/xmalloc.c pulse/xmalloc.h
 
diff --git a/src/map-file b/src/map-file
index 4d747f19..385731dc 100644
--- a/src/map-file
+++ b/src/map-file
@@ -225,6 +225,8 @@ pa_mainloop_quit;
 pa_mainloop_run;
 pa_mainloop_set_poll_func;
 pa_mainloop_wakeup;
+pa_message_param_read_string;
+pa_message_param_split_list;
 pa_msleep;
 pa_operation_cancel;
 pa_operation_get_state;
diff --git a/src/pulse/introspect.c b/src/pulse/introspect.c
index 76bfee41..23901eb3 100644
--- a/src/pulse/introspect.c
+++ b/src/pulse/introspect.c
@@ -2215,8 +2215,15 @@ static void context_string_callback(pa_pdispatch *pd, 
uint32_t command, uint32_t
 response = "";
 
 if (o->callback) {
-pa_context_string_cb_t cb = (pa_context_string_cb_t) o->callback;
-cb(o->context, success, response, o->userdata);
+char *response_copy;
+pa_context_string_cb_t cb;
+
+response_copy = pa_xstrdup(response);
+
+cb = (pa_context_string_cb_t) o->callback;
+cb(o->context, success, response_copy, o->userdata);
+
+pa_xfree(response_copy);
 }
 
 finish:
diff --git a/src/pulse/introspect.h b/src/pulse/introspect.h
index fbe5b668..bcec48da 100644
--- a/src/pulse/introspect.h
+++ b/src/pulse/introspect.h
@@ -449,7 +449,7 @@ pa_operation* pa_context_unload_module(pa_context *c, 
uint32_t 

[pulseaudio-discuss] [PATCH 1/8] core: add simple message interface

2018-04-09 Thread Georg Chini
This patch adds a new feature to the core which allows to send messages
to objects. An object can register/unregister a message handler with
pa_message_handler_{register, unregister}() while a message can be sent
to the handler using the pa_message_handler_send_message() function.
A message has 4 arguments (apart from passing the core):

object_path: The path identifying the object that will receive the message
message: message command
message_parameters: A string containing additional parameters
response: Pointer to a response string that will be filled by the
  message handler. The caller is responsible to free the string.

The patch is a precondition for the following patches that allow clients
to send messages to pulseaudio objects.

There is no restriction on object names, except that an object path
always starts with a "/". The intention is to use a path-like syntax,
for example /core/sink_1 for a sink or /name/instances/index for modules.
The exact naming convention still needs to be agreed.
---
 src/Makefile.am |   1 +
 src/pulsecore/core.c|   4 ++
 src/pulsecore/core.h|   2 +-
 src/pulsecore/message-handler.c | 104 
 src/pulsecore/message-handler.h |  50 +++
 5 files changed, 160 insertions(+), 1 deletion(-)
 create mode 100644 src/pulsecore/message-handler.c
 create mode 100644 src/pulsecore/message-handler.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 59b703db..27e5f875 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -964,6 +964,7 @@ libpulsecore_@PA_MAJORMINOR@_la_SOURCES = \
pulsecore/core-scache.c pulsecore/core-scache.h \
pulsecore/core-subscribe.c pulsecore/core-subscribe.h \
pulsecore/core.c pulsecore/core.h \
+   pulsecore/message-handler.c pulsecore/message-handler.h \
pulsecore/hook-list.c pulsecore/hook-list.h \
pulsecore/ltdl-helper.c pulsecore/ltdl-helper.h \
pulsecore/modargs.c pulsecore/modargs.h \
diff --git a/src/pulsecore/core.c b/src/pulsecore/core.c
index da42a13e..f4723728 100644
--- a/src/pulsecore/core.c
+++ b/src/pulsecore/core.c
@@ -103,6 +103,7 @@ pa_core* pa_core_new(pa_mainloop_api *m, bool shared, bool 
enable_memfd, size_t
 
 c->namereg = pa_hashmap_new(pa_idxset_string_hash_func, 
pa_idxset_string_compare_func);
 c->shared = pa_hashmap_new(pa_idxset_string_hash_func, 
pa_idxset_string_compare_func);
+c->message_handlers = pa_hashmap_new(pa_idxset_string_hash_func, 
pa_idxset_string_compare_func);
 
 c->default_source = NULL;
 c->default_sink = NULL;
@@ -204,6 +205,9 @@ static void core_free(pa_object *o) {
 pa_assert(pa_hashmap_isempty(c->shared));
 pa_hashmap_free(c->shared);
 
+pa_assert(pa_hashmap_isempty(c->message_handlers));
+pa_hashmap_free(c->message_handlers);
+
 pa_assert(pa_hashmap_isempty(c->modules_pending_unload));
 pa_hashmap_free(c->modules_pending_unload);
 
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index 38622f61..d03897c4 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -164,7 +164,7 @@ struct pa_core {
 pa_idxset *clients, *cards, *sinks, *sources, *sink_inputs, 
*source_outputs, *modules, *scache;
 
 /* Some hashmaps for all sorts of entities */
-pa_hashmap *namereg, *shared;
+pa_hashmap *namereg, *shared, *message_handlers;
 
 /* The default sink/source as configured by the user. If the user hasn't
  * explicitly configured anything, these are set to NULL. These are strings
diff --git a/src/pulsecore/message-handler.c b/src/pulsecore/message-handler.c
new file mode 100644
index ..7555a18f
--- /dev/null
+++ b/src/pulsecore/message-handler.c
@@ -0,0 +1,104 @@
+/***
+  This file is part of PulseAudio.
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, see .
+***/
+
+#ifdef HAVE_CONFIG_H
+#include 
+#endif
+
+#include 
+#include 
+
+#include 
+
+#include 
+#include 
+#include 
+#include 
+
+#include "message-handler.h"
+
+/* Message handler functions */
+
+/* Register message handler for the specified object. object_path must be a 
unique name starting with "/". */
+void pa_message_handler_register(pa_core *c, const char *object_path, const 
char *description, pa_message_handler_cb_t cb, void *userdata) {
+struct 

[pulseaudio-discuss] [PATCH 3/8] pactl, pacmd, cli-command: Add send-message command

2018-04-09 Thread Georg Chini
The patch also adds some API documentation.
---
 doc/messaging_api.txt| 15 ++
 man/pactl.1.xml.in   |  7 +++
 man/pulse-cli-syntax.5.xml.in|  7 +++
 shell-completion/bash/pulseaudio |  5 +++--
 shell-completion/zsh/_pulseaudio |  2 ++
 src/pulse/introspect.h   |  3 ++-
 src/pulsecore/cli-command.c  | 44 
 src/utils/pacmd.c|  1 +
 src/utils/pactl.c| 43 ++-
 9 files changed, 123 insertions(+), 4 deletions(-)
 create mode 100644 doc/messaging_api.txt

diff --git a/doc/messaging_api.txt b/doc/messaging_api.txt
new file mode 100644
index ..cbc06e75
--- /dev/null
+++ b/doc/messaging_api.txt
@@ -0,0 +1,15 @@
+Message API reference
+
+The message API allows any object within pulseaudio to register a message
+handler. A message handler is a function that can be called by clients using
+PA_COMMAND_SEND_OBJECT_MESSAGE. A message consists at least of an object path
+and a message command, both specified as strings. Additional parameters can
+be specified using a single string, but are not mandatory. The message handler
+returns an error number as defined in def.h and also returns a string in
+the "response" variable. The following reference lists available messages,
+their parameters and return values.
+
+Recipient:
+Message:
+Parameters:
+Return value:
diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in
index 39569b6b..4052fae3 100644
--- a/man/pactl.1.xml.in
+++ b/man/pactl.1.xml.in
@@ -245,6 +245,13 @@ License along with PulseAudio; if not, see 
.
   'ac3-iec61937, format.rate = "[ 32000, 44100, 48000 ]"').

 
+
+  send-message RECIPIENT MESSAGE 
MESSAGE_PARAMETERS
+  Send a message string to the specified recipient object. If 
applicable an additional string containing
+  message parameters can be specified. A string is returned as a response 
to the message. For available message
+  commands see 
https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/doc/messaging_api.txt.
+
+
 
   subscribe
   Subscribe to events, pactl does not exit by itself, but 
keeps waiting for new events.
diff --git a/man/pulse-cli-syntax.5.xml.in b/man/pulse-cli-syntax.5.xml.in
index 0a0fabaf..83f55d6b 100644
--- a/man/pulse-cli-syntax.5.xml.in
+++ b/man/pulse-cli-syntax.5.xml.in
@@ -297,6 +297,13 @@ License along with PulseAudio; if not, see 
.
   Debug: Show shared properties.
 
 
+
+  send-message recipient message 
message_parameters
+  Send a message string to the specified recipient object. If 
applicable an additional string containing
+  message parameters can be specified. A string is returned as a response 
to the message. For available message
+  commands see 
https://cgit.freedesktop.org/pulseaudio/pulseaudio/tree/doc/messaging_api.txt.
+
+
 
   exit
   Terminate the daemon. If you want to terminate a CLI
diff --git a/shell-completion/bash/pulseaudio b/shell-completion/bash/pulseaudio
index e473b9c2..797ec067 100644
--- a/shell-completion/bash/pulseaudio
+++ b/shell-completion/bash/pulseaudio
@@ -120,7 +120,8 @@ _pactl() {
 set-source-port set-sink-volume set-source-volume
 set-sink-input-volume set-source-output-volume 
set-sink-mute
 set-source-mute set-sink-input-mute set-source-output-mute
-set-sink-formats set-port-latency-offset subscribe help)
+set-sink-formats set-port-latency-offset subscribe 
send-message
+help)
 
 _init_completion -n = || return
 preprev=${words[$cword-2]}
@@ -270,7 +271,7 @@ _pacmd() {
 move-sink-input move-source-output suspend-sink 
suspend-source
 suspend set-card-profile set-sink-port set-source-port
 set-port-latency-offset set-log-target set-log-level 
set-log-meta
-set-log-time set-log-backtrace)
+set-log-time set-log-backtrace send-message)
 _init_completion -n = || return
 preprev=${words[$cword-2]}
 
diff --git a/shell-completion/zsh/_pulseaudio b/shell-completion/zsh/_pulseaudio
index 0e9e89bd..a2817ebb 100644
--- a/shell-completion/zsh/_pulseaudio
+++ b/shell-completion/zsh/_pulseaudio
@@ -263,6 +263,7 @@ _pactl_completion() {
 'set-sink-input-mute: mute a stream'
 'set-source-output-mute: mute a recording stream'
 'set-sink-formats: set supported formats of a sink'
+'send-message: send a message to a pulseaudio object'
 'subscribe: subscribe to events'
 )
 
@@ -561,6 +562,7 @@ _pacmd_completion() {
 'dump: show daemon configuration'
 'dump-volumes: show the state of all volumes'
 'shared: show shared properties'
+

[pulseaudio-discuss] [PATCH 09/10] loopback: Add adjust_time_msec parameter to allow adjust times below 1s

2018-04-09 Thread Georg Chini
A new parameter is introduced to allow specifying smaller adjust_time values
than 1s. This may be useful for a better latency control, although with alsa
devices and the current smoother code no improvement could be found.
This patch also changes the default adjust time to 1s, the old value of 10s
does not allow a tight control of the end to end latency and would lead to
unnecessary jitter.
---
 src/modules/module-loopback.c | 19 +++
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 048fe4f9..fd3f794e 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -44,6 +44,7 @@ PA_MODULE_USAGE(
 "source= "
 "sink= "
 "adjust_time= "
+"adjust_time_msec= "
 "latency_msec= "
 "max_latency_msec= "
 "low_device_latency= "
@@ -68,7 +69,7 @@ PA_MODULE_USAGE(
 
 #define MIN_DEVICE_LATENCY (2.5*PA_USEC_PER_MSEC)
 
-#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC)
+#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
 
 typedef struct loopback_msg loopback_msg;
 
@@ -189,6 +190,7 @@ static const char* const valid_modargs[] = {
 "source",
 "sink",
 "adjust_time",
+"adjust_time_msec",
 "latency_msec",
 "max_latency_msec",
 "low_device_latency",
@@ -1489,6 +1491,7 @@ int pa__init(pa_module *m) {
 bool channels_set = false;
 pa_memchunk silence;
 uint32_t adjust_time_sec;
+uint32_t adjust_time_msec;
 const char *n;
 bool remix = true;
 bool low_device_latency = false;
@@ -1618,10 +1621,18 @@ int pa__init(pa_module *m) {
 goto fail;
 }
 
-if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
+adjust_time_msec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_MSEC;
+if (pa_modargs_get_value_u32(ma, "adjust_time_msec", _time_msec) < 
0 || (adjust_time_msec != 0 && adjust_time_msec < 100)) {
+pa_log("Failed to parse adjust_time_msec value");
+goto fail;
+}
+
+/* If adjust_time and adjust_time_msec are both specified, prefer the 
adjust_time_msec value */
+u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
+if (adjust_time_msec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_MSEC)
+u->adjust_time = adjust_time_msec * PA_USEC_PER_MSEC;
+else if (adjust_time_sec != DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC)
 u->adjust_time = adjust_time_sec * PA_USEC_PER_SEC;
-else
-u->adjust_time = DEFAULT_ADJUST_TIME_USEC;
 
 u->real_adjust_time = u->adjust_time;
 
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 05/10] loopback: Track prediction error; debug and cosmetic changes

2018-04-09 Thread Georg Chini
---
 src/modules/module-loopback.c | 34 +++---
 1 file changed, 23 insertions(+), 11 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 19a40b89..1db39ef4 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -36,7 +36,7 @@
 #include 
 #include 
 
-PA_MODULE_AUTHOR("Pierre-Louis Bossart");
+PA_MODULE_AUTHOR("Pierre-Louis Bossart, Georg Chini");
 PA_MODULE_DESCRIPTION("Loopback from source to sink");
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(false);
@@ -117,9 +117,10 @@ struct userdata {
 double drift_filter;
 double drift_compensation_rate;
 
-/* Variables for Kalman filter */
+/* Variables for Kalman filter and error tracking*/
 double latency_variance;
 double kalman_variance;
+double latency_error;
 
 /* lower latency limit found by underruns */
 pa_usec_t underrun_latency_limit;
@@ -466,6 +467,8 @@ static void adjust_rates(struct userdata *u) {
 u->next_latency_at_optimum_rate_with_drift += u->sink_latency_offset - 
u->last_sink_latency_offset;
 u->next_latency_with_drift += u->source_latency_offset - 
u->last_source_latency_offset;
 u->next_latency_with_drift += u->sink_latency_offset - 
u->last_sink_latency_offset;
+/* Low pass filtered latency error. This value reflects how well the 
measured values match the prediction. */
+u->latency_error = (1 - FILTER_PARAMETER) * u->latency_error + 
FILTER_PARAMETER * (double)abs((int32_t)(current_latency - 
u->next_latency_with_drift));
 /* Low pass filtered latency variance */
 current_latency_error = (double)abs((int32_t)(latency_at_optimum_rate 
- u->next_latency_at_optimum_rate_with_drift));
 u->latency_variance = (1.0 - FILTER_PARAMETER) * u->latency_variance + 
FILTER_PARAMETER * current_latency_error * current_latency_error;
@@ -474,17 +477,22 @@ static void adjust_rates(struct userdata *u) {
 u->kalman_variance = u->kalman_variance * u->latency_variance / 
(u->kalman_variance + u->latency_variance) + u->latency_variance / 4 + 200;
 }
 
-pa_log_debug("Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = 
%0.2f ms",
-(double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC,
-(double) current_buffer_latency / PA_USEC_PER_MSEC,
-(double) u->latency_snapshot.source_latency / PA_USEC_PER_MSEC,
-(double) current_latency / PA_USEC_PER_MSEC);
-
-pa_log_debug("Loopback latency at optimum rate is %0.2f ms", 
(double)latency_at_optimum_rate / PA_USEC_PER_MSEC);
-
 /* Calculate new rate */
 new_rate = rate_controller(u, base_rate, old_rate, 
(int32_t)(filtered_latency - final_latency), latency_difference);
 
+pa_log_debug("Loopback status %s to %s:\nSource latency: %0.2f ms\n
Buffer: %0.2f ms\nSink latency: %0.2f ms\nEnd-to-end latency: %0.2f 
ms\n"
+ "Deviation from target latency at optimum rate: %0.2f 
usec\nAverage prediction error: ± %0.2f usec\nOptimum rate: %0.2f Hz\n  
  Deviation from base rate: %i Hz",
+u->source_output->source->name,
+u->sink_input->sink->name,
+(double) u->latency_snapshot.source_latency / PA_USEC_PER_MSEC,
+(double) current_buffer_latency / PA_USEC_PER_MSEC,
+(double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC,
+(double) current_latency / PA_USEC_PER_MSEC,
+(double) latency_at_optimum_rate - final_latency,
+(double) u->latency_error,
+u->drift_compensation_rate + base_rate,
+(int32_t)(new_rate - base_rate));
+
 /* Save current latency difference at new rate for next cycle and reset 
flags */
 u->last_latency_difference = current_source_sink_latency + 
current_buffer_latency * old_rate / new_rate - final_latency;
 
@@ -522,7 +530,6 @@ static void adjust_rates(struct userdata *u) {
 
 /* Set rate */
 pa_sink_input_set_rate(u->sink_input, new_rate);
-pa_log_debug("[%s] Updated sampling rate to %lu Hz.", 
u->sink_input->sink->name, (unsigned long) new_rate);
 }
 
 /* Called from main context */
@@ -857,9 +864,11 @@ static void source_output_moving_cb(pa_source_output *o, 
pa_source *dest) {
 u->iteration_counter = 0;
 u->underrun_counter = 0;
 
+/* Reset booleans and latency error */
 u->source_sink_changed = true;
 u->underrun_occured = false;
 u->source_latency_offset_changed = false;
+u->latency_error = 0;
 
 /* Send a mesage to the output thread that the source has changed.
  * If the sink is invalid here during a profile switching situation
@@ -1250,9 +1259,11 @@ static void sink_input_moving_cb(pa_sink_input *i, 
pa_sink *dest) {
 u->iteration_counter = 0;
 u->underrun_counter = 0;
 
+/* Reset booleans and latency error */
 

[pulseaudio-discuss] [PATCH 08/10] loopback: Add low_device_latency parameter

2018-04-09 Thread Georg Chini
For USB devices the latency jitter strongly depends on device latency. Therefore
a boolean low_device_latency parameter is introduced to half the device latency.
Normally 1/3 of the configured end-to-end latency is used, with the parameter
this is changed to 1/6. In many situations the parameter can improve latency
stability but it will also lead to significantly higher CPU consumption.
---
 src/modules/module-loopback.c | 20 ++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index eeb93264..048fe4f9 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -46,6 +46,7 @@ PA_MODULE_USAGE(
 "adjust_time= "
 "latency_msec= "
 "max_latency_msec= "
+"low_device_latency= "
 "adjust_threshold_usec= "
 "format= "
 "rate= "
@@ -98,6 +99,7 @@ struct userdata {
 pa_usec_t max_latency;
 pa_usec_t adjust_time;
 uint32_t adjust_threshold;
+bool low_device_latency;
 
 /* Latency boundaries and current values */
 pa_usec_t min_source_latency;
@@ -189,6 +191,7 @@ static const char* const valid_modargs[] = {
 "adjust_time",
 "latency_msec",
 "max_latency_msec",
+"low_device_latency",
 "adjust_threshold_usec",
 "format",
 "rate",
@@ -776,11 +779,14 @@ static void update_effective_source_latency(struct 
userdata *u, pa_source *sourc
  * Set source output latency to one third of the overall latency if possible.
  * The choice of one third is rather arbitrary somewhere between the minimum
  * possible latency which would cause a lot of CPU load and half the configured
- * latency which would quickly lead to underruns */
+ * latency which would quickly lead to underruns. In low device latency mode 
set
+ * source to one sixth of the overall latency. */
 static void set_source_output_latency(struct userdata *u, pa_source *source) {
 pa_usec_t latency, requested_latency;
 
 requested_latency = u->latency / 3;
+if (u->low_device_latency)
+requested_latency = u->latency / 6;
 
 /* Normally we try to configure sink and source latency equally. If the
  * sink latency cannot match the requested source latency try to set the
@@ -1149,11 +1155,14 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, 
int code, void *data, in
  * Set sink input latency to one third of the overall latency if possible.
  * The choice of one third is rather arbitrary somewhere between the minimum
  * possible latency which would cause a lot of CPU load and half the configured
- * latency which would quickly lead to underruns. */
+ * latency which would quickly lead to underruns. In low device latency mode
+ * set sink to one sixth of the overall latency. */
 static void set_sink_input_latency(struct userdata *u, pa_sink *sink) {
  pa_usec_t latency, requested_latency;
 
 requested_latency = u->latency / 3;
+if (u->low_device_latency)
+requested_latency = u->latency / 6;
 
 /* Normally we try to configure sink and source latency equally. If the
  * source latency cannot match the requested sink latency try to set the
@@ -1482,6 +1491,7 @@ int pa__init(pa_module *m) {
 uint32_t adjust_time_sec;
 const char *n;
 bool remix = true;
+bool low_device_latency = false;
 
 pa_assert(m);
 
@@ -1574,6 +1584,11 @@ int pa__init(pa_module *m) {
 max_latency_msec = latency_msec;
 }
 
+if (pa_modargs_get_value_boolean(ma, "low_device_latency", 
_device_latency) < 0) {
+pa_log("Invalid boolean device latency parameter");
+goto fail;
+}
+
 m->userdata = u = pa_xnew0(struct userdata, 1);
 u->core = m->core;
 u->module = m;
@@ -1594,6 +1609,7 @@ int pa__init(pa_module *m) {
 u->latency_error = 0;
 u->adjust_threshold = adjust_threshold;
 u->target_latency_cross_counter = 0;
+u->low_device_latency = low_device_latency;
 u->initial_adjust_pending = true;
 
 adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 06/10] loopback: Add adjust_threshold_usec parameter

2018-04-09 Thread Georg Chini
In many situations, the P-controller is too sensitive and therefore exhibits 
rate hunting.
To avoid rate hunting, the sensibility of the controller is set by the new 
parameter
adjust_threshold_usec. The parameter value is the deviation from the target 
latency in usec
which is needed to produce a 1 Hz deviation from the optimum sample rate.
The default is set to 200 usec, which should be sufficient in most cases. If 
the accuracy
of the latency reports is bad and rate hunting is observed, the parameter must 
be increased,
while it can be lowered to achieve less latency jitter if the latency reports 
are accurate.
More details at
https://www.freedesktop.org/software/pulseaudio/misc/rate_estimator.odt
---
 src/modules/module-loopback.c | 43 +++
 1 file changed, 35 insertions(+), 8 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 1db39ef4..9f18ad1f 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -46,6 +46,7 @@ PA_MODULE_USAGE(
 "adjust_time= "
 "latency_msec= "
 "max_latency_msec= "
+"adjust_threshold_usec= "
 "format= "
 "rate= "
 "channels= "
@@ -60,6 +61,8 @@ PA_MODULE_USAGE(
 
 #define FILTER_PARAMETER 0.125
 
+#define DEFAULT_ADJUST_THRESHOLD_USEC 250
+
 #define MEMBLOCKQ_MAXLENGTH (1024*1024*32)
 
 #define MIN_DEVICE_LATENCY (2.5*PA_USEC_PER_MSEC)
@@ -94,6 +97,7 @@ struct userdata {
 pa_usec_t latency;
 pa_usec_t max_latency;
 pa_usec_t adjust_time;
+uint32_t adjust_threshold;
 
 /* Latency boundaries and current values */
 pa_usec_t min_source_latency;
@@ -184,6 +188,7 @@ static const char* const valid_modargs[] = {
 "adjust_time",
 "latency_msec",
 "max_latency_msec",
+"adjust_threshold_usec",
 "format",
 "rate",
 "channels",
@@ -255,11 +260,10 @@ static void teardown(struct userdata *u) {
 }
 
 /* rate controller, called from main context
- * - maximum deviation from base rate is less than 1%
- * - controller step size is limited to 2.01‰
+ * - maximum deviation from optimum rate for P-controller is less than 1%
+ * - P-controller step size is limited to 2.01‰
  * - will calculate an optimum rate
- * - exhibits hunting with USB or Bluetooth sources
- */
+*/
 static uint32_t rate_controller(
 struct userdata *u,
 uint32_t base_rate, uint32_t old_rate,
@@ -267,7 +271,21 @@ static uint32_t rate_controller(
 int32_t latency_difference_at_base_rate) {
 
 double new_rate, new_rate_1, new_rate_2;
-double min_cycles_1, min_cycles_2, drift_rate, latency_drift;
+double min_cycles_1, min_cycles_2, drift_rate, latency_drift, 
controller_weight, min_weight;
+uint32_t base_rate_with_drift;
+
+base_rate_with_drift = (int)(base_rate + u->drift_compensation_rate);
+
+/* If we are less than 2‰ away from the optimum rate, lower weight of the
+ * P-controller. The weight is determined by the fact that a correction
+ * of 0.5 Hz needs to be applied by the controller when the latency
+ * difference gets larger than the threshold. The weight follows
+ * from the definition of the controller. The minimum will only
+ * be reached when one adjust threshold away from the target. */
+controller_weight = 1;
+min_weight = PA_CLAMP(0.5 / (double)base_rate * (100.0 + 
(double)u->real_adjust_time / u->adjust_threshold), 0, 1.0);
+if ((double)abs((int)(old_rate - base_rate_with_drift)) / 
base_rate_with_drift < 0.002)
+controller_weight = 
PA_CLAMP((double)abs(latency_difference_at_optimum_rate) / u->adjust_threshold 
* min_weight, min_weight, 1.0);
 
 /* Calculate next rate that is not more than 2‰ away from the last rate */
 min_cycles_1 = (double)abs(latency_difference_at_optimum_rate) / 
u->real_adjust_time / 0.002 + 1;
@@ -276,10 +294,11 @@ static uint32_t rate_controller(
 /* Calculate best rate to correct the current latency offset, limit at
  * 1% difference from base_rate */
 min_cycles_2 = (double)abs(latency_difference_at_optimum_rate) / 
u->real_adjust_time / 0.01 + 1;
-new_rate_2 = (double)base_rate * (1.0 + 
(double)latency_difference_at_optimum_rate / min_cycles_2 / 
u->real_adjust_time);
+new_rate_2 = (double)base_rate * (1.0 + controller_weight * 
latency_difference_at_optimum_rate / min_cycles_2 / u->real_adjust_time);
 
-/* Choose the rate that is nearer to base_rate */
-if (abs(new_rate_1 - base_rate) < abs(new_rate_2 - base_rate))
+/* Choose the rate that is nearer to base_rate unless we are already near
+ * to the desired latency and rate */
+if (abs(new_rate_1 - base_rate) < abs(new_rate_2 - base_rate) && 
controller_weight > 0.99)
 new_rate = new_rate_1;
 else
 new_rate = new_rate_2;
@@ -1435,6 +1454,7 @@ int pa__init(pa_module *m) {
 bool source_dont_move;
 uint32_t latency_msec;
 uint32_t 

[pulseaudio-discuss] [PATCH 10/10] loopback: Add log_interval and log_interval_msec parameter

2018-04-09 Thread Georg Chini
Add a log_interval parameter to control the amount of logging. Default is
no logging. Like for adjust_time, two parameters exist: log_interval specifies
the interval in seconds while log_interval_msec is in ms.
If the log interval is too small, logging will occur on every iteration.
---
 src/modules/module-loopback.c | 77 +--
 1 file changed, 60 insertions(+), 17 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index fd3f794e..2eff4c0c 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -48,6 +48,8 @@ PA_MODULE_USAGE(
 "latency_msec= "
 "max_latency_msec= "
 "low_device_latency= "
+"log_interval= "
+"log_interval_msec= "
 "adjust_threshold_usec= "
 "format= "
 "rate= "
@@ -101,6 +103,7 @@ struct userdata {
 pa_usec_t adjust_time;
 uint32_t adjust_threshold;
 bool low_device_latency;
+uint32_t log_interval;
 
 /* Latency boundaries and current values */
 pa_usec_t min_source_latency;
@@ -137,6 +140,7 @@ struct userdata {
 uint32_t underrun_counter;
 uint32_t adjust_counter;
 uint32_t target_latency_cross_counter;
+uint32_t log_counter;
 
 /* Various booleans */
 bool fixed_alsa_source;
@@ -194,6 +198,8 @@ static const char* const valid_modargs[] = {
 "latency_msec",
 "max_latency_msec",
 "low_device_latency",
+"log_interval",
+"log_interval_msec",
 "adjust_threshold_usec",
 "format",
 "rate",
@@ -509,18 +515,25 @@ static void adjust_rates(struct userdata *u) {
 /* Calculate new rate */
 new_rate = rate_controller(u, base_rate, old_rate, 
(int32_t)(filtered_latency - final_latency), latency_difference);
 
-pa_log_debug("Loopback status %s to %s:\nSource latency: %0.2f ms\n
Buffer: %0.2f ms\nSink latency: %0.2f ms\nEnd-to-end latency: %0.2f 
ms\n"
- "Deviation from target latency at optimum rate: %0.2f 
usec\nAverage prediction error: ± %0.2f usec\nOptimum rate: %0.2f Hz\n  
  Deviation from base rate: %i Hz",
-u->source_output->source->name,
-u->sink_input->sink->name,
-(double) u->latency_snapshot.source_latency / PA_USEC_PER_MSEC,
-(double) current_buffer_latency / PA_USEC_PER_MSEC,
-(double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC,
-(double) current_latency / PA_USEC_PER_MSEC,
-(double) latency_at_optimum_rate - final_latency,
-(double) u->latency_error,
-u->drift_compensation_rate + base_rate,
-(int32_t)(new_rate - base_rate));
+/* Log every log_interval iterations if the log_interval parameter is set 
*/
+if (u->log_interval != 0) {
+u->log_counter--;
+if (u->log_counter == 0) {
+pa_log_debug("Loopback status %s to %s:\nSource latency: %0.2f 
ms\nBuffer: %0.2f ms\nSink latency: %0.2f ms\nEnd-to-end latency: 
%0.2f ms\n"
+ "Deviation from target latency at optimum rate: 
%0.2f usec\nAverage prediction error: ± %0.2f usec\nOptimum rate: %0.2f 
Hz\nDeviation from base rate: %i Hz",
+u->source_output->source->name,
+u->sink_input->sink->name,
+(double) u->latency_snapshot.source_latency / 
PA_USEC_PER_MSEC,
+(double) current_buffer_latency / PA_USEC_PER_MSEC,
+(double) u->latency_snapshot.sink_latency / 
PA_USEC_PER_MSEC,
+(double) current_latency / PA_USEC_PER_MSEC,
+(double) latency_at_optimum_rate - final_latency,
+(double) u->latency_error,
+u->drift_compensation_rate + base_rate,
+(int32_t)(new_rate - base_rate));
+u->log_counter = u->log_interval;
+}
+}
 
 /* If the latency difference changed sign, we have crossed the target 
latency. */
 if ((int64_t)latency_difference * u->last_latency_difference < 0)
@@ -900,11 +913,12 @@ static void source_output_moving_cb(pa_source_output *o, 
pa_source *dest) {
 u->iteration_counter = 0;
 u->underrun_counter = 0;
 
-/* Reset booleans, latency error and counter */
+/* Reset booleans, latency error and counters */
 u->source_sink_changed = true;
 u->underrun_occured = false;
 u->source_latency_offset_changed = false;
 u->target_latency_cross_counter = 0;
+u->log_counter = u->log_interval;
 u->latency_error = 0;
 
 /* Send a mesage to the output thread that the source has changed.
@@ -1299,11 +1313,12 @@ static void sink_input_moving_cb(pa_sink_input *i, 
pa_sink *dest) {
 u->iteration_counter = 0;
 u->underrun_counter = 0;
 
-/* 

[pulseaudio-discuss] [PATCH 07/10] loopback: Only use controller weight after target latency has been crossed twice

2018-04-09 Thread Georg Chini
The previous patch slows down initial convergence. Therefore do not use
the controller weight until we can assume that we reached an equilibrium.
Because it takes some time before the reported latency values are reliable,
assume that a steady state is reached when the target latency has been
crossed twice.
---
 src/modules/module-loopback.c | 27 +++
 1 file changed, 23 insertions(+), 4 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 9f18ad1f..eeb93264 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -133,6 +133,7 @@ struct userdata {
 uint32_t iteration_counter;
 uint32_t underrun_counter;
 uint32_t adjust_counter;
+uint32_t target_latency_cross_counter;
 
 /* Various booleans */
 bool fixed_alsa_source;
@@ -281,10 +282,14 @@ static uint32_t rate_controller(
  * of 0.5 Hz needs to be applied by the controller when the latency
  * difference gets larger than the threshold. The weight follows
  * from the definition of the controller. The minimum will only
- * be reached when one adjust threshold away from the target. */
+ * be reached when one adjust threshold away from the target. Start
+ * using the weight after the target latency has been reached for the
+ * second time to accelerate initial convergence. The second time has
+ * been chosen because it takes a while before the smoother returns
+ * reliable latencies. */
 controller_weight = 1;
 min_weight = PA_CLAMP(0.5 / (double)base_rate * (100.0 + 
(double)u->real_adjust_time / u->adjust_threshold), 0, 1.0);
-if ((double)abs((int)(old_rate - base_rate_with_drift)) / 
base_rate_with_drift < 0.002)
+if ((double)abs((int)(old_rate - base_rate_with_drift)) / 
base_rate_with_drift < 0.002 && u->target_latency_cross_counter >= 2)
 controller_weight = 
PA_CLAMP((double)abs(latency_difference_at_optimum_rate) / u->adjust_threshold 
* min_weight, min_weight, 1.0);
 
 /* Calculate next rate that is not more than 2‰ away from the last rate */
@@ -512,6 +517,10 @@ static void adjust_rates(struct userdata *u) {
 u->drift_compensation_rate + base_rate,
 (int32_t)(new_rate - base_rate));
 
+/* If the latency difference changed sign, we have crossed the target 
latency. */
+if ((int64_t)latency_difference * u->last_latency_difference < 0)
+u->target_latency_cross_counter++;
+
 /* Save current latency difference at new rate for next cycle and reset 
flags */
 u->last_latency_difference = current_source_sink_latency + 
current_buffer_latency * old_rate / new_rate - final_latency;
 
@@ -883,10 +892,11 @@ static void source_output_moving_cb(pa_source_output *o, 
pa_source *dest) {
 u->iteration_counter = 0;
 u->underrun_counter = 0;
 
-/* Reset booleans and latency error */
+/* Reset booleans, latency error and counter */
 u->source_sink_changed = true;
 u->underrun_occured = false;
 u->source_latency_offset_changed = false;
+u->target_latency_cross_counter = 0;
 u->latency_error = 0;
 
 /* Send a mesage to the output thread that the source has changed.
@@ -1278,10 +1288,11 @@ static void sink_input_moving_cb(pa_sink_input *i, 
pa_sink *dest) {
 u->iteration_counter = 0;
 u->underrun_counter = 0;
 
-/* Reset booleans and latency error */
+/* Reset booleans, latency error and counter */
 u->source_sink_changed = true;
 u->underrun_occured = false;
 u->sink_latency_offset_changed = false;
+u->target_latency_cross_counter = 0;
 u->latency_error = 0;
 
 u->output_thread_info.pop_called = false;
@@ -1398,6 +1409,7 @@ static int loopback_process_msg_cb(pa_msgobject *o, int 
code, void *userdata, in
 
 u->underrun_counter++;
 u->underrun_occured = true;
+u->target_latency_cross_counter = 0;
 pa_log_debug("Underrun detected, counter incremented to %u", 
u->underrun_counter);
 
 return 0;
@@ -1425,6 +1437,9 @@ static pa_hook_result_t 
sink_port_latency_offset_changed_cb(pa_core *core, pa_si
 u->sink_latency_offset = sink->port_latency_offset;
 update_minimum_latency(u, sink, true);
 
+/* We might need to adjust again, reset counter */
+u->target_latency_cross_counter = 0;
+
 return PA_HOOK_OK;
 }
 
@@ -1440,6 +1455,9 @@ static pa_hook_result_t 
source_port_latency_offset_changed_cb(pa_core *core, pa_
 u->source_latency_offset = source->port_latency_offset;
 update_minimum_latency(u, u->sink_input->sink, true);
 
+/* We might need to adjust again, reset counter */
+u->target_latency_cross_counter = 0;
+
 return PA_HOOK_OK;
 }
 
@@ -1575,6 +1593,7 @@ int pa__init(pa_module *m) {
 u->sink_latency_offset_changed = false;
 u->latency_error = 0;
 u->adjust_threshold = adjust_threshold;
+u->target_latency_cross_counter = 0;
 

[pulseaudio-discuss] [PATCH 03/10] loopback: Optimize adaptive re-sampling

2018-04-09 Thread Georg Chini
The current code assumes that the time domains of source and sink are
equal. This leads to a saw-tooth characteristics of the resulting end
to end latency.
This patch adds an iterative calculation of an optimum rate which accounts
for the difference between the source and sink time domains, thereby massively
enhancing the latency stability. Theoretical background for the calculation
can be found at
https://www.freedesktop.org/software/pulseaudio/misc/rate_estimator.odt
---
 src/modules/module-loopback.c | 118 --
 1 file changed, 103 insertions(+), 15 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 9ca84b9d..12193716 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -58,6 +58,8 @@ PA_MODULE_USAGE(
 
 #define DEFAULT_LATENCY_MSEC 200
 
+#define FILTER_PARAMETER 0.125
+
 #define MEMBLOCKQ_MAXLENGTH (1024*1024*32)
 
 #define MIN_DEVICE_LATENCY (2.5*PA_USEC_PER_MSEC)
@@ -104,6 +106,13 @@ struct userdata {
 int64_t sink_latency_offset;
 pa_usec_t minimum_latency;
 
+/* State variable of the latency controller */
+int32_t last_latency_difference;
+
+/* Filter varables used for 2nd order filter */
+double drift_filter;
+double drift_compensation_rate;
+
 /* lower latency limit found by underruns */
 pa_usec_t underrun_latency_limit;
 
@@ -112,8 +121,13 @@ struct userdata {
 uint32_t underrun_counter;
 uint32_t adjust_counter;
 
+/* Various booleans */
 bool fixed_alsa_source;
 bool source_sink_changed;
+bool underrun_occured;
+bool source_latency_offset_changed;
+bool sink_latency_offset_changed;
+bool initial_adjust_pending;
 
 /* Used for sink input and source output snapshots */
 struct {
@@ -190,6 +204,7 @@ enum {
 LOOPBACK_MESSAGE_SOURCE_LATENCY_RANGE_CHANGED,
 LOOPBACK_MESSAGE_SINK_LATENCY_RANGE_CHANGED,
 LOOPBACK_MESSAGE_UNDERRUN,
+LOOPBACK_MESSAGE_ADJUST_DONE,
 };
 
 static void enable_adjust_timer(struct userdata *u, bool enable);
@@ -233,24 +248,26 @@ static void teardown(struct userdata *u) {
 /* rate controller, called from main context
  * - maximum deviation from base rate is less than 1%
  * - controller step size is limited to 2.01‰
+ * - will calculate an optimum rate
  * - exhibits hunting with USB or Bluetooth sources
  */
 static uint32_t rate_controller(
 struct userdata *u,
 uint32_t base_rate, uint32_t old_rate,
-int32_t latency_difference_usec) {
+int32_t latency_difference_at_optimum_rate,
+int32_t latency_difference_at_base_rate) {
 
-uint32_t new_rate, new_rate_1, new_rate_2;
-double min_cycles_1, min_cycles_2;
+double new_rate, new_rate_1, new_rate_2;
+double min_cycles_1, min_cycles_2, drift_rate, latency_drift;
 
 /* Calculate next rate that is not more than 2‰ away from the last rate */
-min_cycles_1 = (double)abs(latency_difference_usec) / u->real_adjust_time 
/ 0.002 + 1;
-new_rate_1 = old_rate + base_rate * (double)latency_difference_usec / 
min_cycles_1 / u->real_adjust_time;
+min_cycles_1 = (double)abs(latency_difference_at_optimum_rate) / 
u->real_adjust_time / 0.002 + 1;
+new_rate_1 = old_rate + base_rate * 
(double)latency_difference_at_optimum_rate / min_cycles_1 / u->real_adjust_time;
 
 /* Calculate best rate to correct the current latency offset, limit at
  * 1% difference from base_rate */
-min_cycles_2 = (double)abs(latency_difference_usec) / u->real_adjust_time 
/ 0.01 + 1;
-new_rate_2 = (double)base_rate * (1.0 + (double)latency_difference_usec / 
min_cycles_2 / u->real_adjust_time);
+min_cycles_2 = (double)abs(latency_difference_at_optimum_rate) / 
u->real_adjust_time / 0.01 + 1;
+new_rate_2 = (double)base_rate * (1.0 + 
(double)latency_difference_at_optimum_rate / min_cycles_2 / 
u->real_adjust_time);
 
 /* Choose the rate that is nearer to base_rate */
 if (abs(new_rate_1 - base_rate) < abs(new_rate_2 - base_rate))
@@ -258,7 +275,37 @@ static uint32_t rate_controller(
 else
 new_rate = new_rate_2;
 
-return new_rate;
+/* Calculate rate difference between source and sink. Skip calculation
+ * after a source/sink change, an underrun or latency offset change */
+
+if (!u->underrun_occured && !u->source_sink_changed && 
!u->source_latency_offset_changed && !u->sink_latency_offset_changed) {
+/* Latency difference between last iterations */
+latency_drift = latency_difference_at_base_rate - 
u->last_latency_difference;
+
+/* Calculate frequency difference between source and sink */
+drift_rate = latency_drift * old_rate / u->real_adjust_time + old_rate 
- base_rate;
+
+/* The maximum accepted sample rate difference between source and
+ * sink is 1% of the base rate. If the result is larger, something
+ * went wrong, so do not use 

[pulseaudio-discuss] [PATCH 01/10] loopback: Do not detect underruns during initial latency adjustments

2018-04-09 Thread Georg Chini
Currently module-loopback detects underruns even if sink_input_pop_cb()
was not yet called twice and initial latency adjustments are active.
This leads to unnecessary rewind requests.

This patch delays detecting underruns until the initial adjustments
are done.
---
 src/modules/module-loopback.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 31702e32..32780380 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -906,7 +906,8 @@ static int sink_input_process_msg_cb(pa_msgobject *obj, int 
code, void *data, in
  * right-away */
 if (u->sink_input->sink->thread_info.state != PA_SINK_SUSPENDED &&
 u->sink_input->thread_info.underrun_for > 0 &&
-pa_memblockq_is_readable(u->memblockq)) {
+pa_memblockq_is_readable(u->memblockq) &&
+u->output_thread_info.pop_called) {
 
 pa_asyncmsgq_post(pa_thread_mq_get()->outq, 
PA_MSGOBJECT(u->msg), LOOPBACK_MESSAGE_UNDERRUN, NULL, 0, NULL, NULL);
 /* If called from within the pop callback skip the rewind */
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 04/10] loopback: Add latency prediction and Kalman filter

2018-04-09 Thread Georg Chini
A Kalman filter is added to further reduce noise. The Kalman filter needs a
latency prediction as input, so estimate the next expected latency as well.
Again, theory is at
https://www.freedesktop.org/software/pulseaudio/misc/rate_estimator.odt
---
 src/modules/module-loopback.c | 67 ++-
 1 file changed, 66 insertions(+), 1 deletion(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 12193716..19a40b89 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -108,11 +108,19 @@ struct userdata {
 
 /* State variable of the latency controller */
 int32_t last_latency_difference;
+int64_t last_source_latency_offset;
+int64_t last_sink_latency_offset;
+int64_t next_latency_with_drift;
+int64_t next_latency_at_optimum_rate_with_drift;
 
 /* Filter varables used for 2nd order filter */
 double drift_filter;
 double drift_compensation_rate;
 
+/* Variables for Kalman filter */
+double latency_variance;
+double kalman_variance;
+
 /* lower latency limit found by underruns */
 pa_usec_t underrun_latency_limit;
 
@@ -374,6 +382,7 @@ static void adjust_rates(struct userdata *u) {
 pa_usec_t current_buffer_latency, snapshot_delay;
 int64_t current_source_sink_latency, current_latency, 
latency_at_optimum_rate;
 pa_usec_t final_latency, now;
+double filtered_latency, current_latency_error, latency_correction, 
base_rate_with_drift;
 
 pa_assert(u);
 pa_assert_ctl_context();
@@ -443,6 +452,28 @@ static void adjust_rates(struct userdata *u) {
 final_latency = PA_MAX(u->latency, u->minimum_latency);
 latency_difference = (int32_t)(current_latency - final_latency);
 
+/* Do not filter or calculate error if source or sink changed or if there 
was an underrun */
+if (u->source_sink_changed || u->underrun_occured) {
+/* Initial conditions are very unsure, so use a high variance */
+u->kalman_variance = 1000;
+filtered_latency = latency_at_optimum_rate;
+u->next_latency_at_optimum_rate_with_drift = latency_at_optimum_rate;
+u->next_latency_with_drift = current_latency;
+
+} else {
+/* Correct predictions if one of the latency offsets changed between 
iterations */
+u->next_latency_at_optimum_rate_with_drift += u->source_latency_offset 
- u->last_source_latency_offset;
+u->next_latency_at_optimum_rate_with_drift += u->sink_latency_offset - 
u->last_sink_latency_offset;
+u->next_latency_with_drift += u->source_latency_offset - 
u->last_source_latency_offset;
+u->next_latency_with_drift += u->sink_latency_offset - 
u->last_sink_latency_offset;
+/* Low pass filtered latency variance */
+current_latency_error = (double)abs((int32_t)(latency_at_optimum_rate 
- u->next_latency_at_optimum_rate_with_drift));
+u->latency_variance = (1.0 - FILTER_PARAMETER) * u->latency_variance + 
FILTER_PARAMETER * current_latency_error * current_latency_error;
+/* Kalman filter */
+filtered_latency = (latency_at_optimum_rate * u->kalman_variance + 
u->next_latency_at_optimum_rate_with_drift * u->latency_variance) / 
(u->kalman_variance + u->latency_variance);
+u->kalman_variance = u->kalman_variance * u->latency_variance / 
(u->kalman_variance + u->latency_variance) + u->latency_variance / 4 + 200;
+}
+
 pa_log_debug("Loopback overall latency is %0.2f ms + %0.2f ms + %0.2f ms = 
%0.2f ms",
 (double) u->latency_snapshot.sink_latency / PA_USEC_PER_MSEC,
 (double) current_buffer_latency / PA_USEC_PER_MSEC,
@@ -452,7 +483,7 @@ static void adjust_rates(struct userdata *u) {
 pa_log_debug("Loopback latency at optimum rate is %0.2f ms", 
(double)latency_at_optimum_rate / PA_USEC_PER_MSEC);
 
 /* Calculate new rate */
-new_rate = rate_controller(u, base_rate, old_rate, 
(int32_t)(latency_at_optimum_rate - final_latency), latency_difference);
+new_rate = rate_controller(u, base_rate, old_rate, 
(int32_t)(filtered_latency - final_latency), latency_difference);
 
 /* Save current latency difference at new rate for next cycle and reset 
flags */
 u->last_latency_difference = current_source_sink_latency + 
current_buffer_latency * old_rate / new_rate - final_latency;
@@ -460,9 +491,35 @@ static void adjust_rates(struct userdata *u) {
 /* Set variables that may change between calls of adjust_rate() */
 u->source_sink_changed = false;
 u->underrun_occured = false;
+u->last_source_latency_offset = u->source_latency_offset;
+u->last_sink_latency_offset = u->sink_latency_offset;
 u->source_latency_offset_changed = false;
 u->sink_latency_offset_changed = false;
 
+/* Predicton of next latency */
+
+/* Evaluate optimum rate */
+base_rate_with_drift = u->drift_compensation_rate + base_rate;
+
+/* Latency correction on next iteration */
+

[pulseaudio-discuss] [PATCH 02/10] loopback: Limit controller step size to 2.01‰

2018-04-09 Thread Georg Chini
The current loopback controller can produce a rate jump of up to 1% at startup,
which may be audible. To prevent large initial jumps, a second controller is
introduced, which produces a rate, that is not more than 2‰ away from the last
rate. Only during the startup phase, the rates produced by this controller will
be nearer to the base rate than those produced by the original controller.
Therefore choosing the rate which is nearer to the base rate will ensure that
the secondary controller only moderates the startup phase and has no influence
during continued operation.
The maximum step size of the original controller after the initial jump is
limited to 2.01‰ of the base rate, see documentation at
https://www.freedesktop.org/software/pulseaudio/misc/rate_estimator.odt
---
 src/modules/module-loopback.c | 28 +++-
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/src/modules/module-loopback.c b/src/modules/module-loopback.c
index 32780380..9ca84b9d 100644
--- a/src/modules/module-loopback.c
+++ b/src/modules/module-loopback.c
@@ -232,21 +232,31 @@ static void teardown(struct userdata *u) {
 
 /* rate controller, called from main context
  * - maximum deviation from base rate is less than 1%
- * - can create audible artifacts by changing the rate too quickly
+ * - controller step size is limited to 2.01‰
  * - exhibits hunting with USB or Bluetooth sources
  */
 static uint32_t rate_controller(
-uint32_t base_rate,
-pa_usec_t adjust_time,
+struct userdata *u,
+uint32_t base_rate, uint32_t old_rate,
 int32_t latency_difference_usec) {
 
-uint32_t new_rate;
-double min_cycles;
+uint32_t new_rate, new_rate_1, new_rate_2;
+double min_cycles_1, min_cycles_2;
+
+/* Calculate next rate that is not more than 2‰ away from the last rate */
+min_cycles_1 = (double)abs(latency_difference_usec) / u->real_adjust_time 
/ 0.002 + 1;
+new_rate_1 = old_rate + base_rate * (double)latency_difference_usec / 
min_cycles_1 / u->real_adjust_time;
 
 /* Calculate best rate to correct the current latency offset, limit at
- * slightly below 1% difference from base_rate */
-min_cycles = (double)abs(latency_difference_usec) / adjust_time / 0.01 + 1;
-new_rate = base_rate * (1.0 + (double)latency_difference_usec / min_cycles 
/ adjust_time);
+ * 1% difference from base_rate */
+min_cycles_2 = (double)abs(latency_difference_usec) / u->real_adjust_time 
/ 0.01 + 1;
+new_rate_2 = (double)base_rate * (1.0 + (double)latency_difference_usec / 
min_cycles_2 / u->real_adjust_time);
+
+/* Choose the rate that is nearer to base_rate */
+if (abs(new_rate_1 - base_rate) < abs(new_rate_2 - base_rate))
+new_rate = new_rate_1;
+else
+new_rate = new_rate_2;
 
 return new_rate;
 }
@@ -392,7 +402,7 @@ static void adjust_rates(struct userdata *u) {
 pa_log_debug("Loopback latency at base rate is %0.2f ms", 
(double)latency_at_optimum_rate / PA_USEC_PER_MSEC);
 
 /* Calculate new rate */
-new_rate = rate_controller(base_rate, u->real_adjust_time, 
latency_difference);
+new_rate = rate_controller(u, base_rate, old_rate, latency_difference);
 
 u->source_sink_changed = false;
 
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 00/10] loopback: Optimize latency stabilization

2018-04-09 Thread Georg Chini
This is a re-base of the remaining patches from the loopback series
I sent in February 2017. There are no major changes to the series.

Georg Chini (10):
  loopback: Do not detect underruns during initial latency adjustments
  loopback: Limit controller step size to 2.01‰
  loopback: Optimize adaptive re-sampling
  loopback: Add latency prediction and Kalman filter
  loopback: Track prediction error; debug and cosmetic changes
  loopback: Add adjust_threshold_usec parameter
  loopback: Only use controller weight after target latency has been
crossed twice
  loopback: Add low_device_latency parameter
  loopback: Add adjust_time_msec parameter to allow adjust times below
1s
  loopback: Add log_interval and log_interval_msec parameter

 src/modules/module-loopback.c | 364 +-
 1 file changed, 328 insertions(+), 36 deletions(-)

-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 07/10] tunnel: Allow module-tunnel to use alternative smoother code

2018-04-09 Thread Georg Chini
---
 src/modules/module-tunnel.c | 72 +++--
 1 file changed, 69 insertions(+), 3 deletions(-)

diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
index 2068deca..99aba68c 100644
--- a/src/modules/module-tunnel.c
+++ b/src/modules/module-tunnel.c
@@ -48,7 +48,13 @@
 #include 
 #include 
 #include 
+
+#ifdef USE_SMOOTHER_2
+#include 
+#else
 #include 
+#endif
+
 #include 
 #include 
 #include 
@@ -231,7 +237,11 @@ struct userdata {
 
 pa_time_event *time_event;
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2 *smoother;
+#else
 pa_smoother *smoother;
+#endif
 
 char *device_description;
 char *server_fqdn;
@@ -424,9 +434,15 @@ static void check_smoother_status(struct userdata *u, bool 
past) {
 x += u->thread_transport_usec;
 
 if (u->remote_suspended || u->remote_corked)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_pause(u->smoother, x);
+else
+pa_smoother_2_resume(u->smoother, x);
+#else
 pa_smoother_pause(u->smoother, x);
 else
 pa_smoother_resume(u->smoother, x, true);
+#endif
 }
 
 /* Called from IO thread context */
@@ -514,13 +530,18 @@ static int sink_process_msg(pa_msgobject *o, int code, 
void *data, int64_t offse
 }
 
 case PA_SINK_MESSAGE_GET_LATENCY: {
-pa_usec_t yl, yr;
 int64_t *usec = data;
 
+#ifdef USE_SMOOTHER_2
+*usec = pa_smoother_2_get_delay(u->smoother, pa_rtclock_now(), 
u->counter);
+#else
+pa_usec_t yl, yr;
+
 yl = pa_bytes_to_usec((uint64_t) u->counter, 
>sink->sample_spec);
 yr = pa_smoother_get(u->smoother, pa_rtclock_now());
 
 *usec = (int64_t)yl - yr;
+#endif
 return 0;
 }
 
@@ -547,6 +568,21 @@ static int sink_process_msg(pa_msgobject *o, int code, 
void *data, int64_t offse
 return 0;
 
 case SINK_MESSAGE_UPDATE_LATENCY: {
+#ifdef USE_SMOOTHER_2
+int64_t bytes;
+
+if (offset < 0)
+bytes = - pa_usec_to_bytes(- offset, >sink->sample_spec);
+else
+bytes = pa_usec_to_bytes(offset, >sink->sample_spec);
+
+if (u->counter > bytes)
+bytes = u->counter - bytes;
+else
+bytes = 0;
+
+ pa_smoother_2_put(u->smoother, pa_rtclock_now(), bytes);
+#else
 pa_usec_t y;
 
 y = pa_bytes_to_usec((uint64_t) u->counter, >sink->sample_spec);
@@ -557,6 +593,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void 
*data, int64_t offse
 y = 0;
 
 pa_smoother_put(u->smoother, pa_rtclock_now(), y);
+#endif
 
 /* We can access this freely here, since the main thread is 
waiting for us */
 u->thread_transport_usec = u->transport_usec;
@@ -632,13 +669,18 @@ static int source_process_msg(pa_msgobject *o, int code, 
void *data, int64_t off
 }
 
 case PA_SOURCE_MESSAGE_GET_LATENCY: {
-pa_usec_t yr, yl;
 int64_t *usec = data;
 
+#ifdef USE_SMOOTHER_2
+*usec = - pa_smoother_2_get_delay(u->smoother, pa_rtclock_now(), 
u->counter);
+#else
+pa_usec_t yr, yl;
+
 yl = pa_bytes_to_usec((uint64_t) u->counter, 
_SOURCE(o)->sample_spec);
 yr = pa_smoother_get(u->smoother, pa_rtclock_now());
 
 *usec = (int64_t)yr - yl;
+#endif
 return 0;
 }
 
@@ -673,12 +715,25 @@ static int source_process_msg(pa_msgobject *o, int code, 
void *data, int64_t off
 return 0;
 
 case SOURCE_MESSAGE_UPDATE_LATENCY: {
+#ifdef USE_SMOOTHER_2
+int64_t bytes;
+
+if (offset < 0)
+bytes = - pa_usec_to_bytes(- offset, >source->sample_spec);
+else
+bytes = pa_usec_to_bytes(offset, >source->sample_spec);
+
+bytes += u->counter;
+
+pa_smoother_2_put(u->smoother, pa_rtclock_now(), bytes);
+#else
 pa_usec_t y;
 
 y = pa_bytes_to_usec((uint64_t) u->counter, 
>source->sample_spec);
-y += (pa_usec_t) offset;
+y += offset;
 
 pa_smoother_put(u->smoother, pa_rtclock_now(), y);
+#endif
 
 /* We can access this freely here, since the main thread is 
waiting for us */
 u->thread_transport_usec = u->transport_usec;
@@ -1983,6 +2038,7 @@ int pa__init(pa_module*m) {
 u->source_name = pa_xstrdup(pa_modargs_get_value(ma, "source", NULL));;
 u->source = NULL;
 #endif
+#ifndef USE_SMOOTHER_2
 u->smoother = pa_smoother_new(
 PA_USEC_PER_SEC,
 PA_USEC_PER_SEC*2,
@@ -1991,6 +2047,7 @@ int pa__init(pa_module*m) {
 10,
 pa_rtclock_now(),
 false);
+#endif
 u->ctag = 1;
 u->device_index = u->channel = PA_INVALID_INDEX;
 u->time_event = NULL;
@@ -2139,6 +2196,11 @@ int pa__init(pa_module*m) {
 goto fail;

[pulseaudio-discuss] [PATCH 09/10] raop-sink: Allow module-raop-sink to use alternative smoother code

2018-04-09 Thread Georg Chini
This is untested.
---
 src/modules/raop/raop-sink.c | 40 +++-
 1 file changed, 39 insertions(+), 1 deletion(-)

diff --git a/src/modules/raop/raop-sink.c b/src/modules/raop/raop-sink.c
index ec6f8262..5103e8e7 100644
--- a/src/modules/raop/raop-sink.c
+++ b/src/modules/raop/raop-sink.c
@@ -59,7 +59,12 @@
 #include 
 #include 
 #include 
+
+#ifdef USE_SMOOTHER_2
+#include 
+#else
 #include 
+#endif
 
 #include "raop-sink.h"
 #include "raop-client.h"
@@ -87,7 +92,11 @@ struct userdata {
 
 pa_usec_t delay;
 pa_usec_t start;
+#ifdef USE_SMOOTHER_2
+pa_smoother_2 *smoother;
+#else
 pa_smoother *smoother;
+#endif
 uint64_t write_count;
 
 uint32_t latency;
@@ -112,16 +121,22 @@ static void raop_state_cb(pa_raop_state_t state, void 
*userdata) {
 }
 
 static int64_t sink_get_latency(const struct userdata *u) {
+#ifndef USE_SMOOTHER_2
 pa_usec_t now;
+#endif
 int64_t latency;
 
 pa_assert(u);
 pa_assert(u->smoother);
 
+#ifdef USE_SMOOTHER_2
+latency = pa_smoother_2_get_delay(u->smoother, pa_rtclock_now(), 
u->write_count);
+#else
 now = pa_rtclock_now();
 now = pa_smoother_get(u->smoother, now);
 
 latency = pa_bytes_to_usec(u->write_count, >sink->sample_spec) - 
(int64_t) now;
+#endif
 
 /* RAOP default latency */
 latency += u->latency * PA_USEC_PER_MSEC;
@@ -262,7 +277,11 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, 
pa_sink_state_t new_state,
 pa_log_debug("RAOP: RUNNING");
 
 now = pa_rtclock_now();
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_reset(u->smoother, now);
+#else
 pa_smoother_reset(u->smoother, now, false);
+#endif
 
 if (!pa_raop_client_is_alive(u->raop)) {
 /* Connecting will trigger a RECORD and start steaming */
@@ -347,15 +366,22 @@ static void thread_func(void *userdata) {
 pa_log_debug("Thread starting up");
 
 pa_thread_mq_install(>thread_mq);
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_reset(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
+#endif
 
 for (;;) {
 struct pollfd *pollfd = NULL;
 unsigned int i, nbfds = 0;
-pa_usec_t now, estimated, intvl;
+pa_usec_t now, intvl;
 uint64_t position;
 size_t index;
 int ret;
+#ifndef USE_SMOOTHER_2
+pa_usec_t estimated;
+#endif
 
 if (PA_SINK_IS_OPENED(u->sink->thread_info.state)) {
 if (u->sink->thread_info.rewind_requested)
@@ -444,8 +470,12 @@ static void thread_func(void *userdata) {
 position = u->write_count - pa_usec_to_bytes(u->delay, 
>sink->sample_spec);
 
 now = pa_rtclock_now();
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_put(u->smoother, now, position);
+#else
 estimated = pa_bytes_to_usec(position, >sink->sample_spec);
 pa_smoother_put(u->smoother, now, estimated);
+#endif
 
 if (u->oob && !pollfd->revents) {
 /* Sleep until next packet transmission */
@@ -601,6 +631,9 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, 
const char *driver) {
 pa_memchunk_reset(>memchunk);
 
 u->delay = 0;
+#ifdef USE_SMOOTHER_2
+u->smoother = pa_smoother_2_new(5*PA_USEC_PER_SEC, pa_rtclock_now(), 
pa_frame_size(), ss.rate);
+#else
 u->smoother = pa_smoother_new(
 PA_USEC_PER_SEC,
 PA_USEC_PER_SEC*2,
@@ -609,6 +642,7 @@ pa_sink* pa_raop_sink_new(pa_module *m, pa_modargs *ma, 
const char *driver) {
 10,
 0,
 false);
+#endif
 u->write_count = 0;
 
 if (pa_streq(protocol, "TCP")) {
@@ -785,7 +819,11 @@ static void userdata_free(struct userdata *u) {
 u->raop = NULL;
 
 if (u->smoother)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_free(u->smoother);
+#else
 pa_smoother_free(u->smoother);
+#endif
 u->smoother = NULL;
 
 if (u->card)
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 10/10] solaris: Allow module-solaris to use alternative smoother code

2018-04-09 Thread Georg Chini
This is untested.
---
 src/modules/module-solaris.c | 43 +++
 1 file changed, 43 insertions(+)

diff --git a/src/modules/module-solaris.c b/src/modules/module-solaris.c
index 240ed855..4642446a 100644
--- a/src/modules/module-solaris.c
+++ b/src/modules/module-solaris.c
@@ -60,7 +60,12 @@
 #include 
 #include 
 #include 
+
+#ifdef USE_SMOOTHER_2
+#include 
+#else
 #include 
+#endif
 
 PA_MODULE_AUTHOR("Pierre Ossman");
 PA_MODULE_DESCRIPTION("Solaris Sink/Source");
@@ -110,7 +115,11 @@ struct userdata {
 
 int32_t minimum_request;
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2 *smoother;
+#else
 pa_smoother *smoother;
+#endif
 };
 
 static const char* const valid_modargs[] = {
@@ -164,7 +173,11 @@ static uint64_t get_playback_buffered_bytes(struct 
userdata *u) {
 u->prev_playback_samples = info.play.samples;
 played_bytes = (((uint64_t)u->play_samples_msw << 32) + info.play.samples) 
* u->frame_size;
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_put(u->smoother, pa_rtclock_now(), played_bytes);
+#else
 pa_smoother_put(u->smoother, pa_rtclock_now(), 
pa_bytes_to_usec(played_bytes, >sink->sample_spec));
+#endif
 
 if (u->written_bytes > played_bytes)
 return u->written_bytes - played_bytes;
@@ -413,7 +426,11 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, 
pa_sink_state_t new_state,
 
 pa_assert(PA_SINK_IS_OPENED(s->thread_info.state));
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_pause(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_pause(u->smoother, pa_rtclock_now());
+#endif
 
 if (!u->source || u->source_suspended)
 suspend(u);
@@ -425,7 +442,11 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, 
pa_sink_state_t new_state,
 case PA_SINK_RUNNING:
 
 if (s->thread_info.state == PA_SINK_SUSPENDED) {
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_resume(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_resume(u->smoother, pa_rtclock_now(), true);
+#endif
 
 if (!u->source || u->source_suspended) {
 bool mute;
@@ -654,7 +675,11 @@ static void thread_func(void *userdata) {
 
 pa_thread_mq_install(>thread_mq);
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_reset(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
+#endif
 
 for (;;) {
 /* Render some data and write it to the dsp */
@@ -680,7 +705,11 @@ static void thread_func(void *userdata) {
 if (ioctl(u->fd, AUDIO_SETINFO, ) < 0)
 pa_log("AUDIO_SETINFO: %s", pa_cstrerror(errno));
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_reset(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_reset(u->smoother, pa_rtclock_now(), true);
+#endif
 }
 
 for (;;) {
@@ -738,7 +767,11 @@ static void thread_func(void *userdata) {
 }
 
 ysleep_interval = pa_bytes_to_usec(buffered_bytes / 2, 
>sink->sample_spec);
+#ifdef USE_SMOOTHER_2
+xsleep_interval = pa_smoother_2_translate(u->smoother, 
ysleep_interval);
+#else
 xsleep_interval = pa_smoother_translate(u->smoother, xtime0, 
ysleep_interval);
+#endif
 pa_rtpoll_set_timer_absolute(u->rtpoll, xtime0 + 
PA_MIN(xsleep_interval, ysleep_interval));
 } else
 pa_rtpoll_set_timer_disabled(u->rtpoll);
@@ -886,8 +919,10 @@ int pa__init(pa_module *m) {
 
 u = pa_xnew0(struct userdata, 1);
 
+#ifndef USE_SMOOTHER_2
 if (!(u->smoother = pa_smoother_new(PA_USEC_PER_SEC, PA_USEC_PER_SEC * 2, 
true, true, 10, pa_rtclock_now(), true)))
 goto fail;
+#endif
 
 /*
  * For a process (or several processes) to use the same audio device for 
both
@@ -903,6 +938,10 @@ int pa__init(pa_module *m) {
 }
 u->frame_size = pa_frame_size();
 
+#ifdef USE_SMOOTHER_2
+u->smoother = pa_smoother_2_new(5*PA_USEC_PER_SEC, pa_rtclock_now(), 
u->frame_size, ss.rate);
+#endif
+
 u->minimum_request = pa_usec_to_bytes(PA_USEC_PER_SEC / MAX_RENDER_HZ, 
);
 
 buffer_length_msec = 100;
@@ -1144,7 +1183,11 @@ void pa__done(pa_module *m) {
 close(u->fd);
 
 if (u->smoother)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_free(u->smoother);
+#else
 pa_smoother_free(u->smoother);
+#endif
 
 pa_xfree(u->device_name);
 
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 06/10] combine-sink: Allow module-combine-sink to use alternative smoother code

2018-04-09 Thread Georg Chini
---
 src/modules/module-combine-sink.c | 52 ---
 1 file changed, 48 insertions(+), 4 deletions(-)

diff --git a/src/modules/module-combine-sink.c 
b/src/modules/module-combine-sink.c
index 79b4c6d9..1e719f8f 100644
--- a/src/modules/module-combine-sink.c
+++ b/src/modules/module-combine-sink.c
@@ -42,7 +42,13 @@
 #include 
 #include 
 #include 
+
+#ifdef USE_SMOOTHER_2
+#include 
+#else
 #include 
+#endif
+
 #include 
 
 PA_MODULE_AUTHOR("Lennart Poettering");
@@ -164,7 +170,11 @@ struct userdata {
 pa_atomic_t running;  /* we cache that value here, so that every 
thread can query it cheaply */
 pa_usec_t timestamp;
 bool in_null_mode;
-pa_smoother *smoother;
+#ifdef USE_SMOOTHER_2
+pa_smoother_2 *smoother;
+#else
+ pa_smoother *smoother;
+#endif
 uint64_t counter;
 
 uint64_t snapshot_counter;
@@ -399,8 +409,13 @@ static void process_render_null(struct userdata *u, 
pa_usec_t now) {
 
 /* pa_log_debug("Ate in sum %lu bytes (of %lu)", (unsigned long) ate, 
(unsigned long) nbytes); */
 
-pa_smoother_put(u->thread_info.smoother, now,
-pa_bytes_to_usec(u->thread_info.counter, 
>sink->sample_spec) - (u->thread_info.timestamp - now));
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_put(u->thread_info.smoother, now,
+u->thread_info.counter - 
pa_usec_to_bytes(u->thread_info.timestamp - now, >sink->sample_spec));
+#else
+ pa_smoother_put(u->thread_info.smoother, now,
+ pa_bytes_to_usec(u->thread_info.counter, 
>sink->sample_spec) - (u->thread_info.timestamp - now));
+#endif
 }
 
 static void thread_func(void *userdata) {
@@ -855,9 +870,15 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, 
pa_sink_state_t new_state,
 
 if (running) {
 u->thread_info.render_timestamp = 0;
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_resume(u->thread_info.smoother, pa_rtclock_now());
+} else
+pa_smoother_2_pause(u->thread_info.smoother, pa_rtclock_now());
+#else
 pa_smoother_resume(u->thread_info.smoother, pa_rtclock_now(), true);
 } else
 pa_smoother_pause(u->thread_info.smoother, pa_rtclock_now());
+#endif
 
 return 0;
 }
@@ -1005,15 +1026,20 @@ static int sink_process_msg(pa_msgobject *o, int code, 
void *data, int64_t offse
 switch (code) {
 
 case PA_SINK_MESSAGE_GET_LATENCY: {
-pa_usec_t x, y, c;
 int64_t *delay = data;
 
+#ifdef USE_SMOOTHER_2
+*delay = pa_smoother_2_get_delay(u->thread_info.smoother, 
pa_rtclock_now(), u->thread_info.counter);
+#else
+pa_usec_t x, y, c;
+
 x = pa_rtclock_now();
 y = pa_smoother_get(u->thread_info.smoother, x);
 
 c = pa_bytes_to_usec(u->thread_info.counter, 
>sink->sample_spec);
 
 *delay = (int64_t)c - y;
+#endif
 
 return 0;
 }
@@ -1035,6 +1061,12 @@ static int sink_process_msg(pa_msgobject *o, int code, 
void *data, int64_t offse
 return 0;
 
 case SINK_MESSAGE_UPDATE_LATENCY: {
+#ifdef USE_SMOOTHER_2
+size_t latency;
+
+latency = pa_usec_to_bytes((pa_usec_t)offset,  
>sink->sample_spec);
+pa_smoother_2_put(u->thread_info.smoother, 
u->thread_info.snapshot_time, (int64_t)u->thread_info.snapshot_counter - 
latency);
+#else
 pa_usec_t x, y, latency = (pa_usec_t) offset;
 
 /* It may be possible that thread_info.counter has been increased
@@ -1049,6 +1081,7 @@ static int sink_process_msg(pa_msgobject *o, int code, 
void *data, int64_t offse
 y = 0;
 
 pa_smoother_put(u->thread_info.smoother, x, y);
+#endif
 return 0;
 }
 
@@ -1457,6 +1490,7 @@ int pa__init(pa_module*m) {
 
 u->resample_method = resample_method;
 u->outputs = pa_idxset_new(NULL, NULL);
+#ifndef USE_SMOOTHER_2
 u->thread_info.smoother = pa_smoother_new(
 PA_USEC_PER_SEC,
 PA_USEC_PER_SEC*2,
@@ -1465,6 +1499,7 @@ int pa__init(pa_module*m) {
 10,
 pa_rtclock_now(),
 true);
+#endif
 
 adjust_time_sec = DEFAULT_ADJUST_TIME_USEC / PA_USEC_PER_SEC;
 if (pa_modargs_get_value_u32(ma, "adjust_time", _time_sec) < 0) {
@@ -1571,6 +1606,11 @@ int pa__init(pa_module*m) {
 goto fail;
 }
 
+#ifdef USE_SMOOTHER_2
+/* The smoother window size needs to be larger than the time between 
updates */
+u->thread_info.smoother = pa_smoother_2_new(u->adjust_time + 
5*PA_USEC_PER_SEC, pa_rtclock_now(), pa_frame_size(>sink->sample_spec), 
u->sink->sample_spec.rate);
+#endif
+
 u->sink->parent.process_msg = sink_process_msg;
 u->sink->set_state_in_main_thread = sink_set_state_in_main_thread_cb;
 u->sink->set_state_in_io_thread = sink_set_state_in_io_thread_cb;
@@ -1712,7 +1752,11 @@ void pa__done(pa_module*m) {
 u->core->mainloop->time_free(u->time_event);
 
 if 

[pulseaudio-discuss] [PATCH 04/10] bluetooth: Allow bluetooth to use alternative smoother code

2018-04-09 Thread Georg Chini
---
 src/modules/bluetooth/module-bluez5-device.c | 57 ++--
 1 file changed, 53 insertions(+), 4 deletions(-)

diff --git a/src/modules/bluetooth/module-bluez5-device.c 
b/src/modules/bluetooth/module-bluez5-device.c
index d40bbb0c..935ee2ce 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -43,7 +43,12 @@
 #include 
 #include 
 #include 
+
+#ifdef USE_SMOOTHER_2
+#include 
+#else
 #include 
+#endif
 
 #include "a2dp-codecs.h"
 #include "bluez5-util.h"
@@ -147,7 +152,13 @@ struct userdata {
 uint64_t read_index;
 uint64_t write_index;
 pa_usec_t started_at;
+
+#ifdef USE_SMOOTHER_2
+pa_smoother_2 *read_smoother;
+#else
 pa_smoother *read_smoother;
+#endif
+
 pa_memchunk write_memchunk;
 pa_sample_spec sample_spec;
 struct sbc_info sbc_info;
@@ -408,8 +419,13 @@ static int sco_process_push(struct userdata *u) {
 u->source_buffered_blocks++;
 
 if (u->source_buffered_blocks >= max_blocks) {
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_resume(u->read_smoother, tstamp);
+pa_smoother_2_put(u->read_smoother, tstamp, u->read_index);
+#else
 pa_smoother_put(u->read_smoother, tstamp, 
pa_bytes_to_usec(u->read_index, >sample_spec));
 pa_smoother_resume(u->read_smoother, tstamp, true);
+#endif
 
 u->source_buffer.length = u->source_buffer.index;
 u->source_buffer.index = 0;
@@ -663,8 +679,14 @@ static int a2dp_process_push(struct userdata *u) {
 }
 
 u->read_index += (uint64_t) total_written;
+
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_resume(u->read_smoother, tstamp);
+pa_smoother_2_put(u->read_smoother, tstamp, u->read_index);
+#else
 pa_smoother_put(u->read_smoother, tstamp, 
pa_bytes_to_usec(u->read_index, >sample_spec));
 pa_smoother_resume(u->read_smoother, tstamp, true);
+#endif
 
 memchunk.length -= to_write;
 
@@ -791,7 +813,11 @@ static void teardown_stream(struct userdata *u) {
 }
 
 if (u->read_smoother) {
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_free(u->read_smoother);
+#else
 pa_smoother_free(u->read_smoother);
+#endif
 u->read_smoother = NULL;
 }
 
@@ -935,7 +961,11 @@ static void setup_stream(struct userdata *u) {
 u->stream_setup_done = true;
 
 if (u->source)
+#ifdef USE_SMOOTHER_2
+u->read_smoother = pa_smoother_2_new(5*PA_USEC_PER_SEC, 
pa_rtclock_now(), pa_frame_size(>sample_spec), u->sample_spec.rate);
+#else
 u->read_smoother = pa_smoother_new(PA_USEC_PER_SEC, 2*PA_USEC_PER_SEC, 
true, true, 10, pa_rtclock_now(), true);
+#endif
 }
 
 /* Called from I/O thread, returns true if the transport was acquired or
@@ -962,13 +992,19 @@ static int source_process_msg(pa_msgobject *o, int code, 
void *data, int64_t off
 switch (code) {
 
 case PA_SOURCE_MESSAGE_GET_LATENCY: {
-int64_t wi, ri;
+#ifndef USE_SMOOTHER_2
+ int64_t wi, ri;
+#endif
 
 if (u->read_smoother) {
+#ifdef USE_SMOOTHER_2
+*((int64_t*) data) = u->source->thread_info.fixed_latency - 
pa_smoother_2_get_delay(u->read_smoother, pa_rtclock_now(), u->read_index);
+#else
 wi = pa_smoother_get(u->read_smoother, pa_rtclock_now());
 ri = pa_bytes_to_usec(u->read_index, >sample_spec);
 
 *((int64_t*) data) = u->source->thread_info.fixed_latency + wi 
- ri;
+#endif
 *((int64_t*) data) += u->source_buffered_blocks * 
pa_bytes_to_usec(u->read_block_size, >source->sample_spec);
 } else
 *((int64_t*) data) = 0;
@@ -1004,8 +1040,11 @@ static int source_set_state_in_io_thread_cb(pa_source 
*s, pa_source_state_t new_
 transport_release(u);
 
 if (u->read_smoother)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_pause(u->read_smoother, pa_rtclock_now());
+#else
 pa_smoother_pause(u->read_smoother, pa_rtclock_now());
-
+#endif
 break;
 
 case PA_SOURCE_IDLE:
@@ -1139,17 +1178,23 @@ static int sink_process_msg(pa_msgobject *o, int code, 
void *data, int64_t offse
 switch (code) {
 
 case PA_SINK_MESSAGE_GET_LATENCY: {
-int64_t wi = 0, ri = 0;
+int64_t wi, ri, delay = 0;
 
 if (u->read_smoother) {
+#ifdef USE_SMOOTHER_2
+delay = pa_smoother_2_get_delay(u->read_smoother, 
pa_rtclock_now(), u->write_index + u->write_block_size);
+#else
 ri = pa_smoother_get(u->read_smoother, pa_rtclock_now());
 wi = pa_bytes_to_usec(u->write_index + u->write_block_size, 
>sample_spec);
+delay = wi - ri;
+#endif
 } else if (u->started_at) {
 ri = pa_rtclock_now() - u->started_at;
 wi = pa_bytes_to_usec(u->write_index, >sample_spec);
+delay = wi - ri;
 }
 
-*((int64_t*) data) = 

[pulseaudio-discuss] [PATCH 08/10] esound-sink: Allow module-esound-sink to use alternative smoother code

2018-04-09 Thread Georg Chini
This is untested.
---
 src/modules/module-esound-sink.c | 51 +++-
 1 file changed, 50 insertions(+), 1 deletion(-)

diff --git a/src/modules/module-esound-sink.c b/src/modules/module-esound-sink.c
index 5ff04516..52bfa496 100644
--- a/src/modules/module-esound-sink.c
+++ b/src/modules/module-esound-sink.c
@@ -60,7 +60,13 @@
 #include 
 #include 
 #include 
+
+#ifdef USE_SMOOTHER_2
+#include 
+#else
 #include 
+#endif
+
 #include 
 #include 
 #include 
@@ -110,7 +116,12 @@ struct userdata {
 esd_format_t format;
 int32_t rate;
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2 *smoother;
+#else
 pa_smoother *smoother;
+#endif
+
 int fd;
 
 int64_t offset;
@@ -142,12 +153,16 @@ static int sink_process_msg(pa_msgobject *o, int code, 
void *data, int64_t offse
 switch (code) {
 
 case PA_SINK_MESSAGE_GET_LATENCY: {
+#ifdef USE_SMOOTHER_2
+*((int64_t*) data) = pa_smoother_2_get_delay(u->smoother, 
pa_rtclock_now(), (uint64_t)u->offset + u->memchunk.length);
+#else
 pa_usec_t w, r;
 
 r = pa_smoother_get(u->smoother, pa_rtclock_now());
 w = pa_bytes_to_usec((uint64_t) u->offset + u->memchunk.length, 
>sink->sample_spec);
 
 *((int64_t*) data) = (int64_t)w - r;
+#endif
 return 0;
 }
 
@@ -185,15 +200,22 @@ static int sink_set_state_in_io_thread_cb(pa_sink *s, 
pa_sink_state_t new_state,
 case PA_SINK_SUSPENDED:
 pa_assert(PA_SINK_IS_OPENED(s->thread_info.state));
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_pause(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_pause(u->smoother, pa_rtclock_now());
+#endif
 break;
 
 case PA_SINK_IDLE:
 case PA_SINK_RUNNING:
 
 if (s->thread_info.state == PA_SINK_SUSPENDED)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_resume(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_resume(u->smoother, pa_rtclock_now(), true);
-
+#endif
 break;
 
 case PA_SINK_UNLINKED:
@@ -215,7 +237,11 @@ static void thread_func(void *userdata) {
 
 pa_thread_mq_install(>thread_mq);
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_reset(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_set_time_offset(u->smoother, pa_rtclock_now());
+#endif
 
 for (;;) {
 int ret;
@@ -229,7 +255,11 @@ static void thread_func(void *userdata) {
 
 /* Render some data and write it to the fifo */
 if (PA_SINK_IS_OPENED(u->sink->thread_info.state) && 
pollfd->revents) {
+#ifdef USE_SMOOTHER_2
+size_t bytes;
+#else
 pa_usec_t usec;
+#endif
 int64_t n;
 
 for (;;) {
@@ -300,6 +330,16 @@ static void thread_func(void *userdata) {
 }
 #endif
 
+#ifdef USE_SMOOTHER_2
+bytes = pa_usec_to_bytes(u->latency, >sink->sample_spec);
+
+if ((uint64_t)n > bytes)
+bytes = n - bytes;
+else
+bytes = 0;
+
+pa_smoother_2_put(u->smoother, pa_rtclock_now(), bytes);
+#else
 usec = pa_bytes_to_usec((uint64_t) n, >sink->sample_spec);
 
 if (usec > u->latency)
@@ -308,6 +348,7 @@ static void thread_func(void *userdata) {
 usec = 0;
 
 pa_smoother_put(u->smoother, pa_rtclock_now(), usec);
+#endif
 }
 
 /* Hmm, nothing to do. Let's sleep */
@@ -561,6 +602,9 @@ int pa__init(pa_module*m) {
 u->module = m;
 m->userdata = u;
 u->fd = -1;
+#ifdef USE_SMOOTHER_2
+u->smoother = pa_smoother_2_new(5*PA_USEC_PER_SEC, pa_rtclock_now(), 
pa_frame_size(), ss.rate);
+#else
 u->smoother = pa_smoother_new(
 PA_USEC_PER_SEC,
 PA_USEC_PER_SEC*2,
@@ -569,6 +613,7 @@ int pa__init(pa_module*m) {
 10,
 0,
 false);
+#endif
 pa_memchunk_reset(>memchunk);
 u->offset = 0;
 
@@ -725,7 +770,11 @@ void pa__done(pa_module*m) {
 pa_xfree(u->write_data);
 
 if (u->smoother)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_free(u->smoother);
+#else
 pa_smoother_free(u->smoother);
+#endif
 
 if (u->fd >= 0)
 pa_close(u->fd);
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 02/10] Add configure option --enable-smoother-2 to enable alternative smoother code

2018-04-09 Thread Georg Chini
---
 configure.ac | 7 +++
 1 file changed, 7 insertions(+)

diff --git a/configure.ac b/configure.ac
index b0855a46..57cb3a92 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1399,7 +1399,14 @@ AS_IF([test "x$os_is_win32" != "x1"],
 AS_IF([test "x$ax_pthread_ok" = "xyes"],
 AC_DEFINE([_POSIX_PTHREAD_SEMANTICS], 1, [Needed on Solaris]))
 
+ Support for new smoother code (optional) 
 
+AC_ARG_ENABLE([smoother-2],
+AS_HELP_STRING([--enable-smoother-2],[Enable optional new smoother 
support]))
+
+AS_IF([test "x$enable_smoother_2" == "xyes"], [USE_SMOOTHER_2=1], 
[USE_SMOOTHER_2=0])
+
+AS_IF([test "x$USE_SMOOTHER_2" = "x1"], AC_DEFINE([USE_SMOOTHER_2], 1, [Define 
this to enable new smoother support]))
 
 ###
 #Output   #
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 05/10] stream: Allow stream.c to use alternative smoother code

2018-04-09 Thread Georg Chini
---
 src/pulse/internal.h | 10 ++
 src/pulse/stream.c   | 52 +++-
 2 files changed, 61 insertions(+), 1 deletion(-)

diff --git a/src/pulse/internal.h b/src/pulse/internal.h
index 01d2b6e4..0d18aa71 100644
--- a/src/pulse/internal.h
+++ b/src/pulse/internal.h
@@ -40,7 +40,13 @@
 #include 
 #include 
 #include 
+
+#ifdef USE_SMOOTHER_2
+#include 
+#else
 #include 
+#endif
+
 #ifdef HAVE_DBUS
 #include 
 #endif
@@ -204,7 +210,11 @@ struct pa_stream {
 pa_time_event *auto_timing_update_event;
 pa_usec_t auto_timing_interval_usec;
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2 *smoother;
+#else
 pa_smoother *smoother;
+#endif
 
 /* Callbacks */
 pa_stream_notify_cb_t state_callback;
diff --git a/src/pulse/stream.c b/src/pulse/stream.c
index ee95757f..461eb6dd 100644
--- a/src/pulse/stream.c
+++ b/src/pulse/stream.c
@@ -48,9 +48,11 @@
 #define AUTO_TIMING_INTERVAL_START_USEC (10*PA_USEC_PER_MSEC)
 #define AUTO_TIMING_INTERVAL_END_USEC (1500*PA_USEC_PER_MSEC)
 
-#define SMOOTHER_ADJUST_TIME (1000*PA_USEC_PER_MSEC)
 #define SMOOTHER_HISTORY_TIME (5000*PA_USEC_PER_MSEC)
+#ifndef USE_SMOOTHER_2
+#define SMOOTHER_ADJUST_TIME (1000*PA_USEC_PER_MSEC)
 #define SMOOTHER_MIN_HISTORY (4)
+#endif
 
 pa_stream *pa_stream_new(pa_context *c, const char *name, const pa_sample_spec 
*ss, const pa_channel_map *map) {
 return pa_stream_new_with_proplist(c, name, ss, map, NULL);
@@ -303,7 +305,11 @@ static void stream_free(pa_stream *s) {
 pa_proplist_free(s->proplist);
 
 if (s->smoother)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_free(s->smoother);
+#else
 pa_smoother_free(s->smoother);
+#endif
 
 for (i = 0; i < s->n_formats; i++)
 pa_format_info_free(s->req_formats[i]);
@@ -463,7 +469,11 @@ static void check_smoother_status(pa_stream *s, bool 
aposteriori, bool force_sta
 }
 
 if (s->suspended || s->corked || force_stop)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_pause(s->smoother, x);
+#else
 pa_smoother_pause(s->smoother, x);
+#endif
 else if (force_start || s->buffer_attr.prebuf == 0) {
 
 if (!s->timing_info_valid &&
@@ -482,7 +492,11 @@ static void check_smoother_status(pa_stream *s, bool 
aposteriori, bool force_sta
 return;
 }
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_resume(s->smoother, x);
+#else
 pa_smoother_resume(s->smoother, x, true);
+#endif
 }
 
 /* Please note that we have no idea if playback actually started
@@ -1120,6 +1134,11 @@ void pa_create_stream_callback(pa_pdispatch *pd, 
uint32_t command, uint32_t tag,
 s->sample_spec = ss;
 }
 
+#ifdef USE_SMOOTHER_2
+if (s->flags & PA_STREAM_INTERPOLATE_TIMING)
+pa_smoother_2_set_sample_spec(s->smoother, pa_rtclock_now(), 
>sample_spec);
+#endif
+
 if (s->context->version >= 13 && s->direction != PA_STREAM_UPLOAD) {
 pa_usec_t usec;
 
@@ -1254,6 +1273,9 @@ static int create_stream(
 x = pa_rtclock_now();
 
 pa_assert(!s->smoother);
+#ifdef USE_SMOOTHER_2
+s->smoother = pa_smoother_2_new(SMOOTHER_HISTORY_TIME, x, 0, 0);
+#else
 s->smoother = pa_smoother_new(
 SMOOTHER_ADJUST_TIME,
 SMOOTHER_HISTORY_TIME,
@@ -1262,6 +1284,7 @@ static int create_stream(
 SMOOTHER_MIN_HISTORY,
 x,
 true);
+#endif
 }
 
 if (!dev)
@@ -1792,6 +1815,12 @@ static pa_usec_t calc_time(pa_stream *s, bool 
ignore_transport) {
 return usec;
 }
 
+#ifdef USE_SMOOTHER_2
+static inline size_t calc_bytes(pa_stream *s, bool ignore_transport) {
+return pa_usec_to_bytes(calc_time(s, ignore_transport), >sample_spec);
+}
+#endif
+
 static void stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t 
command, uint32_t tag, pa_tagstruct *t, void *userdata) {
 pa_operation *o = userdata;
 struct timeval local, remote, now;
@@ -1950,15 +1979,27 @@ static void 
stream_get_timing_info_callback(pa_pdispatch *pd, uint32_t command,
 }
 
 if (!i->playing)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_pause(o->stream->smoother, x);
+#else
 pa_smoother_pause(o->stream->smoother, x);
+#endif
 
 /* Update the smoother */
 if ((o->stream->direction == PA_STREAM_PLAYBACK && 
!i->read_index_corrupt) ||
 (o->stream->direction == PA_STREAM_RECORD && 
!i->write_index_corrupt))
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_put(o->stream->smoother, u, 
calc_bytes(o->stream, true));
+#else
 pa_smoother_put(o->stream->smoother, u, calc_time(o->stream, 
true));
+#endif
 
 if (i->playing)
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_resume(o->stream->smoother, x);
+#else
 pa_smoother_resume(o->stream->smoother, x, true);
+#endif
 }
 }
 
@@ -2467,7 +2508,12 @@ int pa_stream_get_time(pa_stream *s, pa_usec_t *r_usec) {
   

[pulseaudio-discuss] [PATCH 00/10] pulsecore: Add new time smoother

2018-04-09 Thread Georg Chini
This patch set adds a new time smoother to PA, which delivers higher
precision than the current version. More details in the commit message
of the first patch.

The first patch adds the functionality, the other patches are a
straight forward addition of the new smoother functions to all
callers of the time smoother. A configure option is used to switch
between the two smoother implementations. It is currently disabled
by default but should be enabled once some broader testing was done.

The last three patches could not be tested, so any tests would be
appreciated.

Georg Chini (10):
  pulsecore: Add alternative time smoother implementation
  Add configure option --enable-smoother-2 to enable alternative
smoother code
  alsa sink/source: Allow alsa to use alternative smoother code
  bluetooth: Allow bluetooth to use alternative smoother code
  stream: Allow stream.c to use alternative smoother code
  combine-sink: Allow module-combine-sink to use alternative smoother
code
  tunnel: Allow module-tunnel to use alternative smoother code
  esound-sink: Allow module-esound-sink to use alternative smoother code
  raop-sink: Allow module-raop-sink to use alternative smoother code
  solaris: Allow module-solaris to use alternative smoother code

 configure.ac |   7 +
 po/POTFILES.in   |   1 +
 src/Makefile.am  |   1 +
 src/modules/alsa/alsa-sink.c |  86 +-
 src/modules/alsa/alsa-source.c   |  67 -
 src/modules/bluetooth/module-bluez5-device.c |  57 +++-
 src/modules/module-combine-sink.c|  52 +++-
 src/modules/module-esound-sink.c |  51 +++-
 src/modules/module-solaris.c |  43 +++
 src/modules/module-tunnel.c  |  72 -
 src/modules/raop/raop-sink.c |  40 ++-
 src/pulse/internal.h |  10 +
 src/pulse/stream.c   |  52 +++-
 src/pulsecore/time-smoother_2.c  | 408 +++
 src/pulsecore/time-smoother_2.h  |  53 
 15 files changed, 979 insertions(+), 21 deletions(-)
 create mode 100644 src/pulsecore/time-smoother_2.c
 create mode 100644 src/pulsecore/time-smoother_2.h

-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 03/10] alsa sink/source: Allow alsa to use alternative smoother code

2018-04-09 Thread Georg Chini
---
 src/modules/alsa/alsa-sink.c   | 86 --
 src/modules/alsa/alsa-source.c | 67 ++--
 2 files changed, 146 insertions(+), 7 deletions(-)

diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c
index eb79a444..d2e495e6 100644
--- a/src/modules/alsa/alsa-sink.c
+++ b/src/modules/alsa/alsa-sink.c
@@ -51,7 +51,12 @@
 #include 
 #include 
 #include 
+
+#ifdef USE_SMOOTHER_2
+#include 
+#else
 #include 
+#endif
 
 #include 
 
@@ -77,11 +82,15 @@
 #define TSCHED_MIN_SLEEP_USEC (10*PA_USEC_PER_MSEC)/* 10ms  -- 
Sleep at least 10ms on each iteration */
 #define TSCHED_MIN_WAKEUP_USEC (4*PA_USEC_PER_MSEC)/* 4ms   -- 
Wakeup at least this long before the buffer runs empty*/
 
+#ifdef USE_SMOOTHER_2
+#define SMOOTHER_WINDOW_USEC  (15*PA_USEC_PER_SEC) /* 15s   -- 
smoother windows size */
+#else
 #define SMOOTHER_WINDOW_USEC  (10*PA_USEC_PER_SEC) /* 10s   -- 
smoother windows size */
 #define SMOOTHER_ADJUST_USEC  (1*PA_USEC_PER_SEC)  /* 1s-- 
smoother adjust time */
 
 #define SMOOTHER_MIN_INTERVAL (2*PA_USEC_PER_MSEC) /* 2ms   -- 
min smoother update interval */
 #define SMOOTHER_MAX_INTERVAL (200*PA_USEC_PER_MSEC)   /* 200ms -- 
max smoother update interval */
+#endif
 
 #define VOLUME_ACCURACY (PA_VOLUME_NORM/100)  /* don't require volume 
adjustments to be perfectly correct. don't necessarily extend granularity in 
software unless the differences get greater than this level */
 
@@ -144,11 +153,18 @@ struct userdata {
 
 pa_rtpoll_item *alsa_rtpoll_item;
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2 *smoother;
+#else
 pa_smoother *smoother;
+#endif
 uint64_t write_count;
 uint64_t since_start;
+
+#ifndef USE_SMOOTHER_2
 pa_usec_t smoother_interval;
 pa_usec_t last_smoother_update;
+#endif
 
 pa_idxset *formats;
 
@@ -870,7 +886,10 @@ static void update_smoother(struct userdata *u) {
 snd_pcm_sframes_t delay = 0;
 int64_t position;
 int err;
-pa_usec_t now1 = 0, now2;
+pa_usec_t now1 = 0;
+#ifndef USE_SMOOTHER_2
+pa_usec_t now2;
+#endif
 snd_pcm_status_t *status;
 snd_htimestamp_t htstamp = { 0, 0 };
 
@@ -893,13 +912,16 @@ static void update_smoother(struct userdata *u) {
 if (now1 <= 0)
 now1 = pa_rtclock_now();
 
+position = (int64_t) u->write_count - ((int64_t) delay * (int64_t) 
u->frame_size);
+
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_put(u->smoother, now1, position);
+#else
 /* check if the time since the last update is bigger than the interval */
 if (u->last_smoother_update > 0)
 if (u->last_smoother_update + u->smoother_interval > now1)
 return;
 
-position = (int64_t) u->write_count - ((int64_t) delay * (int64_t) 
u->frame_size);
-
 if (PA_UNLIKELY(position < 0))
 position = 0;
 
@@ -910,18 +932,26 @@ static void update_smoother(struct userdata *u) {
 u->last_smoother_update = now1;
 /* exponentially increase the update interval up to the MAX limit */
 u->smoother_interval = PA_MIN (u->smoother_interval * 2, 
SMOOTHER_MAX_INTERVAL);
+#endif
 }
 
 static int64_t sink_get_latency(struct userdata *u) {
 int64_t delay;
-pa_usec_t now1, now2;
+pa_usec_t now1;
+#ifndef USE_SMOOTHER_2
+pa_usec_t now2;
+#endif
 
 pa_assert(u);
 
 now1 = pa_rtclock_now();
+#ifdef USE_SMOOTHER_2
+delay = pa_smoother_2_get_delay(u->smoother, now1, u->write_count);
+#else
 now2 = pa_smoother_get(u->smoother, now1);
 
 delay = (int64_t) pa_bytes_to_usec(u->write_count, >sink->sample_spec) 
- (int64_t) now2;
+#endif
 
 if (u->memchunk.memblock)
 delay += pa_bytes_to_usec(u->memchunk.length, >sink->sample_spec);
@@ -947,7 +977,11 @@ static void suspend(struct userdata *u) {
 pa_assert(u);
 pa_assert(u->pcm_handle);
 
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_pause(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_pause(u->smoother, pa_rtclock_now());
+#endif
 
 /* Let's suspend -- we don't call snd_pcm_drain() here since that might
  * take awfully long with our long buffer sizes today. */
@@ -1145,9 +1179,13 @@ static int unsuspend(struct userdata *u) {
 goto fail;
 
 u->write_count = 0;
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_reset(u->smoother, pa_rtclock_now());
+#else
 pa_smoother_reset(u->smoother, pa_rtclock_now(), true);
 u->smoother_interval = SMOOTHER_MIN_INTERVAL;
 u->last_smoother_update = 0;
+#endif
 
 u->first = true;
 u->since_start = 0;
@@ -1693,6 +1731,9 @@ static int sink_reconfigure_cb(pa_sink *s, pa_sample_spec 
*spec, bool passthroug
 if (!PA_SINK_IS_OPENED(s->state)) {
 pa_log_info("Updating rate for device %s, new rate is %d", 
u->device_name, spec->rate);
 u->sink->sample_spec.rate = spec->rate;
+#ifdef USE_SMOOTHER_2
+pa_smoother_2_set_rate(u->smoother, 

[pulseaudio-discuss] [PATCH 01/10] pulsecore: Add alternative time smoother implementation

2018-04-09 Thread Georg Chini
This patch adds an alternative time smoother implementation based on the theory
found at 
https://www.freedesktop.org/software/pulseaudio/misc/rate_estimator.odt.

The functions were written to replace the current smoother functions nearly on
a one-to-one basis, though there are a few differences:
- The smoother_2_put() function takes a byte count instead of a sound card
  time as argument. This was changed because in most places a sample count
  was converted to a time before passing it to the smoother.
- The smoother needs to know sample rate and frame size to convert byte
  counts to time.
- A smoother_2_get_delay() function was added to directly retrieve the stream
  delay from the smoother.
- A hack for USB devices was added which works around an issue in the alsa
  latency reports for USB devices.

The smoother delivers much better precision than the current implementation.
For results, see the document referenced above.

The new functions are still unused. The following patches will convert all
callers of the smoother functions so that they can use both smoother
implementations, depending on a configure option.
---
 po/POTFILES.in  |   1 +
 src/Makefile.am |   1 +
 src/pulsecore/time-smoother_2.c | 408 
 src/pulsecore/time-smoother_2.h |  53 ++
 4 files changed, 463 insertions(+)
 create mode 100644 src/pulsecore/time-smoother_2.c
 create mode 100644 src/pulsecore/time-smoother_2.h

diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0b519464..1269435f 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -170,6 +170,7 @@ src/pulsecore/thread-mq.c
 src/pulsecore/thread-posix.c
 src/pulsecore/thread-win32.c
 src/pulsecore/time-smoother.c
+src/pulsecore/time-smoother_2.c
 src/pulsecore/tokenizer.c
 src/pulsecore/x11prop.c
 src/pulsecore/x11wrap.c
diff --git a/src/Makefile.am b/src/Makefile.am
index aba8e1f2..59b703db 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -733,6 +733,7 @@ libpulsecommon_@PA_MAJORMINOR@_la_SOURCES = \
pulsecore/svolume_mmx.c pulsecore/svolume_sse.c \
pulsecore/tagstruct.c pulsecore/tagstruct.h \
pulsecore/time-smoother.c pulsecore/time-smoother.h \
+   pulsecore/time-smoother_2.c pulsecore/time-smoother_2.h \
pulsecore/tokenizer.c pulsecore/tokenizer.h \
pulsecore/usergroup.c pulsecore/usergroup.h \
pulsecore/sndfile-util.c pulsecore/sndfile-util.h \
diff --git a/src/pulsecore/time-smoother_2.c b/src/pulsecore/time-smoother_2.c
new file mode 100644
index ..8f4447e0
--- /dev/null
+++ b/src/pulsecore/time-smoother_2.c
@@ -0,0 +1,408 @@
+/***
+  This file is part of PulseAudio.
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as
+  published by the Free Software Foundation; either version 2.1 of the
+  License, or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with PulseAudio; if not, see .
+***/
+
+/* The code in this file is based on the theoretical background found at
+ * https://www.freedesktop.org/software/pulseaudio/misc/rate_estimator.odt.
+ * The theory has never been reviewed, so it may be inaccurate in places. */
+
+#ifdef HAVE_CONFIG_H
+#include 
+#endif
+
+#include 
+#include 
+#include 
+#include 
+
+#include "time-smoother_2.h"
+
+struct pa_smoother_2 {
+/* Values set when the smoother is created */
+pa_usec_t smoother_window_time;
+uint32_t rate;
+uint32_t frame_size;
+
+/* USB hack parameters */
+bool usb_hack;
+bool enable_usb_hack;
+uint32_t hack_threshold;
+
+/* Smoother state */
+bool init;
+bool paused;
+
+/* Current byte count start value */
+double start_pos;
+/* System time corresponding to start_pos */
+pa_usec_t start_time;
+/* Conversion factor between time domains */
+double time_factor;
+
+/* Used if the smoother is paused while still in init state */
+pa_usec_t fixup_time;
+
+/* Time offset for USB devices */
+int64_t time_offset;
+
+/* Various time stamps */
+pa_usec_t resume_time;
+pa_usec_t pause_time;
+pa_usec_t smoother_start_time;
+pa_usec_t last_time;
+
+/* Variables used for Kalman filter */
+double time_variance;
+double time_factor_variance;
+double kalman_variance;
+
+/* Variables used for low pass filter */
+double drift_filter;
+double drift_filter_1;
+};
+
+/* Create new smoother */
+pa_smoother_2* pa_smoother_2_new(pa_usec_t window, pa_usec_t time_stamp, 
uint32_t frame_size, 

[pulseaudio-discuss] [PATCH 4/6] combine-sink: Use configured resampler, reduce update time to 1s

2018-04-09 Thread Georg Chini
Currently the combine-sink uses the trivial resampler by default.

This patch changes the default to the configured resampler.
Also the default update time is changed from 10s to 1s to achieve
faster convergence and higher precision.
---
 src/modules/module-combine-sink.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/modules/module-combine-sink.c 
b/src/modules/module-combine-sink.c
index 559f6575..79b4c6d9 100644
--- a/src/modules/module-combine-sink.c
+++ b/src/modules/module-combine-sink.c
@@ -64,7 +64,7 @@ PA_MODULE_USAGE(
 
 #define MEMBLOCKQ_MAXLENGTH (1024*1024*16)
 
-#define DEFAULT_ADJUST_TIME_USEC (10*PA_USEC_PER_SEC)
+#define DEFAULT_ADJUST_TIME_USEC (1*PA_USEC_PER_SEC)
 
 #define BLOCK_USEC (PA_USEC_PER_MSEC * 200)
 
@@ -1421,7 +1421,7 @@ int pa__init(pa_module*m) {
 struct userdata *u;
 pa_modargs *ma = NULL;
 const char *slaves, *rm;
-int resample_method = PA_RESAMPLER_TRIVIAL;
+int resample_method;
 pa_sample_spec ss;
 pa_channel_map map;
 struct output *o;
@@ -1437,6 +1437,7 @@ int pa__init(pa_module*m) {
 goto fail;
 }
 
+resample_method = m->core->resample_method;
 if ((rm = pa_modargs_get_value(ma, "resample_method", NULL))) {
 if ((resample_method = pa_parse_resample_method(rm)) < 0) {
 pa_log("invalid resample method '%s'", rm);
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 5/6] tunnel: Fix latency calculations

2018-04-09 Thread Georg Chini
Currently module-tunnel uses only a rough estimate of the current stream
latency and reports wrong latencies in certain situations. This leads to
very inexact and unstable latency reports for the virtual sink.

This patch fixes the issue by introducing latency snapshots like they
are used in module-loopback. Because the latency reports are now correct,
the update interval for latency re-calculations can be reduced to 1s.
---
 src/modules/module-tunnel.c | 48 +
 1 file changed, 36 insertions(+), 12 deletions(-)

diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
index a9f26ad7..c21c7847 100644
--- a/src/modules/module-tunnel.c
+++ b/src/modules/module-tunnel.c
@@ -121,7 +121,7 @@ static const char* const valid_modargs[] = {
 
 #define DEFAULT_TIMEOUT 5
 
-#define LATENCY_INTERVAL (10*PA_USEC_PER_SEC)
+#define LATENCY_INTERVAL (1*PA_USEC_PER_SEC)
 
 #define MIN_NETWORK_LATENCY_USEC (8*PA_USEC_PER_MSEC)
 
@@ -131,6 +131,7 @@ enum {
 SINK_MESSAGE_REQUEST = PA_SINK_MESSAGE_MAX,
 SINK_MESSAGE_REMOTE_SUSPEND,
 SINK_MESSAGE_UPDATE_LATENCY,
+SINK_MESSAGE_GET_LATENCY_SNAPSHOT,
 SINK_MESSAGE_POST
 };
 
@@ -142,7 +143,8 @@ enum {
 enum {
 SOURCE_MESSAGE_POST = PA_SOURCE_MESSAGE_MAX,
 SOURCE_MESSAGE_REMOTE_SUSPEND,
-SOURCE_MESSAGE_UPDATE_LATENCY
+SOURCE_MESSAGE_UPDATE_LATENCY,
+SOURCE_MESSAGE_GET_LATENCY_SNAPSHOT
 };
 
 #define DEFAULT_FRAGSIZE_MSEC 25
@@ -212,7 +214,9 @@ struct userdata {
 uint32_t device_index;
 uint32_t channel;
 
-int64_t counter, counter_delta;
+int64_t counter;
+uint64_t receive_counter;
+uint64_t receive_snapshot;
 
 bool remote_corked:1;
 bool remote_suspended:1;
@@ -517,6 +521,13 @@ static int sink_process_msg(pa_msgobject *o, int code, 
void *data, int64_t offse
 return 0;
 }
 
+case SINK_MESSAGE_GET_LATENCY_SNAPSHOT: {
+int64_t *send_counter = data;
+
+*send_counter = u->counter;
+return 0;
+}
+
 case SINK_MESSAGE_REQUEST:
 
 pa_assert(offset > 0);
@@ -559,7 +570,7 @@ static int sink_process_msg(pa_msgobject *o, int code, void 
*data, int64_t offse
 
 pa_pstream_send_memblock(u->pstream, u->channel, 0, 
PA_SEEK_RELATIVE, chunk);
 
-u->counter_delta += (int64_t) chunk->length;
+u->receive_counter += chunk->length;
 
 return 0;
 }
@@ -628,6 +639,13 @@ static int source_process_msg(pa_msgobject *o, int code, 
void *data, int64_t off
 return 0;
 }
 
+case SOURCE_MESSAGE_GET_LATENCY_SNAPSHOT: {
+int64_t *send_counter = data;
+
+*send_counter = u->counter;
+return 0;
+}
+
 case SOURCE_MESSAGE_POST: {
 pa_memchunk c;
 
@@ -779,6 +797,9 @@ static void stream_get_latency_callback(pa_pdispatch *pd, 
uint32_t command, uint
 struct timeval local, remote, now;
 pa_sample_spec *ss;
 int64_t delay;
+#ifdef TUNNEL_SINK
+uint64_t send_counter;
+#endif
 
 pa_assert(pd);
 pa_assert(u);
@@ -826,7 +847,7 @@ static void stream_get_latency_callback(pa_pdispatch *pd, 
uint32_t command, uint
 pa_gettimeofday();
 
 /* Calculate transport usec */
-if (pa_timeval_cmp(, ) < 0 && pa_timeval_cmp(, )) {
+if (pa_timeval_cmp(, ) < 0 && pa_timeval_cmp(, ) < 
0) {
 /* local and remote seem to have synchronized clocks */
 #ifdef TUNNEL_SINK
 u->transport_usec = pa_timeval_diff(, );
@@ -859,11 +880,12 @@ static void stream_get_latency_callback(pa_pdispatch *pd, 
uint32_t command, uint
 delay += (int64_t) u->transport_usec;
 #endif
 
-/* Now correct by what we have have read/written since we requested the 
update */
+/* Now correct by what we have have written since we requested the update. 
This
+ * is not necessary for the source, because if data is received between 
request
+ * and reply, it was already posted before we requested the source 
latency. */
 #ifdef TUNNEL_SINK
-delay += (int64_t) pa_bytes_to_usec((uint64_t) u->counter_delta, ss);
-#else
-delay -= (int64_t) pa_bytes_to_usec((uint64_t) u->counter_delta, ss);
+pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), 
SINK_MESSAGE_GET_LATENCY_SNAPSHOT, _counter, 0, NULL);
+delay += (int64_t) pa_bytes_to_usec(send_counter - u->receive_snapshot, 
ss);
 #endif
 
 #ifdef TUNNEL_SINK
@@ -901,7 +923,7 @@ static void request_latency(struct userdata *u) {
 pa_pdispatch_register_reply(u->pdispatch, tag, DEFAULT_TIMEOUT, 
stream_get_latency_callback, u, NULL);
 
 u->ignore_latency_before = tag;
-u->counter_delta = 0;
+u->receive_snapshot = u->receive_counter;
 }
 
 /* Called from main context */
@@ -1815,7 +1837,7 @@ static void pstream_memblock_callback(pa_pstream *p, 
uint32_t channel, int64_t o
 
 pa_asyncmsgq_send(u->source->asyncmsgq, PA_MSGOBJECT(u->source), 
SOURCE_MESSAGE_POST, 

[pulseaudio-discuss] [PATCH 6/6] tunnel: Make fixed latency configurable

2018-04-09 Thread Georg Chini
Currently, module-tunnel uses the default fixed latency of 250ms as fixed
latency.

There is no reason for such a large latency. This patch adds a parameter
latency_msec to the module to set the fixed latency at load time of the
module. The parameter can range from 5 to 500 milliseconds. With this
patch, I was able to run a tunnel sink at 7ms latency without problems.
---
 src/modules/module-tunnel.c | 27 +--
 1 file changed, 21 insertions(+), 6 deletions(-)

diff --git a/src/modules/module-tunnel.c b/src/modules/module-tunnel.c
index c21c7847..2068deca 100644
--- a/src/modules/module-tunnel.c
+++ b/src/modules/module-tunnel.c
@@ -79,6 +79,7 @@ PA_MODULE_USAGE(
 "format= "
 "channels= "
 "rate= "
+"latency_msec= "
 "channel_map=");
 #else
 PA_MODULE_DESCRIPTION("Tunnel module for sources");
@@ -92,6 +93,7 @@ PA_MODULE_USAGE(
 "format= "
 "channels= "
 "rate= "
+"latency_msec= "
 "channel_map=");
 #endif
 
@@ -106,6 +108,7 @@ static const char* const valid_modargs[] = {
 "format",
 "channels",
 "rate",
+"latency_msec",
 #ifdef TUNNEL_SINK
 "sink_name",
 "sink_properties",
@@ -135,8 +138,7 @@ enum {
 SINK_MESSAGE_POST
 };
 
-#define DEFAULT_TLENGTH_MSEC 150
-#define DEFAULT_MINREQ_MSEC 25
+#define DEFAULT_LATENCY_MSEC 100
 
 #else
 
@@ -147,7 +149,7 @@ enum {
 SOURCE_MESSAGE_GET_LATENCY_SNAPSHOT
 };
 
-#define DEFAULT_FRAGSIZE_MSEC 25
+#define DEFAULT_LATENCY_MSEC 25
 
 #endif
 
@@ -213,6 +215,7 @@ struct userdata {
 uint32_t ctag;
 uint32_t device_index;
 uint32_t channel;
+uint32_t latency;
 
 int64_t counter;
 uint64_t receive_counter;
@@ -1672,11 +1675,11 @@ static void setup_complete_callback(pa_pdispatch *pd, 
uint32_t command, uint32_t
 u->maxlength = 4*1024*1024;
 
 #ifdef TUNNEL_SINK
-u->tlength = (uint32_t) pa_usec_to_bytes(PA_USEC_PER_MSEC * 
DEFAULT_TLENGTH_MSEC, >sink->sample_spec);
-u->minreq = (uint32_t) pa_usec_to_bytes(PA_USEC_PER_MSEC * 
DEFAULT_MINREQ_MSEC, >sink->sample_spec);
+u->tlength = (uint32_t) pa_usec_to_bytes(PA_USEC_PER_MSEC * u->latency, 
>sink->sample_spec);
+u->minreq = (uint32_t) pa_usec_to_bytes(PA_USEC_PER_MSEC * u->latency / 4, 
>sink->sample_spec);
 u->prebuf = u->tlength;
 #else
-u->fragsize = (uint32_t) pa_usec_to_bytes(PA_USEC_PER_MSEC * 
DEFAULT_FRAGSIZE_MSEC, >source->sample_spec);
+u->fragsize = (uint32_t) pa_usec_to_bytes(PA_USEC_PER_MSEC * u->latency, 
>source->sample_spec);
 #endif
 
 #ifdef TUNNEL_SINK
@@ -1946,6 +1949,7 @@ int pa__init(pa_module*m) {
 pa_sample_spec ss;
 pa_channel_map map;
 char *dn = NULL;
+uint32_t latency_msec;
 #ifdef TUNNEL_SINK
 pa_sink_new_data data;
 #else
@@ -2009,6 +2013,15 @@ int pa__init(pa_module*m) {
 goto fail;
 }
 
+/* Allow latencies between 5ms and 500ms */
+latency_msec = DEFAULT_LATENCY_MSEC;
+if (pa_modargs_get_value_u32(ma, "latency_msec", _msec) < 0 || 
latency_msec < 5 || latency_msec > 500) {
+pa_log("Invalid latency specification");
+goto fail;
+}
+
+u->latency = latency_msec;
+
 cookie_path = pa_modargs_get_value(ma, "cookie", NULL);
 server = pa_xstrdup(pa_modargs_get_value(ma, "server", NULL));
 
@@ -2190,6 +2203,7 @@ int pa__init(pa_module*m) {
 
 pa_sink_set_asyncmsgq(u->sink, u->thread_mq.inq);
 pa_sink_set_rtpoll(u->sink, u->rtpoll);
+pa_sink_set_fixed_latency(u->sink, latency_msec * PA_USEC_PER_MSEC);
 
 #else
 
@@ -2230,6 +2244,7 @@ int pa__init(pa_module*m) {
 
 pa_source_set_asyncmsgq(u->source, u->thread_mq.inq);
 pa_source_set_rtpoll(u->source, u->rtpoll);
+pa_source_set_fixed_latency(u->source, latency_msec * PA_USEC_PER_MSEC);
 
 u->mcalign = pa_mcalign_new(pa_frame_size(>source->sample_spec));
 #endif
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 1/6] combine-sink: Fix latency calculations

2018-04-09 Thread Georg Chini
Currently module-combine-sink uses only a rough estimate of the current
slave sink latencies to calculate the rate for the various sink inputs.
This leads to very inexact and unstable latency reports for the virtual
sink.

This patch fixes the issue by introducing latency snapshots like they
are used in module-loopback. It also changes the definition of the
target latency to ensure that there is always one sink which uses the
base rate.
---
 src/modules/module-combine-sink.c | 176 +++---
 1 file changed, 145 insertions(+), 31 deletions(-)

diff --git a/src/modules/module-combine-sink.c 
b/src/modules/module-combine-sink.c
index f7649a36..75677fb0 100644
--- a/src/modules/module-combine-sink.c
+++ b/src/modules/module-combine-sink.c
@@ -115,6 +115,14 @@ struct output {
 
 /* For communication of the stream latencies to the main thread */
 pa_usec_t total_latency;
+struct {
+pa_usec_t timestamp;
+pa_usec_t sink_latency;
+size_t output_memblockq_size;
+uint64_t receive_counter;
+} latency_snapshot;
+
+uint64_t receive_counter;
 
 /* For communication of the stream parameters to the sink thread */
 pa_atomic_t max_request;
@@ -158,21 +166,33 @@ struct userdata {
 bool in_null_mode;
 pa_smoother *smoother;
 uint64_t counter;
+
+uint64_t snapshot_counter;
+pa_usec_t snapshot_time;
+
+pa_usec_t render_timestamp;
 } thread_info;
 };
 
+struct sink_snapshot {
+pa_usec_t timestamp;
+uint64_t send_counter;
+};
+
 enum {
 SINK_MESSAGE_ADD_OUTPUT = PA_SINK_MESSAGE_MAX,
 SINK_MESSAGE_REMOVE_OUTPUT,
 SINK_MESSAGE_NEED,
 SINK_MESSAGE_UPDATE_LATENCY,
 SINK_MESSAGE_UPDATE_MAX_REQUEST,
-SINK_MESSAGE_UPDATE_LATENCY_RANGE
+SINK_MESSAGE_UPDATE_LATENCY_RANGE,
+SINK_MESSAGE_GET_SNAPSHOT
 };
 
 enum {
 SINK_INPUT_MESSAGE_POST = PA_SINK_INPUT_MESSAGE_MAX,
-SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY
+SINK_INPUT_MESSAGE_SET_REQUESTED_LATENCY,
+SINK_INPUT_MESSAGE_LATENCY_SNAPSHOT
 };
 
 static void output_disable(struct output *o);
@@ -182,10 +202,16 @@ static int output_create_sink_input(struct output *o);
 
 static void adjust_rates(struct userdata *u) {
 struct output *o;
-pa_usec_t max_sink_latency = 0, min_total_latency = (pa_usec_t) -1, 
target_latency, avg_total_latency = 0;
+struct sink_snapshot rdata;
+pa_usec_t avg_total_latency = 0;
+pa_usec_t target_latency = 0;
+pa_usec_t max_sink_latency = 0;
+pa_usec_t min_total_latency = (pa_usec_t)-1;
 uint32_t base_rate;
 uint32_t idx;
 unsigned n = 0;
+pa_usec_t now;
+struct output *o_max;
 
 pa_assert(u);
 pa_sink_assert_ref(u->sink);
@@ -193,42 +219,82 @@ static void adjust_rates(struct userdata *u) {
 if (pa_idxset_size(u->outputs) <= 0)
 return;
 
-if (!PA_SINK_IS_OPENED(pa_sink_get_state(u->sink)))
+if (pa_sink_get_state(u->sink) != PA_SINK_RUNNING)
+return;
+
+/* Get sink snapshot */
+pa_asyncmsgq_send(u->sink->asyncmsgq, PA_MSGOBJECT(u->sink), 
SINK_MESSAGE_GET_SNAPSHOT, , 0, NULL);
+
+/* The sink snapshot time is the time when the last data was rendered.
+ * Latency is calculated for that point in time. */
+now = rdata.timestamp;
+
+/* Sink snapshot is not yet valid. */
+if (!now)
 return;
 
 PA_IDXSET_FOREACH(o, u->outputs, idx) {
-pa_usec_t sink_latency;
+pa_usec_t snapshot_latency;
+int64_t time_difference;
 
 if (!o->sink_input || !PA_SINK_IS_OPENED(pa_sink_get_state(o->sink)))
 continue;
 
-o->total_latency = pa_sink_input_get_latency(o->sink_input, 
_latency);
-o->total_latency += sink_latency;
-
-if (sink_latency > max_sink_latency)
-max_sink_latency = sink_latency;
-
-if (min_total_latency == (pa_usec_t) -1 || o->total_latency < 
min_total_latency)
+/* The difference may become negative, because it is probable, that 
the last
+ * render time was before the sink input snapshot. In this case, the 
sink
+ * had some more latency at the render time, so subtracting the value 
still
+ * gives the right result. */
+time_difference = (int64_t)now - 
(int64_t)o->latency_snapshot.timestamp;
+
+/* Latency at sink snapshot time is sink input snapshot latency minus 
time
+ * passed between the two snapshots. */
+snapshot_latency = o->latency_snapshot.sink_latency
+   + 
pa_bytes_to_usec(o->latency_snapshot.output_memblockq_size, 
>sink_input->sample_spec)
+   - time_difference;
+
+/* Add the data that was sent between taking the sink input snapshot
+ * and the sink snapshot. */
+snapshot_latency += pa_bytes_to_usec(rdata.send_counter - 
o->latency_snapshot.receive_counter, >sink_input->sample_spec);
+
+/* This is the current combined 

[pulseaudio-discuss] [PATCH 3/6] combine-sink: Improve initial latency reports

2018-04-09 Thread Georg Chini
Currently, it takes one adjust time before the smoother is updated after an
unsuspend. Before the first update, the smoother will not be aware of the
slave sink latencies, leading to incorrect latency reports.

This patch moves the first smoother update to one latency time after the
sink was unsuspended, thereby improving initial latency reports. This
only partially resolves the problem because the smoother takes multiple
updates to adapt to the slave sink latencies.
---
 src/modules/module-combine-sink.c | 10 +++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/modules/module-combine-sink.c 
b/src/modules/module-combine-sink.c
index 7c111246..559f6575 100644
--- a/src/modules/module-combine-sink.c
+++ b/src/modules/module-combine-sink.c
@@ -788,9 +788,6 @@ static void unsuspend(struct userdata *u) {
 PA_IDXSET_FOREACH(o, u->outputs, idx)
 output_enable(o);
 
-if (!u->time_event && u->adjust_time > 0)
-u->time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + 
u->adjust_time, time_callback, u);
-
 pa_log_info("Resumed successfully...");
 }
 
@@ -822,6 +819,13 @@ static int sink_set_state_in_main_thread_cb(pa_sink *sink, 
pa_sink_state_t state
 if (pa_sink_get_state(u->sink) == PA_SINK_SUSPENDED)
 unsuspend(u);
 
+/* The first smoother update should be done early, otherwise the 
smoother will
+ * not be aware of the slave sink latencies and report far too 
small values.
+ * This is especially important if after an unsuspend the sink 
runs on a different
+ * latency than before. */
+if (state == PA_SINK_RUNNING && !u->time_event && u->adjust_time > 
0)
+u->time_event = pa_core_rttime_new(u->core, pa_rtclock_now() + 
pa_sink_get_requested_latency(u->sink), time_callback, u);
+
 break;
 
 case PA_SINK_UNLINKED:
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH 0/6] combine-sink, tunnel-sink: Latency fixes

2018-04-09 Thread Georg Chini
This series fixes the latency calculations for module-tunnel-sink and
module-combine-sink. In some situations, both modules did not take into
account all components that contribute to the latency which lead to
jumps in the reported values.

Additionally, module-tunnel used a fixed latency of 250ms which seems
unnecessary high. The fixed latency can now be configured.

Default adjust times are updated to reflect the increased reliability
of the latency reports.

Georg Chini (6):
  combine-sink: Fix latency calculations
  combine-sink: Add rate controller
  combine-sink: Improve initial latency reports
  combine-sink: Use configured resampler, reduce update time to 1s
  tunnel: Fix latency calculations
  tunnel: Make fixed latency configurable

 src/modules/module-combine-sink.c | 242 ++
 src/modules/module-tunnel.c   |  75 +---
 2 files changed, 247 insertions(+), 70 deletions(-)

-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH] bluez5-device, bluez5-discover: add hsp_source_buffer_msec argument

2018-04-09 Thread Georg Chini
Currently the PA bluetooth code calls source_push() for each HSP source packet.
The default HSP MTU is 48 bytes, this means that source_push() is called every
3ms, which leads to increased CPU load especially on embedded systems.

This patch adds a hsp_source_buffer_msec argument to module-bluez5-discover and
module-bluez5-device. The argument gives the number of milliseconds of audio 
which
are buffered, before source_push() is called. The value can range from 0 to 
100ms,
and is rounded down to the next multiple of the MTU size. Therefore a value of 
less
than 2*MTU time corresponds to the original behavior.
---
 src/modules/bluetooth/module-bluetooth-discover.c |  1 +
 src/modules/bluetooth/module-bluez5-device.c  | 68 +--
 src/modules/bluetooth/module-bluez5-discover.c| 14 -
 3 files changed, 64 insertions(+), 19 deletions(-)

diff --git a/src/modules/bluetooth/module-bluetooth-discover.c 
b/src/modules/bluetooth/module-bluetooth-discover.c
index 63195d3e..14c0a38f 100644
--- a/src/modules/bluetooth/module-bluetooth-discover.c
+++ b/src/modules/bluetooth/module-bluetooth-discover.c
@@ -32,6 +32,7 @@ PA_MODULE_LOAD_ONCE(true);
 PA_MODULE_USAGE(
 "headset=ofono|native|auto (bluez 5 only)"
 "autodetect_mtu= (bluez 5 only)"
+"hsp_source_buffer_msec=<0 - 100> (bluez5 only)"
 );
 
 struct userdata {
diff --git a/src/modules/bluetooth/module-bluez5-device.c 
b/src/modules/bluetooth/module-bluez5-device.c
index b81c233c..d40bbb0c 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -54,7 +54,8 @@ PA_MODULE_DESCRIPTION("BlueZ 5 Bluetooth audio sink and 
source");
 PA_MODULE_VERSION(PACKAGE_VERSION);
 PA_MODULE_LOAD_ONCE(false);
 PA_MODULE_USAGE("path="
-"autodetect_mtu=");
+"autodetect_mtu="
+"hsp_source_buffer_msec=<0 - 100>");
 
 #define FIXED_LATENCY_PLAYBACK_A2DP (25 * PA_USEC_PER_MSEC)
 #define FIXED_LATENCY_PLAYBACK_SCO  (25 * PA_USEC_PER_MSEC)
@@ -68,6 +69,7 @@ PA_MODULE_USAGE("path="
 static const char* const valid_modargs[] = {
 "path",
 "autodetect_mtu",
+"hsp_source_buffer_msec",
 NULL
 };
 
@@ -123,6 +125,9 @@ struct userdata {
 pa_card *card;
 pa_sink *sink;
 pa_source *source;
+uint32_t source_buffer_usec;
+uint32_t source_buffered_blocks;
+pa_memchunk source_buffer;
 pa_bluetooth_profile_t profile;
 char *output_port_name;
 char *input_port_name;
@@ -314,10 +319,10 @@ static int sco_process_render(struct userdata *u) {
 /* Run from IO thread */
 static int sco_process_push(struct userdata *u) {
 ssize_t l;
-pa_memchunk memchunk;
 struct cmsghdr *cm;
 struct msghdr m;
 bool found_tstamp = false;
+uint32_t max_blocks;
 pa_usec_t tstamp = 0;
 
 pa_assert(u);
@@ -326,11 +331,17 @@ static int sco_process_push(struct userdata *u) {
 pa_assert(u->source);
 pa_assert(u->read_smoother);
 
-memchunk.memblock = pa_memblock_new(u->core->mempool, u->read_block_size);
-memchunk.index = memchunk.length = 0;
+max_blocks = u->source_buffer_usec / pa_bytes_to_usec(u->read_block_size, 
>source->sample_spec);
+if (max_blocks == 0)
+max_blocks = 1;
+
+if (!u->source_buffer.memblock) {
+u->source_buffer.memblock = pa_memblock_new(u->core->mempool, 
max_blocks * u->read_block_size);
+u->source_buffer.index = u->source_buffer.length = 0;
+}
 
 for (;;) {
-void *p;
+uint8_t *p;
 uint8_t aux[1024];
 struct iovec iov;
 
@@ -343,11 +354,11 @@ static int sco_process_push(struct userdata *u) {
 m.msg_control = aux;
 m.msg_controllen = sizeof(aux);
 
-p = pa_memblock_acquire(memchunk.memblock);
-iov.iov_base = p;
-iov.iov_len = pa_memblock_get_length(memchunk.memblock);
+p = pa_memblock_acquire(u->source_buffer.memblock);
+iov.iov_base = p + u->source_buffer.index;
+iov.iov_len = u->read_block_size;
 l = recvmsg(u->stream_fd, , 0);
-pa_memblock_release(memchunk.memblock);
+pa_memblock_release(u->source_buffer.memblock);
 
 if (l > 0)
 break;
@@ -356,8 +367,6 @@ static int sco_process_push(struct userdata *u) {
 /* Retry right away if we got interrupted */
 continue;
 
-pa_memblock_unref(memchunk.memblock);
-
 if (l < 0 && errno == EAGAIN)
 /* Hmm, apparently the socket was not readable, give up for now. */
 return 0;
@@ -366,7 +375,7 @@ static int sco_process_push(struct userdata *u) {
 return -1;
 }
 
-pa_assert((size_t) l <= pa_memblock_get_length(memchunk.memblock));
+pa_assert((size_t) l <= u->read_block_size);
 
 /* In some rare occasions, we might receive packets of a very strange
  * size. This could potentially be possible if the SCO packet was
@@ -376,11 +385,10 @@ static int sco_process_push(struct 

[pulseaudio-discuss] [PATCH] bluez5-device: Fix memory leak in sco_process_render()

2018-04-09 Thread Georg Chini
sco_process_render does not unref the memblock when it encounters an error.

This patch fixes the issue. It also changes the return value to 1 in the case
of EAGAIN. Because the data was already rendered and cannot be re-sent, we
have to discard the block.
---
 src/modules/bluetooth/module-bluez5-device.c | 12 +---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/modules/bluetooth/module-bluez5-device.c 
b/src/modules/bluetooth/module-bluez5-device.c
index 95d288ef..b81c233c 100644
--- a/src/modules/bluetooth/module-bluez5-device.c
+++ b/src/modules/bluetooth/module-bluez5-device.c
@@ -282,9 +282,13 @@ static int sco_process_render(struct userdata *u) {
 if (errno == EINTR)
 /* Retry right away if we got interrupted */
 continue;
-else if (errno == EAGAIN)
-/* Hmm, apparently the socket was not writable, give up for now */
-return 0;
+
+pa_memblock_unref(memchunk.memblock);
+
+if (errno == EAGAIN)
+/* Hmm, apparently the socket was not writable, give up for now.
+ * Because the data was already rendered, let's discard the block. 
*/
+return 1;
 
 pa_log_error("Failed to write data to SCO socket: %s", 
pa_cstrerror(errno));
 return -1;
@@ -296,6 +300,8 @@ static int sco_process_render(struct userdata *u) {
 pa_log_error("Wrote memory block to socket only partially! %llu 
written, wanted to write %llu.",
 (unsigned long long) l,
 (unsigned long long) memchunk.length);
+
+pa_memblock_unref(memchunk.memblock);
 return -1;
 }
 
-- 
2.14.1

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


[pulseaudio-discuss] [PATCH] null-source: fix multiple bugs

2018-04-09 Thread Georg Chini
The current null-source implementation has several bugs:

1) The latency reported is the negative of the correct latency.
2) The memchunk passed to pa_source_post() is not initialized
with silence.
3) In PA_SOURCE_MESSAGE_SET_STATE the timestamp is always set
when the source transitions to RUNNING state. This should only
happen when the source transitions from SUSPENDED to RUNNING
but also if it changes from SUSPENDED to IDLE.
4) The timing of the thread function is incorrect. It always
uses u->latency_time, regardless of the specified source
latency.
5) The latency_time argument seems pointless because the source
is defined with dynamic latency.

This patch fixes the issues by
1) inverting the sign of the reported latency,
2) initializing the memchunk with silence,
3) changing the logic in PA_SOURCE_MESSAGE_SET_STATE so that
the timestamp is set when needed,
4) using u->block_usec instead of u->latency_time for setting
the rtpoll timer and checking if the timer has elapsed,
5) removing the latency_time option.
---
 src/modules/module-null-source.c | 43 
 1 file changed, 22 insertions(+), 21 deletions(-)

diff --git a/src/modules/module-null-source.c b/src/modules/module-null-source.c
index 0e4c8d2f..e3aba5dc 100644
--- a/src/modules/module-null-source.c
+++ b/src/modules/module-null-source.c
@@ -51,12 +51,11 @@ PA_MODULE_USAGE(
 "rate= "
 "source_name= "
 "channel_map= "
-"description= "
-"latency_time=");
+"description= ");
 
 #define DEFAULT_SOURCE_NAME "source.null"
-#define DEFAULT_LATENCY_TIME 20
 #define MAX_LATENCY_USEC (PA_USEC_PER_SEC * 2)
+#define MIN_LATENCY_USEC (500)
 
 struct userdata {
 pa_core *core;
@@ -71,7 +70,6 @@ struct userdata {
 
 pa_usec_t block_usec;
 pa_usec_t timestamp;
-pa_usec_t latency_time;
 };
 
 static const char* const valid_modargs[] = {
@@ -81,7 +79,6 @@ static const char* const valid_modargs[] = {
 "source_name",
 "channel_map",
 "description",
-"latency_time",
 NULL
 };
 
@@ -93,7 +90,7 @@ static int source_process_msg(pa_msgobject *o, int code, void 
*data, int64_t off
 pa_usec_t now;
 
 now = pa_rtclock_now();
-*((int64_t*) data) = (int64_t)u->timestamp - (int64_t)now;
+*((int64_t*) data) = (int64_t)now - (int64_t)u->timestamp;
 
 return 0;
 }
@@ -109,8 +106,10 @@ static int source_set_state_in_io_thread_cb(pa_source *s, 
pa_source_state_t new_
 pa_assert(s);
 pa_assert_se(u = s->userdata);
 
-if (new_state == PA_SOURCE_RUNNING)
-u->timestamp = pa_rtclock_now();
+if (s->thread_info.state == PA_SOURCE_SUSPENDED || s->thread_info.state == 
PA_SOURCE_INIT) {
+if (PA_SOURCE_IS_OPENED(new_state))
+u->timestamp = pa_rtclock_now();
+}
 
 return 0;
 }
@@ -123,10 +122,14 @@ static void source_update_requested_latency_cb(pa_source 
*s) {
 pa_assert(u);
 
 u->block_usec = pa_source_get_requested_latency_within_thread(s);
+if (u->block_usec == (pa_usec_t)-1)
+u->block_usec = u->source->thread_info.max_latency;
 }
 
 static void thread_func(void *userdata) {
 struct userdata *u = userdata;
+bool timer_elapsed = false;
+size_t max_block_size;
 
 pa_assert(u);
 
@@ -134,6 +137,7 @@ static void thread_func(void *userdata) {
 
 pa_thread_mq_install(>thread_mq);
 
+max_block_size = 
pa_frame_align(pa_mempool_block_size_max(u->core->mempool), 
>source->sample_spec);
 u->timestamp = pa_rtclock_now();
 
 for (;;) {
@@ -146,17 +150,20 @@ static void thread_func(void *userdata) {
 
 now = pa_rtclock_now();
 
-if ((chunk.length = pa_usec_to_bytes(now - u->timestamp, 
>source->sample_spec)) > 0) {
+if (timer_elapsed && (chunk.length = pa_usec_to_bytes(now - 
u->timestamp, >source->sample_spec)) > 0) {
+
+chunk.length = PA_MIN(max_block_size, chunk.length);
 
-chunk.memblock = pa_memblock_new(u->core->mempool, (size_t) 
-1); /* or chunk.length? */
+chunk.memblock = pa_memblock_new(u->core->mempool, 
chunk.length);
 chunk.index = 0;
+pa_silence_memchunk(, >source->sample_spec);
 pa_source_post(u->source, );
 pa_memblock_unref(chunk.memblock);
 
-u->timestamp = now;
+u->timestamp += pa_bytes_to_usec(chunk.length, 
>source->sample_spec);
 }
 
-pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp + 
u->latency_time * PA_USEC_PER_MSEC);
+pa_rtpoll_set_timer_absolute(u->rtpoll, u->timestamp + 
u->block_usec);
 } else
 pa_rtpoll_set_timer_disabled(u->rtpoll);
 
@@ -164,6 +171,8 @@ static void thread_func(void *userdata) {
 if ((ret = pa_rtpoll_run(u->rtpoll)) < 0)
 goto fail;
 
+timer_elapsed = pa_rtpoll_timer_elapsed(u->rtpoll);
+
 if 

Re: [pulseaudio-discuss] [PATCH] man: unify pactl and pacmd suspend command documentation

2018-04-09 Thread Georg Chini

On 08.04.2018 12:23, Tanu Kaskinen wrote:

The suspend-sink and suspend-source documentation for pacmd was quite
terse, so I copied the more complete documentation from pactl. I
couldn't resist doing some other minor edits along the way too.

Bug-link: https://bugs.freedesktop.org/show_bug.cgi?id=105907
---
  man/pactl.1.xml.in| 32 
  man/pulse-cli-syntax.5.xml.in | 15 ---
  2 files changed, 28 insertions(+), 19 deletions(-)

diff --git a/man/pactl.1.xml.in b/man/pactl.1.xml.in
index 39569b6bb..ee8fd51ea 100644
--- a/man/pactl.1.xml.in
+++ b/man/pactl.1.xml.in
@@ -129,27 +129,27 @@ License along with PulseAudio; if not, see 
.
  
  
  

-  suspend-sink SINK 1|0
+  suspend-sink SINK true|false
Suspend or resume the specified sink (which may be
-  specified either by its symbolic name, or by its numeric index),
-  depending whether 1 (suspend) or 0 (resume) is passed as last
-  argument. Suspending a sink will pause all playback. Depending
-  on the module implementing the sink this might have the effect
-  that the underlying device is closed, making it available for
-  other applications to use. The exact behaviour depends on the
-  module.
+specified either by its name or index), depending whether true
+(suspend) or false (resume) is passed as last argument. Suspending
+a sink will pause all playback. Depending on the module implementing
+the sink this might have the effect that the underlying device is
+closed, making it available for other applications to use. The exact
+behaviour depends on the module.
+  
  
  
  

-  suspend-source SOURCE 1|0
+  suspend-source SOURCE true|false
Suspend or resume the specified source (which may be
-  specified either by its symbolic name, or by its numeric index),
-  depending whether 1 (suspend) or 0 (resume) is passed as last
-  argument. Suspending a source will pause all
-  capturing. Depending on the module implementing the source this
-  might have the effect that the underlying device is closed,
-  making it available for other applications to use. The exact
-  behaviour depends on the module.
+specified either by its name or index), depending whether true
+(suspend) or false (resume) is passed as last argument. Suspending
+a source will pause all capturing. Depending on the module implementing
+the source this might have the effect that the underlying device is
+closed, making it available for other applications to use. The exact
+behaviour depends on the module.
+  
  
  
  

diff --git a/man/pulse-cli-syntax.5.xml.in b/man/pulse-cli-syntax.5.xml.in
index 0a0fabaf0..42cd73a63 100644
--- a/man/pulse-cli-syntax.5.xml.in
+++ b/man/pulse-cli-syntax.5.xml.in
@@ -163,9 +163,18 @@ License along with PulseAudio; if not, see 
.
  
  
  

-  suspend-sink|suspend-source index|name 
boolean
-  Suspend (i.e. disconnect from the underlying hardware) a sink
-  (resp. source).
+  suspend-sink|suspend-source name|index
+true|false
+  
+  Suspend or resume the specified sink or source (which may be
+specified either by its name or index), depending whether true
+(suspend) or false (resume) is passed as last argument. Suspending
+a sink will pause all playback and suspending a source will pause all
+capturing. Depending on the module implementing the sink or source this
+might have the effect that the underlying device is closed, making it
+available for other applications to use. The exact behaviour depends on
+the module.
+  
  
  
  


LGTM

___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


Re: [pulseaudio-discuss] Updating the WebRTC AudioProcessing library

2018-04-09 Thread Tomaž Šolc

Dear Tanu,

thanks for your comments. My answers to individual questions are in-line
below.

In general I think there's a question of how heavy a wrapper the
"module-echo-cancellation" is supposed to be around the webrtc code. The
upstream is very actively reworking large parts of the code. Most
likely closely following the changes and ensuring backwards
compatibility in PulseAudio, even at the level of module arguments, 
isn't feasible in the long term.


On 01. 04. 2018 12:42, Tanu Kaskinen wrote:

You removed the "trace" module argument, which can break previously-
 working configuration files. Since you added the logging support 
back in a different way, the "trace" module argument doesn't really 
need to be removed, since it could still control whether the logging 
should be enabled or not.


The whole "trace" functionality has been removed upstream. I think
retaining that parameter isn't useful and using it for something else is 
confusing. As far as I can see, its existence wasn't even documented 
anywhere.


The previous logging code had separation for different log levels, 
now only pa_log() is used (which is an alias for pa_log_error()). 
Does webrtc not provide any more log level separation? The logging

is setup with this call:

rtc::LogMessage::AddLogToStream(logsink, rtc::LS_INFO);

Is LS_INFO the webrtc log level? If so, then it looks like all other 
log levels than "info" are discarded.


Unfortunately, the only way webrtc provides for plugging into its 
logging system is via the generic LogSink class, which doesn't receive 
the numeric log level. You can only set the minimum level for the 
messages that it receives. So at best, all webrtc log levels must be 
squashed into a single pa_log level (I've changed that to pa_log_info() now)


I've now added a "log_level" argument that allows you to set the minimum 
webrtc level that is logged by PA:


https://github.com/avian2/pulseaudio/commit/d97eb2122e9e5f1b251e97661b1b241b959c7b7c

Anyway, upstream webrtc logging doesn't seem very useful at the moment. 
Even at highest verbosity only a few messages are generated on module 
load (which is why I originally didn't bother too much with it).


I: webrtc: (audio_processing_impl.cc:441): Capture post processor 
activated: 0

I: Render pre processor activated: 0
I: webrtc: (audio_processing_impl.cc:704): Highpass filter activated: 0
I: webrtc: (audio_processing_impl.cc:717): Gain Controller 2 activated: 0

You removed DEFAULT_HIGH_PASS_FILTER, which I think is not a good 
change. It's good to be able to control the defaults in pulseaudio.


I removed this default because the new webrtc configuration class seems 
to define some sensible defaults itself. Individual components no longer 
need to be explicitly enabled/disabled like before.


I think upstream has a much better knowledge of which defaults make 
sense. For example, high pass filter is disabled by default in the 
current upstream (while DEFAULT_HIGH_PASS_FILTER was set to true).


The version in AC_INIT in configure.ac needs to be incremented (to 
0.4, I presume).


Regarding Debian dependency handling: The original binary package 
name was libwebrtc-audio-processing0 (note the 0 at the end).

Version 0.3 broke API and ABI compatibility, which is why the package
name was changed to libwebrtc-audio-processing1, so in fact Debian
does depend on a particular version. Since it looks like
compatibility will be broken again, the Debian package name will
change to libwebrtc-audio- processing2.


That makes sense. Thanks for the suggestion.

Best regards
Tomaž
___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


Re: [pulseaudio-discuss] [PATCH v10 2/3] pipe-source: respect frame boundaries

2018-04-09 Thread Georg Chini

On 20.03.2018 13:27, Raman Shyshniou wrote:

Currently pipe-source doesn't track frame boundaries while posting
data. This can lead to assertion failure in source-output for any
sample specs except s8/u8 mono. The simplest way to reproduce is
to write s24le format to pipe:
$ pacat -r -d null --format=s24le > /tmp/music.input

E: [pipe-source] source-output.c: Assertion
'pa_frame_aligned(chunk->length, >source->sample_spec)' failed at
pulsecore/source-output.c:738, function pa_source_output_push().
---
  src/modules/module-pipe-source.c | 52 
  1 file changed, 47 insertions(+), 5 deletions(-)

diff --git a/src/modules/module-pipe-source.c b/src/modules/module-pipe-source.c
index c069dd4..4fd515b 100644
--- a/src/modules/module-pipe-source.c
+++ b/src/modules/module-pipe-source.c
@@ -202,6 +202,7 @@ static void thread_func(void *userdata) {
  struct userdata *u = userdata;
  struct pollfd *pollfd;
  int read_type = 0;
+size_t fs;
  
  pa_assert(u);
  
@@ -209,6 +210,8 @@ static void thread_func(void *userdata) {
  
  pa_thread_mq_install(>thread_mq);
  
+fs = pa_frame_size(>source->sample_spec);

+
  u->memchunk.memblock = pa_memblock_new(u->core->mempool, u->pipe_size);
  u->timestamp = pa_rtclock_now();
  
@@ -229,7 +232,7 @@ static void thread_func(void *userdata) {

  pa_assert(u->memchunk.length == 0);
  
  p = pa_memblock_acquire(u->memchunk.memblock);

-l = pa_read(u->fd, p, pa_memblock_get_length(u->memchunk.memblock), 
_type);
+l = pa_read(u->fd, (uint8_t*) p + u->memchunk.index, 
pa_memblock_get_length(u->memchunk.memblock) - u->memchunk.index, _type);
  pa_memblock_release(u->memchunk.memblock);
  
  if (l < 0) {

@@ -252,6 +255,7 @@ static void thread_func(void *userdata) {
  }
  
  u->writer_connected = false;

+u->memchunk.index = 0;
  }
  
  } else {

@@ -269,21 +273,59 @@ static void thread_func(void *userdata) {
  
  /* Post data to source */

  if (PA_SOURCE_IS_OPENED(u->source->thread_info.state)) {
+size_t total_len = u->memchunk.index + u->memchunk.length;
+size_t frames = total_len / fs;
+size_t tail = total_len % fs;
  
  if (u->writer_connected && u->corkfd >= 0) {

  pa_assert_se(pa_close(u->corkfd) == 0);
  u->corkfd = -1;
  }
  
-if (u->memchunk.length > 0) {

+if (frames > 0) {
+pa_memblock *new_memblock;
+
+new_memblock = pa_memblock_new(u->core->mempool, u->pipe_size);
+
+u->memchunk.index = 0;
+u->memchunk.length = frames * fs;
+
+if (tail > 0) {
+void *src, *dst;
+
+dst = pa_memblock_acquire(new_memblock);
+src = pa_memblock_acquire(u->memchunk.memblock);
+
+memcpy(dst, (uint8_t *) src + u->memchunk.length, tail);
+
+pa_memblock_release(u->memchunk.memblock);
+pa_memblock_release(new_memblock);
+}
+
  pa_source_post(u->source, >memchunk);
  pa_memblock_unref(u->memchunk.memblock);
-u->memchunk.memblock = pa_memblock_new(u->core->mempool, 
u->pipe_size);
-u->memchunk.length = 0;
+u->memchunk.memblock = new_memblock;
+}
+
+u->memchunk.index = tail;
+u->memchunk.length = 0;
+
+} else if (u->suspended_by_user) {
+size_t total_len = u->memchunk.index + u->memchunk.length;
+size_t frames = total_len / fs;
+size_t tail = total_len % fs;


You can move the three lines above outside the if-block because you
are doing the same thing in both branches.


+
+if (frames > 0 && tail > 0) {
+void *p;
+
+p = pa_memblock_acquire(u->memchunk.memblock);
+memmove(p, (uint8_t *) p + frames * fs, tail);
+pa_memblock_release(u->memchunk.memblock);
  }
  
-} else if (u->suspended_by_user)

+u->memchunk.index = tail;
  u->memchunk.length = 0;
+}
  
  pollfd->events = u->memchunk.length ? 0 : POLLIN;
  



___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss


Re: [pulseaudio-discuss] Starting pulseaudio on embedded as root

2018-04-09 Thread Tomaž Šolc

Hi

On 01. 04. 2018 13:22, Sherif Omran wrote:
i created a thin linux image using yocto and i have pulseaudio in the 
system being installed. But when i start it manually, i get
pulse is not intented to start as root. I tried to enable the spawn but 
even it has not been solved. I have systemv, does it cause a problem?


Search for "pulseaudio system mode". This page is a good start:

https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/SystemWide/

I got PulseAudio running on an embedded system using system mode 
generally following the instructions on that page. It works, but it will 
complain a lot in the logs about running as root.


You also need to disable all the different ways PA gets automatically 
spawned in user mode (at least under systemd that is quite a pain: you 
have the autospawn feature - /etc/pulse/client.conf and 
/etc/pulse/client.conf.d/00-disable-autospawn.conf, the systemd per-user 
units and possibly some other ways I haven't figured out - I still get a 
spurious non-system mode PA daemon spawned sometimes).


You also (usually) need to have PULSE_RUNTIME_PATH=/var/run/pulse 
environment set for clients.


Best regards
Tomaž
___
pulseaudio-discuss mailing list
pulseaudio-discuss@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/pulseaudio-discuss