[pulseaudio-discuss] [RFC PATCH] Log module: new module to log PCM chunks.

Vincent Becker vincentx.becker at intel.com
Mon Mar 21 07:51:19 PDT 2011


This module logs Pulseaudio devices like sinks and sources, and their ports if needed, to log files. Log files can be saved in wav files, by giving a target directory.
Example : load module-log-pcm sinks_to_log=sink1,sink2 sources_to_log=source1,source2 trace_ports=yes log_target=./log/wav_files/
---
 src/Makefile.am                            |   11 +-
 src/modules/log/local-file-serializer.c    |  181 +++++
 src/modules/log/local-file-serializer.h    |   47 ++
 src/modules/log/log-pcm-serializer.c       |   50 ++
 src/modules/log/log-pcm-serializer.h       |   52 ++
 src/modules/log/module-log-pcm.c           | 1113 ++++++++++++++++++++++++++++
 src/modules/log/pcm-logger-data.h          |   57 ++
 src/modules/log/remote-device-serializer.c |  140 ++++
 src/modules/log/remote-device-serializer.h |   48 ++
 src/pulsecore/core.h                       |    2 +
 src/pulsecore/sink-input.c                 |   18 +
 src/pulsecore/sink-input.h                 |    5 +
 src/pulsecore/source-output.c              |   27 +-
 src/pulsecore/source-output.h              |    6 +
 14 files changed, 1753 insertions(+), 4 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am
index 71ad19b..6bd7ca3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1039,7 +1039,8 @@ modlibexec_LTLIBRARIES += \
 		module-cork-music-on-phone.la \
 		module-loopback.la \
 		module-virtual-sink.la \
-		module-virtual-source.la
+		module-virtual-source.la \
+		module-log-pcm.la
 
 # See comment at librtp.la above
 if !OS_IS_WIN32
@@ -1324,7 +1325,8 @@ SYMDEF_FILES = \
 		module-dbus-protocol-symdef.h \
 		module-loopback-symdef.h \
 		module-virtual-sink-symdef.h \
-		module-virtual-source-symdef.h
+		module-virtual-source-symdef.h \
+		module-log-pcm-symdef.h
 
 EXTRA_DIST += $(SYMDEF_FILES)
 BUILT_SOURCES += $(SYMDEF_FILES) builddirs
@@ -1505,6 +1507,11 @@ module_virtual_source_la_SOURCES = modules/module-virtual-source.c
 module_virtual_source_la_LDFLAGS = $(MODULE_LDFLAGS)
 module_virtual_source_la_LIBADD = $(MODULE_LIBADD)
 
+# PCM LOG
+module_log_pcm_la_SOURCES = modules/log/module-log-pcm.c modules/log/log-pcm-serializer.c modules/log/log-pcm-serializer.h modules/log/pcm-logger.h modules/log/local-file-serializer.c modules/log/local-file-serializer.h modules/log/remote-device-serializer.c modules/log/remote-device-serializer.h
+module_log_pcm_la_LDFLAGS = $(MODULE_LDFLAGS)
+module_log_pcm_la_LIBADD = $(MODULE_LIBADD)
+
 # X11
 
 module_x11_bell_la_SOURCES = modules/x11/module-x11-bell.c
