[pulseaudio-commits] 16 commits - configure.ac src/.gitignore src/Makefile.am src/modules src/pulse src/pulsecore src/tests

Arun Raghavan arun at kemper.freedesktop.org
Wed Jun 22 15:35:16 UTC 2016


 configure.ac                      |    4 
 src/.gitignore                    |    1 
 src/Makefile.am                   |   15 
 src/modules/alsa/alsa-mixer.c     |    1 
 src/modules/alsa/alsa-ucm.c       |   37 --
 src/modules/alsa/alsa-ucm.h       |    1 
 src/modules/echo-cancel/webrtc.cc |    4 
 src/pulse/format.c                |  226 ++++++-------
 src/pulse/json.c                  |  614 ++++++++++++++++++++++++++++++++++++++
 src/pulse/json.h                  |   53 +++
 src/pulsecore/device-port.c       |    7 
 src/pulsecore/device-port.h       |    3 
 src/pulsecore/modargs.c           |   54 ++-
 src/pulsecore/modargs.h           |    2 
 src/pulsecore/pstream.c           |   31 +
 src/pulsecore/sink.c              |   28 -
 src/pulsecore/sink.h              |   10 
 src/pulsecore/source.c            |   28 -
 src/pulsecore/source.h            |   10 
 src/tests/json-test.c             |  280 +++++++++++++++++
 20 files changed, 1199 insertions(+), 210 deletions(-)

New commits:
commit 87f437d0ddbf16312130f72385c901b28ba980b6
Author: Ahmed S. Darwish <darwish.07 at gmail.com>
Date:   Thu Jun 16 10:27:37 2016 +0200

    pstream: Add rationale for pa_cmsg_ancil_data_close_fds()
    
    Signed-off-by: Ahmed S. Darwish <darwish.07 at gmail.com>

diff --git a/src/pulsecore/pstream.c b/src/pulsecore/pstream.c
index 2c9e452..3b94a3a 100644
--- a/src/pulsecore/pstream.c
+++ b/src/pulsecore/pstream.c
@@ -186,9 +186,34 @@ struct pa_pstream {
 };
 
 #ifdef HAVE_CREDS
-/* Don't close the ancillary fds by your own! Always call this method;
- * it guarantees necessary cleanups after fds close.. This method is
- * also multiple-invocations safe. */
+/*
+ * memfd-backed SHM pools blocks transfer occur without passing the pool's
+ * fd every time, thus minimizing overhead and avoiding fd leaks. A
+ * REGISTER_MEMFD_SHMID command is sent, with the pool's memfd fd, very early
+ * on. This command has an ID that uniquely identifies the pool in question.
+ * Further pool's block references can then be exclusively done using such ID;
+ * the fd can be safely closed – on both ends – afterwards.
+ *
+ * On the sending side of this command, we want to close the passed fds
+ * directly after being sent. Meanwhile we're only allowed to asynchronously
+ * schedule packet writes to the pstream, so the job of closing passed fds is
+ * left to the pstream's actual writing function do_write(): it knows the
+ * exact point in time where the fds are passed to the other end through
+ * iochannels and the sendmsg() system call.
+ *
+ * Nonetheless not all code paths in the system desire their socket-passed
+ * fds to be closed after the send. srbchannel needs the passed fds to still
+ * be open for further communication. System-wide global memfd-backed pools
+ * also require the passed fd to be open: they pass the same fd, with the same
+ * ID registration mechanism, for each newly connected client to the system.
+ *
+ * So from all of the above, never close the ancillary fds by your own and
+ * always call below method instead. It takes care of closing the passed fds
+ * _only if allowed_ by the code paths that originally created them to do so.
+ * Moreover, it is multiple-invocations safe: failure handlers can, and
+ * should, call it for passed fds cleanup without worrying too much about
+ * the system state.
+ */
 void pa_cmsg_ancil_data_close_fds(struct pa_cmsg_ancil_data *ancil) {
     if (ancil && ancil->nfd > 0 && ancil->close_fds_on_cleanup) {
         int i;

commit 06fbdcaa3ecdd733b52e66976ec39893d1e53fe0
Author: Arun Raghavan <git at arunraghavan.net>
Date:   Tue May 3 18:26:51 2016 +0530

    modargs: Add a mechanism to append modargs
    
    This allows us to parse an extra set of modargs to tack on to an
    existing set. Duplicates in the second set are ignored, since this fits
    our use best. In the future, this could be extended to support different
    merge modes (ignore dupes vs. replace with dupes), but I've left this
    out since there isn't a clear need and it would be dead code for now.

diff --git a/src/pulsecore/modargs.c b/src/pulsecore/modargs.c
index b3c0313..bce5891 100644
--- a/src/pulsecore/modargs.c
+++ b/src/pulsecore/modargs.c
@@ -43,7 +43,7 @@ struct entry {
     char *key, *value;
 };
 
-static int add_key_value(pa_modargs *ma, char *key, char *value, const char* const valid_keys[]) {
+static int add_key_value(pa_modargs *ma, char *key, char *value, const char* const valid_keys[], bool ignore_dupes) {
     struct entry *e;
     char *raw;
 
@@ -56,7 +56,11 @@ static int add_key_value(pa_modargs *ma, char *key, char *value, const char* con
     if (pa_hashmap_get(ma->unescaped, key)) {
         pa_xfree(key);
         pa_xfree(value);
-        return -1;
+
+        if (ignore_dupes)
+            return 0;
+        else
+            return -1;
     }
 
     if (valid_keys) {
@@ -100,7 +104,7 @@ static void free_func(void *p) {
     pa_xfree(e);
 }
 
-pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
+static int parse(pa_modargs *ma, const char *args, const char* const* valid_keys, bool ignore_dupes) {
     enum {
         WHITESPACE,
         KEY,
@@ -115,13 +119,6 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
 
     const char *p, *key = NULL, *value = NULL;
     size_t key_len = 0, value_len = 0;
-    pa_modargs *ma = pa_xnew(pa_modargs, 1);
-
-    ma->raw = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, free_func);
-    ma->unescaped = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, free_func);
-
-    if (!args)
-        return ma;
 
     state = WHITESPACE;
 
@@ -160,7 +157,8 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
                     if (add_key_value(ma,
                                       pa_xstrndup(key, key_len),
                                       pa_xstrdup(""),
-                                      valid_keys) < 0)
+                                      valid_keys,
+                                      ignore_dupes) < 0)
                         goto fail;
                     state = WHITESPACE;
                 } else if (*p == '\\') {
@@ -179,7 +177,8 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
                     if (add_key_value(ma,
                                       pa_xstrndup(key, key_len),
                                       pa_xstrndup(value, value_len),
-                                      valid_keys) < 0)
+                                      valid_keys,
+                                      ignore_dupes) < 0)
                         goto fail;
                     state = WHITESPACE;
                 } else if (*p == '\\') {
@@ -199,7 +198,8 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
                     if (add_key_value(ma,
                                       pa_xstrndup(key, key_len),
                                       pa_xstrndup(value, value_len),
-                                      valid_keys) < 0)
+                                      valid_keys,
+                                      ignore_dupes) < 0)
                         goto fail;
                     state = WHITESPACE;
                 } else if (*p == '\\') {
@@ -219,7 +219,8 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
                     if (add_key_value(ma,
                                       pa_xstrndup(key, key_len),
                                       pa_xstrndup(value, value_len),
-                                      valid_keys) < 0)
+                                      valid_keys,
+                                      ignore_dupes) < 0)
                         goto fail;
                     state = WHITESPACE;
                 } else if (*p == '\\') {
@@ -237,23 +238,40 @@ pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
     }
 
     if (state == VALUE_START) {
-        if (add_key_value(ma, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys) < 0)
+        if (add_key_value(ma, pa_xstrndup(key, key_len), pa_xstrdup(""), valid_keys, ignore_dupes) < 0)
             goto fail;
     } else if (state == VALUE_SIMPLE) {
-        if (add_key_value(ma, pa_xstrndup(key, key_len), pa_xstrdup(value), valid_keys) < 0)
+        if (add_key_value(ma, pa_xstrndup(key, key_len), pa_xstrdup(value), valid_keys, ignore_dupes) < 0)
             goto fail;
     } else if (state != WHITESPACE)
         goto fail;
 
-    return ma;
+    return 0;
 
 fail:
+    return -1;
+}
 
-    pa_modargs_free(ma);
+pa_modargs *pa_modargs_new(const char *args, const char* const* valid_keys) {
+    pa_modargs *ma = pa_xnew(pa_modargs, 1);
+
+    ma->raw = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, free_func);
+    ma->unescaped = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func, NULL, free_func);
+
+    if (args && parse(ma, args, valid_keys, false) < 0)
+        goto fail;
 
+    return ma;
+
+fail:
+    pa_modargs_free(ma);
     return NULL;
 }
 
