C++程序  |  982行  |  26.77 KB

/* Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <inttypes.h>
#include <sys/param.h>
#include <syslog.h>

#include "cras_util.h"
#include "cras_dsp_module.h"
#include "cras_dsp_pipeline.h"
#include "dsp_util.h"

/* We have a static representation of the dsp graph in a "struct ini",
 * and here we will construct a dynamic representation of it in a
 * "struct pipeline". The difference between the static one and the
 * dynamic one is that we will only include the subset of the dsp
 * graph actually needed in the dynamic one (so those plugins that are
 * disabled will not be included). Here are the mapping between the
 * static representation and the dynamic representation:
 *
 *      static                      dynamic
 *  -------------    --------------------------------------
 *  struct ini       struct pipeline
 *  struct plugin    struct instance
 *  strict port      struct audio_port, struct control_port
 *
 * For example, if the ini file specifies these plugins and their
 * connections:
 *
 * [A]
 * output_0={audio}
 * [B]
 * input_0={audio}
 * output_1={result}
 * [C]
 * input_0={result}
 *
 * That is, A connects to B, and B connects to C. If the plugin B is
 * now disabled, in the pipeline we construct there will only be two
 * instances (A and C) and the audio ports on these instances will
 * connect to each other directly, bypassing B.
 */

/* This represents an audio port on an instance. */
struct audio_port {
	struct audio_port *peer;  /* the audio port this port connects to */
	struct plugin *plugin;  /* the plugin corresponds to the instance */
	int original_index;  /* the port index in the plugin */
	int buf_index; /* the buffer index in the pipeline */
};

/* This represents a control port on an instance. */
struct control_port {
	struct control_port *peer;  /* the control port this port connects to */
	struct plugin *plugin;  /* the plugin corresponds to the instance */
	int original_index;  /* the port index in the plugin */
	float value;  /* the value of the control port */
};

DECLARE_ARRAY_TYPE(struct audio_port, audio_port_array);
DECLARE_ARRAY_TYPE(struct control_port, control_port_array);

/* An instance is a dynamic representation of a plugin. We only create
 * an instance when a plugin is needed (data actually flows through it
 * and it is not disabled). An instance also contains a pointer to a
 * struct dsp_module, which is the implementation of the plugin */
struct instance {
	/* The plugin this instance corresponds to */
	struct plugin *plugin;

	/* These are the ports on this instance. The difference
	 * between this and the port array in a struct plugin is that
	 * the ports skip disabled plugins and connect to the upstream
	 * ports directly.
	 */
	audio_port_array input_audio_ports;
	audio_port_array output_audio_ports;
	control_port_array input_control_ports;
	control_port_array output_control_ports;

	/* The implementation of the plugin */
	struct dsp_module *module;

	/* Whether this module's instantiate() function has been called */
	int instantiated;

	/* This caches the value returned from get_properties() of a module */
	int properties;

	/* This is the total buffering delay from source to this instance. It is
	 * in number of frames. */
	int total_delay;
};

DECLARE_ARRAY_TYPE(struct instance, instance_array)

/* An pipeline is a dynamic representation of a dsp ini file. */
struct pipeline {
	/* The purpose of the pipeline. "playback" or "capture" */
	const char *purpose;

	/* The ini file this pipeline comes from */
	struct ini *ini;

	/* All needed instances for this pipeline. It is sorted in an
	 * order that if instance B depends on instance A, then A will
	 * appear in front of B. */
	instance_array instances;

	/* The maximum number of audio buffers that will be used at
	 * the same time for this pipeline */
	int peak_buf;

	/* The audio data buffers */
	float **buffers;

	/* The instance where the audio data flow in */
	struct instance *source_instance;

	/* The instance where the audio data flow out */
	struct instance *sink_instance;

	/* The number of audio channels for this pipeline */
	int input_channels;
	int output_channels;

	/* The audio sampling rate for this pipleine. It is zero if
	 * cras_dsp_pipeline_instantiate() has not been called. */
	int sample_rate;

	/* The total time it takes to run the pipeline, in nanoseconds. */
	int64_t total_time;

	/* The max/min time it takes to run the pipeline, in nanoseconds. */
	int64_t max_time;
	int64_t min_time;

	/* The number of blocks the pipeline. */
	int64_t total_blocks;