diff --git a/src/modules/log/local-file-serializer.c b/src/modules/log/local-file-serializer.c
new file mode 100755
index 0000000..80d312b
--- /dev/null
+++ b/src/modules/log/local-file-serializer.c
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * Contact:  Vincent Becker <vincentx.becker at intel.com>
+ *
+ * These PulseAudio Modules are 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
+ * version 2.1 of the License.
+ *
+ * PulseAudio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/core-util.h>
+
+#include "local-file-serializer.h"
+
+PA_DEFINE_PUBLIC_CLASS(local_file_serializer, log_pcm_serializer);
+
+static void local_serializer_free(pa_object *l);
+
+
+log_pcm_serializer* local_file_serializer_new(void) {
+    local_file_serializer *l;
+
+    l = log_pcm_serializer_new(local_file_serializer);
+
+    l->parent.parent.free = &local_serializer_free;
+    l->parent.write_pcm_log = &write_in_wav_files;
+    l->parent.open_target = &open_wav_file;
+    l->parent.close_target = &close_wav_file;
+
+    local_file_serializer_ref(l);
+
+    return LOG_PCM_SERIALIZER(l);
+}
+
+int open_wav_file(const char *t, struct pcm_log_data *pl) {
+    int file_format;
+    char filename[256];
+
+    pa_log_debug("%d: %s() called", __LINE__, __FUNCTION__);
+
+    pa_zero(pl->sfi);
+
+    file_format = pa_sndfile_format_from_string("wav");
+
+    if (pa_sndfile_write_sample_spec(&pl->sfi, &pl->sample_spec_to_log) < 0) {
+        pa_log_error("Failed to generate sample specification for file.");
+        return -1;
+    }
+
+    pl->sfi.format |= file_format;
+    pl->sfi.seekable = TRUE;
+
+    pa_log_debug("SFI format : 0x%x\nSFI channels : %d\nSFI Seekable : %d\nSFI Sample Rate : %d\n", pl->sfi.format, pl->sfi.channels, pl->sfi.seekable, pl->sfi.samplerate);
+
+    pa_snprintf(filename, sizeof(filename), "%s%s%s%s", t, "/", pl->audio_flow_name, ".wav");
+
+    pa_log_debug("OPEN WAV FILE : filename : %s", filename);
+    if((pl->file_des = open(filename, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU)) >=0)
+        pa_log_debug("Opened a regular file descriptor : %d for creating a sound file", pl->file_des);
+    else {
+        pa_log_error("Could not open local file : %s", strerror(errno));
+        return -1;
+    }
+
+    pa_log_debug("Path of the wave file : %s", filename);
+
+    if (!(pl->sndfile = sf_open_fd(pl->file_des, SFM_WRITE, &pl->sfi, SF_FALSE))) {
+        pa_log_error("Error opening sound file : %s", sf_strerror(pl->sndfile));
+        return -1;
+    }
+
+    if(pa_sndfile_write_channel_map(pl->sndfile, &pl->channel_map_to_log) < 0)
+        pa_log_error("Fail to write channel map");
+
+    pl->write_sound_file = pa_sndfile_writef_function(&pl->sample_spec_to_log);
+
+    return 0;
+}
+
+int write_in_wav_files(const void *rawdata, int length, struct pcm_log_data *pl) {
+    size_t sample_size;
+    size_t frame_size;
+    sf_count_t frames_written;
+    uint32_t *raw2432data = NULL;
+    const uint32_t *tmprawdata = NULL;
+    uint32_t *tmpraw2432data = NULL;
+    int nbsamples;
+    int nbframes;
+
+    /* Function pointer depending on the sample spec */
+    if (pl->write_sound_file) {
+        /* Number of samples in the chunk */
+        sample_size = pa_sample_size(&pl->sample_spec_to_log);
+        nbsamples = length/sample_size;
+
+        /* Number of frames in the chunk. Shall be half the size of the number of samples, in case of stereo ..*/
+        frame_size = pa_frame_size(&pl->sample_spec_to_log);
+        nbframes = length/frame_size;
+
+        /* For the 24/32 format, we need to shift 8 bits to the left so that the 24 bits containing
+	   data fit into 32 bits. The MSB will then have a consistent value and the audio quality
+	   will be restored in the wav file */
+        if(pl->sample_spec_to_log.format == PA_SAMPLE_S24_32NE) {
+            int sample;
+            pa_log_debug("Format 24-32, décalage de 8 bits vers la gauche.");
+
+            /* Copy chunk data into another buffer */
+            raw2432data = alloca(nbsamples * sizeof(uint32_t));
+            tmprawdata = (const uint32_t *)rawdata;
+            tmpraw2432data = raw2432data;
+
+            /* Shift left every sample */
+            for(sample = 0; sample < nbsamples; sample++) {
+                *tmpraw2432data = *(uint32_t *)tmprawdata;
+                *tmpraw2432data = (*tmpraw2432data) << 8;
+                tmprawdata++;
+                tmpraw2432data++;
+            }
+
+            if ((frames_written = pl->write_sound_file(pl->sndfile, raw2432data, (sf_count_t)(nbframes))) > 0) {
+                pl->pcm_samples += frames_written;
+            }
+            else {
+                pa_log_error("Error writing in wav file : %s", sf_strerror(pl->sndfile));
+                return -1;
+            }
+        }
+        /* For other formats, write data as-is */
+        else {
+            if ((frames_written = pl->write_sound_file(pl->sndfile, rawdata, (sf_count_t)(nbframes))) > 0)
+                pl->pcm_samples += frames_written;
+            else
+                return -1;
+        }
+    }
+
+    return 0;
+}
+
+int close_wav_file(struct pcm_log_data *pl) {
+    int r;
+
+    if ((r = sf_close(pl->sndfile))) {
+        pa_log_error("Error closing sound file : %s", sf_strerror(pl->sndfile));
+        return r;
+    }
+
+    return 0;
+}
+
+/* Called from main context */
+static void local_serializer_free(pa_object *o) {
+    local_file_serializer *l = LOCAL_FILE_SERIALIZER(o);
+
+    local_file_serializer_unref(l);
+    local_file_serializer_assert_ref(l);
+
+    pa_assert(l);
+
+    pa_xfree(l);
+}
diff --git a/src/modules/log/local-file-serializer.h b/src/modules/log/local-file-serializer.h
new file mode 100755
index 0000000..91ff96a
--- /dev/null
+++ b/src/modules/log/local-file-serializer.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * Contact:  Vincent Becker <vincentx.becker at intel.com>
+ *
+ * These PulseAudio Modules are 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
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+#ifndef local_file_serializer_h
+#define local_file_serializer_h
+
+#include "log-pcm-serializer.h"
+
+typedef struct local_file_serializer local_file_serializer;
+
+
+struct local_file_serializer {
+    log_pcm_serializer parent;
+    int i;
+};
+
+log_pcm_serializer* local_file_serializer_new(void);
+
+int open_wav_file(const char *t, struct pcm_log_data *pl);
+
+int write_in_wav_files(const void *ptr, int length, struct pcm_log_data *pl);
+
+int close_wav_file(struct pcm_log_data *pl);
+
+PA_DECLARE_PUBLIC_CLASS(local_file_serializer);
+
+#define LOCAL_FILE_SERIALIZER(f) (local_file_serializer_cast(f))
+
+#endif
diff --git a/src/modules/log/log-pcm-serializer.c b/src/modules/log/log-pcm-serializer.c
new file mode 100755
index 0000000..4b2b090
--- /dev/null
+++ b/src/modules/log/log-pcm-serializer.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * Contact:  Vincent Becker <vincentx.becker at intel.com>
+ *
+ * These PulseAudio Modules are 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
+ * version 2.1 of the License.
+ *
+ * PulseAudio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "log-pcm-serializer.h"
+
+PA_DEFINE_PUBLIC_CLASS(log_pcm_serializer, pa_object);
+
+/* Mother class of local-file-serializer and remote-device-serializer classes */
+log_pcm_serializer *log_pcm_serializer_new_internal(size_t size, const char *type_id, pa_bool_t (*check_type)(const char *type_name)) {
+    log_pcm_serializer *l;
+
+    pa_assert(size > sizeof(log_pcm_serializer));
+    pa_assert(type_id);
+
+    if (!check_type)
+        check_type = log_pcm_serializer_check_type;
+
+    pa_assert(check_type(type_id));
+    pa_assert(check_type(pa_object_type_id));
+    pa_assert(check_type(log_pcm_serializer_type_id));
+
+    l = LOG_PCM_SERIALIZER(pa_object_new_internal(size, type_id, check_type));
+    l->open_target = NULL;
+    l->close_target = NULL;
+    l->write_pcm_log = NULL;
+
+    return l;
+}
diff --git a/src/modules/log/log-pcm-serializer.h b/src/modules/log/log-pcm-serializer.h
new file mode 100755
index 0000000..da29924
--- /dev/null
+++ b/src/modules/log/log-pcm-serializer.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * Contact:  Vincent Becker <vincentx.becker at intel.com>
+ *
+ * These PulseAudio Modules are 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
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+#ifndef __LOG_PCM_SERIALIZER_H__
+#define __LOG_PCM_SERIALIZER_H__
+
+#include <sys/types.h>
+
+#include <pulse/xmalloc.h>
+#include <pulsecore/refcnt.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/object.h>
+
+#include "pcm-logger-data.h"
+
+typedef struct log_pcm_serializer log_pcm_serializer;
+
+struct log_pcm_serializer {
+    pa_object parent;
+    int (*write_pcm_log)(const void *ptr, int length, struct pcm_log_data *pl);
+    int (*open_target)(const char *t, struct pcm_log_data *pl);
+    int (*close_target)(struct pcm_log_data *pl);
+};
+
+log_pcm_serializer *log_pcm_serializer_new_internal(size_t size, const char *type_id, pa_bool_t (*check_type)(const char *type_name));
+
+#define log_pcm_serializer_new(type) ((type*) log_pcm_serializer_new_internal(sizeof(type), type##_type_id, type##_check_type))
+#define log_pcm_serializer_free ((void (*) (log_pcm_serializer* l)) pa_object_free)
+
+#define LOG_PCM_SERIALIZER(l) log_pcm_serializer_cast(l)
+
+PA_DECLARE_PUBLIC_CLASS(log_pcm_serializer);
+
+#endif
diff --git a/src/modules/log/module-log-pcm.c b/src/modules/log/module-log-pcm.c
new file mode 100755
index 0000000..730d567
--- /dev/null
+++ b/src/modules/log/module-log-pcm.c
@@ -0,0 +1,1113 @@
+/*
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * Contact:  Vincent Becker <vincentx.becker at intel.com>
+ *
+ * These PulseAudio Modules are 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
+ * version 2.1 of the License.
+ *
+ * PulseAudio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sndfile.h>
+
+#include <pulse/pulseaudio.h>
+#include <pulse/xmalloc.h>
+
+#include <pulsecore/module.h>
+#include <pulsecore/log.h>
+#include <pulsecore/namereg.h>
+#include <pulsecore/modargs.h>
+#include <pulsecore/macro.h>
+#include <pulsecore/core-util.h>
+#include <pulsecore/dynarray.h>
+
+#include "module-log-pcm-symdef.h"
+#include "remote-device-serializer.h"
+#include "local-file-serializer.h"
+
+PA_MODULE_AUTHOR("Vincent Becker");
+PA_MODULE_DESCRIPTION("Log PCM samples from memory chunks");
+PA_MODULE_VERSION(PACKAGE_VERSION);
+PA_MODULE_LOAD_ONCE(TRUE);
+PA_MODULE_USAGE("sinks_to_log=<> sources_to_log=<> log_target=<> trace_ports=<yes/no>");
+
+#define LOG_PCM_SOURCE_OUTPUT_NAME "module-log-pcm working source output for"
+
+static const char* valid_modargs[] = {
+    "sources_to_log",
+    "sinks_to_log",
+    "log_target",
+    "trace_ports",
+};
+
+typedef struct sink_port_logger sink_port_logger;
+typedef struct source_port_logger source_port_logger;
+
+struct sink_port_logger {
+    pa_hook_slot
+        *sink_input_pop_memchunk_slot,
+        *sink_input_new_hook_slot,
+        *sink_input_put_slot,
+        *sink_input_move_slot,
+        *sink_input_unlink_slot;
+};
+
+struct source_port_logger {
+    pa_hook_slot
+        *source_output_push_memchunk_slot,
+        *source_output_new_hook_slot,
+        *source_output_put_slot,
+        *source_output_move_slot,
+        *source_output_unlink_slot;
+};
+
+struct userdata {
+    pa_core *core;
+    pa_module *module;
+    pa_modargs *modargs;
+
+    pa_hashmap *audiotree_log_map;
+
+    pa_subscription *port_subscription;
+    pa_subscription *end_subscription;
+
+    pa_hook_slot *sink_state_changed_slot,
+                 *source_state_changed_slot;
+
+    log_pcm_serializer *l;
+
+    sink_port_logger *sink_port_logger;
+    source_port_logger *source_port_logger;
+
+    /* Specify a character/block device or a directory for local save */
+    char target_pathname[256];
+
+    /* Counts the number of sink input or source output moves during the module lifetime */
+    unsigned int sink_input_moves;
+    unsigned int source_output_moves;
+
+    unsigned int handovers;
+
+    /* Used to log sink inputs and/or source outputs */
+    pa_bool_t trace_ports;
+};
+
+
+static void free_audiolog_data(void *v, void *userdata) {
+    struct pcm_log_data *p = v;
+
+    pa_assert(p);
+
+    pa_xfree(p);
+}
+
+
+static void* acquire_data_at_format(pa_sample_format_t format, const pa_memchunk *chunk) {
+    void *rawsnd;
+    uint8_t *rawsnd8;
+    short *rawsnd16;
+    uint32_t *rawsnd32;
+
+    switch (format) {
+        case PA_SAMPLE_U8:
+        case PA_SAMPLE_ULAW:
+        case PA_SAMPLE_ALAW:
+        case PA_SAMPLE_S24NE:
+            rawsnd8 = (uint8_t*)(pa_memblock_acquire(chunk->memblock)) + chunk->index/sizeof(uint8_t);
+            rawsnd = rawsnd8;
+            break;
+
+        case PA_SAMPLE_S16NE:
+            rawsnd16 = (short*)(pa_memblock_acquire(chunk->memblock)) + chunk->index/sizeof(short);
+            rawsnd = rawsnd16;
+            break;
+
+        case PA_SAMPLE_S24_32NE:
+        case PA_SAMPLE_S32NE:
+        case PA_SAMPLE_FLOAT32NE:
+            rawsnd32 = (uint32_t*)(pa_memblock_acquire(chunk->memblock)) + chunk->index/sizeof(uint32_t);
+            rawsnd = rawsnd32;
+            break;
+
+        default:
+            rawsnd8 = (uint8_t*)(pa_memblock_acquire(chunk->memblock)) + chunk->index/sizeof(uint8_t);
+            rawsnd = rawsnd8;
+            break;
+    }
+
+    return rawsnd;
+}
+
+static pa_bool_t is_a_working_so(pa_source_output *o) {
+    const char *n;
+    pa_bool_t is_wso = FALSE;
+
+    if((n = pa_proplist_gets(o->proplist, PA_PROP_MEDIA_NAME)) && pa_startswith(n, LOG_PCM_SOURCE_OUTPUT_NAME)) {
+        is_wso = TRUE;
+        pa_log_info("Source output used for log : %s", n);
+    }
+
+    return is_wso;
+}
+
+/* Push callback for all the sources/sinks logged. To get the corresponding source/sink,
+   search the name in the audio tree map */
+static void log_working_source_output_push_cb(pa_source_output *o, const pa_memchunk *chunk) {
+    struct userdata *u;
+    struct pcm_log_data *p;
+    void *rawsnd = NULL;
+    unsigned nb_uncorked_so;
+
+    pa_assert_se(u = o->userdata);
+
+    /* Check the number of uncorked sources. If it falls to one, it means that only the working source
+       output is uncorked and that this source is not logged anymore. */
+    nb_uncorked_so = pa_idxset_size(o->source->outputs) - o->source->n_corked;
+    p = pa_hashmap_get(u->audiotree_log_map, o->source->name);
+
+    /* We tried unsuccessfully to search with a sink.monitor key. Get the name of the sink instead */
+    if(!p) {
+        p = pa_hashmap_get(u->audiotree_log_map, o->source->monitor_of->name);
+        /* For a sink, only one source output is considered (from sink.monitor) */
+        nb_uncorked_so = 2;
+    }
+
+    if(nb_uncorked_so > 1) {
+        pa_assert(p);
+
+        /* Get the binary data from the sample specification format */
+        rawsnd = acquire_data_at_format(p->sample_spec_to_log.format, chunk);
+
+        if(p->file_des != -1) {
+            if(u->l->write_pcm_log(rawsnd, chunk->length, p) < 0) {
+                u->l->close_target(p);
+                p->file_des = -1;
+            }
+        }
+
+        pa_memblock_release(chunk->memblock);
+    }
+}
+
+
+static pa_hook_result_t log_source_output_new_cb(pa_core *c, pa_source_output_new_data *new_data, struct userdata *u) {
+    pa_core_assert_ref(c);
+    pa_assert(new_data);
+    pa_assert(u);
+
+    pa_log_debug("New source output added. It may be logged...");
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_state_changed_hook_cb(pa_core *c, pa_sink *s, struct userdata* u) {
+    pa_source_output *so;
+    uint32_t idx;
+
+    pa_assert(c);
+    pa_assert(s);
+    pa_assert(u);
+
+    if(pa_sink_get_state(s) == PA_SINK_RUNNING) {
+        PA_IDXSET_FOREACH(so, s->monitor_source->outputs, idx) {
+            if(is_a_working_so(so)) {
+                pa_log_debug("Sink %s is running. Working source output needs to be uncorked (state : %d) to start logging !", s->name, so->state);
+                pa_source_output_cork(so, FALSE);
+            }
+        }
+    }
+    else {
+        PA_IDXSET_FOREACH(so, s->monitor_source->outputs, idx) {
+            if(is_a_working_so(so)) {
+                pa_log_debug("Sink %s is NOT running (idle, susp..). Working source output needs to be corked (state : %d) to stop logging !", s->name, so->state);
+                pa_source_output_cork(so, TRUE);
+            }
+        }
+    }
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t source_state_changed_hook_cb(pa_core *c, pa_source *s, struct userdata* u) {
+    pa_source_output *so;
+    uint32_t idx;
+
+    pa_assert(c);
+    pa_assert(s);
+    pa_assert(u);
+
+   /* Monitor source of sink state is always in state PA_SOURCE_RUNNING even if the parent sink is suspended or idle. */
+    if(pa_source_get_state(s) == PA_SOURCE_RUNNING) {
+        PA_IDXSET_FOREACH(so, s->outputs, idx) {
+            if(is_a_working_so(so)) {
+                pa_log_debug("Source %s is running. Working source output needs to be uncorked (state : %d) to start logging !", s->name, so->state);
+                pa_source_output_cork(so, FALSE);
+            }
+        }
+    }
+    else {
+        PA_IDXSET_FOREACH(so, s->outputs, idx) {
+            if(is_a_working_so(so)) {
+                pa_log_debug("Source %s is NOT running (idle, susp..). Working source output needs to be corked (state : %d) to stop logging !", s->name, so->state);
+                pa_source_output_cork(so, TRUE);
+            }
+        }
+    }
+
+    return PA_HOOK_OK;
+}
+
+
+static pa_hook_result_t log_source_output_unlink_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
+    void *m_state = NULL;
+    struct pcm_log_data *p;
+
+    pa_assert(core);
+    pa_assert(o);
+    pa_assert(u);
+
+    if(is_a_working_so(o)) {
+        pa_log_debug("Unlink working source output");
+        pa_source_output_unlink(o);
+    }
+
+    /* Close target file or device and remove from audio tree log map for given index */
+    PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+        if(p->source_output_to_log && (o->index == p->source_output_to_log->index)) {
+            struct pcm_log_data *sop;
+
+            pa_log_debug("Stop logging source output index %d as the parent source/sink monitor is in the wished log list : %s", o->index, p->audio_flow_name);
+
+            /* Close the target and unregister the source output from the audio tree hashmap */
+            if(u->l->close_target(p) < 0)
+                pa_log_error("Unlink source output cb : error closing target : %s", strerror(errno));
+
+            /* Safe as current entry is removed during hashmap iteration */
+            sop = pa_hashmap_remove(u->audiotree_log_map, p->audio_flow_name);
+            free_audiolog_data(sop, NULL);
+        }
+    }
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t log_source_output_put_cb(pa_core *core, pa_source_output *o, struct userdata *u) {
+    void *m_state = NULL;
+    struct pcm_log_data *p;
+    struct pcm_log_data *sop = NULL;
+    pa_source_output *so;
+    uint32_t idx;
+
+    pa_core_assert_ref(core);
+    pa_source_output_assert_ref(o);
+    pa_assert(u);
+
+    /* It is a working source output. We sure don't want to log it itself */
+    if(is_a_working_so(o))
+        return PA_HOOK_OK;
+
+    PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+        if(p->source_to_log && (strcmp(p->source_to_log->name, o->source->name) == 0)) {
+            pa_log_debug("Log this new source output as the parent source is in the wished log list : %s", p->source_to_log->name);
+
+            /* Register the source output in the audio tree hashmap and open the target */
+            sop = pa_xnew0(struct pcm_log_data, 1);
+            sop->source_output_to_log = o;
+            sop->source_to_log = o->source;
+            sop->sample_spec_to_log = o->sample_spec;
+            sop->channel_map_to_log = o->channel_map;
+            pa_snprintf(sop->audio_flow_name, sizeof(sop->audio_flow_name), "%s_%d", o->source->name, o->index);
+
+            /* Find the working source output and activate it. Search in idxset outputs */
+            PA_IDXSET_FOREACH(so, o->source->outputs, idx) {
+                if(is_a_working_so(so)) {
+                    if (PA_SOURCE_OUTPUT_IS_LINKED(so->state)) {
+                        pa_log_notice("Working source output needs to be uncorked for source %s as it isn't linked", o->source->name);
+                        pa_source_output_cork(so, FALSE);
+                    }
+                    else
+                        pa_log_notice("Working source output not uncorked as the source %s might be in an unlinked or invalid state.", o->source->name);
+                }
+            }
+            break;
+        }
+    }
+
+    /* Put the new source output in the audio log tree hashmap */
+    if(sop) {
+        pa_log_notice("Register the source output in the audio tree hashmap and open the target");
+        pa_hashmap_put(u->audiotree_log_map, sop->audio_flow_name, sop);
+        u->l->open_target(u->target_pathname, sop);
+    }
+
+    return PA_HOOK_OK;
+ }
+
+
+static pa_hook_result_t log_source_output_move_cb(pa_core *c, pa_source_output *o, struct userdata *u) {
+    void *m_state = NULL;
+    struct pcm_log_data *p;
+    struct pcm_log_data *sop = NULL;
+
+    pa_assert(c);
+    pa_source_output_assert_ref(o);
+    pa_assert(u);
+
+    /* Check if source output index is currently being logged. SI index is unique in Pulseaudio */
+    PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+        if(p->source_output_to_log && (o->index == p->source_output_to_log->index)) {
+            struct pcm_log_data *pfree;
+            pa_log_debug("This source output is currently logged. Close the old audio flow to change to a new name as moving to a new source");
+
+            /* Close the target and unregister the source output from the audio tree hashmap */
+            if(u->l->close_target(p) < 0)
+                pa_log_error("Moving source output cb : error closing target : %s", pa_strerror(errno));
+
+            /* Safe as current entry is removed during hashmap iteration */
+            pfree = pa_hashmap_remove(u->audiotree_log_map, p->audio_flow_name);
+            free_audiolog_data(pfree, NULL);
+
+            /* Rewind hashmap iteration to head */
+            m_state = NULL;
+
+            PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+                pa_log_debug("Move to recipient source : %s. Check if it is logged", o->source->name);
+                /* Create new audio flow or close the previous one */
+                if(p->source_to_log && (strcmp( o->source->name, p->source_to_log->name) == 0)) {
+                    pa_log_debug("Go for log! This moved source output as the parent source is in the wished logged sources list : %s", p->source_to_log->name);
+
+                    /* Register the source output in the audio tree hashmap and open the target */
+                    sop = pa_xnew0(struct pcm_log_data, 1);
+                    sop->source_output_to_log = o;
+                    sop->source_to_log = o->source;
+                    sop->sample_spec_to_log = o->sample_spec;
+                    sop->channel_map_to_log = o->channel_map;
+                    pa_snprintf(sop->audio_flow_name, sizeof(sop->audio_flow_name), "%s_%d__%s%u", o->source->name, o->index, "move", u->source_output_moves++);
+                    break;
+                }
+            }
+
+            /* Move handling terminated. Exit hashmap iteration */
+            break;
+        }
+    }
+
+    /* Put the new source output in the audio tree hashmap */
+    if(sop) {
+        pa_log_notice("Register the moved source output in the audio tree hashmap and open the target");
+        pa_hashmap_put(u->audiotree_log_map, sop->audio_flow_name, sop);
+        u->l->open_target(u->target_pathname, sop);
+    }
+
+    return PA_HOOK_OK;
+}
+
+static void log_source_output_kill_cb(pa_source_output *o) {
+    pa_source_output_assert_ref(o);
+
+    pa_log_debug("Unref working source output for PCM log purposes.");
+
+    pa_source_output_unlink(o);
+    pa_source_output_unref(o);
+}
+
+static pa_hook_result_t source_output_push_memchunk_cb(pa_core *core, pa_source_output_push_memchunk_hook_data *hook_data, struct userdata *u) {
+    void *m_state = NULL;
+    void *rawsnd;
+    struct pcm_log_data *p;
+
+    pa_assert(core);
+    pa_assert(hook_data->source_output);
+    pa_assert(u);
+
+    PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+        /* Check if we have the same source output indexes */
+        if(p->source_output_to_log && (hook_data->source_output->index == p->source_output_to_log->index)) {
+            /* Get the binary data from the sample specification format */
+            rawsnd = acquire_data_at_format(p->sample_spec_to_log.format, hook_data->memchunk);
+
+            if(p->file_des != -1) {
+                if(u->l->write_pcm_log(rawsnd, hook_data->memchunk->length, p) < 0) {
+                    u->l->close_target(p);
+                    p->file_des = -1;
+                }
+            }
+            pa_memblock_release(hook_data->memchunk->memblock);
+        }
+    }
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t log_sink_input_new_cb(pa_core *c, pa_sink_input_new_data *new_data, struct userdata *u) {
+    pa_core_assert_ref(c);
+    pa_assert(new_data);
+    pa_assert(u);
+
+    pa_log_debug("New sink input added. It may be logged...");
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t log_sink_input_unlink_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
+    void *m_state = NULL;
+    struct pcm_log_data *p;
+
+    pa_sink_input_assert_ref(i);
+    pa_assert(u);
+
+    /* Close target file or device */
+    PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+        /* Find the right sink input index */
+        if(p->sink_input_to_log && (i->index == p->sink_input_to_log->index)) {
+            struct pcm_log_data *pfree;
+
+            pa_log_debug("Stop logging sink input index %d as the sink input with the corresponding index is in the wished log list : %s", i->index, p->audio_flow_name);
+
+            /* Close the target and unregister the sink input from the audio tree hashmap */
+            if(u->l->close_target(p) < 0)
+                pa_log_error("Unlink sink input cb : error closing target : %s", strerror(errno));
+
+            /* Safe as current entry is removed during hashmap iteration */
+            pfree = pa_hashmap_remove(u->audiotree_log_map, p->audio_flow_name);
+            free_audiolog_data(pfree, NULL);
+        }
+    }
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t log_sink_input_put_cb(pa_core *core, pa_sink_input *i, struct userdata *u) {
+    void *m_state = NULL;
+    struct pcm_log_data *p;
+    struct pcm_log_data *sip = NULL;
+
+    pa_core_assert_ref(core);
+    pa_sink_input_assert_ref(i);
+    pa_assert(u);
+
+    pa_log_info("log_sink_input_put_cb : Connected sink and Sink Input index: %s %d", i->sink->name, i->index);
+
+    PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+        /* Entry has a valid sink to log and sink input is connected to this sink to log */
+        if(p->sink_to_log && (strcmp(i->sink->name, p->sink_to_log->name) == 0)) {
+            pa_log_debug("Log this new sink input as the parent sink is in the wished logged sinks list : %s", p->sink_to_log->name);
+
+            /* Register the sink input in the audio tree hashmap and open the target */
+            sip = pa_xnew0(struct pcm_log_data, 1);
+            sip->sink_input_to_log = i;
+            sip->sink_to_log = i->sink;
+            sip->sample_spec_to_log = i->sample_spec;
+            sip->channel_map_to_log = i->channel_map;
+            pa_snprintf(sip->audio_flow_name, sizeof(sip->audio_flow_name), "%s_%d", i->sink->name, i->index);
+            break;
+        }
+    }
+
+    /* Put the new sink input in the audio tree hashmap */
+    if(sip) {
+        pa_log_notice("Register the sink input in the audio tree hashmap and open the target");
+        pa_hashmap_put(u->audiotree_log_map, sip->audio_flow_name, sip);
+        u->l->open_target(u->target_pathname, sip);
+    }
+
+    return PA_HOOK_OK;
+}
+
+
+/* Sink input move event */
+static pa_hook_result_t log_sink_input_move_cb(pa_core *c, pa_sink_input *i, void *userdata) {
+    struct userdata *u = userdata;
+    void *m_state = NULL;
+    struct pcm_log_data *p;
+    struct pcm_log_data *sip = NULL;
+
+    pa_assert(c);
+    pa_sink_input_assert_ref(i);
+    pa_assert(u);
+
+    pa_log_debug("Moved sink input index : %d", i->index);
+
+    /* Check if sink input index is currently being logged. SI index is unique in Pulseaudio */
+    PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+        if(p->sink_input_to_log && (i->index == p->sink_input_to_log->index)) {
+            struct pcm_log_data *pfree;
+
+            pa_log_debug("This sink input is currently logged. Close the old audio flow to change to a new name as moving to a new sink");
+
+            /* Close the target and unregister the sink input from the audio tree hashmap */
+            if(u->l->close_target(p) < 0)
+                pa_log_error("Moving sink input cb : error closing target : %s", strerror(errno));
+
+            /* Safe as current entry is removed during hashmap iteration */
+            pfree = pa_hashmap_remove(u->audiotree_log_map, p->audio_flow_name);
+            free_audiolog_data(pfree, NULL);
+
+            /* Rewind hashmap iteration to head */
+            m_state = NULL;
+
+            PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+                pa_log_debug("Move to recipient sink : %s. Check if it is logged", i->sink->name);
+                /* Create a new audio flow */
+                if(p->sink_to_log && (strcmp(i->sink->name, p->sink_to_log->name) == 0)) {
+                    pa_log_debug("Go for log! This moved sink input as the parent sink is in the wished logged sinks list : %s", p->sink_to_log->name);
+
+                    /* Register the sink input in the audio tree hashmap and open the target */
+                    sip = pa_xnew0(struct pcm_log_data, 1);
+                    sip->sink_input_to_log = i;
+                    sip->sink_to_log = i->sink;
+                    sip->sample_spec_to_log = i->sample_spec;
+                    sip->channel_map_to_log = i->channel_map;
+                    pa_snprintf(sip->audio_flow_name, sizeof(sip->audio_flow_name), "%s_%d__%s%u", i->sink->name, i->index, "move", u->sink_input_moves++);
+                    break;
+                }
+            }
+
+            /* Move handling terminated. Exit hashmap iteration */
+            break;
+        }
+    }
+
+    /* Put the new sink input in the audio tree hashmap */
+    if(sip) {
+        pa_log_notice("Register the moved sink input in the audio tree hashmap and open the target");
+        pa_hashmap_put(u->audiotree_log_map, sip->audio_flow_name, sip);
+        u->l->open_target(u->target_pathname, sip);
+    }
+
+    return PA_HOOK_OK;
+}
+
+static pa_hook_result_t sink_input_pop_memchunk_cb(pa_core *core, pa_sink_input_pop_memchunk_hook_data *hook_data, struct userdata *u) {
+    void *m_state = NULL;
+    void *rawsnd;
+    struct pcm_log_data *p;
+
+    /* References should not be asserted as link/unlink mechanisms may happen at the same time,
+     leading to object refcnt assertions */
+    pa_assert(core);
+    pa_assert(hook_data->sink_input);
+    pa_assert(u);
+
+    PA_HASHMAP_FOREACH(p, u->audiotree_log_map, m_state) {
+        /* Check if we have the same sink input index */
+        if(p->sink_input_to_log && (hook_data->sink_input->index == p->sink_input_to_log->index)) {
+            if(pa_sink_get_state(hook_data->sink_input->sink) == PA_SINK_RUNNING) {
+                /* Get the binary data from the sample specification format */
+                rawsnd = acquire_data_at_format(p->sample_spec_to_log.format, hook_data->memchunk);
+
+                if(p->file_des != -1) {
+                    if(u->l->write_pcm_log(rawsnd, hook_data->memchunk->length, p) < 0) {
+                        pa_close(p->file_des);
+                        p->file_des = -1;
+                    }
+                }
+                pa_memblock_release(hook_data->memchunk->memblock);
+            }
+        }
+    }
+
+    return PA_HOOK_OK;
+}
+
+static int handle_end_prelog_tasks(struct userdata *u, pa_sink *sink_log, pa_source *source_log) {
+    pa_source_output *working_source_output_log;
+    struct pcm_log_data *p;
+    uint32_t idx;
+    pa_source_output_new_data so_log_data;
+    char t[128];
+    unsigned int i;
+
+    pa_assert(u);
+
+    p = pa_xnew0(struct pcm_log_data, 1);
+
+    if(sink_log) {
+        pa_sink_input *si;
+
+        /* Sink has already been logged */
+        if(pa_hashmap_get(u->audiotree_log_map, sink_log)) {
+            /* It means that the sink has been removed and added again, might be due to a technical handover ? */
+            pa_snprintf(p->audio_flow_name, sizeof(p->audio_flow_name), "%s%u", sink_log->name, u->handovers++);
+            pa_log_debug("Audio flow name \"%s\" (sink)", p->audio_flow_name);
+        }
+        else {  /* Usual normal case */
+            pa_snprintf(p->audio_flow_name, sizeof(p->audio_flow_name), "%s", sink_log->name);
+            pa_log_debug("Audio flow name \"%s\" (sink)", p->audio_flow_name);
+        }
+
+        p->sink_to_log = sink_log;
+        p->source_to_log = sink_log->monitor_source;
+        p->sample_spec_to_log = sink_log->sample_spec;
+        p->channel_map_to_log = sink_log->channel_map;
+
+        if(u->trace_ports) {
+            /* Register all sink inputs created at start up, if any */
+            PA_IDXSET_FOREACH(si, sink_log->inputs, idx) {
+                struct pcm_log_data *sip = NULL;
+
+                /* Register the sink input in the audio tree hashmap and open the target */
+                sip = pa_xnew0(struct pcm_log_data, 1);
+                sip->sink_input_to_log = si;
+                sip->sink_to_log = sink_log;
+                sip->sample_spec_to_log = si->sample_spec;
+                sip->channel_map_to_log = si->channel_map;
+                pa_snprintf(sip->audio_flow_name, sizeof(sip->audio_flow_name), "%s_%d", sink_log->name, si->index);
+
+                pa_log_debug("Register sink input named '%s' in the audio tree hashmap and open the target", sip->audio_flow_name);
+                pa_hashmap_put(u->audiotree_log_map, sip->audio_flow_name, sip);
+                if (u->l->open_target(u->target_pathname, sip) < 0)
+                    return -1;
+            }
+        }
+    }
+    else if(source_log) {
+        pa_source_output *so;
+
+        if(pa_hashmap_get(u->audiotree_log_map, source_log)) {
+            /* It means that the source has been removed and added again, might be due to a technical handover ? */
+            pa_snprintf(p->audio_flow_name, sizeof(p->audio_flow_name), "%s%u", source_log->name, u->handovers++);
+            pa_log_debug("Audio flow name \"%s\" (source)", p->audio_flow_name);
+        }
+        else {  /* Usual normal case */
+            pa_snprintf(p->audio_flow_name, sizeof(p->audio_flow_name), "%s", source_log->name);
+            pa_log_debug("Audio flow name \"%s\" (source)", p->audio_flow_name);
+        }
+
+        p->source_to_log = source_log;
+        p->sample_spec_to_log = source_log->sample_spec;
+        p->channel_map_to_log = source_log->channel_map;
+
+        if(u->trace_ports) {
+            /* Register all source outputs created at start up, if any */
+            PA_IDXSET_FOREACH(so, source_log->outputs, idx) {
+                if(!is_a_working_so(so)) {
+                    struct pcm_log_data *sop = NULL;
+
+                    /* Register the source output in the audio tree hashmap and open the target */
+                    sop = pa_xnew0(struct pcm_log_data, 1);
+                    sop->source_output_to_log = so;
+                    sop->source_to_log = source_log;
+                    sop->sample_spec_to_log = so->sample_spec;
+                    sop->channel_map_to_log = so->channel_map;
+                    pa_snprintf(sop->audio_flow_name, sizeof(sop->audio_flow_name), "%s_%d", source_log->name, so->index);
+
+                    pa_log_debug("Register source output named '%s' in the audio tree hashmap and open the target", sop->audio_flow_name);
+                    pa_hashmap_put(u->audiotree_log_map, sop->audio_flow_name, sop);
+                    if (u->l->open_target(u->target_pathname, sop) < 0)
+                        return -1;
+                }
+            }
+        }
+    }
+    else {
+        pa_log_error("Wrong or none object given for logging");
+        return -1;
+    }
+
+    if(pa_hashmap_put(u->audiotree_log_map, p->audio_flow_name, p)) {
+        pa_log_debug("Entry already exists in audio tree map, check your names");
+        return 0;
+    }
+
+    /* Open target local file or remote device */
+    if (u->l->open_target(u->target_pathname, p) < 0)
+        return -1;
+
+    /* Create source output for logging purposes of the end, sink or source */
+    i = pa_hashmap_size(u->audiotree_log_map);
+
+    /* Generate working source output name. This key should be unique */
+    pa_snprintf(t, sizeof(t), "%s %s_%u", LOG_PCM_SOURCE_OUTPUT_NAME, p->audio_flow_name, i);
+    pa_log_debug("WORKING SO name %s ", t);
+    pa_source_output_new_data_init(&so_log_data);
+    so_log_data.flags = PA_SOURCE_OUTPUT_START_CORKED;
+    so_log_data.driver = __FILE__;
+    so_log_data.module = u->module;
+    so_log_data.source = p->source_to_log;
+    pa_proplist_sets(so_log_data.proplist, PA_PROP_MEDIA_NAME, t);
+    pa_proplist_sets(so_log_data.proplist, PA_PROP_APPLICATION_NAME, t);
+
+    pa_source_output_new_data_set_sample_spec(&so_log_data, &p->sample_spec_to_log);
+    pa_source_output_new_data_set_channel_map(&so_log_data, &p->channel_map_to_log);
+
+    pa_source_output_new(&working_source_output_log, u->core, &so_log_data);
+    pa_source_output_new_data_done(&so_log_data);
+
+    if (!working_source_output_log) {
+        pa_log_info("Failed to create working source output to source \"%s\".", p->source_to_log->name);
+        return -1;
+    }
+
+    working_source_output_log->push = log_working_source_output_push_cb;
+    working_source_output_log->kill = log_source_output_kill_cb;
+
+    working_source_output_log->userdata = u;
+
+    if(working_source_output_log) {
+        pa_log_debug("Created working source output for log purpose !");
+        pa_source_output_put(working_source_output_log);
+    }
+
+    return 0;
+}
+
+static void log_end_subscribe_cb(pa_core *c, pa_subscription_event_type_t t, uint32_t idx, void *userdata) {
+    struct userdata *u = userdata;
+    const char *state = NULL;
+
+    /* todovb : Add remove event handling */
+    if (t != (PA_SUBSCRIPTION_EVENT_SINK|PA_SUBSCRIPTION_EVENT_NEW) &&
+        t != (PA_SUBSCRIPTION_EVENT_SOURCE|PA_SUBSCRIPTION_EVENT_NEW))
+        return;
+
+    pa_log_debug("PA End (sink or source) event : 0x00%x", t);
+
+    /* Register sink or source log in audio tree hashmap according to the module parameters */
+    if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SINK) {
+        char *sink_name;
+        char const *sinks_names;
+
+        sinks_names = pa_modargs_get_value(u->modargs, valid_modargs[1], NULL);
+
+        if(sinks_names) {
+          /* If list of sinks is given in argument, sinks names are separated by a comma */
+            while ((sink_name = pa_split(sinks_names, ",", &state))) {
+                pa_sink *new_sink;
+
+                if (!(new_sink = pa_idxset_get_by_index(u->core->sinks, idx)))
+                    return;
+
+                if(strcmp(sink_name, new_sink->name) == 0) {
+                    pa_log_info("New sink is in the log list : %s", new_sink->name);
+                    if(handle_end_prelog_tasks(u, new_sink, NULL) < 0)
+                        pa_log_error("Failed to initiate log for new sink \"%s\"", new_sink->name);
+                }
+                pa_xfree(sink_name);
+            }
+        }
+    }
+
+    if ((t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE) {
+        char *source_name;
+        char const *sources_names;
+
+        sources_names = pa_modargs_get_value(u->modargs, valid_modargs[0], NULL);
+
+        if(sources_names) {
+            /* If list of sources is given in argument, sources names are separated by a comma */
+            while ((source_name = pa_split(sources_names, ",", &state))) {
+                pa_source *new_source;
+
+                if (!(new_source = pa_idxset_get_by_index(u->core->sources, idx)))
+                    return;
+
+                if(strcmp(source_name, new_source->name) == 0) {
+                    pa_log_info("New source is in the log list : %s", new_source->name);
+                    if(handle_end_prelog_tasks(u, NULL, new_source) < 0)
+                        pa_log_error("Failed to initiate log for new source \"%s\"", new_source->name);
+                }
+                pa_xfree(source_name);
+            }
+        }
+    }
+}
+
+
+int pa__init(pa_module *m) {
+    pa_modargs *ma = NULL;
+    struct userdata *u = NULL;
+    const char *sources_names;
+    const char *sinks_names;
+    const char *t;
+    const char monitor_str[]=".monitor";
+    const char *state = NULL;
+    char *sink_name;
+    char *source_name;
+    struct stat buf;
+
+    pa_assert(m);
+
+    /* Note : ma is freed along with u->moargs in pa__done */
+    if (!(ma = pa_modargs_new(m->argument, valid_modargs))) {
+        pa_log_error("Failed to parse module arguments");
+        goto fail;
+    }
+
+    u = pa_xnew0(struct userdata, 1);
+    m->userdata = u;
+    u->modargs = ma;
+    u->core = m->core;
+    u->module = m;
+    u->audiotree_log_map = pa_hashmap_new(pa_idxset_string_hash_func, pa_idxset_string_compare_func);
+
+    /* Default value, log all sink inputs and source outputs */
+    u->trace_ports = TRUE;
+
+    /* Register sink or source log in audio tree hashmap according to the module parameters */
+    sources_names = pa_modargs_get_value(ma, valid_modargs[0], NULL);
+    sinks_names = pa_modargs_get_value(ma, valid_modargs[1], NULL);
+
+    if(!sinks_names && !sources_names)
+        goto fail;
+
+    u->sink_port_logger = pa_xnew0(struct sink_port_logger, 1);
+    u->source_port_logger = pa_xnew0(struct source_port_logger, 1);
+    u->sink_input_moves = 0;
+    u->source_output_moves = 0;
+    u->handovers = 0;
+
+    t = pa_modargs_get_value(ma, valid_modargs[2], NULL);
+
+    if(t) {
+        pa_strlcpy(u->target_pathname, t, strlen(t));
+        t = NULL;
+    }
+    else {
+        pa_log_error("Error : target device or directory missing in module arguments.");
+        goto fail;
+    }
+
+    /* Set the serializer object depending on the log target type */
+    if(u->target_pathname) {
+      if(stat(u->target_pathname, &buf) == 0 && S_ISDIR(buf.st_mode)) {
+          pa_log_info("Log PCM : directory already exists: Rename it !?");
+          /* todovb : rename the directory that contains the wav files of audio tree*/
+      }
+      /* Directory does not exist */
+      else if (stat(u->target_pathname, &buf) < 0) {
+          pa_log_error("No directory : %s. Create one.", strerror(errno));
+          if(mkdir(u->target_pathname, 0777) <0) {
+              pa_log_error("Failed to create directory : %s", strerror(errno));
+              goto fail;
+          }
+      }
+
+      /* Redetermine file type by refreshing buf structure */
+      stat(u->target_pathname, &buf);
+
+      /* Directory is specified. Files will be saved under it */
+      if(S_ISDIR(buf.st_mode)) {
+          u->l = local_file_serializer_new();
+      }
+      /* Block or char device is specified. Data will be written to a remote target */
+      else if(S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode)) {
+          pa_log_info("Target is a block or char device");
+          u->l = remote_device_serializer_new();
+      }
+      else {
+          pa_log_error("Close but no cigar, target is not loggable. Choose a directory or a remote device : %s", strerror(errno));
+          goto fail;
+      }
+    }
+    else {
+        pa_log_error("Log PCM : No target specified. specify one");
+        goto fail;
+    }
+
+    t = pa_modargs_get_value(ma, valid_modargs[3], NULL);
+
+    if(t) {
+        if(strcmp(t, "no") == 0)
+            u->trace_ports = FALSE;
+    }
+    else
+        pa_log_warn("Warning : trace_ports option is missing argument (yes/no). Yes set by default");
+
+    if(sinks_names) {
+        /* If list of sinks is given in argument, sinks names are separated by a comma */
+        while ((sink_name = pa_split(sinks_names, ",", &state))) {
+            if(pa_endswith(sink_name, monitor_str)) {
+                pa_log_error("Eh mec! You probably wanted to log a monitor source of a sink. Then put the name <your_sink.monitor> after the sources_to_log argument of the module");
+                pa_xfree(sink_name);
+                goto fail;
+            }
+            pa_xfree(sink_name);
+        }
+
+        /* Rewind pa_split list */
+        state = NULL;
+
+        while ((sink_name = pa_split(sinks_names, ",", &state))) {
+            pa_sink* sink_log;
+
+            if(!sink_name) {
+              pa_xfree(sink_name);
+              goto fail;
+            }
+
+            if (!(sink_log = pa_namereg_get(m->core, sink_name, PA_NAMEREG_SINK)))
+                pa_log_warn("Sink to log \"%s\" not found. Wrong name or may be it will show up during log....", sink_name);
+            else {
+                if(handle_end_prelog_tasks(u, sink_log, NULL) < 0) {
+                    pa_log_error("Failed to do prelog tasks for sink  %s", sink_name);
+                    pa_xfree(sink_name);
+                    goto fail;
+                }
+                pa_log_info("Sink \"%s\" found !", sink_name);
+            }
+            pa_xfree(sink_name);
+        }
+
+        if(u->trace_ports) {
+            /* Notifies the log module every time PCM chunks are popped before resampling */
+            u->sink_port_logger->sink_input_pop_memchunk_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_POP_MEMCHUNK], PA_HOOK_LATE, (pa_hook_cb_t) sink_input_pop_memchunk_cb, u);
+
+            /* To be notified from new, moving or unlinked sink inputs */
+            u->sink_port_logger->sink_input_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_NEW], PA_HOOK_NORMAL, (pa_hook_cb_t) log_sink_input_new_cb, u);
+            u->sink_port_logger->sink_input_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) log_sink_input_put_cb, u);
+            u->sink_port_logger->sink_input_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_UNLINK_POST], PA_HOOK_NORMAL, (pa_hook_cb_t) log_sink_input_unlink_cb, u);
+            u->sink_port_logger->sink_input_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_INPUT_MOVE_FINISH], PA_HOOK_NORMAL, (pa_hook_cb_t) log_sink_input_move_cb, u);
+        }
+
+        u->sink_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SINK_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) sink_state_changed_hook_cb, u);
+    }
+
+    /* For pa_split */
+    state = NULL;
+
+    if(sources_names) {
+       /* If list of sources is given in argument, sources names are separated by a comma */
+       while ((source_name = pa_split(sources_names, ",", &state))) {
+            pa_source *source_log;
+
+            if(!source_name) {
+                pa_xfree(source_name);
+                goto fail;
+            }
+
+            if (!(source_log = pa_namereg_get(m->core, source_name, PA_NAMEREG_SOURCE)))
+                pa_log_warn("Source to log \"%s\" not found. Wrong name or may be it will show up during log..", source_name);
+            else {
+                pa_log_info("Source \"%s\" found !", source_name);
+
+                if(handle_end_prelog_tasks(u, NULL, source_log) < 0) {
+                    pa_xfree(source_name);
+                    goto fail;
+                }
+            }
+            pa_xfree(source_name);
+        }
+
+        if(u->trace_ports) {
+            /* Notifies the log module every time PCM chunks are pushed after resampling */
+            u->source_port_logger->source_output_push_memchunk_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUSH_MEMCHUNK], PA_HOOK_LATE, (pa_hook_cb_t) source_output_push_memchunk_cb, u);
+            /* To be notified from new, moving or unlinked source outputs */
+            u->source_port_logger->source_output_new_hook_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_NEW], PA_HOOK_NORMAL, (pa_hook_cb_t) log_source_output_new_cb, u);
+            u->source_port_logger->source_output_put_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUT], PA_HOOK_NORMAL, (pa_hook_cb_t) log_source_output_put_cb, u);
+            u->source_port_logger->source_output_unlink_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_UNLINK_POST], PA_HOOK_NORMAL, (pa_hook_cb_t) log_source_output_unlink_cb, u);
+            u->source_port_logger->source_output_move_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_MOVE_FINISH], PA_HOOK_NORMAL, (pa_hook_cb_t) log_source_output_move_cb, u);
+        }
+
+        u->source_state_changed_slot = pa_hook_connect(&m->core->hooks[PA_CORE_HOOK_SOURCE_STATE_CHANGED], PA_HOOK_NORMAL, (pa_hook_cb_t) source_state_changed_hook_cb, u);
+    }
+
+    u->end_subscription = pa_subscription_new(m->core, PA_SUBSCRIPTION_MASK_SINK|PA_SUBSCRIPTION_MASK_SOURCE, log_end_subscribe_cb, u);
+
+    return 0;
+
+fail:
+
+    pa__done(m);
+
+    return -1;
+}
+
+void pa__done(pa_module*m) {
+    struct userdata *u = m->userdata;
+    struct pcm_log_data *p;
+    pa_source_output *so;
+    uint32_t idx;
+
+    pa_assert(m);
+
+    if(!u)
+        return;
+
+    if(u->end_subscription)
+        pa_subscription_free(u->end_subscription);
+
+    if(u->sink_port_logger && u->trace_ports) {
+        if(u->sink_port_logger->sink_input_pop_memchunk_slot)
+            pa_hook_slot_free(u->sink_port_logger->sink_input_pop_memchunk_slot);
+
+        if(u->sink_port_logger->sink_input_new_hook_slot)
+            pa_hook_slot_free(u->sink_port_logger->sink_input_new_hook_slot);
+
+        if(u->sink_port_logger->sink_input_put_slot)
+            pa_hook_slot_free(u->sink_port_logger->sink_input_put_slot);
+
+        if(u->sink_port_logger->sink_input_move_slot)
+            pa_hook_slot_free(u->sink_port_logger->sink_input_move_slot);
+
+        if(u->sink_port_logger->sink_input_unlink_slot)
+            pa_hook_slot_free(u->sink_port_logger->sink_input_unlink_slot);
+    }
+
+    if(u->source_port_logger) {
+        if(u->source_port_logger->source_output_push_memchunk_slot)
+            pa_hook_slot_free(u->source_port_logger->source_output_push_memchunk_slot);
+
+        if(u->source_port_logger->source_output_new_hook_slot)
+            pa_hook_slot_free(u->source_port_logger->source_output_new_hook_slot);
+
+        if(u->source_port_logger->source_output_put_slot)
+            pa_hook_slot_free(u->source_port_logger->source_output_put_slot);
+
+        if(u->source_port_logger->source_output_move_slot)
+            pa_hook_slot_free(u->source_port_logger->source_output_move_slot);
+
+        if(u->source_port_logger->source_output_unlink_slot)
+            pa_hook_slot_free(u->source_port_logger->source_output_unlink_slot);
+    }
+
+    PA_IDXSET_FOREACH(so, u->core->source_outputs, idx) {
+        if(is_a_working_so(so)) {
+            pa_log_debug("Working source output unlinked and unreffed");
+            pa_source_output_unlink(so);
+            pa_source_output_unref(so);
+            so = NULL;
+        }
+    }
+
+    if(u->sink_state_changed_slot)
+        pa_hook_slot_free(u->sink_state_changed_slot);
+
+    if(u->source_state_changed_slot)
+        pa_hook_slot_free(u->source_state_changed_slot);
+
+    /* Frees the value of every entry in the hashmap and free the hashmap itself */
+    if (u->audiotree_log_map) {
+        while ((p = pa_hashmap_steal_first(u->audiotree_log_map)))
+            free_audiolog_data(p, NULL);
+
+        pa_hashmap_free(u->audiotree_log_map, NULL, NULL);
+    }
+
+    if (u->modargs)
+        pa_modargs_free(u->modargs);
+
+    pa_xfree(u);
+}
diff --git a/src/modules/log/pcm-logger-data.h b/src/modules/log/pcm-logger-data.h
new file mode 100755
index 0000000..17f3e0c
--- /dev/null
+++ b/src/modules/log/pcm-logger-data.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * Contact:  Vincent Becker <vincentx.becker at intel.com>
+ *
+ * These PulseAudio Modules are 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
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+#ifndef __PCM_LOGGER_DATA_H__
+#define __PCM_LOGGER_DATA_H__
+
+#include <pulsecore/sink.h>
+#include <pulsecore/sndfile-util.h>
+
+struct pcm_log_data {
+    pa_sink *sink_to_log;
+
+    pa_sink_input *sink_input_to_log;
+
+    pa_source *source_to_log;
+
+    pa_source_output *source_output_to_log;
+
+    pa_sample_spec sample_spec_to_log;
+
+    pa_channel_map channel_map_to_log;
+
+    int file_des; /* File descriptor of the char/block/device or local wav file */
+
+    SF_INFO sfi;  /* Header of the audio flow or local wav file */
+
+    SNDFILE* sndfile;  /* used when logging to a local file */
+
+    sf_count_t (*write_sound_file)(SNDFILE *_sndfile, const void *ptr, sf_count_t frames);
+
+    /* Used when logging to a char/block device.
+       for source outputs and sink inputs : connected source or sink + index and __move<move_nber> in case of a move event
+       for sources and sinks : source or sink name */
+    char audio_flow_name[128];
+
+    /* Number of PCM samples written in a file. todovb : write this value before closing the target */
+    sf_count_t pcm_samples;
+};
+
+#endif
diff --git a/src/modules/log/remote-device-serializer.c b/src/modules/log/remote-device-serializer.c
new file mode 100755
index 0000000..b924fc0
--- /dev/null
+++ b/src/modules/log/remote-device-serializer.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * Contact:  Vincent Becker <vincentx.becker at intel.com>
+ *
+ * These PulseAudio Modules are 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
+ * version 2.1 of the License.
+ *
+ * PulseAudio is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include <pulsecore/core.h>
+#include <pulsecore/core-util.h>
+
+#include "remote-device-serializer.h"
+
+PA_DEFINE_PUBLIC_CLASS(remote_device_serializer, log_pcm_serializer);
+
+static void remote_serializer_free(pa_object *l);
+
+log_pcm_serializer* remote_device_serializer_new(void) {
+    remote_device_serializer *r;
+
+    r = log_pcm_serializer_new(remote_device_serializer);
+
+    r->parent.parent.free = &remote_serializer_free;
+    r->parent.write_pcm_log = &write_remote_device;
+    r->parent.open_target = &open_remote_device;
+    r->parent.close_target = &close_remote_device;
+
+    remote_device_serializer_ref(r);
+
+    return LOG_PCM_SERIALIZER(r);
+}
+
+/* Writes header to give information on aufio flow name, sample spec, ... */
+static int write_header(struct pcm_log_data *pl) {
+    int file_format;
+    int wrtype = 1;
+    int saved_errno = errno;
+    char audio_header[256];
+
+    pa_log_debug("%d: %s() called", __LINE__, __FUNCTION__);
+
+    pa_zero(pl->sfi);
+
+    file_format = pa_sndfile_format_from_string("wav");
+
+    if (pa_sndfile_write_sample_spec(&pl->sfi, &pl->sample_spec_to_log) < 0) {
+        pa_log_error("Failed to generate sample specification for file.");
+        return -1;
+    }
+
+    pl->sfi.format |= file_format;
+    pl->sfi.seekable = TRUE;
+
+    /* Format the header */
+    pa_snprintf(audio_header, sizeof(audio_header), "%s %i %i %i %i", pl->audio_flow_name, pl->sfi.samplerate, pl->sfi.channels, pl->sfi.format, pl->sfi.seekable);
+
+    if (pa_write(pl->file_des, audio_header, strlen(audio_header), &wrtype) < 0) {
+        saved_errno = errno;
+        pa_log_error("Write remote device error (header) : %s", strerror(saved_errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+
+int open_remote_device(const char *t, struct pcm_log_data *pl) {
+  pa_assert(pl);
+
+  if((pl->file_des = open(t, O_RDWR|O_CREAT|O_TRUNC, S_IRWXU)) >=0)
+      pa_log_debug("Open remote device file desc : %d", pl->file_des);
+  else {
+      pa_log_error("Could not open target device : %s", strerror(errno));
+      return -1;
+  }
+
+  if( write_header(pl) < 0)
+      return -1;
+
+  pa_log_info("Opened target is a block or char device");
+
+  return 0;
+}
+
+int write_remote_device(const void *rawdata, int length, struct pcm_log_data *pl) {
+    int wrtype = 1; /* write to regular file, not socket */
+    int saved_errno = errno;
+
+    if (pa_write(pl->file_des, rawdata, length, &wrtype) < 0) {
+        saved_errno = errno;
+        pa_log_error("Write remote device error : %s", strerror(saved_errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+int close_remote_device(struct pcm_log_data *pl) {
+    if(pa_close(pl->file_des) >=0) {
+        pa_log_debug("Close remote device file desc : %d", pl->file_des);
+        return 0;
+    }
+    else {
+        pa_log_error("Failed to close remote device : %s", strerror(errno));
+        return -1;
+    }
+}
+
+/* Called from main context */
+static void remote_serializer_free(pa_object *o) {
+    remote_device_serializer *r = REMOTE_DEVICE_SERIALIZER(o);
+
+    remote_device_serializer_unref(r);
+    remote_device_serializer_assert_ref(r);
+
+    pa_assert(r);
+
+    pa_xfree(r);
+}
diff --git a/src/modules/log/remote-device-serializer.h b/src/modules/log/remote-device-serializer.h
new file mode 100755
index 0000000..eed4aef
--- /dev/null
+++ b/src/modules/log/remote-device-serializer.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010 Intel Corporation.
+ *
+ * Contact:  Vincent Becker <vincentx.becker at intel.com>
+ *
+ * These PulseAudio Modules are 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
+ * version 2.1 of the License.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ * USA.
+ */
+
+#ifndef remote_device_serializer_h
+#define remote_device_serializer_h
+
+#include "log-pcm-serializer.h"
+
+
+typedef struct remote_device_serializer remote_device_serializer;
+
+
+struct remote_device_serializer {
+    log_pcm_serializer parent;
+    int i;
+};
+
+log_pcm_serializer* remote_device_serializer_new(void);
+
+int open_remote_device(const char *t, struct pcm_log_data *pl);
+
+int write_remote_device(const void *ptr, int length, struct pcm_log_data *pl);
+
+int close_remote_device(struct pcm_log_data *pl);
+
+PA_DECLARE_PUBLIC_CLASS(remote_device_serializer);
+
+#define REMOTE_DEVICE_SERIALIZER(f) (remote_device_serializer_cast(f))
+
+#endif
diff --git a/src/pulsecore/core.h b/src/pulsecore/core.h
index 358b98d..921a41e 100644
--- a/src/pulsecore/core.h
+++ b/src/pulsecore/core.h
@@ -94,6 +94,7 @@ typedef enum pa_core_hook {
     PA_CORE_HOOK_SINK_INPUT_STATE_CHANGED,
     PA_CORE_HOOK_SINK_INPUT_PROPLIST_CHANGED,
     PA_CORE_HOOK_SINK_INPUT_SEND_EVENT,
+    PA_CORE_HOOK_SINK_INPUT_POP_MEMCHUNK,
     PA_CORE_HOOK_SOURCE_OUTPUT_NEW,
     PA_CORE_HOOK_SOURCE_OUTPUT_FIXATE,
     PA_CORE_HOOK_SOURCE_OUTPUT_PUT,
@@ -105,6 +106,7 @@ typedef enum pa_core_hook {
     PA_CORE_HOOK_SOURCE_OUTPUT_STATE_CHANGED,
     PA_CORE_HOOK_SOURCE_OUTPUT_PROPLIST_CHANGED,
     PA_CORE_HOOK_SOURCE_OUTPUT_SEND_EVENT,
+    PA_CORE_HOOK_SOURCE_OUTPUT_PUSH_MEMCHUNK,
     PA_CORE_HOOK_CLIENT_NEW,
     PA_CORE_HOOK_CLIENT_PUT,
     PA_CORE_HOOK_CLIENT_UNLINK,
diff --git a/src/pulsecore/sink-input.c b/src/pulsecore/sink-input.c
index 2bcf112..69f6440 100644
--- a/src/pulsecore/sink-input.c
+++ b/src/pulsecore/sink-input.c
@@ -653,6 +653,21 @@ pa_usec_t pa_sink_input_get_latency(pa_sink_input *i, pa_usec_t *sink_latency) {
     return r[0];
 }
 
+
+/* Called from thread context */
+static void pa_sink_input_pop_memchunk_hook(pa_sink_input *i, pa_memchunk *chunk) {
+    pa_sink_input_pop_memchunk_hook_data hook_data;
+
+    pa_assert(chunk);
+    pa_assert(pa_frame_aligned(chunk->length, &i->sample_spec));
+
+    hook_data.sink_input = i;
+    hook_data.memchunk = chunk;
+
+    pa_hook_fire(&i->core->hooks[PA_CORE_HOOK_SINK_INPUT_POP_MEMCHUNK],
+                 &hook_data);
+}
+
 /* Called from thread context */
 void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, pa_memchunk *chunk, pa_cvolume *volume) {
     pa_bool_t do_volume_adj_here, need_volume_factor_sink;
@@ -724,6 +739,9 @@ void pa_sink_input_peek(pa_sink_input *i, size_t slength /* in sink frames */, p
                 i->thread_info.underrun_for += ilength;
             break;
         }
+	/* Capture PCM log before resampling */
+	else
+	  pa_sink_input_pop_memchunk_hook(i, &tchunk);
 
         pa_atomic_store(&i->thread_info.drained, 0);
 
diff --git a/src/pulsecore/sink-input.h b/src/pulsecore/sink-input.h
index 588005f..6478c81 100644
--- a/src/pulsecore/sink-input.h
+++ b/src/pulsecore/sink-input.h
@@ -259,6 +259,11 @@ typedef struct pa_sink_input_send_event_hook_data {
     pa_proplist *data;
 } pa_sink_input_send_event_hook_data;
 
+typedef struct pa_sink_input_pop_memchunk_hook_data {
+    pa_sink_input *sink_input;
+    pa_memchunk *memchunk;
+} pa_sink_input_pop_memchunk_hook_data;
+
 typedef struct pa_sink_input_new_data {
     pa_sink_input_flags_t flags;
 
diff --git a/src/pulsecore/source-output.c b/src/pulsecore/source-output.c
index 0bb8899..b9b51f1 100644
--- a/src/pulsecore/source-output.c
+++ b/src/pulsecore/source-output.c
@@ -435,6 +435,24 @@ pa_usec_t pa_source_output_get_latency(pa_source_output *o, pa_usec_t *source_la
 }
 
 /* Called from thread context */
+static void pa_source_output_push_memchunk_hook(pa_source_output *o, const pa_memchunk *chunk) {
+    pa_source_output_push_memchunk_hook_data hook_data;
+
+    pa_source_output_assert_ref(o);
+    pa_source_output_assert_io_context(o);
+    pa_assert(PA_SOURCE_OUTPUT_IS_LINKED(o->thread_info.state));
+    pa_assert(chunk);
+    pa_assert(pa_frame_aligned(chunk->length, &o->sample_spec));
+
+
+    hook_data.source_output = o;
+    hook_data.memchunk = chunk;
+
+    pa_hook_fire(&o->core->hooks[PA_CORE_HOOK_SOURCE_OUTPUT_PUSH_MEMCHUNK],
+                 &hook_data);
+}
+
+/* Called from thread context */
 void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
     size_t length;
     size_t limit, mbs = 0;
@@ -489,8 +507,11 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
 
         pa_assert(qchunk.length > 0);
 
-        if (!o->thread_info.resampler)
+        if (!o->thread_info.resampler) {
+	  /* Log the PCM chunks after resampling */
+	    pa_source_output_push_memchunk_hook(o, &qchunk);
             o->push(o, &qchunk);
+	}
         else {
             pa_memchunk rchunk;
 
@@ -502,8 +523,10 @@ void pa_source_output_push(pa_source_output *o, const pa_memchunk *chunk) {
 
             pa_resampler_run(o->thread_info.resampler, &qchunk, &rchunk);
 
-            if (rchunk.length > 0)
+            if (rchunk.length > 0) {
+	        pa_source_output_push_memchunk_hook(o, &rchunk);
                 o->push(o, &rchunk);
+	    }
 
             if (rchunk.memblock)
                 pa_memblock_unref(rchunk.memblock);
diff --git a/src/pulsecore/source-output.h b/src/pulsecore/source-output.h
index f16f952..3c25182 100644
--- a/src/pulsecore/source-output.h
+++ b/src/pulsecore/source-output.h
@@ -201,6 +201,12 @@ typedef struct pa_source_output_send_event_hook_data {
     pa_proplist *data;
 } pa_source_output_send_event_hook_data;
 
+typedef struct pa_source_output_push_hook_data {
+    pa_source_output *source_output;
+    pa_memchunk *memchunk;
+} pa_source_output_push_memchunk_hook_data;
+
+
 typedef struct pa_source_output_new_data {
     pa_source_output_flags_t flags;
 
-- 
1.7.2.3

---------------------------------------------------------------------
Intel Corporation SAS (French simplified joint stock company)
Registered headquarters: "Les Montalets"- 2, rue de Paris, 
92196 Meudon Cedex, France
Registration Number:  302 456 199 R.C.S. NANTERRE
Capital: 4,572,000 Euros

This e-mail and any attachments may contain confidential material for
the sole use of the intended recipient(s). Any review or distribution
by others is strictly prohibited. If you are not the intended
recipient, please contact the sender and delete all copies.


More information about the pulseaudio-discuss mailing list