+int pa_modargs_append(pa_modargs *ma, const char *args, const char* const* valid_keys) {
+    return parse(ma, args, valid_keys, true);
+}
+
 void pa_modargs_free(pa_modargs*ma) {
     pa_assert(ma);
 
diff --git a/src/pulsecore/modargs.h b/src/pulsecore/modargs.h
index 776a1e6..50cf6c5 100644
--- a/src/pulsecore/modargs.h
+++ b/src/pulsecore/modargs.h
@@ -34,6 +34,8 @@ typedef struct pa_modargs pa_modargs;
 
 /* Parse the string args. The NULL-terminated array keys contains all valid arguments. */
 pa_modargs *pa_modargs_new(const char *args, const char* const keys[]);
+/* Parse the string args, and add any keys that are not already present. */
+int pa_modargs_append(pa_modargs *ma, const char *args, const char* const* valid_keys);
 void pa_modargs_free(pa_modargs*ma);
 
 /* Return the module argument for the specified name as a string. If

commit b793f68f2edf2a4836ea907a0c058997d41ee67a
Author: Arun Raghavan <git at arunraghavan.net>
Date:   Tue May 3 18:22:09 2016 +0530

    alsa: Use pa_device_port->impl_free() for freeing port data
    
    This allows us to clean up ucm port data associated with a port during
    port clean up, instead of having to track this separately using a
    dynarray.

diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index 8079147..3dbf6b1 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -4754,6 +4754,7 @@ static pa_device_port* device_port_alsa_init(pa_hashmap *ports, /* card ports */
         pa_proplist_update(p->proplist, PA_UPDATE_REPLACE, path->proplist);
 
         data = PA_DEVICE_PORT_DATA(p);
+        /* Ownership of the path and setting is not transferred to the port data, so we don't deal with freeing them */
         data->path = path;
         data->setting = setting;
         path->port = p;
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index 42f3242..b42c040 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -90,9 +90,9 @@ struct ucm_port {
     pa_dynarray *devices; /* pa_alsa_ucm_device */
 };
 
-static struct ucm_port *ucm_port_new(pa_alsa_ucm_config *ucm, pa_device_port *core_port, pa_alsa_ucm_device **devices,
-                                     unsigned n_devices);
-static void ucm_port_free(struct ucm_port *port);
+static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
+                          pa_alsa_ucm_device **devices, unsigned n_devices);
+static void ucm_port_free(pa_device_port *port);
 static void ucm_port_update_available(struct ucm_port *port);
 
 static struct ucm_items item[] = {
@@ -576,8 +576,6 @@ int pa_alsa_ucm_query_profiles(pa_alsa_ucm_config *ucm, int card_index) {
     const char **verb_list;
     int num_verbs, i, err = 0;
 
-    ucm->ports = pa_dynarray_new((pa_free_cb_t) ucm_port_free);
-
     /* is UCM available for this card ? */
     err = snd_card_get_name(card_index, &card_name);
     if (err < 0) {
@@ -769,12 +767,12 @@ static void ucm_add_port_combination(
         pa_device_port_new_data_set_description(&port_data, desc);
         pa_device_port_new_data_set_direction(&port_data, is_sink ? PA_DIRECTION_OUTPUT : PA_DIRECTION_INPUT);
 
-        port = pa_device_port_new(core, &port_data, sizeof(struct ucm_port *));
+        port = pa_device_port_new(core, &port_data, sizeof(struct ucm_port));
+        port->impl_free = ucm_port_free;
         pa_device_port_new_data_done(&port_data);
 
-        ucm_port = ucm_port_new(context->ucm, port, pdevices, num);
-        pa_dynarray_append(context->ucm->ports, ucm_port);
-        *((struct ucm_port **) PA_DEVICE_PORT_DATA(port)) = ucm_port;
+        ucm_port = PA_DEVICE_PORT_DATA(port);
+        ucm_port_init(ucm_port, context->ucm, port, pdevices, num);
 
         pa_hashmap_put(ports, port->name, port);
         pa_log_debug("Add port %s: %s", port->name, port->description);
@@ -1681,9 +1679,6 @@ void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
     pa_alsa_ucm_verb *vi, *vn;
     pa_alsa_jack *ji, *jn;
 
-    if (ucm->ports)
-        pa_dynarray_free(ucm->ports);
-
     PA_LLIST_FOREACH_SAFE(vi, vn, ucm->verbs) {
         PA_LLIST_REMOVE(pa_alsa_ucm_verb, ucm->verbs, vi);
         free_verb(vi);
@@ -1838,16 +1833,14 @@ void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) {
     device_set_available(device, available);
 }
 
-static struct ucm_port *ucm_port_new(pa_alsa_ucm_config *ucm, pa_device_port *core_port, pa_alsa_ucm_device **devices,
-                                     unsigned n_devices) {
-    struct ucm_port *port;
+static void ucm_port_init(struct ucm_port *port, pa_alsa_ucm_config *ucm, pa_device_port *core_port,
+                          pa_alsa_ucm_device **devices, unsigned n_devices) {
     unsigned i;
 
     pa_assert(ucm);
     pa_assert(core_port);
     pa_assert(devices);
 
-    port = pa_xnew0(struct ucm_port, 1);
     port->ucm = ucm;
     port->core_port = core_port;
     port->devices = pa_dynarray_new(NULL);
@@ -1858,17 +1851,17 @@ static struct ucm_port *ucm_port_new(pa_alsa_ucm_config *ucm, pa_device_port *co
     }
 
     ucm_port_update_available(port);
-
-    return port;
 }
 
-static void ucm_port_free(struct ucm_port *port) {
+static void ucm_port_free(pa_device_port *port) {
+    struct ucm_port *ucm_port;
+
     pa_assert(port);
 
-    if (port->devices)
-        pa_dynarray_free(port->devices);
+    ucm_port = PA_DEVICE_PORT_DATA(port);
 
-    pa_xfree(port);
+    if (ucm_port->devices)
+        pa_dynarray_free(ucm_port->devices);
 }
 
 static void ucm_port_update_available(struct ucm_port *port) {
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
index 0930303..53abf3f 100644
--- a/src/modules/alsa/alsa-ucm.h
+++ b/src/modules/alsa/alsa-ucm.h
@@ -196,7 +196,6 @@ struct pa_alsa_ucm_config {
 
     PA_LLIST_HEAD(pa_alsa_ucm_verb, verbs);
     PA_LLIST_HEAD(pa_alsa_jack, jacks);
-    pa_dynarray *ports; /* struct ucm_port */
 };
 
 struct pa_alsa_ucm_mapping_context {

commit 9e10c1caa34524c52538283083b134ccf0046998
Author: Arun Raghavan <git at arunraghavan.net>
Date:   Tue May 3 18:22:08 2016 +0530

    device-port: Add mechanism to free implementation data
    
    This will be needed if the implementation data stores pointers to
    additional data that needs to be freed as well.

diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c
index ab3b2e8..7c9ddf3 100644
--- a/src/pulsecore/device-port.c
+++ b/src/pulsecore/device-port.c
@@ -104,6 +104,9 @@ static void device_port_free(pa_object *o) {
     pa_assert(p);
     pa_assert(pa_device_port_refcnt(p) == 0);
 
+    if (p->impl_free)
+        p->impl_free(p);
+
     if (p->proplist)
         pa_proplist_free(p->proplist);
 
diff --git a/src/pulsecore/device-port.h b/src/pulsecore/device-port.h
index 85c41fa..fbdce1a 100644
--- a/src/pulsecore/device-port.h
+++ b/src/pulsecore/device-port.h
@@ -52,6 +52,9 @@ struct pa_device_port {
     pa_direction_t direction;
     int64_t latency_offset;
 
+    /* Free the extra implementation specific data. Called before other members are freed. */
+    void (*impl_free)(pa_device_port *port);
+
     /* .. followed by some implementation specific data */
 };
 

commit 694662d9363d1f61a1021e45fab80ca1e81f3ab1
Author: Chris Billington <chrisjbillington at gmail.com>
Date:   Sat Jan 23 12:31:34 2016 +1100

    sink, source, device-port: renames to distinguish latency offsets
    
    Renamed all variables pertaining to latency offsets of sinks and sources,
    calling them "port_latency_offset" or similar instead. All of these variables
    refer to latency offsets inherited from ports, rather than being unique to
    the sinks or sources themselves.
    
    This change is to pave the way for additional functionality for setting
    latency offsets on sources and sinks independenly from the value they inherit
    from their port. In order to implement them we first need this rename so that
    the two latency offsets can be stored individually and summed when reporting
    the total latency of the source or sink.
    
    The renames made are:
    
    pa_sink_set_latency_offset() -> pa_sink_set_port_latency_offset()
    pa_source_set_latency_offset() -> pa_source_set_port_latency_offset()
    sink->latency_offset -> sink->port_latency_offset
    sink->thread_info.latency_offset -> sink->thread_info.port_latency_offset
    source->latency_offset -> source->port_latency_offset
    source->thread_info.latency_offset -> source->thread_info.port_latency_offset
    PA_SINK_MESSAGE_SET_LATENCY_OFFSET -> PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET
    PA_SOURCE_MESSAGE_SET_LATENCY_OFFSET -> PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET

diff --git a/src/pulsecore/device-port.c b/src/pulsecore/device-port.c
index 5807d3e..ab3b2e8 100644
--- a/src/pulsecore/device-port.c
+++ b/src/pulsecore/device-port.c
@@ -161,7 +161,7 @@ void pa_device_port_set_latency_offset(pa_device_port *p, int64_t offset) {
 
             PA_IDXSET_FOREACH(sink, p->core->sinks, state) {
                 if (sink->active_port == p) {
-                    pa_sink_set_latency_offset(sink, p->latency_offset);
+                    pa_sink_set_port_latency_offset(sink, p->latency_offset);
                     break;
                 }
             }
@@ -174,7 +174,7 @@ void pa_device_port_set_latency_offset(pa_device_port *p, int64_t offset) {
 
             PA_IDXSET_FOREACH(source, p->core->sources, state) {
                 if (source->active_port == p) {
-                    pa_source_set_latency_offset(source, p->latency_offset);
+                    pa_source_set_port_latency_offset(source, p->latency_offset);
                     break;
                 }
             }
diff --git a/src/pulsecore/sink.c b/src/pulsecore/sink.c
index 9bdf9be..a41a78a 100644
--- a/src/pulsecore/sink.c
+++ b/src/pulsecore/sink.c
@@ -310,9 +310,9 @@ pa_sink* pa_sink_new(
         s->active_port = pa_device_port_find_best(s->ports);
 
     if (s->active_port)
-        s->latency_offset = s->active_port->latency_offset;
+        s->port_latency_offset = s->active_port->latency_offset;
     else
-        s->latency_offset = 0;
+        s->port_latency_offset = 0;
 
     s->save_volume = data->save_volume;
     s->save_muted = data->save_muted;
@@ -345,7 +345,7 @@ pa_sink* pa_sink_new(
     pa_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
     s->thread_info.volume_change_safety_margin = core->deferred_volume_safety_margin_usec;
     s->thread_info.volume_change_extra_delay = core->deferred_volume_extra_delay_usec;
-    s->thread_info.latency_offset = s->latency_offset;
+    s->thread_info.port_latency_offset = s->port_latency_offset;
 
     /* FIXME: This should probably be moved to pa_sink_put() */
     pa_assert_se(pa_idxset_put(core->sinks, s, &s->index) >= 0);
@@ -1506,8 +1506,8 @@ pa_usec_t pa_sink_get_latency(pa_sink *s) {
 
     /* usec is unsigned, so check that the offset can be added to usec without
      * underflowing. */
-    if (-s->latency_offset <= (int64_t) usec)
-        usec += s->latency_offset;
+    if (-s->port_latency_offset <= (int64_t) usec)
+        usec += s->port_latency_offset;
     else
         usec = 0;
 
@@ -1540,8 +1540,8 @@ pa_usec_t pa_sink_get_latency_within_thread(pa_sink *s) {
 
     /* usec is unsigned, so check that the offset can be added to usec without
      * underflowing. */
-    if (-s->thread_info.latency_offset <= (int64_t) usec)
-        usec += s->thread_info.latency_offset;
+    if (-s->thread_info.port_latency_offset <= (int64_t) usec)
+        usec += s->thread_info.port_latency_offset;
     else
         usec = 0;
 
@@ -2887,8 +2887,8 @@ int pa_sink_process_msg(pa_msgobject *o, int code, void *userdata, int64_t offse
             pa_sink_get_mute(s, true);
             return 0;
 
-        case PA_SINK_MESSAGE_SET_LATENCY_OFFSET:
-            s->thread_info.latency_offset = offset;
+        case PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET:
+            s->thread_info.port_latency_offset = offset;
             return 0;
 
         case PA_SINK_MESSAGE_GET_LATENCY:
@@ -3287,15 +3287,15 @@ void pa_sink_set_fixed_latency_within_thread(pa_sink *s, pa_usec_t latency) {
 }
 
 /* Called from main context */
-void pa_sink_set_latency_offset(pa_sink *s, int64_t offset) {
+void pa_sink_set_port_latency_offset(pa_sink *s, int64_t offset) {
     pa_sink_assert_ref(s);
 
-    s->latency_offset = offset;
+    s->port_latency_offset = offset;
 
     if (PA_SINK_IS_LINKED(s->state))
-        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_LATENCY_OFFSET, NULL, offset, NULL) == 0);
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET, NULL, offset, NULL) == 0);
     else
-        s->thread_info.latency_offset = offset;
+        s->thread_info.port_latency_offset = offset;
 }
 
 /* Called from main context */
@@ -3368,7 +3368,7 @@ int pa_sink_set_port(pa_sink *s, const char *name, bool save) {
     s->active_port = port;
     s->save_port = save;
 
-    pa_sink_set_latency_offset(s, s->active_port->latency_offset);
+    pa_sink_set_port_latency_offset(s, s->active_port->latency_offset);
 
     pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SINK_PORT_CHANGED], s);
 
diff --git a/src/pulsecore/sink.h b/src/pulsecore/sink.h
index b64a666..c549869 100644
--- a/src/pulsecore/sink.h
+++ b/src/pulsecore/sink.h
@@ -118,7 +118,7 @@ struct pa_sink {
     pa_atomic_t mixer_dirty;
 
     /* The latency offset is inherited from the currently active port */
-    int64_t latency_offset;
+    int64_t port_latency_offset;
 
     unsigned priority;
 
@@ -287,8 +287,8 @@ struct pa_sink {
          * in changing it */
         pa_usec_t fixed_latency; /* for sinks with PA_SINK_DYNAMIC_LATENCY this is 0 */
 
-        /* This latency offset is a direct copy from s->latency_offset */
-        int64_t latency_offset;
+        /* This latency offset is a direct copy from s->port_latency_offset */
+        int64_t port_latency_offset;
 
         /* Delayed volume change events are queued here. The events
          * are stored in expiration order. The one expiring next is in
@@ -337,7 +337,7 @@ typedef enum pa_sink_message {
     PA_SINK_MESSAGE_SET_MAX_REQUEST,
     PA_SINK_MESSAGE_SET_PORT,
     PA_SINK_MESSAGE_UPDATE_VOLUME_AND_MUTE,
-    PA_SINK_MESSAGE_SET_LATENCY_OFFSET,
+    PA_SINK_MESSAGE_SET_PORT_LATENCY_OFFSET,
     PA_SINK_MESSAGE_MAX
 } pa_sink_message_t;
 
@@ -423,7 +423,7 @@ unsigned pa_device_init_priority(pa_proplist *p);
 /**** May be called by everyone, from main context */
 
 int pa_sink_update_rate(pa_sink *s, uint32_t rate, bool passthrough);
-void pa_sink_set_latency_offset(pa_sink *s, int64_t offset);
+void pa_sink_set_port_latency_offset(pa_sink *s, int64_t offset);
 
 /* The returned value is supposed to be in the time domain of the sound card! */
 pa_usec_t pa_sink_get_latency(pa_sink *s);
diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index ee77425..84f8428 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -298,9 +298,9 @@ pa_source* pa_source_new(
         s->active_port = pa_device_port_find_best(s->ports);
 
     if (s->active_port)
-        s->latency_offset = s->active_port->latency_offset;
+        s->port_latency_offset = s->active_port->latency_offset;
     else
-        s->latency_offset = 0;
+        s->port_latency_offset = 0;
 
     s->save_volume = data->save_volume;
     s->save_muted = data->save_muted;
@@ -330,7 +330,7 @@ pa_source* pa_source_new(
     pa_sw_cvolume_multiply(&s->thread_info.current_hw_volume, &s->soft_volume, &s->real_volume);
     s->thread_info.volume_change_safety_margin = core->deferred_volume_safety_margin_usec;
     s->thread_info.volume_change_extra_delay = core->deferred_volume_extra_delay_usec;
-    s->thread_info.latency_offset = s->latency_offset;
+    s->thread_info.port_latency_offset = s->port_latency_offset;
 
     /* FIXME: This should probably be moved to pa_source_put() */
     pa_assert_se(pa_idxset_put(core->sources, s, &s->index) >= 0);
@@ -1101,8 +1101,8 @@ pa_usec_t pa_source_get_latency(pa_source *s) {
 
     /* usec is unsigned, so check that the offset can be added to usec without
      * underflowing. */
-    if (-s->latency_offset <= (int64_t) usec)
-        usec += s->latency_offset;
+    if (-s->port_latency_offset <= (int64_t) usec)
+        usec += s->port_latency_offset;
     else
         usec = 0;
 
@@ -1135,8 +1135,8 @@ pa_usec_t pa_source_get_latency_within_thread(pa_source *s) {
 
     /* usec is unsigned, so check that the offset can be added to usec without
      * underflowing. */
-    if (-s->thread_info.latency_offset <= (int64_t) usec)
-        usec += s->thread_info.latency_offset;
+    if (-s->thread_info.port_latency_offset <= (int64_t) usec)
+        usec += s->thread_info.port_latency_offset;
     else
         usec = 0;
 
@@ -2242,8 +2242,8 @@ int pa_source_process_msg(pa_msgobject *object, int code, void *userdata, int64_
             pa_source_get_mute(s, true);
             return 0;
 
-        case PA_SOURCE_MESSAGE_SET_LATENCY_OFFSET:
-            s->thread_info.latency_offset = offset;
+        case PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET:
+            s->thread_info.port_latency_offset = offset;
             return 0;
 
         case PA_SOURCE_MESSAGE_MAX:
@@ -2570,15 +2570,15 @@ void pa_source_set_fixed_latency_within_thread(pa_source *s, pa_usec_t latency)
 }
 
 /* Called from main thread */
-void pa_source_set_latency_offset(pa_source *s, int64_t offset) {
+void pa_source_set_port_latency_offset(pa_source *s, int64_t offset) {
     pa_source_assert_ref(s);
 
-    s->latency_offset = offset;
+    s->port_latency_offset = offset;
 
     if (PA_SOURCE_IS_LINKED(s->state))
-        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_LATENCY_OFFSET, NULL, offset, NULL) == 0);
+        pa_assert_se(pa_asyncmsgq_send(s->asyncmsgq, PA_MSGOBJECT(s), PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET, NULL, offset, NULL) == 0);
     else
-        s->thread_info.latency_offset = offset;
+        s->thread_info.port_latency_offset = offset;
 }
 
 /* Called from main thread */
@@ -2637,7 +2637,7 @@ int pa_source_set_port(pa_source *s, const char *name, bool save) {
     s->active_port = port;
     s->save_port = save;
 
-    pa_source_set_latency_offset(s, s->active_port->latency_offset);
+    pa_source_set_port_latency_offset(s, s->active_port->latency_offset);
 
     pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], s);
 
diff --git a/src/pulsecore/source.h b/src/pulsecore/source.h
index 91e8674..3a6b5c3 100644
--- a/src/pulsecore/source.h
+++ b/src/pulsecore/source.h
@@ -119,7 +119,7 @@ struct pa_source {
     pa_atomic_t mixer_dirty;
 
     /* The latency offset is inherited from the currently active port */
-    int64_t latency_offset;
+    int64_t port_latency_offset;
 
     unsigned priority;
 
@@ -229,8 +229,8 @@ struct pa_source {
 
         pa_usec_t fixed_latency; /* for sources with PA_SOURCE_DYNAMIC_LATENCY this is 0 */
 
-        /* This latency offset is a direct copy from s->latency_offset */
-        int64_t latency_offset;
+        /* This latency offset is a direct copy from s->port_latency_offset */
+        int64_t port_latency_offset;
 
         /* Delayed volume change events are queued here. The events
          * are stored in expiration order. The one expiring next is in
@@ -275,7 +275,7 @@ typedef enum pa_source_message {
     PA_SOURCE_MESSAGE_SET_MAX_REWIND,
     PA_SOURCE_MESSAGE_SET_PORT,
     PA_SOURCE_MESSAGE_UPDATE_VOLUME_AND_MUTE,
-    PA_SOURCE_MESSAGE_SET_LATENCY_OFFSET,
+    PA_SOURCE_MESSAGE_SET_PORT_LATENCY_OFFSET,
     PA_SOURCE_MESSAGE_MAX
 } pa_source_message_t;
 
@@ -356,7 +356,7 @@ void pa_source_update_flags(pa_source *s, pa_source_flags_t mask, pa_source_flag
 
 /*** May be called by everyone, from main context */
 
-void pa_source_set_latency_offset(pa_source *s, int64_t offset);
+void pa_source_set_port_latency_offset(pa_source *s, int64_t offset);
 
 /* The returned value is supposed to be in the time domain of the sound card! */
 pa_usec_t pa_source_get_latency(pa_source *s);

commit 99c3bc69d5c33228390b38724d8bf0c182f789b9
Author: Tanu Kaskinen <tanuk at iki.fi>
Date:   Thu Jun 2 16:00:51 2016 +0300

    webrtc: improve comment about mic geometry
    
    The first mic channel position is not relevant for the target
    direction definition.

diff --git a/src/modules/echo-cancel/webrtc.cc b/src/modules/echo-cancel/webrtc.cc
index db6873d..aadb1af 100644
--- a/src/modules/echo-cancel/webrtc.cc
+++ b/src/modules/echo-cancel/webrtc.cc
@@ -184,8 +184,8 @@ static bool parse_mic_geometry(const char **mic_geometry, std::vector<webrtc::Po
     /* The target direction is expected to be in spherical point form:
      *   a,e,r
      *
-     * Where 'a is the azimuth of the first mic channel, 'e' its elevation,
-     * and 'r' the radius.
+     * Where 'a' is the azimuth of the target point relative to the center of
+     * the array, 'e' its elevation, and 'r' the radius.
      *
      * 0 radians azimuth is to the right of the array, and positive angles
      * move in a counter-clockwise direction.

commit d2d3d0e141f5fc83301632c30af63231be00886a
Author: Chris Billington <chrisjbillington at gmail.com>
Date:   Sat Jan 23 12:31:33 2016 +1100

    source: Fixed bug: pa_source_set_port() did not update the latency_offset.
    
    Unlike pa_sink_set_port(), which calls pa_sink_set_latency_offset() to update
    the latency offset of the sink to match that of its newly set port,
    pa_source_set_port() did not do so. This patch adds the appropriate call to
    pa_source_set_latency_offset() in pa_source_set_port() to fix this.

diff --git a/src/pulsecore/source.c b/src/pulsecore/source.c
index 8a527d8..ee77425 100644
--- a/src/pulsecore/source.c
+++ b/src/pulsecore/source.c
@@ -2637,6 +2637,8 @@ int pa_source_set_port(pa_source *s, const char *name, bool save) {
     s->active_port = port;
     s->save_port = save;
 
+    pa_source_set_latency_offset(s, s->active_port->latency_offset);
+
     pa_hook_fire(&s->core->hooks[PA_CORE_HOOK_SOURCE_PORT_CHANGED], s);
 
     return 0;

commit e3148f9ac291baba1efa1ef22adc274283e375fa
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:39 2016 +0530

    json: Drop refcounting of json objects
    
    We don't actually use the refcounting bits.
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/format.c b/src/pulse/format.c
index ee8b7ac..8474978 100644
--- a/src/pulse/format.c
+++ b/src/pulse/format.c
@@ -300,7 +300,7 @@ pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char
             break;
     }
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return type;
 }
 
@@ -324,12 +324,12 @@ int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v
 
     if (pa_json_object_get_type(o) != PA_JSON_TYPE_INT) {
         pa_log_debug("Format info property '%s' type is not int.", key);
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
         return -PA_ERR_INVALID;
     }
 
     *v = pa_json_object_get_int(o);
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     return 0;
 }
@@ -376,7 +376,7 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid int range.", key);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return ret;
 }
 
@@ -423,7 +423,7 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid int array.", key);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return ret;
 }
 
@@ -447,12 +447,12 @@ int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, cha
 
     if (pa_json_object_get_type(o) != PA_JSON_TYPE_STRING) {
         pa_log_debug("Format info property '%s' type is not string.", key);
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
         return -PA_ERR_INVALID;
     }
 
     *v = pa_xstrdup(pa_json_object_get_string(o));
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     return 0;
 }
@@ -500,7 +500,7 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid string array.", key);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return ret;
 }
 
@@ -675,9 +675,9 @@ static int pa_format_info_prop_compatible(const char *one, const char *two) {
 
 out:
     if (o1)
-        pa_json_object_unref(o1);
+        pa_json_object_free(o1);
     if (o2)
-        pa_json_object_unref(o2);
+        pa_json_object_free(o2);
 
     return ret;
 }
diff --git a/src/pulse/json.c b/src/pulse/json.c
index 04501b7..d126712 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -27,13 +27,11 @@
 #include <pulse/xmalloc.h>
 #include <pulsecore/core-util.h>
 #include <pulsecore/hashmap.h>
-#include <pulsecore/refcnt.h>
 #include <pulsecore/strbuf.h>
 
 #define MAX_NESTING_DEPTH 20 /* Arbitrary number to make sure we don't have a stack overflow */
 
 struct pa_json_object {
-    PA_REFCNT_DECLARE;
     pa_json_type type;
 
     union {
@@ -309,7 +307,7 @@ static const char *parse_object(const char *str, pa_json_object *obj, unsigned i
     pa_json_object *name = NULL, *value = NULL;
 
     obj->object_values = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
-                                             pa_xfree, (pa_free_cb_t) pa_json_object_unref);
+                                             pa_xfree, (pa_free_cb_t) pa_json_object_free);
 
     while (*str != '}') {
         str++; /* Consume leading '{' or ',' */
@@ -330,7 +328,7 @@ static const char *parse_object(const char *str, pa_json_object *obj, unsigned i
         }
 
         pa_hashmap_put(obj->object_values, pa_xstrdup(pa_json_object_get_string(name)), value);
-        pa_json_object_unref(name);
+        pa_json_object_free(name);
 
         name = NULL;
         value = NULL;
@@ -349,9 +347,9 @@ error:
     obj->object_values = NULL;
 
     if (name)
-        pa_json_object_unref(name);
+        pa_json_object_free(name);
     if (value)
-        pa_json_object_unref(value);
+        pa_json_object_free(value);
 
     return NULL;
 }
@@ -390,7 +388,7 @@ static const char *parse_array(const char *str, pa_json_object *obj, unsigned in
     return str;
 
 error:
-    pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_unref);
+    pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_free);
     obj->array_values = NULL;
     return NULL;
 }
@@ -467,7 +465,7 @@ static const char* parse_value(const char *str, const char *end, pa_json_object
     return str;
 
 error:
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
     return NULL;
 }
 
@@ -484,7 +482,7 @@ pa_json_object* pa_json_parse(const char *str) {
 
     if (*str != '\0') {
         pa_log("Unable to parse complete JSON string, remainder is: %s", str);
-        pa_json_object_unref(obj);
+        pa_json_object_free(obj);
         return NULL;
     }
 
@@ -495,9 +493,7 @@ pa_json_type pa_json_object_get_type(const pa_json_object *obj) {
     return obj->type;
 }
 
-void pa_json_object_unref(pa_json_object *obj) {
-    if (PA_REFCNT_DEC(obj) > 0)
-        return;
+void pa_json_object_free(pa_json_object *obj) {
 
     switch (pa_json_object_get_type(obj)) {
         case PA_JSON_TYPE_INIT:
@@ -516,7 +512,7 @@ void pa_json_object_unref(pa_json_object *obj) {
             break;
 
         case PA_JSON_TYPE_ARRAY:
-            pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_unref);
+            pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_free);
             break;
 
         default:
diff --git a/src/pulse/json.h b/src/pulse/json.h
index d8a946f..7759bf2 100644
--- a/src/pulse/json.h
+++ b/src/pulse/json.h
@@ -36,7 +36,7 @@ typedef struct pa_json_object pa_json_object;
 
 pa_json_object* pa_json_parse(const char *str);
 pa_json_type pa_json_object_get_type(const pa_json_object *obj);
-void pa_json_object_unref(pa_json_object *obj);
+void pa_json_object_free(pa_json_object *obj);
 
 /* All pointer members that are returned are valid while the corresponding object is valid */
 
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 08b2ff6..3e956db 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -45,7 +45,7 @@ START_TEST (string_test) {
         fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING);
         fail_unless(pa_streq(pa_json_object_get_string(o), strings_compare[i]));
 
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
     }
 }
 END_TEST
@@ -63,7 +63,7 @@ START_TEST(int_test) {
         fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
         fail_unless(pa_json_object_get_int(o) == ints_compare[i]);
 
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
     }
 }
 END_TEST
@@ -85,7 +85,7 @@ START_TEST(double_test) {
         fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
         fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
 
-        pa_json_object_unref(o);
+        pa_json_object_free(o);
     }
 }
 END_TEST
@@ -98,7 +98,7 @@ START_TEST(null_test) {
     fail_unless(o != NULL);
     fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_NULL);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 }
 END_TEST
 
@@ -111,7 +111,7 @@ START_TEST(bool_test) {
     fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(o) == true);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("false");
 
@@ -119,7 +119,7 @@ START_TEST(bool_test) {
     fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(o) == false);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 }
 END_TEST
 
@@ -137,7 +137,7 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
     fail_unless(pa_streq(pa_json_object_get_string(v), "A Person"));
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse(" { \"age\" : -45.3e-0 } ");
 
@@ -149,7 +149,7 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
     fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), -45.3));
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("{\"person\":true}");
 
@@ -161,7 +161,7 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(v) == true);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("{ \"parent\": { \"child\": false } }");
     fail_unless(o != NULL);
@@ -174,7 +174,7 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(v) == false);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 }
 END_TEST
 
@@ -188,7 +188,7 @@ START_TEST(array_test) {
     fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
     fail_unless(pa_json_object_get_array_length(o) == 0);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("[\"a member\"]");
 
@@ -201,7 +201,7 @@ START_TEST(array_test) {
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
     fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 
     o = pa_json_parse("[\"a member\", 1234.5, { \"another\": true } ]");
 
@@ -225,7 +225,7 @@ START_TEST(array_test) {
     fail_unless(pa_json_object_get_type(v2) == PA_JSON_TYPE_BOOL);
     fail_unless(pa_json_object_get_bool(v2) == true);
 
-    pa_json_object_unref(o);
+    pa_json_object_free(o);
 }
 END_TEST
 

commit 8f45d83bdbf823f499b022571b5f724ab1f3fdad
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:38 2016 +0530

    json: Add some more negative test cases
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 4edfa09..08b2ff6 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -242,6 +242,9 @@ START_TEST(bad_test) {
         "-" /* Bad number string */,
         "{ \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": {  \"a\": { } } } } } } } } } } } } } } } } } } } } } }" /* Nested too deep */,
         "[ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ { \"a\": \"b\" } ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]" /* Nested too deep */,
+        "asdf" /* Unquoted string */,
+        "{ a: true }" /* Unquoted key in object */,
+        "\"    \a\"" /* Alarm is not a valid character */
     };
 
     for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {

commit 1879beab87db51ab29116eb5ffb2b0201f0ecfda
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:37 2016 +0530

    json: Add a positive test for nested objects
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 3f8ed92..4edfa09 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -162,6 +162,19 @@ START_TEST(object_test) {
     fail_unless(pa_json_object_get_bool(v) == true);
 
     pa_json_object_unref(o);
+
+    o = pa_json_parse("{ \"parent\": { \"child\": false } }");
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+    v = pa_json_object_get_object_member(o, "parent");
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);
+    v = pa_json_object_get_object_member(v, "child");
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(v) == false);
+
+    pa_json_object_unref(o);
 }
 END_TEST
 

commit 0c1dbf5c799272892da6f6a6f268b2207945f1f9
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:36 2016 +0530

    json: Error out for objects and arrays that are nested too deep
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/json.c b/src/pulse/json.c
index 3c89a85..04501b7 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -30,6 +30,8 @@
 #include <pulsecore/refcnt.h>
 #include <pulsecore/strbuf.h>
 
+#define MAX_NESTING_DEPTH 20 /* Arbitrary number to make sure we don't have a stack overflow */
+
 struct pa_json_object {
     PA_REFCNT_DECLARE;
     pa_json_type type;
@@ -44,7 +46,7 @@ struct pa_json_object {
     };
 };
 
-static const char* parse_value(const char *str, const char *end, pa_json_object **obj);
+static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth);
 
 static pa_json_object* json_object_new(void) {
     pa_json_object *obj;
@@ -303,7 +305,7 @@ error:
     return NULL;
 }
 
-static const char *parse_object(const char *str, pa_json_object *obj) {
+static const char *parse_object(const char *str, pa_json_object *obj, unsigned int depth) {
     pa_json_object *name = NULL, *value = NULL;
 
     obj->object_values = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
@@ -312,7 +314,7 @@ static const char *parse_object(const char *str, pa_json_object *obj) {
     while (*str != '}') {
         str++; /* Consume leading '{' or ',' */
 
-        str = parse_value(str, ":", &name);
+        str = parse_value(str, ":", &name, depth + 1);
         if (!str || pa_json_object_get_type(name) != PA_JSON_TYPE_STRING) {
             pa_log("Could not parse key for object");
             goto error;
@@ -321,7 +323,7 @@ static const char *parse_object(const char *str, pa_json_object *obj) {
         /* Consume the ':' */
         str++;
 
-        str = parse_value(str, ",}", &value);
+        str = parse_value(str, ",}", &value, depth + 1);
         if (!str) {
             pa_log("Could not parse value for object");
             goto error;
@@ -354,7 +356,7 @@ error:
     return NULL;
 }
 
-static const char *parse_array(const char *str, pa_json_object *obj) {
+static const char *parse_array(const char *str, pa_json_object *obj, unsigned int depth) {
     pa_json_object *value;
 
     obj->array_values = pa_idxset_new(NULL, NULL);
@@ -370,7 +372,7 @@ static const char *parse_array(const char *str, pa_json_object *obj) {
         if (*str == ']')
             break;
 
-        str = parse_value(str, ",]", &value);
+        str = parse_value(str, ",]", &value, depth + 1);
         if (!str) {
             pa_log("Could not parse value for array");
             goto error;
@@ -398,7 +400,7 @@ typedef enum {
     JSON_PARSER_STATE_FINISH,
 } json_parser_state;
 
-static const char* parse_value(const char *str, const char *end, pa_json_object **obj) {
+static const char* parse_value(const char *str, const char *end, pa_json_object **obj, unsigned int depth) {
     json_parser_state state = JSON_PARSER_STATE_INIT;
     pa_json_object *o;
 
@@ -406,6 +408,11 @@ static const char* parse_value(const char *str, const char *end, pa_json_object
 
     o = json_object_new();
 
+    if (depth > MAX_NESTING_DEPTH) {
+        pa_log("Exceeded maximum permitted nesting depth of objects (%u)", MAX_NESTING_DEPTH);
+        goto error;
+    }
+
     while (!is_end(*str, end)) {
         switch (state) {
             case JSON_PARSER_STATE_INIT:
@@ -424,10 +431,10 @@ static const char* parse_value(const char *str, const char *end, pa_json_object
                     str = parse_number(str, o);
                     state = JSON_PARSER_STATE_FINISH;
                 } else if (*str == '{') {
-                    str = parse_object(str, o);
+                    str = parse_object(str, o, depth);
                     state = JSON_PARSER_STATE_FINISH;
                 } else if (*str == '[') {
-                    str = parse_array(str, o);
+                    str = parse_array(str, o, depth);
                     state = JSON_PARSER_STATE_FINISH;
                 } else {
                     pa_log("Invalid JSON string: %s", str);
@@ -468,7 +475,7 @@ error:
 pa_json_object* pa_json_parse(const char *str) {
     pa_json_object *obj;
 
-    str = parse_value(str, NULL, &obj);
+    str = parse_value(str, NULL, &obj, 0);
 
     if (!str) {
         pa_log("JSON parsing failed");
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index ca92877..3f8ed92 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -227,6 +227,8 @@ START_TEST(bad_test) {
         "1." /* Bad number string */,
         "1.e3" /* Bad number string */,
         "-" /* Bad number string */,
+        "{ \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": { \"a\": {  \"a\": { } } } } } } } } } } } } } } } } } } } } } }" /* Nested too deep */,
+        "[ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ [ { \"a\": \"b\" } ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ] ]" /* Nested too deep */,
     };
 
     for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {

commit 5b1bd849023bcbf495cdb91eef9552734efb9ca2
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:35 2016 +0530

    json: Handle error cases while parsing numbers
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/json.c b/src/pulse/json.c
index d77c7ad..3c89a85 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -194,7 +194,7 @@ error:
 }
 
 static const char* parse_number(const char *str, pa_json_object *obj) {
-    bool negative = false, has_fraction = false, has_exponent = false;
+    bool negative = false, has_fraction = false, has_exponent = false, valid = false;
     unsigned int integer = 0;
     unsigned int fraction = 0;
     unsigned int fraction_digits = 0;
@@ -206,11 +206,14 @@ static const char* parse_number(const char *str, pa_json_object *obj) {
     }
 
     if (*str == '0') {
+        valid = true;
         str++;
         goto fraction;
     }
 
     while (is_digit(*str)) {
+        valid = true;
+
         if (integer > ((negative ? INT_MAX : UINT_MAX) / 10)) {
             pa_log("Integer overflow while parsing number");
             goto error;
@@ -221,11 +224,20 @@ static const char* parse_number(const char *str, pa_json_object *obj) {
     }
 
 fraction:
+
+    if (!valid) {
+        pa_log("Missing digits while parsing number");
+        goto error;
+    }
+
     if (*str == '.') {
         has_fraction = true;
         str++;
+        valid = false;
 
         while (is_digit(*str)) {
+            valid = true;
+
             if (fraction > (UINT_MAX / 10)) {
                 pa_log("Integer overflow while parsing fractional part of number");
                 goto error;
@@ -235,6 +247,11 @@ fraction:
             fraction_digits++;
             str++;
         }
+
+        if (!valid) {
+            pa_log("No digit after '.' while parsing fraction");
+            goto error;
+        }
     }
 
     if (*str == 'e' || *str == 'E') {
@@ -242,6 +259,7 @@ fraction:
 
         has_exponent = true;
         str++;
+        valid = false;
 
         if (*str == '-') {
             exponent_negative = true;
@@ -250,6 +268,8 @@ fraction:
             str++;
 
         while (is_digit(*str)) {
+            valid = true;
+
             if (exponent > (INT_MAX / 10)) {
                 pa_log("Integer overflow while parsing exponent part of number");
                 goto error;
@@ -259,6 +279,11 @@ fraction:
             str++;
         }
 
+        if (!valid) {
+            pa_log("No digit in exponent while parsing fraction");
+            goto error;
+        }
+
         if (exponent_negative)
             exponent *= -1;
     }
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index a5f1f74..ca92877 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -223,6 +223,10 @@ START_TEST(bad_test) {
         "123456789012345678901234567890" /* Overflow */,
         "0.123456789012345678901234567890" /* Overflow */,
         "1e123456789012345678901234567890" /* Overflow */,
+        "1e" /* Bad number string */,
+        "1." /* Bad number string */,
+        "1.e3" /* Bad number string */,
+        "-" /* Bad number string */,
     };
 
     for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {

commit 777a5091f613d1a2cf67248e33da3a8961ab9bbb
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:34 2016 +0530

    json: Add overflow checks for integer and float parsing
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/json.c b/src/pulse/json.c
index 6297902..d77c7ad 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -211,6 +211,11 @@ static const char* parse_number(const char *str, pa_json_object *obj) {
     }
 
     while (is_digit(*str)) {
+        if (integer > ((negative ? INT_MAX : UINT_MAX) / 10)) {
+            pa_log("Integer overflow while parsing number");
+            goto error;
+        }
+
         integer = (integer * 10) + (*str - '0');
         str++;
     }
@@ -221,6 +226,11 @@ fraction:
         str++;
 
         while (is_digit(*str)) {
+            if (fraction > (UINT_MAX / 10)) {
+                pa_log("Integer overflow while parsing fractional part of number");
+                goto error;
+            }
+
             fraction = (fraction * 10) + (*str - '0');
             fraction_digits++;
             str++;
@@ -240,6 +250,11 @@ fraction:
             str++;
 
         while (is_digit(*str)) {
+            if (exponent > (INT_MAX / 10)) {
+                pa_log("Integer overflow while parsing exponent part of number");
+                goto error;
+            }
+
             exponent = (exponent * 10) + (*str - '0');
             str++;
         }
@@ -258,6 +273,9 @@ fraction:
     }
 
     return str;
+
+error:
+    return NULL;
 }
 
 static const char *parse_object(const char *str, pa_json_object *obj) {
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 7d273d7..a5f1f74 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -220,6 +220,9 @@ START_TEST(bad_test) {
     unsigned int i;
     const char *bad_parse[] = {
         "\"" /* Quote not closed */,
+        "123456789012345678901234567890" /* Overflow */,
+        "0.123456789012345678901234567890" /* Overflow */,
+        "1e123456789012345678901234567890" /* Overflow */,
     };
 
     for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {

commit 708b4aac91ce8220480df6a34ccb491be2b8d490
Author: Arun Raghavan <arun at arunraghavan.net>
Date:   Wed Jun 1 17:18:33 2016 +0530

    json: Correctly handle bad strings with missing closing quotes
    
    Also add a test for this case.
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/pulse/json.c b/src/pulse/json.c
index 8484bba..6297902 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -122,7 +122,7 @@ static const char* parse_string(const char *str, pa_json_object *obj) {
 
     str++; /* Consume leading '"' */
 
-    while (*str != '"') {
+    while (*str && *str != '"') {
         if (*str != '\\') {
             /* We only accept ASCII printable characters. */
             if (*str < 0x20 || *str > 0x7E) {
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index 2e1ca6b..7d273d7 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -216,6 +216,18 @@ START_TEST(array_test) {
 }
 END_TEST
 
+START_TEST(bad_test) {
+    unsigned int i;
+    const char *bad_parse[] = {
+        "\"" /* Quote not closed */,
+    };
+
+    for (i = 0; i < PA_ELEMENTSOF(bad_parse); i++) {
+        fail_unless(pa_json_parse(bad_parse[i]) == NULL);
+    }
+}
+END_TEST
+
 int main(int argc, char *argv[]) {
     int failed = 0;
     Suite *s;
@@ -231,6 +243,7 @@ int main(int argc, char *argv[]) {
     tcase_add_test(tc, bool_test);
     tcase_add_test(tc, object_test);
     tcase_add_test(tc, array_test);
+    tcase_add_test(tc, bad_test);
     suite_add_tcase(s, tc);
 
     sr = srunner_create(s);

commit c692ec3afdba560398fd9a6871cddcca6d5c9cc5
Author: Arun Raghavan <git at arunraghavan.net>
Date:   Wed Jun 1 17:18:32 2016 +0530

    format: Drop dependency on json-c
    
    json-c has a symbol clash (json_object_get_type) with json-glib (which
    at least a number of our GNOME clients use). This patch moves to our own
    JSON parser so that we can avoid this kind of situation altogether.
    
    Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=95135
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/configure.ac b/configure.ac
index 4edc8e0..7b484a9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -676,10 +676,6 @@ AS_IF([test "x$enable_tests" = "xyes" && test "x$HAVE_LIBCHECK" = "x0"],
 
 AM_CONDITIONAL([HAVE_TESTS], [test "x$HAVE_LIBCHECK" = x1])
 
-#### json parsing ####
-
-PKG_CHECK_MODULES(LIBJSON, [ json-c >= 0.11 ])
-
 #### Sound file ####
 
 PKG_CHECK_MODULES(LIBSNDFILE, [ sndfile >= 1.0.20 ])
diff --git a/src/Makefile.am b/src/Makefile.am
index c6b998c..7b19497 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -739,9 +739,9 @@ else
 libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES += pulsecore/poll-posix.c pulsecore/poll.h
 endif
 
-libpulsecommon_ at PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS) $(LIBSNDFILE_CFLAGS)
+libpulsecommon_ at PA_MAJORMINOR@_la_CFLAGS = $(AM_CFLAGS) $(LIBSNDFILE_CFLAGS)
 libpulsecommon_ at PA_MAJORMINOR@_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) -avoid-version
-libpulsecommon_ at PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBJSON_LIBS)  $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS)
+libpulsecommon_ at PA_MAJORMINOR@_la_LIBADD = $(AM_LIBADD) $(LIBWRAP_LIBS) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBSNDFILE_LIBS)
 
 if HAVE_MEMFD
 libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES += \
@@ -893,8 +893,8 @@ libpulse_la_SOURCES = \
 		pulse/volume.c pulse/volume.h \
 		pulse/xmalloc.c pulse/xmalloc.h
 
-libpulse_la_CFLAGS = $(AM_CFLAGS) $(LIBJSON_CFLAGS)
-libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) $(LIBJSON_LIBS) libpulsecommon- at PA_MAJORMINOR@.la
+libpulse_la_CFLAGS = $(AM_CFLAGS)
+libpulse_la_LIBADD = $(AM_LIBADD) $(WINSOCK_LIBS) $(LTLIBICONV) libpulsecommon- at PA_MAJORMINOR@.la
 libpulse_la_LDFLAGS = $(AM_LDFLAGS) $(AM_LIBLDFLAGS) $(VERSIONING_LDFLAGS) -version-info $(LIBPULSE_VERSION_INFO)
 
 if HAVE_DBUS
diff --git a/src/pulse/format.c b/src/pulse/format.c
index c2a1552..ee8b7ac 100644
--- a/src/pulse/format.c
+++ b/src/pulse/format.c
@@ -23,8 +23,7 @@
 #include <config.h>
 #endif
 
-#include <json.h>
-
+#include <pulse/json.h>
 #include <pulse/internal.h>
 #include <pulse/xmalloc.h>
 
@@ -32,6 +31,7 @@
 #include <pulsecore/core-util.h>
 #include <pulsecore/i18n.h>
 #include <pulsecore/macro.h>
+#include <pulsecore/strbuf.h>
 
 #include "format.h"
 
@@ -236,7 +236,8 @@ int pa_format_info_to_sample_spec(const pa_format_info *f, pa_sample_spec *ss, p
 
 pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char *key) {
     const char *str;
-    json_object *o, *o1;
+    pa_json_object *o;
+    const pa_json_object *o1;
     pa_prop_type_t type;
 
     pa_assert(f);
@@ -246,47 +247,47 @@ pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char
     if (!str)
         return PA_PROP_TYPE_INVALID;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o)
         return PA_PROP_TYPE_INVALID;
 
-    switch (json_object_get_type(o)) {
-        case json_type_int:
+    switch (pa_json_object_get_type(o)) {
+        case PA_JSON_TYPE_INT:
             type = PA_PROP_TYPE_INT;
             break;
 
-        case json_type_string:
+        case PA_JSON_TYPE_STRING:
             type = PA_PROP_TYPE_STRING;
             break;
 
-        case json_type_array:
-            if (json_object_array_length(o) == 0) {
+        case PA_JSON_TYPE_ARRAY:
+            if (pa_json_object_get_array_length(o) == 0) {
                 /* Unlikely, but let's account for this anyway. We need at
                  * least one element to figure out the array type. */
                 type = PA_PROP_TYPE_INVALID;
                 break;
             }
 
-            o1 = json_object_array_get_idx(o, 1);
+            o1 = pa_json_object_get_array_member(o, 0);
 
-            if (json_object_get_type(o1) == json_type_int)
+            if (pa_json_object_get_type(o1) == PA_JSON_TYPE_INT)
                 type = PA_PROP_TYPE_INT_ARRAY;
-            else if (json_object_get_type(o1) == json_type_string)
+            else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_STRING)
                 type = PA_PROP_TYPE_STRING_ARRAY;
             else
                 type = PA_PROP_TYPE_INVALID;
 
             break;
 
-        case json_type_object:
+        case PA_JSON_TYPE_OBJECT:
             /* We actually know at this point that it's a int range, but let's
              * confirm. */
-            if (!json_object_object_get_ex(o, PA_JSON_MIN_KEY, NULL)) {
+            if (!pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) {
                 type = PA_PROP_TYPE_INVALID;
                 break;
             }
 
-            if (!json_object_object_get_ex(o, PA_JSON_MAX_KEY, NULL)) {
+            if (!pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) {
                 type = PA_PROP_TYPE_INVALID;
                 break;
             }
@@ -299,13 +300,13 @@ pa_prop_type_t pa_format_info_get_prop_type(const pa_format_info *f, const char
             break;
     }
 
-    json_object_put(o);
+    pa_json_object_unref(o);
     return type;
 }
 
 int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v) {
     const char *str;
-    json_object *o;
+    pa_json_object *o;
 
     pa_assert(f);
     pa_assert(key);
@@ -315,27 +316,28 @@ int pa_format_info_get_prop_int(const pa_format_info *f, const char *key, int *v
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_int) {
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_INT) {
         pa_log_debug("Format info property '%s' type is not int.", key);
-        json_object_put(o);
+        pa_json_object_unref(o);
         return -PA_ERR_INVALID;
     }
 
-    *v = json_object_get_int(o);
-    json_object_put(o);
+    *v = pa_json_object_get_int(o);
+    pa_json_object_unref(o);
 
     return 0;
 }
 
 int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key, int *min, int *max) {
     const char *str;
-    json_object *o, *o1;
+    pa_json_object *o;
+    const pa_json_object *o1;
     int ret = -PA_ERR_INVALID;
 
     pa_assert(f);
@@ -347,24 +349,26 @@ int pa_format_info_get_prop_int_range(const pa_format_info *f, const char *key,
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_object)
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_OBJECT)
         goto out;
 
-    if (!json_object_object_get_ex(o, PA_JSON_MIN_KEY, &o1))
+    if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MIN_KEY)) ||
+            (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT))
         goto out;
 
-    *min = json_object_get_int(o1);
+    *min = pa_json_object_get_int(o1);
 
-    if (!json_object_object_get_ex(o, PA_JSON_MAX_KEY, &o1))
+    if (!(o1 = pa_json_object_get_object_member(o, PA_JSON_MAX_KEY)) ||
+            (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT))
         goto out;
 
-    *max = json_object_get_int(o1);
+    *max = pa_json_object_get_int(o1);
 
     ret = 0;
 
@@ -372,13 +376,14 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid int range.", key);
 
-    json_object_put(o);
+    pa_json_object_unref(o);
     return ret;
 }
 
 int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key, int **values, int *n_values) {
     const char *str;
-    json_object *o, *o1;
+    pa_json_object *o;
+    const pa_json_object *o1;
     int i, ret = -PA_ERR_INVALID;
 
     pa_assert(f);
@@ -390,26 +395,26 @@ int pa_format_info_get_prop_int_array(const pa_format_info *f, const char *key,
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_array)
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY)
         goto out;
 
-    *n_values = json_object_array_length(o);
+    *n_values = pa_json_object_get_array_length(o);
     *values = pa_xnew(int, *n_values);
 
     for (i = 0; i < *n_values; i++) {
-        o1 = json_object_array_get_idx(o, i);
+        o1 = pa_json_object_get_array_member(o, i);
 
-        if (json_object_get_type(o1) != json_type_int) {
+        if (pa_json_object_get_type(o1) != PA_JSON_TYPE_INT) {
             goto out;
         }
 
-        (*values)[i] = json_object_get_int(o1);
+        (*values)[i] = pa_json_object_get_int(o1);
     }
 
     ret = 0;
@@ -418,13 +423,13 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid int array.", key);
 
-    json_object_put(o);
+    pa_json_object_unref(o);
     return ret;
 }
 
 int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, char **v) {
     const char *str = NULL;
-    json_object *o;
+    pa_json_object *o;
 
     pa_assert(f);
     pa_assert(key);
@@ -434,27 +439,28 @@ int pa_format_info_get_prop_string(const pa_format_info *f, const char *key, cha
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_string) {
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_STRING) {
         pa_log_debug("Format info property '%s' type is not string.", key);
-        json_object_put(o);
+        pa_json_object_unref(o);
         return -PA_ERR_INVALID;
     }
 
-    *v = pa_xstrdup(json_object_get_string(o));
-    json_object_put(o);
+    *v = pa_xstrdup(pa_json_object_get_string(o));
+    pa_json_object_unref(o);
 
     return 0;
 }
 
 int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *key, char ***values, int *n_values) {
     const char *str;
-    json_object *o, *o1;
+    pa_json_object *o;
+    const pa_json_object *o1;
     int i, ret = -PA_ERR_INVALID;
 
     pa_assert(f);
@@ -466,26 +472,26 @@ int pa_format_info_get_prop_string_array(const pa_format_info *f, const char *ke
     if (!str)
         return -PA_ERR_NOENTITY;
 
-    o = json_tokener_parse(str);
+    o = pa_json_parse(str);
     if (!o) {
         pa_log_debug("Failed to parse format info property '%s'.", key);
         return -PA_ERR_INVALID;
     }
 
-    if (json_object_get_type(o) != json_type_array)
+    if (pa_json_object_get_type(o) != PA_JSON_TYPE_ARRAY)
         goto out;
 
-    *n_values = json_object_array_length(o);
+    *n_values = pa_json_object_get_array_length(o);
     *values = pa_xnew(char *, *n_values);
 
     for (i = 0; i < *n_values; i++) {
-        o1 = json_object_array_get_idx(o, i);
+        o1 = pa_json_object_get_array_member(o, i);
 
-        if (json_object_get_type(o1) != json_type_string) {
+        if (pa_json_object_get_type(o1) != PA_JSON_TYPE_STRING) {
             goto out;
         }
 
-        (*values)[i] = pa_xstrdup(json_object_get_string(o1));
+        (*values)[i] = pa_xstrdup(pa_json_object_get_string(o1));
     }
 
     ret = 0;
@@ -494,7 +500,7 @@ out:
     if (ret < 0)
         pa_log_debug("Format info property '%s' is not a valid string array.", key);
 
-    json_object_put(o);
+    pa_json_object_unref(o);
     return ret;
 }
 
@@ -528,85 +534,76 @@ void pa_format_info_set_channel_map(pa_format_info *f, const pa_channel_map *map
 }
 
 void pa_format_info_set_prop_int(pa_format_info *f, const char *key, int value) {
-    json_object *o;
-
     pa_assert(f);
     pa_assert(key);
 
-    o = json_object_new_int(value);
-
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
-
-    json_object_put(o);
+    pa_proplist_setf(f->plist, key, "%d", value);
 }
 
 void pa_format_info_set_prop_int_array(pa_format_info *f, const char *key, const int *values, int n_values) {
-    json_object *o;
+    pa_strbuf *buf;
+    char *str;
     int i;
 
     pa_assert(f);
     pa_assert(key);
+    pa_assert(n_values > 0);
 
-    o = json_object_new_array();
+    buf = pa_strbuf_new();
 
-    for (i = 0; i < n_values; i++)
-        json_object_array_add(o, json_object_new_int(values[i]));
+    pa_strbuf_printf(buf, "[ %d", values[0]);
 
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
+    for (i = 1; i < n_values; i++)
+        pa_strbuf_printf(buf, ", %d", values[i]);
 
-    json_object_put(o);
+    pa_strbuf_printf(buf, " ]");
+    str = pa_strbuf_to_string_free(buf);
+
+    pa_proplist_sets(f->plist, key, str);
+    pa_xfree (str);
 }
 
 void pa_format_info_set_prop_int_range(pa_format_info *f, const char *key, int min, int max) {
-    json_object *o;
-
     pa_assert(f);
     pa_assert(key);
 
-    o = json_object_new_object();
-
-    json_object_object_add(o, PA_JSON_MIN_KEY, json_object_new_int(min));
-    json_object_object_add(o, PA_JSON_MAX_KEY, json_object_new_int(max));
-
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
-
-    json_object_put(o);
+    pa_proplist_setf(f->plist, key, "{ \"" PA_JSON_MIN_KEY "\": %d, \"" PA_JSON_MAX_KEY "\": %d }",
+            min, max);
 }
 
 void pa_format_info_set_prop_string(pa_format_info *f, const char *key, const char *value) {
-    json_object *o;
-
     pa_assert(f);
     pa_assert(key);
 
-    o = json_object_new_string(value);
-
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
-
-    json_object_put(o);
+    pa_proplist_setf(f->plist, key, "\"%s\"", value);
 }
 
 void pa_format_info_set_prop_string_array(pa_format_info *f, const char *key, const char **values, int n_values) {
-    json_object *o;
+    pa_strbuf *buf;
+    char *str;
     int i;
 
     pa_assert(f);
     pa_assert(key);
 
-    o = json_object_new_array();
+    buf = pa_strbuf_new();
 
-    for (i = 0; i < n_values; i++)
-        json_object_array_add(o, json_object_new_string(values[i]));
+    pa_strbuf_printf(buf, "[ \"%s\"", values[0]);
+
+    for (i = 1; i < n_values; i++)
+        pa_strbuf_printf(buf, ", \"%s\"", values[i]);
 
-    pa_proplist_sets(f->plist, key, json_object_to_json_string(o));
+    pa_strbuf_printf(buf, " ]");
+    str = pa_strbuf_to_string_free(buf);
 
-    json_object_put(o);
+    pa_proplist_sets(f->plist, key, str);
+    pa_xfree (str);
 }
 
-static bool pa_json_is_fixed_type(json_object *o) {
-    switch(json_object_get_type(o)) {
-        case json_type_object:
-        case json_type_array:
+static bool pa_json_is_fixed_type(pa_json_object *o) {
+    switch(pa_json_object_get_type(o)) {
+        case PA_JSON_TYPE_OBJECT:
+        case PA_JSON_TYPE_ARRAY:
             return false;
 
         default:
@@ -614,20 +611,15 @@ static bool pa_json_is_fixed_type(json_object *o) {
     }
 }
 
-static int pa_json_value_equal(json_object *o1, json_object *o2) {
-    return (json_object_get_type(o1) == json_object_get_type(o2)) &&
-        pa_streq(json_object_to_json_string(o1), json_object_to_json_string(o2));
-}
-
 static int pa_format_info_prop_compatible(const char *one, const char *two) {
-    json_object *o1 = NULL, *o2 = NULL;
+    pa_json_object *o1 = NULL, *o2 = NULL;
     int i, ret = 0;
 
-    o1 = json_tokener_parse(one);
+    o1 = pa_json_parse(one);
     if (!o1)
         goto out;
 
-    o2 = json_tokener_parse(two);
+    o2 = pa_json_parse(two);
     if (!o2)
         goto out;
 
@@ -635,46 +627,46 @@ static int pa_format_info_prop_compatible(const char *one, const char *two) {
     pa_return_val_if_fail(pa_json_is_fixed_type(o1) || pa_json_is_fixed_type(o2), false);
 
     if (pa_json_is_fixed_type(o1) && pa_json_is_fixed_type(o2)) {
-        ret = pa_json_value_equal(o1, o2);
+        ret = pa_json_object_equal(o1, o2);
         goto out;
     }
 
     if (pa_json_is_fixed_type(o1)) {
-        json_object *tmp = o2;
+        pa_json_object *tmp = o2;
         o2 = o1;
         o1 = tmp;
     }
 
     /* o2 is now a fixed type, and o1 is not */
 
-    if (json_object_get_type(o1) == json_type_array) {
-        for (i = 0; i < json_object_array_length(o1); i++) {
-            if (pa_json_value_equal(json_object_array_get_idx(o1, i), o2)) {
+    if (pa_json_object_get_type(o1) == PA_JSON_TYPE_ARRAY) {
+        for (i = 0; i < pa_json_object_get_array_length(o1); i++) {
+            if (pa_json_object_equal(pa_json_object_get_array_member(o1, i), o2)) {
                 ret = 1;
                 break;
             }
         }
-    } else if (json_object_get_type(o1) == json_type_object) {
+    } else if (pa_json_object_get_type(o1) == PA_JSON_TYPE_OBJECT) {
         /* o1 should be a range type */
         int min, max, v;
-        json_object *o_min = NULL, *o_max = NULL;
+        const pa_json_object *o_min = NULL, *o_max = NULL;
 
-        if (json_object_get_type(o2) != json_type_int) {
+        if (pa_json_object_get_type(o2) != PA_JSON_TYPE_INT) {
             /* We don't support non-integer ranges */
             goto out;
         }
 
-        if (!json_object_object_get_ex(o1, PA_JSON_MIN_KEY, &o_min) ||
-            json_object_get_type(o_min) != json_type_int)
+        if (!(o_min = pa_json_object_get_object_member(o1, PA_JSON_MIN_KEY)) ||
+            pa_json_object_get_type(o_min) != PA_JSON_TYPE_INT)
             goto out;
 
-        if (!json_object_object_get_ex(o1, PA_JSON_MAX_KEY, &o_max) ||
-            json_object_get_type(o_max) != json_type_int)
+        if (!(o_max = pa_json_object_get_object_member(o1, PA_JSON_MAX_KEY)) ||
+            pa_json_object_get_type(o_max) != PA_JSON_TYPE_INT)
             goto out;
 
-        v = json_object_get_int(o2);
-        min = json_object_get_int(o_min);
-        max = json_object_get_int(o_max);
+        v = pa_json_object_get_int(o2);
+        min = pa_json_object_get_int(o_min);
+        max = pa_json_object_get_int(o_max);
 
         ret = v >= min && v <= max;
     } else {
@@ -683,9 +675,9 @@ static int pa_format_info_prop_compatible(const char *one, const char *two) {
 
 out:
     if (o1)
-        json_object_put(o1);
+        pa_json_object_unref(o1);
     if (o2)
-        json_object_put(o2);
+        pa_json_object_unref(o2);
 
     return ret;
 }
diff --git a/src/pulse/json.c b/src/pulse/json.c
index 7cb33ef..8484bba 100644
--- a/src/pulse/json.c
+++ b/src/pulse/json.c
@@ -44,8 +44,6 @@ struct pa_json_object {
     };
 };
 
-#define JSON_OBJECT_TYPE(o) ((o)->type)
-
 static const char* parse_value(const char *str, const char *end, pa_json_object **obj);
 
 static pa_json_object* json_object_new(void) {
@@ -272,7 +270,7 @@ static const char *parse_object(const char *str, pa_json_object *obj) {
         str++; /* Consume leading '{' or ',' */
 
         str = parse_value(str, ":", &name);
-        if (!str || JSON_OBJECT_TYPE(name) != PA_JSON_TYPE_STRING) {
+        if (!str || pa_json_object_get_type(name) != PA_JSON_TYPE_STRING) {
             pa_log("Could not parse key for object");
             goto error;
         }
@@ -408,7 +406,7 @@ static const char* parse_value(const char *str, const char *end, pa_json_object
         }
     }
 
-    if (JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_INIT) {
+    if (pa_json_object_get_type(o) == PA_JSON_TYPE_INIT) {
         /* We didn't actually get any data */
         pa_log("No data while parsing json string: '%s' till '%s'", str, pa_strnull(end));
         goto error;
@@ -444,14 +442,14 @@ pa_json_object* pa_json_parse(const char *str) {
 }
 
 pa_json_type pa_json_object_get_type(const pa_json_object *obj) {
-    return JSON_OBJECT_TYPE(obj);
+    return obj->type;
 }
 
 void pa_json_object_unref(pa_json_object *obj) {
     if (PA_REFCNT_DEC(obj) > 0)
         return;
 
-    switch (JSON_OBJECT_TYPE(obj)) {
+    switch (pa_json_object_get_type(obj)) {
         case PA_JSON_TYPE_INIT:
         case PA_JSON_TYPE_INT:
         case PA_JSON_TYPE_DOUBLE:
@@ -479,36 +477,92 @@ void pa_json_object_unref(pa_json_object *obj) {
 }
 
 int pa_json_object_get_int(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_INT, 0);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
     return o->int_value;
 }
 
 double pa_json_object_get_double(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_DOUBLE, 0);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
     return o->double_value;
 }
 
 bool pa_json_object_get_bool(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_BOOL, false);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
     return o->bool_value;
 }
 
 const char* pa_json_object_get_string(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_STRING, NULL);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING);
     return o->string_value;
 }
 
 const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_OBJECT, NULL);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
     return pa_hashmap_get(o->object_values, name);
 }
 
 int pa_json_object_get_array_length(const pa_json_object *o) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_ARRAY, 0);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
     return pa_idxset_size(o->array_values);
 }
 
 const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index) {
-    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_ARRAY, NULL);
+    pa_assert(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
     return pa_idxset_get_by_index(o->array_values, index);
 }
+
+bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2) {
+    int i;
+
+    if (pa_json_object_get_type(o1) != pa_json_object_get_type(o2))
+        return false;
+
+    switch (pa_json_object_get_type(o1)) {
+        case PA_JSON_TYPE_NULL:
+            return true;
+
+        case PA_JSON_TYPE_BOOL:
+            return o1->bool_value == o2->bool_value;
+
+        case PA_JSON_TYPE_INT:
+            return o1->int_value == o2->int_value;
+
+        case PA_JSON_TYPE_DOUBLE:
+            return PA_DOUBLE_IS_EQUAL(o1->double_value, o2->double_value);
+
+        case PA_JSON_TYPE_STRING:
+            return pa_streq(o1->string_value, o2->string_value);
+
+        case PA_JSON_TYPE_ARRAY:
+            if (pa_json_object_get_array_length(o1) != pa_json_object_get_array_length(o2))
+                return false;
+
+            for (i = 0; i < pa_json_object_get_array_length(o1); i++) {
+                if (!pa_json_object_equal(pa_json_object_get_array_member(o1, i),
+                            pa_json_object_get_array_member(o2, i)))
+                    return false;
+            }
+
+            return true;
+
+        case PA_JSON_TYPE_OBJECT: {
+            void *state;
+            const char *key;
+            const pa_json_object *v1, *v2;
+
+            if (pa_hashmap_size(o1->object_values) != pa_hashmap_size(o2->object_values))
+                return false;
+
+            PA_HASHMAP_FOREACH_KV(key, v1, o1->object_values, state) {
+                v2 = pa_json_object_get_object_member(o2, key);
+                if (!v2 || !pa_json_object_equal(v1, v2))
+                    return false;
+            }
+
+            return true;
+        }
+
+        default:
+            pa_assert_not_reached();
+    }
+}
diff --git a/src/pulse/json.h b/src/pulse/json.h
index 99c22ec..d8a946f 100644
--- a/src/pulse/json.h
+++ b/src/pulse/json.h
@@ -19,6 +19,8 @@
 
 #include <stdbool.h>
 
+#define PA_DOUBLE_IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001)
+
 typedef enum {
     PA_JSON_TYPE_INIT = 0,
     PA_JSON_TYPE_NULL,
@@ -47,3 +49,5 @@ const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o,
 
 int pa_json_object_get_array_length(const pa_json_object *o);
 const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index);
+
+bool pa_json_object_equal(const pa_json_object *o1, const pa_json_object *o2);
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
index e028e68..2e1ca6b 100644
--- a/src/tests/json-test.c
+++ b/src/tests/json-test.c
@@ -26,8 +26,6 @@
 #include <pulse/json.h>
 #include <pulsecore/core-util.h>
 
-#define IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001)
-
 START_TEST (string_test) {
     pa_json_object *o;
     unsigned int i;
@@ -85,7 +83,7 @@ START_TEST(double_test) {
 
         fail_unless(o != NULL);
         fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
-        fail_unless(IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
+        fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
 
         pa_json_object_unref(o);
     }
@@ -149,7 +147,7 @@ START_TEST(object_test) {
     v = pa_json_object_get_object_member(o, "age");
     fail_unless(v != NULL);
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
-    fail_unless(IS_EQUAL(pa_json_object_get_double(v), -45.3));
+    fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), -45.3));
 
     pa_json_object_unref(o);
 
@@ -205,7 +203,7 @@ START_TEST(array_test) {
     v = pa_json_object_get_array_member(o, 1);
     fail_unless(v != NULL);
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
-    fail_unless(IS_EQUAL(pa_json_object_get_double(v), 1234.5));
+    fail_unless(PA_DOUBLE_IS_EQUAL(pa_json_object_get_double(v), 1234.5));
     v = pa_json_object_get_array_member(o, 2);
     fail_unless(v != NULL);
     fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);

commit 6741e5ae760715c3294908ffd38dfdc71c0b1cd7
Author: Arun Raghavan <git at arunraghavan.net>
Date:   Wed Jun 1 17:18:31 2016 +0530

    pulse: Add a JSON-parsing library
    
    Adding this to be able to drop dependency on json-c.
    
    Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=95135
    
    Signed-off-by: Arun Raghavan <arun at arunraghavan.net>

diff --git a/src/.gitignore b/src/.gitignore
index bfe74bd..f7ec1bc 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -50,6 +50,7 @@ gtk-test
 hook-list-test
 interpol-test
 ipacl-test
+json-test
 lfe-filter-test
 lock-autospawn-test
 lo-latency-test
diff --git a/src/Makefile.am b/src/Makefile.am
index a311575..c6b998c 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -249,6 +249,7 @@ TESTS_default = \
 		thread-mainloop-test \
 		utf8-test \
 		format-test \
+		json-test \
 		get-binary-name-test \
 		hook-list-test \
 		memblock-test \
@@ -381,6 +382,11 @@ format_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
 format_test_LDADD = $(AM_LDADD) libpulsecore- at PA_MAJORMINOR@.la libpulse.la libpulsecommon- at PA_MAJORMINOR@.la
 format_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
 
+json_test_SOURCES = tests/json-test.c
+json_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
+json_test_LDADD = $(AM_LDADD) libpulse.la libpulsecommon- at PA_MAJORMINOR@.la
+json_test_LDFLAGS = $(AM_LDFLAGS) $(BINLDFLAGS) $(LIBCHECK_LIBS)
+
 srbchannel_test_SOURCES = tests/srbchannel-test.c
 srbchannel_test_CFLAGS = $(AM_CFLAGS) $(LIBCHECK_CFLAGS)
 srbchannel_test_LDADD = $(AM_LDADD) libpulse.la libpulsecommon- at PA_MAJORMINOR@.la
@@ -652,6 +658,7 @@ libpulsecommon_ at PA_MAJORMINOR@_la_SOURCES = \
 		pulse/client-conf.c pulse/client-conf.h \
 		pulse/fork-detect.c pulse/fork-detect.h \
 		pulse/format.c pulse/format.h \
+		pulse/json.c pulse/json.h \
 		pulse/xmalloc.c pulse/xmalloc.h \
 		pulse/proplist.c pulse/proplist.h \
 		pulse/utf8.c pulse/utf8.h \
diff --git a/src/pulse/json.c b/src/pulse/json.c
new file mode 100644
index 0000000..7cb33ef
--- /dev/null
+++ b/src/pulse/json.c
@@ -0,0 +1,514 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2016 Arun Raghavan <mail at arunraghavan.net>
+
+  PulseAudio is free software; you can redistribute it 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 <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <math.h>
+
+#include <pulse/json.h>
+#include <pulse/xmalloc.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/hashmap.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/strbuf.h>
+
+struct pa_json_object {
+    PA_REFCNT_DECLARE;
+    pa_json_type type;
+
+    union {
+        int int_value;
+        double double_value;
+        bool bool_value;
+        char *string_value;
+        pa_hashmap *object_values; /* name -> object */
+        pa_idxset *array_values; /* objects */
+    };
+};
+
+#define JSON_OBJECT_TYPE(o) ((o)->type)
+
+static const char* parse_value(const char *str, const char *end, pa_json_object **obj);
+
+static pa_json_object* json_object_new(void) {
+    pa_json_object *obj;
+
+    obj = pa_xnew0(pa_json_object, 1);
+
+    return obj;
+}
+
+static bool is_whitespace(char c) {
+    return c == '\t' || c == '\n' || c == '\r' || c == ' ';
+}
+
+static bool is_digit(char c) {
+    return c >= '0' && c <= '9';
+}
+
+static bool is_end(const char c, const char *end) {
+    if (!end)
+        return c == '\0';
+    else  {
+        while (*end) {
+            if (c == *end)
+                return true;
+            end++;
+        }
+    }
+
+    return false;
+}
+
+static const char* consume_string(const char *str, const char *expect) {
+    while (*expect) {
+        if (*str != *expect)
+            return NULL;
+
+        str++;
+        expect++;
+    }
+
+    return str;
+}
+
+static const char* parse_null(const char *str, pa_json_object *obj) {
+    str = consume_string(str, "null");
+
+    if (str)
+        obj->type = PA_JSON_TYPE_NULL;
+
+    return str;
+}
+
+static const char* parse_boolean(const char *str, pa_json_object *obj) {
+    const char *tmp;
+
+    tmp = consume_string(str, "true");
+
+    if (tmp) {
+        obj->type = PA_JSON_TYPE_BOOL;
+        obj->bool_value = true;
+    } else {
+        tmp = consume_string(str, "false");
+
+        if (str) {
+            obj->type = PA_JSON_TYPE_BOOL;
+            obj->bool_value = false;
+        }
+    }
+
+    return tmp;
+}
+
+static const char* parse_string(const char *str, pa_json_object *obj) {
+    pa_strbuf *buf = pa_strbuf_new();
+
+    str++; /* Consume leading '"' */
+
+    while (*str != '"') {
+        if (*str != '\\') {
+            /* We only accept ASCII printable characters. */
+            if (*str < 0x20 || *str > 0x7E) {
+                pa_log("Invalid non-ASCII character: 0x%x", (unsigned int) *str);
+                goto error;
+            }
+
+            /* Normal character, juts consume */
+            pa_strbuf_putc(buf, *str);
+        } else {
+            /* Need to unescape */
+            str++;
+
+            switch (*str) {
+                case '"':
+                case '\\':
+                case '/':
+                    pa_strbuf_putc(buf, *str);
+                    break;
+
+                case 'b':
+                    pa_strbuf_putc(buf, '\b' /* backspace */);
+                    break;
+
+                case 'f':
+                    pa_strbuf_putc(buf, '\f' /* form feed */);
+                    break;
+
+                case 'n':
+                    pa_strbuf_putc(buf, '\n' /* new line */);
+                    break;
+
+                case 'r':
+                    pa_strbuf_putc(buf, '\r' /* carriage return */);
+                    break;
+
+                case 't':
+                    pa_strbuf_putc(buf, '\t' /* horizontal tab */);
+                    break;
+
+                case 'u':
+                    pa_log("Unicode code points are currently unsupported");
+                    goto error;
+
+                default:
+                    pa_log("Unexepcted escape value: %c", *str);
+                    goto error;
+            }
+        }
+
+        str++;
+    }
+
+    if (*str != '"') {
+        pa_log("Failed to parse remainder of string: %s", str);
+        goto error;
+    }
+
+    str++;
+
+    obj->type = PA_JSON_TYPE_STRING;
+    obj->string_value = pa_strbuf_to_string_free(buf);
+
+    return str;
+
+error:
+    pa_strbuf_free(buf);
+    return NULL;
+}
+
+static const char* parse_number(const char *str, pa_json_object *obj) {
+    bool negative = false, has_fraction = false, has_exponent = false;
+    unsigned int integer = 0;
+    unsigned int fraction = 0;
+    unsigned int fraction_digits = 0;
+    int exponent = 0;
+
+    if (*str == '-') {
+        negative = true;
+        str++;
+    }
+
+    if (*str == '0') {
+        str++;
+        goto fraction;
+    }
+
+    while (is_digit(*str)) {
+        integer = (integer * 10) + (*str - '0');
+        str++;
+    }
+
+fraction:
+    if (*str == '.') {
+        has_fraction = true;
+        str++;
+
+        while (is_digit(*str)) {
+            fraction = (fraction * 10) + (*str - '0');
+            fraction_digits++;
+            str++;
+        }
+    }
+
+    if (*str == 'e' || *str == 'E') {
+        bool exponent_negative = false;
+
+        has_exponent = true;
+        str++;
+
+        if (*str == '-') {
+            exponent_negative = true;
+            str++;
+        } else if (*str == '+')
+            str++;
+
+        while (is_digit(*str)) {
+            exponent = (exponent * 10) + (*str - '0');
+            str++;
+        }
+
+        if (exponent_negative)
+            exponent *= -1;
+    }
+
+    if (has_fraction || has_exponent) {
+        obj->type = PA_JSON_TYPE_DOUBLE;
+        obj->double_value =
+            (negative ? -1.0 : 1.0) * (integer + (double) fraction / pow(10, fraction_digits)) * pow(10, exponent);
+    } else {
+        obj->type = PA_JSON_TYPE_INT;
+        obj->int_value = (negative ? -1 : 1) * integer;
+    }
+
+    return str;
+}
+
+static const char *parse_object(const char *str, pa_json_object *obj) {
+    pa_json_object *name = NULL, *value = NULL;
+
+    obj->object_values = pa_hashmap_new_full(pa_idxset_string_hash_func, pa_idxset_string_compare_func,
+                                             pa_xfree, (pa_free_cb_t) pa_json_object_unref);
+
+    while (*str != '}') {
+        str++; /* Consume leading '{' or ',' */
+
+        str = parse_value(str, ":", &name);
+        if (!str || JSON_OBJECT_TYPE(name) != PA_JSON_TYPE_STRING) {
+            pa_log("Could not parse key for object");
+            goto error;
+        }
+
+        /* Consume the ':' */
+        str++;
+
+        str = parse_value(str, ",}", &value);
+        if (!str) {
+            pa_log("Could not parse value for object");
+            goto error;
+        }
+
+        pa_hashmap_put(obj->object_values, pa_xstrdup(pa_json_object_get_string(name)), value);
+        pa_json_object_unref(name);
+
+        name = NULL;
+        value = NULL;
+    }
+
+    /* Drop trailing '}' */
+    str++;
+
+    /* We now know the value was correctly parsed */
+    obj->type = PA_JSON_TYPE_OBJECT;
+
+    return str;
+
+error:
+    pa_hashmap_free(obj->object_values);
+    obj->object_values = NULL;
+
+    if (name)
+        pa_json_object_unref(name);
+    if (value)
+        pa_json_object_unref(value);
+
+    return NULL;
+}
+
+static const char *parse_array(const char *str, pa_json_object *obj) {
+    pa_json_object *value;
+
+    obj->array_values = pa_idxset_new(NULL, NULL);
+
+    while (*str != ']') {
+        str++; /* Consume leading '[' or ',' */
+
+        /* Need to chew up whitespaces as a special case to deal with the
+         * possibility of an empty array */
+        while (is_whitespace(*str))
+            str++;
+
+        if (*str == ']')
+            break;
+
+        str = parse_value(str, ",]", &value);
+        if (!str) {
+            pa_log("Could not parse value for array");
+            goto error;
+        }
+
+        pa_idxset_put(obj->array_values, value, NULL);
+    }
+
+    /* Drop trailing ']' */
+    str++;
+
+    /* We now know the value was correctly parsed */
+    obj->type = PA_JSON_TYPE_ARRAY;
+
+    return str;
+
+error:
+    pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_unref);
+    obj->array_values = NULL;
+    return NULL;
+}
+
+typedef enum {
+    JSON_PARSER_STATE_INIT,
+    JSON_PARSER_STATE_FINISH,
+} json_parser_state;
+
+static const char* parse_value(const char *str, const char *end, pa_json_object **obj) {
+    json_parser_state state = JSON_PARSER_STATE_INIT;
+    pa_json_object *o;
+
+    pa_assert(str != NULL);
+
+    o = json_object_new();
+
+    while (!is_end(*str, end)) {
+        switch (state) {
+            case JSON_PARSER_STATE_INIT:
+                if (is_whitespace(*str)) {
+                    str++;
+                } else if (*str == 'n') {
+                    str = parse_null(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (*str == 't' || *str == 'f') {
+                    str = parse_boolean(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (*str == '"') {
+                    str = parse_string(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (is_digit(*str) || *str == '-') {
+                    str = parse_number(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (*str == '{') {
+                    str = parse_object(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else if (*str == '[') {
+                    str = parse_array(str, o);
+                    state = JSON_PARSER_STATE_FINISH;
+                } else {
+                    pa_log("Invalid JSON string: %s", str);
+                    goto error;
+                }
+
+                if (!str)
+                    goto error;
+
+                break;
+
+            case JSON_PARSER_STATE_FINISH:
+                /* Consume trailing whitespaces */
+                if (is_whitespace(*str)) {
+                    str++;
+                } else {
+                    goto error;
+                }
+        }
+    }
+
+    if (JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_INIT) {
+        /* We didn't actually get any data */
+        pa_log("No data while parsing json string: '%s' till '%s'", str, pa_strnull(end));
+        goto error;
+    }
+
+    *obj = o;
+
+    return str;
+
+error:
+    pa_json_object_unref(o);
+    return NULL;
+}
+
+
+pa_json_object* pa_json_parse(const char *str) {
+    pa_json_object *obj;
+
+    str = parse_value(str, NULL, &obj);
+
+    if (!str) {
+        pa_log("JSON parsing failed");
+        return NULL;
+    }
+
+    if (*str != '\0') {
+        pa_log("Unable to parse complete JSON string, remainder is: %s", str);
+        pa_json_object_unref(obj);
+        return NULL;
+    }
+
+    return obj;
+}
+
+pa_json_type pa_json_object_get_type(const pa_json_object *obj) {
+    return JSON_OBJECT_TYPE(obj);
+}
+
+void pa_json_object_unref(pa_json_object *obj) {
+    if (PA_REFCNT_DEC(obj) > 0)
+        return;
+
+    switch (JSON_OBJECT_TYPE(obj)) {
+        case PA_JSON_TYPE_INIT:
+        case PA_JSON_TYPE_INT:
+        case PA_JSON_TYPE_DOUBLE:
+        case PA_JSON_TYPE_BOOL:
+        case PA_JSON_TYPE_NULL:
+            break;
+
+        case PA_JSON_TYPE_STRING:
+            pa_xfree(obj->string_value);
+            break;
+
+        case PA_JSON_TYPE_OBJECT:
+            pa_hashmap_free(obj->object_values);
+            break;
+
+        case PA_JSON_TYPE_ARRAY:
+            pa_idxset_free(obj->array_values, (pa_free_cb_t) pa_json_object_unref);
+            break;
+
+        default:
+            pa_assert_not_reached();
+    }
+
+    pa_xfree(obj);
+}
+
+int pa_json_object_get_int(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_INT, 0);
+    return o->int_value;
+}
+
+double pa_json_object_get_double(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_DOUBLE, 0);
+    return o->double_value;
+}
+
+bool pa_json_object_get_bool(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_BOOL, false);
+    return o->bool_value;
+}
+
+const char* pa_json_object_get_string(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_STRING, NULL);
+    return o->string_value;
+}
+
+const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_OBJECT, NULL);
+    return pa_hashmap_get(o->object_values, name);
+}
+
+int pa_json_object_get_array_length(const pa_json_object *o) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_ARRAY, 0);
+    return pa_idxset_size(o->array_values);
+}
+
+const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index) {
+    pa_return_val_if_fail(JSON_OBJECT_TYPE(o) == PA_JSON_TYPE_ARRAY, NULL);
+    return pa_idxset_get_by_index(o->array_values, index);
+}
diff --git a/src/pulse/json.h b/src/pulse/json.h
new file mode 100644
index 0000000..99c22ec
--- /dev/null
+++ b/src/pulse/json.h
@@ -0,0 +1,49 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2016 Arun Raghavan <mail at arunraghavan.net>
+
+  PulseAudio is free software; you can redistribute it 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 <http://www.gnu.org/licenses/>.
+***/
+
+#include <stdbool.h>
+
+typedef enum {
+    PA_JSON_TYPE_INIT = 0,
+    PA_JSON_TYPE_NULL,
+    PA_JSON_TYPE_INT,
+    PA_JSON_TYPE_DOUBLE,
+    PA_JSON_TYPE_BOOL,
+    PA_JSON_TYPE_STRING,
+    PA_JSON_TYPE_ARRAY,
+    PA_JSON_TYPE_OBJECT,
+} pa_json_type;
+
+typedef struct pa_json_object pa_json_object;
+
+pa_json_object* pa_json_parse(const char *str);
+pa_json_type pa_json_object_get_type(const pa_json_object *obj);
+void pa_json_object_unref(pa_json_object *obj);
+
+/* All pointer members that are returned are valid while the corresponding object is valid */
+
+int pa_json_object_get_int(const pa_json_object *o);
+double pa_json_object_get_double(const pa_json_object *o);
+bool pa_json_object_get_bool(const pa_json_object *o);
+const char* pa_json_object_get_string(const pa_json_object *o);
+
+const pa_json_object* pa_json_object_get_object_member(const pa_json_object *o, const char *name);
+
+int pa_json_object_get_array_length(const pa_json_object *o);
+const pa_json_object* pa_json_object_get_array_member(const pa_json_object *o, int index);
diff --git a/src/tests/json-test.c b/src/tests/json-test.c
new file mode 100644
index 0000000..e028e68
--- /dev/null
+++ b/src/tests/json-test.c
@@ -0,0 +1,244 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2016 Arun Raghavan <mail at arunraghavan.net>
+
+  PulseAudio is free software; you can redistribute it 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 <http://www.gnu.org/licenses/>.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <check.h>
+
+#include <pulse/json.h>
+#include <pulsecore/core-util.h>
+
+#define IS_EQUAL(x, y) (((x) - (y)) < 0.000001 && ((x) - (y)) > -0.000001)
+
+START_TEST (string_test) {
+    pa_json_object *o;
+    unsigned int i;
+    const char *strings_parse[] = {
+        "\"\"", "\"test\"", "\"test123\"", "\"123\"", "\"newline\\n\"", "\"  spaces \"",
+        "   \"lots of spaces\"     ", "\"esc\\nape\"", "\"escape a \\\" quote\"",
+    };
+    const char *strings_compare[] = {
+        "", "test", "test123", "123", "newline\n", "  spaces ",
+        "lots of spaces", "esc\nape", "escape a \" quote",
+    };
+
+    for (i = 0; i < PA_ELEMENTSOF(strings_parse); i++) {
+        o = pa_json_parse(strings_parse[i]);
+
+        fail_unless(o != NULL);
+        fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_STRING);
+        fail_unless(pa_streq(pa_json_object_get_string(o), strings_compare[i]));
+
+        pa_json_object_unref(o);
+    }
+}
+END_TEST
+
+START_TEST(int_test) {
+    pa_json_object *o;
+    unsigned int i;
+    const char *ints_parse[] = { "1", "-1", "1234", "0" };
+    const int ints_compare[] = { 1, -1, 1234, 0 };
+
+    for (i = 0; i < PA_ELEMENTSOF(ints_parse); i++) {
+        o = pa_json_parse(ints_parse[i]);
+
+        fail_unless(o != NULL);
+        fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_INT);
+        fail_unless(pa_json_object_get_int(o) == ints_compare[i]);
+
+        pa_json_object_unref(o);
+    }
+}
+END_TEST
+
+START_TEST(double_test) {
+    pa_json_object *o;
+    unsigned int i;
+    const char *doubles_parse[] = {
+        "1.0", "-1.1", "1234e2", "1234e0", "0.1234", "-0.1234", "1234e-1", "1234.5e-1", "1234.5e+2",
+    };
+    const double doubles_compare[] = {
+        1.0, -1.1, 123400.0, 1234.0, 0.1234, -0.1234, 123.4, 123.45, 123450.0,
+    };
+
+    for (i = 0; i < PA_ELEMENTSOF(doubles_parse); i++) {
+        o = pa_json_parse(doubles_parse[i]);
+
+        fail_unless(o != NULL);
+        fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_DOUBLE);
+        fail_unless(IS_EQUAL(pa_json_object_get_double(o), doubles_compare[i]));
+
+        pa_json_object_unref(o);
+    }
+}
+END_TEST
+
+START_TEST(null_test) {
+    pa_json_object *o;
+
+    o = pa_json_parse("null");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_NULL);
+
+    pa_json_object_unref(o);
+}
+END_TEST
+
+START_TEST(bool_test) {
+    pa_json_object *o;
+
+    o = pa_json_parse("true");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(o) == true);
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse("false");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(o) == false);
+
+    pa_json_object_unref(o);
+}
+END_TEST
+
+START_TEST(object_test) {
+    pa_json_object *o;
+    const pa_json_object *v;
+
+    o = pa_json_parse(" { \"name\" : \"A Person\" } ");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+    v = pa_json_object_get_object_member(o, "name");
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
+    fail_unless(pa_streq(pa_json_object_get_string(v), "A Person"));
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse(" { \"age\" : -45.3e-0 } ");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+    v = pa_json_object_get_object_member(o, "age");
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
+    fail_unless(IS_EQUAL(pa_json_object_get_double(v), -45.3));
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse("{\"person\":true}");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_OBJECT);
+
+    v = pa_json_object_get_object_member(o, "person");
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(v) == true);
+
+    pa_json_object_unref(o);
+}
+END_TEST
+
+START_TEST(array_test) {
+    pa_json_object *o;
+    const pa_json_object *v, *v2;
+
+    o = pa_json_parse(" [  ] ");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
+    fail_unless(pa_json_object_get_array_length(o) == 0);
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse("[\"a member\"]");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
+    fail_unless(pa_json_object_get_array_length(o) == 1);
+
+    v = pa_json_object_get_array_member(o, 0);
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
+    fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
+
+    pa_json_object_unref(o);
+
+    o = pa_json_parse("[\"a member\", 1234.5, { \"another\": true } ]");
+
+    fail_unless(o != NULL);
+    fail_unless(pa_json_object_get_type(o) == PA_JSON_TYPE_ARRAY);
+    fail_unless(pa_json_object_get_array_length(o) == 3);
+
+    v = pa_json_object_get_array_member(o, 0);
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_STRING);
+    fail_unless(pa_streq(pa_json_object_get_string(v), "a member"));
+    v = pa_json_object_get_array_member(o, 1);
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_DOUBLE);
+    fail_unless(IS_EQUAL(pa_json_object_get_double(v), 1234.5));
+    v = pa_json_object_get_array_member(o, 2);
+    fail_unless(v != NULL);
+    fail_unless(pa_json_object_get_type(v) == PA_JSON_TYPE_OBJECT);
+    v2 =pa_json_object_get_object_member(v, "another");
+    fail_unless(v2 != NULL);
+    fail_unless(pa_json_object_get_type(v2) == PA_JSON_TYPE_BOOL);
+    fail_unless(pa_json_object_get_bool(v2) == true);
+
+    pa_json_object_unref(o);
+}
+END_TEST
+
+int main(int argc, char *argv[]) {
+    int failed = 0;
+    Suite *s;
+    TCase *tc;
+    SRunner *sr;
+
+    s = suite_create("JSON");
+    tc = tcase_create("json");
+    tcase_add_test(tc, string_test);
+    tcase_add_test(tc, int_test);
+    tcase_add_test(tc, double_test);
+    tcase_add_test(tc, null_test);
+    tcase_add_test(tc, bool_test);
+    tcase_add_test(tc, object_test);
+    tcase_add_test(tc, array_test);
+    suite_add_tcase(s, tc);
+
+    sr = srunner_create(s);
+    srunner_run_all(sr, CK_NORMAL);
+    failed = srunner_ntests_failed(sr);
+    srunner_free(sr);
+
+    return (failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}



More information about the pulseaudio-commits mailing list