	/* The total number of sample frames the pipeline processed */
	int64_t total_samples;
};

static struct instance *find_instance_by_plugin(instance_array *instances,
						struct plugin *plugin)
{
	int i;
	struct instance *instance;

	FOR_ARRAY_ELEMENT(instances, i, instance) {
		if (instance->plugin == plugin)
			return instance;
	}

	return NULL;
}

/* Finds out where the data sent to plugin:index come from. The issue
 * we need to handle here is the previous plugin may be disabled, so
 * we need to go upstream until we find the real origin */
static int find_origin_port(struct ini *ini, instance_array *instances,
			    struct plugin *plugin, int index,
			    struct plugin **origin, int *origin_index)
{
	enum port_type type;
	struct port *port;
	int flow_id;
	struct flow *flow;
	int i, k;
	int found;

	port = ARRAY_ELEMENT(&plugin->ports, index);
	type = port->type;
	flow_id = port->flow_id;
	if (flow_id == INVALID_FLOW_ID)
		return -1;
	flow = ARRAY_ELEMENT(&ini->flows, flow_id);

	/* move to the previous plugin */
	plugin = flow->from;
	index = flow->from_port;

	/* if the plugin is not disabled, it will be pointed by some instance */
	if (find_instance_by_plugin(instances, plugin)) {
		*origin = plugin;
		*origin_index = index;
		return 0;
	}

	/* Now we know the previous plugin is disabled, we need to go
	 * upstream. We assume the k-th output port of the plugin
	 * corresponds to the k-th input port of the plugin (with the
	 * same type) */

	k = 0;
	found = 0;
	FOR_ARRAY_ELEMENT(&plugin->ports, i, port) {
		if (index == i) {
			found = 1;
			break;
		}
		if (port->direction == PORT_OUTPUT && port->type == type)
			k++;
	}
	if (!found)
		return -1;

	found = 0;
	FOR_ARRAY_ELEMENT(&plugin->ports, i, port) {
		if (port->direction == PORT_INPUT && port->type == type) {
			if (k-- == 0) {
				index = i;
				found = 1;
				break;
			}
		}
	}
	if (!found)
		return -1;

	return find_origin_port(ini, instances, plugin, index, origin,
				origin_index);
}

static struct audio_port *find_output_audio_port(instance_array *instances,
						 struct plugin *plugin,
						 int index)
{
	int i;
	struct instance *instance;
	struct audio_port *audio_port;

	instance = find_instance_by_plugin(instances, plugin);
	if (!instance)
		return NULL;

	FOR_ARRAY_ELEMENT(&instance->output_audio_ports, i, audio_port) {
		if (audio_port->original_index == index)
			return audio_port;
	}

	return NULL;
}

static struct control_port *find_output_control_port(instance_array *instances,
						     struct plugin *plugin,
						     int index)
{
	int i;
	struct instance *instance;
	struct control_port *control_port;

	instance = find_instance_by_plugin(instances, plugin);
	if (!instance)
		return NULL;

	FOR_ARRAY_ELEMENT(&instance->output_control_ports, i, control_port) {
		if (control_port->original_index == index)
			return control_port;
	}

	return NULL;
}

static char is_disabled(struct plugin *plugin, struct cras_expr_env *env)
{
	char disabled;
	return (plugin->disable_expr &&
		cras_expr_expression_eval_boolean(
			plugin->disable_expr, env, &disabled) == 0 &&
		disabled == 1);
}

static int topological_sort(struct pipeline *pipeline,
			    struct cras_expr_env *env,
			    struct plugin *plugin, char* visited)
{
	struct port *port;
	struct flow *flow;
	int index;
	int i;
	int flow_id;
	struct instance *instance;
	struct ini *ini = pipeline->ini;

	index = ARRAY_INDEX(&ini->plugins, plugin);
	if (visited[index])
		return 0;
	visited[index] = 1;

	FOR_ARRAY_ELEMENT(&plugin->ports, i, port) {
		if (port->flow_id == INVALID_FLOW_ID)
			continue;
		flow_id = port->flow_id;
		flow = ARRAY_ELEMENT(&ini->flows, flow_id);
		if (!flow->from) {
			syslog(LOG_ERR, "no plugin flows to %s:%d",
			       plugin->title, i);
			return -1;
		}
		if (topological_sort(pipeline, env, flow->from, visited) < 0)
			return -1;
	}

	/* if the plugin is disabled, we don't construct an instance for it */
	if (is_disabled(plugin, env))
		return 0;

	instance = ARRAY_APPEND_ZERO(&pipeline->instances);
	instance->plugin = plugin;

	/* constructs audio and control ports for the instance */
	FOR_ARRAY_ELEMENT(&plugin->ports, i, port) {
		int need_connect = (port->flow_id != INVALID_FLOW_ID &&
				    port->direction == PORT_INPUT);
                struct plugin *origin = NULL;
		int origin_index = 0;

		if (need_connect) {
			if (find_origin_port(ini, &pipeline->instances, plugin,
					     i, &origin, &origin_index) < 0)
				return -1;
		}

		if (port->type == PORT_AUDIO) {
			audio_port_array *audio_port_array =
				(port->direction == PORT_INPUT) ?
				&instance->input_audio_ports :
				&instance->output_audio_ports;
			struct audio_port *audio_port =
				ARRAY_APPEND_ZERO(audio_port_array);
			audio_port->plugin = plugin;
			audio_port->original_index = i;
			if (need_connect) {
				struct audio_port *from;
				from = find_output_audio_port(
					&pipeline->instances, origin,
					origin_index);
				if (!from)
					return -1;
				from->peer = audio_port;
				audio_port->peer = from;
			}
		} else if (port->type == PORT_CONTROL) {
			control_port_array *control_port_array =
				(port->direction == PORT_INPUT) ?
				&instance->input_control_ports :
				&instance->output_control_ports;
			struct control_port *control_port =
				ARRAY_APPEND_ZERO(control_port_array);
			control_port->plugin = plugin;
			control_port->original_index = i;
			control_port->value = port->init_value;
			if (need_connect) {
				struct control_port *from;
				from = find_output_control_port(
					&pipeline->instances, origin,
					origin_index);
				if (!from)
					return -1;
				from->peer = control_port;
				control_port->peer = from;
			}
		}
	}

	return 0;
}

static struct plugin *find_enabled_builtin_plugin(struct ini *ini,
						  const char *label,
						  const char *purpose,
						  struct cras_expr_env *env)
{
	int i;
	struct plugin *plugin, *found = NULL;

	FOR_ARRAY_ELEMENT(&ini->plugins, i, plugin) {
		if (strcmp(plugin->library, "builtin") != 0)
			continue;
		if (strcmp(plugin->label, label) != 0)
			continue;
		if (!plugin->purpose || strcmp(plugin->purpose, purpose) != 0)
			continue;
		if (is_disabled(plugin, env))
			continue;
		if (found) {
			syslog(LOG_ERR, "two %s plugins enabled: %s and %s",
			       label, found->title, plugin->title);
			return NULL;
		}
		found = plugin;
	}

	return found;
}

struct pipeline *cras_dsp_pipeline_create(struct ini *ini,
					  struct cras_expr_env *env,
					  const char *purpose)
{
	struct pipeline *pipeline;
	int n;
	char *visited;
	int rc;
	struct plugin *source = find_enabled_builtin_plugin(
		ini, "source", purpose, env);
	struct plugin *sink = find_enabled_builtin_plugin(
		ini, "sink", purpose, env);

	if (!source || !sink) {
		syslog(LOG_DEBUG,
		       "no enabled source or sink found %p/%p for %s",
		       source, sink, purpose);
		return NULL;
	}

	pipeline = calloc(1, sizeof(struct pipeline));
	if (!pipeline) {
		syslog(LOG_ERR, "no memory for pipeline");
		return NULL;
	}

	pipeline->ini = ini;
	pipeline->purpose = purpose;
	/* create instances for needed plugins, in the order of dependency */
	n = ARRAY_COUNT(&ini->plugins);
	visited = calloc(1, n);
	rc = topological_sort(pipeline, env, sink, visited);
	free(visited);

	if (rc < 0) {
		syslog(LOG_ERR, "failed to construct pipeline");
		return NULL;
	}

	pipeline->source_instance = find_instance_by_plugin(
		&pipeline->instances, source);
	pipeline->sink_instance = find_instance_by_plugin(
		&pipeline->instances, sink);

	if (!pipeline->source_instance || !pipeline->sink_instance) {
		syslog(LOG_ERR, "source(%p) or sink(%p) missing/disabled?",
		       source, sink);
		cras_dsp_pipeline_free(pipeline);
		return NULL;
	}

	pipeline->input_channels = ARRAY_COUNT(
		&pipeline->source_instance->output_audio_ports);
	pipeline->output_channels = ARRAY_COUNT(
		&pipeline->sink_instance->input_audio_ports);
	if (pipeline->output_channels > pipeline->input_channels) {
		/* Can't increase channel count, no where to put them. */
		syslog(LOG_ERR, "DSP output more channels than input\n");
		cras_dsp_pipeline_free(pipeline);
		return NULL;
	}

	return pipeline;
}

static int load_module(struct plugin *plugin, struct instance *instance)
{
	struct dsp_module *module;
	module = cras_dsp_module_load_builtin(plugin);
	if (!module)
		module = cras_dsp_module_load_ladspa(plugin);
	if (!module)
		return -1;
	instance->module = module;
	instance->properties = module->get_properties(module);
	return 0;
}

static void use_buffers(char *busy, audio_port_array *audio_ports)
{
	int i, k = 0;
	struct audio_port *audio_port;

	FOR_ARRAY_ELEMENT(audio_ports, i, audio_port) {
		while (busy[k])
			k++;
		audio_port->buf_index = k;
		busy[k] = 1;
	}
}

static void unuse_buffers(char *busy, audio_port_array *audio_ports)
{
	int i;
	struct audio_port *audio_port;

	FOR_ARRAY_ELEMENT(audio_ports, i, audio_port) {
		busy[audio_port->buf_index] = 0;
	}
}

/* assign which buffer each audio port on each instance should use */
static int allocate_buffers(struct pipeline *pipeline)
{
	int i;
	struct instance *instance;
	int need_buf = 0, peak_buf = 0;
	char *busy;

	/* first figure out how many buffers do we need */
	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		int in = ARRAY_COUNT(&instance->input_audio_ports);
		int out = ARRAY_COUNT(&instance->output_audio_ports);

		if (instance->properties & MODULE_INPLACE_BROKEN) {
			/* We cannot reuse input buffer as output
			 * buffer, so we need to use extra buffers */
			need_buf += out;
			peak_buf = MAX(peak_buf, need_buf);
			need_buf -= in;
		} else {
			need_buf += out - in;
			peak_buf = MAX(peak_buf, need_buf);
		}
	}

	/* then allocate the buffers */
	pipeline->peak_buf = peak_buf;
	pipeline->buffers = (float **)calloc(peak_buf, sizeof(float *));

	if (!pipeline->buffers) {
		syslog(LOG_ERR, "failed to allocate buffers");
		return -1;
	}

	for (i = 0; i < peak_buf; i++) {
		size_t size = DSP_BUFFER_SIZE * sizeof(float);
		float *buf = calloc(1, size);
		if (!buf) {
			syslog(LOG_ERR, "failed to allocate buf");
			return -1;
		}
		pipeline->buffers[i] = buf;
	}

	/* Now assign buffer index for each instance's input/output ports */
	busy = calloc(peak_buf, sizeof(*busy));
	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		int j;
		struct audio_port *audio_port;

		/* Collect input buffers from upstream */
		FOR_ARRAY_ELEMENT(&instance->input_audio_ports, j, audio_port) {
			audio_port->buf_index = audio_port->peer->buf_index;
		}

		/* If the module has the MODULE_INPLACE_BROKEN flag,
		 * we cannot reuse input buffers as output buffers, so
		 * we need to use extra buffers. For example, in this graph
		 *
		 * [A]
		 * output_0={x}
		 * output_1={y}
		 * output_2={z}
		 * output_3={w}
		 * [B]
		 * input_0={x}
		 * input_1={y}
		 * input_2={z}
		 * input_3={w}
		 * output_4={u}
		 *
		 * Then peak_buf for this pipeline is 4. However if
		 * plugin B has the MODULE_INPLACE_BROKEN flag, then
		 * peak_buf is 5 because plugin B cannot output to the
		 * same buffer used for input.
		 *
		 * This means if we don't have the flag, we can free
		 * the input buffers then allocate the output buffers,
		 * but if we have the flag, we have to allocate the
		 * output buffers before freeing the input buffers.
		 */
		if (instance->properties & MODULE_INPLACE_BROKEN) {
			use_buffers(busy, &instance->output_audio_ports);
			unuse_buffers(busy, &instance->input_audio_ports);
		} else {
			unuse_buffers(busy, &instance->input_audio_ports);
			use_buffers(busy, &instance->output_audio_ports);
		}
	}
	free(busy);

	return 0;
}

int cras_dsp_pipeline_load(struct pipeline *pipeline)
{
	int i;
	struct instance *instance;

	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		struct plugin *plugin = instance->plugin;
		if (load_module(plugin, instance) != 0)
			return -1;
	}

	if (allocate_buffers(pipeline) != 0)
		return -1;

	return 0;
}

/* Calculates the total buffering delay of each instance from the source */
static void calculate_audio_delay(struct pipeline *pipeline)
{
	int i;
	struct instance *instance;

	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		struct dsp_module *module = instance->module;
		audio_port_array *audio_in = &instance->input_audio_ports;
		struct audio_port *audio_port;
		int delay = 0;
		int j;

		/* Finds the max delay of all modules that provide input to this
		 * instance. */
		FOR_ARRAY_ELEMENT(audio_in, j, audio_port) {
			struct instance *upstream = find_instance_by_plugin(
				&pipeline->instances, audio_port->peer->plugin);
			delay = MAX(upstream->total_delay, delay);
		}

		instance->total_delay = delay + module->get_delay(module);
	}
}

int cras_dsp_pipeline_instantiate(struct pipeline *pipeline, int sample_rate)
{
	int i;
	struct instance *instance;

	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		struct dsp_module *module = instance->module;
		if (module->instantiate(module, sample_rate) != 0)
			return -1;
		instance->instantiated = 1;
		syslog(LOG_DEBUG, "instantiate %s", instance->plugin->label);
	}
	pipeline->sample_rate = sample_rate;

	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		audio_port_array *audio_in = &instance->input_audio_ports;
		audio_port_array *audio_out = &instance->output_audio_ports;
		control_port_array *control_in = &instance->input_control_ports;
		control_port_array *control_out =
			&instance->output_control_ports;
		int j;
		struct audio_port *audio_port;
		struct control_port *control_port;
		struct dsp_module *module = instance->module;

		/* connect audio ports */
		FOR_ARRAY_ELEMENT(audio_in, j, audio_port) {
			float *buf = pipeline->buffers[audio_port->buf_index];
			module->connect_port(module,
					     audio_port->original_index,
					     buf);
			syslog(LOG_DEBUG, "connect audio buf %d to %s:%d (in)",
			       audio_port->buf_index, instance->plugin->title,
			       audio_port->original_index);
		}
		FOR_ARRAY_ELEMENT(audio_out, j, audio_port) {
			float *buf = pipeline->buffers[audio_port->buf_index];
			module->connect_port(module,
					     audio_port->original_index,
					     buf);
			syslog(LOG_DEBUG, "connect audio buf %d to %s:%d (out)",
			       audio_port->buf_index, instance->plugin->title,
			       audio_port->original_index);
		}

		/* connect control ports */
		FOR_ARRAY_ELEMENT(control_in, j, control_port) {
			/* Note for input control ports which has a
			 * peer, we use &control_port->peer->value, so
			 * we can get the peer port's output value
			 * directly */
			float *value = control_port->peer ?
				&control_port->peer->value :
				&control_port->value;
			module->connect_port(module,
					     control_port->original_index,
					     value);
			syslog(LOG_DEBUG,
			       "connect control (val=%g) to %s:%d (in)",
			       control_port->value, instance->plugin->title,
			       control_port->original_index);
		}
		FOR_ARRAY_ELEMENT(control_out, j, control_port) {
			module->connect_port(module,
					     control_port->original_index,
					     &control_port->value);
			syslog(LOG_DEBUG,
			       "connect control (val=%g) to %s:%d (out)",
			       control_port->value, instance->plugin->title,
			       control_port->original_index);
		}
	}

	calculate_audio_delay(pipeline);
	return 0;
}

void cras_dsp_pipeline_deinstantiate(struct pipeline *pipeline)
{
	int i;
	struct instance *instance;

	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		struct dsp_module *module = instance->module;
		if (instance->instantiated) {
			module->deinstantiate(module);
			instance->instantiated = 0;
		}
	}
	pipeline->sample_rate = 0;
}

int cras_dsp_pipeline_get_delay(struct pipeline *pipeline)
{
	return pipeline->sink_instance->total_delay;
}

int cras_dsp_pipeline_get_sample_rate(struct pipeline *pipeline)
{
	return pipeline->sample_rate;
}

int cras_dsp_pipeline_get_num_input_channels(struct pipeline *pipeline)
{
	return pipeline->input_channels;
}

int cras_dsp_pipeline_get_num_output_channels(struct pipeline *pipeline)
{
	return pipeline->output_channels;
}

int cras_dsp_pipeline_get_peak_audio_buffers(struct pipeline *pipeline)
{
	return pipeline->peak_buf;
}

static float *find_buffer(struct pipeline *pipeline,
			  audio_port_array *audio_ports,
			  int index)
{
	int i;
	struct audio_port *audio_port;

	FOR_ARRAY_ELEMENT(audio_ports, i, audio_port) {
		if (audio_port->original_index == index)
			return pipeline->buffers[audio_port->buf_index];
	}
	return NULL;
}

float *cras_dsp_pipeline_get_source_buffer(struct pipeline *pipeline, int index)
{
	return find_buffer(pipeline,
			   &pipeline->source_instance->output_audio_ports,
			   index);
}

float *cras_dsp_pipeline_get_sink_buffer(struct pipeline *pipeline, int index)
{
	return find_buffer(pipeline,
			   &pipeline->sink_instance->input_audio_ports,
			   index);
}

void cras_dsp_pipeline_run(struct pipeline *pipeline, int sample_count)
{
	int i;
	struct instance *instance;

	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		struct dsp_module *module = instance->module;
		module->run(module, sample_count);
	}
}

void cras_dsp_pipeline_add_statistic(struct pipeline *pipeline,
				     const struct timespec *time_delta,
				     int samples)
{
	int64_t t;
	if (samples <= 0)
		return;

	t = time_delta->tv_sec * 1000000000LL + time_delta->tv_nsec;

	if (pipeline->total_blocks == 0) {
		pipeline->max_time = t;
		pipeline->min_time = t;
	} else {
		pipeline->max_time = MAX(pipeline->max_time, t);
		pipeline->min_time = MIN(pipeline->min_time, t);
	}

	pipeline->total_blocks++;
	pipeline->total_samples += samples;
	pipeline->total_time += t;
}

void cras_dsp_pipeline_apply(struct pipeline *pipeline,
			     uint8_t *buf, unsigned int frames)
{
	size_t remaining;
	size_t chunk;
	size_t i;
	int16_t *target;
	unsigned int input_channels = pipeline->input_channels;
	unsigned int output_channels = pipeline->output_channels;
	float *source[input_channels];
	float *sink[output_channels];
	struct timespec begin, end, delta;

	if (!pipeline || frames == 0)
		return;

	clock_gettime(CLOCK_THREAD_CPUTIME_ID, &begin);

	target = (int16_t *)buf;

	/* get pointers to source and sink buffers */
	for (i = 0; i < input_channels; i++)
		source[i] = cras_dsp_pipeline_get_source_buffer(pipeline, i);
	for (i = 0; i < output_channels; i++)
		sink[i] = cras_dsp_pipeline_get_sink_buffer(pipeline, i);

	remaining = frames;

	/* process at most DSP_BUFFER_SIZE frames each loop */
	while (remaining > 0) {
		chunk = MIN(remaining, (size_t)DSP_BUFFER_SIZE);

		/* deinterleave and convert to float */
		dsp_util_deinterleave(target, source, input_channels, chunk);

		/* Run the pipeline */
		cras_dsp_pipeline_run(pipeline, chunk);

		/* interleave and convert back to int16_t */
		dsp_util_interleave(sink, target, output_channels, chunk);

		target += chunk * output_channels;
		remaining -= chunk;
	}

	clock_gettime(CLOCK_THREAD_CPUTIME_ID, &end);
	subtract_timespecs(&end, &begin, &delta);
	cras_dsp_pipeline_add_statistic(pipeline, &delta, frames);
}

void cras_dsp_pipeline_free(struct pipeline *pipeline)
{
	int i;
	struct instance *instance;

	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		struct dsp_module *module = instance->module;
		instance->plugin = NULL;
		ARRAY_FREE(&instance->input_audio_ports);
		ARRAY_FREE(&instance->input_control_ports);
		ARRAY_FREE(&instance->output_audio_ports);
		ARRAY_FREE(&instance->output_control_ports);

		if (module) {
			if (instance->instantiated) {
				module->deinstantiate(module);
				instance->instantiated = 0;
			}
			module->free_module(module);
			instance->module = NULL;
		}
	}

	pipeline->ini = NULL;
	ARRAY_FREE(&pipeline->instances);

	for (i = 0; i < pipeline->peak_buf; i++)
		free(pipeline->buffers[i]);
	free(pipeline->buffers);
	free(pipeline);
}

static void dump_audio_ports(struct dumper *d, const char *name,
			     audio_port_array *audio_ports)
{
	int i;
	struct audio_port *audio_port;
	int n = ARRAY_COUNT(audio_ports);

	if (n == 0)
		return;
	dumpf(d, "   %s (%d) =\n", name, n);

	FOR_ARRAY_ELEMENT(audio_ports, i, audio_port) {
		dumpf(d, "   %p, peer %p, orig=%d, buf=%d\n",
		      audio_port, audio_port->peer,
		      audio_port->original_index, audio_port->buf_index);
	}
}

static void dump_control_ports(struct dumper *d, const char *name,
			       control_port_array *control_ports)
{
	int i;
	struct control_port *control_port;
	int n = ARRAY_COUNT(control_ports);

	if (n == 0)
		return;
	dumpf(d, "   %s (%d) =\n", name, ARRAY_COUNT(control_ports));

	FOR_ARRAY_ELEMENT(control_ports, i, control_port) {
		dumpf(d, "   %p, peer %p, orig=%d, value=%g\n",
		      control_port, control_port->peer,
		      control_port->original_index, control_port->value);
	}
}

void cras_dsp_pipeline_dump(struct dumper *d, struct pipeline *pipeline)
{
	int i;
	struct instance *instance;

	dumpf(d, "---- pipeline dump begin ----\n");
	dumpf(d, "pipeline (%s):\n", pipeline->purpose);
	dumpf(d, " input channels: %d\n", pipeline->input_channels);
	dumpf(d, " output channels: %d\n", pipeline->output_channels);
	dumpf(d, " sample_rate: %d\n", pipeline->sample_rate);
	dumpf(d, " processed samples: %" PRId64 "\n", pipeline->total_samples);
	dumpf(d, " processed blocks: %" PRId64 "\n", pipeline->total_blocks);
	dumpf(d, " total processing time: %" PRId64 "ns\n",
	      pipeline->total_time);
	if (pipeline->total_blocks) {
		dumpf(d, " average block size: %" PRId64 "\n",
		      pipeline->total_samples / pipeline->total_blocks);
		dumpf(d, " avg processing time per block: %" PRId64 "ns\n",
		      pipeline->total_time / pipeline->total_blocks);
	}
	dumpf(d, " min processing time per block: %" PRId64 "ns\n",
	      pipeline->min_time);
	dumpf(d, " max processing time per block: %" PRId64 "ns\n",
	      pipeline->max_time);
	dumpf(d, " cpu load: %g%%\n", pipeline->total_time * 1e-9
	      / pipeline->total_samples * pipeline->sample_rate * 100);
	dumpf(d, " instances (%d):\n",
	      ARRAY_COUNT(&pipeline->instances));
	FOR_ARRAY_ELEMENT(&pipeline->instances, i, instance) {
		struct dsp_module *module = instance->module;
		dumpf(d, "  [%d]%s mod=%p, total delay=%d\n",
		      i, instance->plugin->title, module,
		      instance->total_delay);
		if (module)
			module->dump(module, d);
		dump_audio_ports(d, "input_audio_ports",
				 &instance->input_audio_ports);
		dump_audio_ports(d, "output_audio_ports",
				 &instance->output_audio_ports);
		dump_control_ports(d, "input_control_ports",
				   &instance->input_control_ports);
		dump_control_ports(d, "output_control_ports",
				   &instance->output_control_ports);
	}
	dumpf(d, " peak_buf = %d\n", pipeline->peak_buf);
	dumpf(d, "---- pipeline dump end ----\n");
}