/* 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.
 */

/* For now just use speex, can add more resamplers later. */
#include <speex/speex_resampler.h>
#include <sys/param.h>
#include <syslog.h>

#include "cras_fmt_conv.h"
#include "cras_audio_format.h"
#include "cras_util.h"
#include "linear_resampler.h"

/* The quality level is a value between 0 and 10. This is a tradeoff between
 * performance, latency, and quality. */
#define SPEEX_QUALITY_LEVEL 4
/* Max number of converters, src, down/up mix, 2xformat, and linear resample. */
#define MAX_NUM_CONVERTERS 5
/* Channel index for stereo. */
#define STEREO_L 0
#define STEREO_R 1

typedef void (*sample_format_converter_t)(const uint8_t *in,
					  size_t in_samples,
					  uint8_t *out);
typedef size_t (*channel_converter_t)(struct cras_fmt_conv *conv,
				      const int16_t *in,
				      size_t in_frames,
				      int16_t *out);

/* Member data for the resampler. */
struct cras_fmt_conv {
	SpeexResamplerState *speex_state;
	channel_converter_t channel_converter;
	float **ch_conv_mtx; /* Coefficient matrix for mixing channels. */
	sample_format_converter_t in_format_converter;
	sample_format_converter_t out_format_converter;
	struct linear_resampler *resampler;
	struct cras_audio_format in_fmt;
	struct cras_audio_format out_fmt;
	uint8_t *tmp_bufs[MAX_NUM_CONVERTERS - 1];
	size_t tmp_buf_frames;
	size_t pre_linear_resample;
	size_t num_converters; /* Incremented once for SRC, channel, format. */
};

/* Add and clip two s16 samples. */
static int16_t s16_add_and_clip(int16_t a, int16_t b)
{
	int32_t sum;

	sum = a + b;
	sum = MAX(sum, -0x8000);
	sum = MIN(sum, 0x7fff);
	return (int16_t)sum;
}

/*
 * Convert between different sample formats.
 */

/* Converts from U8 to S16. */
static void convert_u8_to_s16le(const uint8_t *in, size_t in_samples,
				uint8_t *out)
{
	size_t i;
	uint16_t *_out = (uint16_t *)out;

	for (i = 0; i < in_samples; i++, in++, _out++)
		*_out = ((int16_t)*in - 0x80) << 8;
}

/* Converts from S24 to S16. */
static void convert_s24le_to_s16le(const uint8_t *in, size_t in_samples,
				   uint8_t *out)
{
	size_t i;
	int32_t *_in = (int32_t *)in;
	uint16_t *_out = (uint16_t *)out;

	for (i = 0; i < in_samples; i++, _in++, _out++)
		*_out = (int16_t)((*_in & 0x00ffffff) >> 8);
}

/* Converts from S32 to S16. */
static void convert_s32le_to_s16le(const uint8_t *in, size_t in_samples,
				   uint8_t *out)
{
	size_t i;
	int32_t *_in = (int32_t *)in;
	uint16_t *_out = (uint16_t *)out;

	for (i = 0; i < in_samples; i++, _in++, _out++)
		*_out = (int16_t)(*_in >> 16);
}

/* Converts from S24_3LE to S16. */
static void convert_s243le_to_s16le(const uint8_t *in, size_t in_samples,
				   uint8_t *out)
{
	/* find how to calculate in and out size, implement the conversion
	 * between S24_3LE and S16 */

	size_t i;
	int8_t *_in = (int8_t *)in;
	uint16_t *_out = (uint16_t *)out;

	for (i = 0; i < in_samples; i++, _in += 3, _out++)
		memcpy(_out, _in + 1, 2);
}

/* Converts from S16 to U8. */
static void convert_s16le_to_u8(const uint8_t *in, size_t in_samples,
				uint8_t *out)
{
	size_t i;
	int16_t *_in = (int16_t *)in;

	for (i = 0; i < in_samples; i++, _in++, out++)
		*out = (uint8_t)(*_in >> 8) + 128;
}

/* Converts from S16 to S24. */
static void convert_s16le_to_s24le(const uint8_t *in, size_t in_samples,
				   uint8_t *out)
{
	size_t i;
	int16_t *_in = (int16_t *)in;
	uint32_t *_out = (uint32_t *)out;

	for (i = 0; i < in_samples; i++, _in++, _out++)
		*_out = ((int32_t)*_in << 8);
}

/* Converts from S16 to S32. */
static void convert_s16le_to_s32le(const uint8_t *in, size_t in_samples,
				   uint8_t *out)
{
	size_t i;
	int16_t *_in = (int16_t *)in;
	uint32_t *_out = (uint32_t *)out;

	for (i = 0; i < in_samples; i++, _in++, _out++)
		*_out = ((int32_t)*_in << 16);
}

/* Converts from S16 to S24_3LE. */
static void convert_s16le_to_s243le(const uint8_t *in, size_t in_samples,
				   uint8_t *out)
{
	size_t i;
	int16_t *_in = (int16_t *)in;
	uint8_t *_out = (uint8_t *)out;

	for (i = 0; i < in_samples; i++, _in++, _out += 3) {
		*_out = 0;
		memcpy(_out + 1, _in, 2);
	}
}

/*
 * Convert between different channel numbers.
 */

/* Converts S16 mono to S16 stereo. The out buffer must be double the size of
 * the input buffer. */
static size_t s16_mono_to_stereo(struct cras_fmt_conv *conv,
				const int16_t *in, size_t in_frames,
				int16_t *out)
{
	size_t i;

	for (i = 0; i < in_frames; i++) {
		out[2 * i] = in[i];
		out[2 * i + 1] = in[i];
	}
	return in_frames;
}

/* Converts S16 Stereo to S16 mono.  The output buffer only need be big enough
 * for mono samples. */
static size_t s16_stereo_to_mono(struct cras_fmt_conv *conv,
				const int16_t *in, size_t in_frames,
				int16_t *out)
{
	size_t i;

	for (i = 0; i < in_frames; i++)
		out[i] = s16_add_and_clip(in[2 * i], in[2 * i + 1]);
	return in_frames;
}

/* Converts S16 mono to 5.1 surround. Fit mono to front center of the
 * output, or split to front left/right if front center is missing
 * from the output channel layout.
 */
static size_t s16_mono_to_51(struct cras_fmt_conv *conv,
			     const int16_t *in, size_t in_frames,
			     int16_t *out)
{
	size_t i, left, right, center;

	memset(out, 0, sizeof(*out) * 6 * in_frames);
	left = conv->out_fmt.channel_layout[CRAS_CH_FL];
	right = conv->out_fmt.channel_layout[CRAS_CH_FR];
	center = conv->out_fmt.channel_layout[CRAS_CH_FC];

	if (center != -1)
		for (i = 0; i < in_frames; i++)
			out[6 * i + center] = in[i];
	else if (left != -1 && right != -1)
		for (i = 0; i < in_frames; i++) {
			out[6 * i + right] = in[i] / 2;
			out[6 * 1 + left] = in[i] / 2;
		}
	else
		/* Select the first channel to convert to as the
		 * default behavior.
		 */
		for (i = 0; i < in_frames; i++)
			out[6 * i] = in[i];

	return in_frames;
}

/* Converts S16 stereo to 5.1 surround. Fit the left/right of input
 * to the front left/right of output respectively and fill others
 * with zero. If any of the front left/right is missed from the output
 * channel layout, mix to front center.
 */
static size_t s16_stereo_to_51(struct cras_fmt_conv *conv,
			       const int16_t *in, size_t in_frames,
			       int16_t *out)
{
	size_t i, left, right, center;

	memset(out, 0, sizeof(*out) * 6 * in_frames);
	left = conv->out_fmt.channel_layout[CRAS_CH_FL];
	right = conv->out_fmt.channel_layout[CRAS_CH_FR];
	center = conv->out_fmt.channel_layout[CRAS_CH_FC];

	if (left != -1 && right != -1)
		for (i = 0; i < in_frames; i++) {
			out[6 * i + left] = in[2 * i];
			out[6 * i + right] = in[2 * i + 1];
		}
	else if (center != -1)
		for (i = 0; i < in_frames; i++)
			out[6 * i + center] = s16_add_and_clip(
					in[2 * i], in[2 * i + 1]);
	else
		/* Select the first two channels to convert to as the
		 * default behavior.
		 */
		for (i = 0; i < in_frames; i++) {
			out[6 * i] = in[2 * i];
			out[6 * i + 1] = in[2 * i + 1];
		}

	return in_frames;
}

/* Converts S16 5.1 to S16 stereo. The out buffer can have room for just
 * stereo samples. This convert function is used as the default behavior
 * when channel layout is not set from the client side. */
static size_t s16_51_to_stereo(struct cras_fmt_conv *conv,
			       const int16_t *in, size_t in_frames,
			       int16_t *out)
{
	static const unsigned int left_idx = 0;
	static const unsigned int right_idx = 1;
	/* static const unsigned int left_surround_idx = 2; */
	/* static const unsigned int right_surround_idx = 3; */
	static const unsigned int center_idx = 4;
	/* static const unsigned int lfe_idx = 5; */
	size_t i;

	for (i = 0; i < in_frames; i++) {
		unsigned int half_center;

		half_center = in[6 * i + center_idx] / 2;
		out[2 * i + left_idx] = s16_add_and_clip(in[6 * i + left_idx],
							 half_center);
		out[2 * i + right_idx] = s16_add_and_clip(in[6 * i + right_idx],
							  half_center);
	}
	return in_frames;
}

/* Converts S16 N channels to S16 M channels.  The out buffer must have room for
 * M channel. This convert function is used as the default behavior when channel
 * layout is not set from the client side. */
static size_t s16_default_all_to_all(struct cras_fmt_conv *conv,
				     const int16_t *in, size_t in_frames,
				     int16_t *out)
{
	unsigned int num_in_ch = conv->in_fmt.num_channels;
	unsigned int num_out_ch = conv->out_fmt.num_channels;
	unsigned int in_ch, out_ch, i;

	memset(out, 0, num_out_ch * in_frames *
				cras_get_format_bytes(&conv->out_fmt));
	for (out_ch = 0; out_ch < num_out_ch; out_ch++) {
		for (in_ch = 0; in_ch < num_in_ch; in_ch++) {
			for (i = 0; i < in_frames; i++) {
				out[out_ch + i * num_out_ch] +=
					in[in_ch + i * num_in_ch] / num_in_ch;
			}
		}
	}
	return in_frames;
}

static int is_channel_layout_equal(const struct cras_audio_format *a,
				   const struct cras_audio_format *b)
{
	int ch;
	for (ch = 0; ch < CRAS_CH_MAX; ch++)
		if (a->channel_layout[ch] != b->channel_layout[ch])
			return 0;

	return 1;
}

/* Multiplies buffer vector with coefficient vector. */
static int16_t multiply_buf_with_coef(float *coef,
				      const int16_t *buf,
				      size_t size)
{
	int32_t sum = 0;
	int i;

	for (i = 0; i < size; i++)
		sum += coef[i] * buf[i];
	sum = MAX(sum, -0x8000);
	sum = MIN(sum, 0x7fff);
	return (int16_t)sum;
}

static void normalize_buf(float *buf, size_t size)
{
	int i;
	float squre_sum = 0.0;
	for (i = 0; i < size; i++)
		squre_sum += buf[i] * buf[i];
	for (i = 0; i < size; i ++)
		buf[i] /= squre_sum;
}

/* Converts channels based on the channel conversion
 * coefficient matrix.
 */
static size_t convert_channels(struct cras_fmt_conv *conv,
			       const int16_t *in,
			       size_t in_frames,
			       int16_t *out)
{
	unsigned i, fr;
	unsigned in_idx = 0;
	unsigned out_idx = 0;

	for (fr = 0; fr < in_frames; fr++) {
		for (i = 0; i < conv->out_fmt.num_channels; i++)
			out[out_idx + i] = multiply_buf_with_coef(
					conv->ch_conv_mtx[i],
					&in[in_idx],
					conv->in_fmt.num_channels);
		in_idx += conv->in_fmt.num_channels;
		out_idx += conv->out_fmt.num_channels;
	}

	return in_frames;
}

/* Populates the down mix matrix by rules:
 * 1. Front/side left(right) channel will mix to left(right) of
 *    full scale.
 * 2. Center and LFE will be split equally to left and right.
 *    Rear
 * 3. Rear left/right will split 1/4 of the power to opposite
 *    channel.
 */
static void surround51_to_stereo_downmix_mtx(float **mtx,
					     int8_t layout[CRAS_CH_MAX])
{
	if (layout[CRAS_CH_FC] != -1) {
		mtx[STEREO_L][layout[CRAS_CH_FC]] = 0.707;
		mtx[STEREO_R][layout[CRAS_CH_FC]] = 0.707;
	}
	if (layout[CRAS_CH_FL] != -1 && layout[CRAS_CH_FR] != -1) {
		mtx[STEREO_L][layout[CRAS_CH_FL]] = 1.0;
		mtx[STEREO_R][layout[CRAS_CH_FR]] = 1.0;
	}
	if (layout[CRAS_CH_SL] != -1 && layout[CRAS_CH_SR] != -1) {
		mtx[STEREO_L][layout[CRAS_CH_SL]] = 1.0;
		mtx[STEREO_R][layout[CRAS_CH_SR]] = 1.0;
	}
	if (layout[CRAS_CH_RL] != -1 && layout[CRAS_CH_RR] != -1) {
		/* Split 1/4 power to the other side */
		mtx[STEREO_L][layout[CRAS_CH_RL]] = 0.866;
		mtx[STEREO_R][layout[CRAS_CH_RL]] = 0.5;
		mtx[STEREO_R][layout[CRAS_CH_RR]] = 0.866;
		mtx[STEREO_L][layout[CRAS_CH_RR]] = 0.5;
	}
	if (layout[CRAS_CH_LFE] != -1) {
		mtx[STEREO_L][layout[CRAS_CH_LFE]] = 0.707;
		mtx[STEREO_R][layout[CRAS_CH_LFE]] = 0.707;
	}

	normalize_buf(mtx[STEREO_L], 6);
	normalize_buf(mtx[STEREO_R], 6);
}

/*
 * Exported interface
 */

struct cras_fmt_conv *cras_fmt_conv_create(const struct cras_audio_format *in,
					   const struct cras_audio_format *out,
					   size_t max_frames,
					   size_t pre_linear_resample)
{
	struct cras_fmt_conv *conv;
	int rc;
	unsigned i;

	conv = calloc(1, sizeof(*conv));
	if (conv == NULL)
		return NULL;
	conv->in_fmt = *in;
	conv->out_fmt = *out;
	conv->tmp_buf_frames = max_frames;
	conv->pre_linear_resample = pre_linear_resample;

	/* Set up sample format conversion. */
	/* TODO(dgreid) - modify channel and sample rate conversion so
	 * converting to s16 isnt necessary. */
	if (in->format != SND_PCM_FORMAT_S16_LE) {
		conv->num_converters++;
		syslog(LOG_DEBUG, "Convert from format %d to %d.",
		       in->format, out->format);
		switch(in->format) {
		case SND_PCM_FORMAT_U8:
			conv->in_format_converter = convert_u8_to_s16le;
			break;
		case SND_PCM_FORMAT_S24_LE:
			conv->in_format_converter = convert_s24le_to_s16le;
			break;
		case SND_PCM_FORMAT_S32_LE:
			conv->in_format_converter = convert_s32le_to_s16le;
			break;
		case SND_PCM_FORMAT_S24_3LE:
			conv->in_format_converter = convert_s243le_to_s16le;
			break;
		default:
			syslog(LOG_WARNING, "Invalid format %d", in->format);
			cras_fmt_conv_destroy(conv);
			return NULL;
		}
	}
	if (out->format != SND_PCM_FORMAT_S16_LE) {
		conv->num_converters++;
		syslog(LOG_DEBUG, "Convert from format %d to %d.",
		       in->format, out->format);
		switch (out->format) {
		case SND_PCM_FORMAT_U8:
			conv->out_format_converter = convert_s16le_to_u8;
			break;
		case SND_PCM_FORMAT_S24_LE:
			conv->out_format_converter = convert_s16le_to_s24le;
			break;
		case SND_PCM_FORMAT_S32_LE:
			conv->out_format_converter = convert_s16le_to_s32le;
			break;
		case SND_PCM_FORMAT_S24_3LE:
			conv->out_format_converter = convert_s16le_to_s243le;
			break;
		default:
			syslog(LOG_WARNING, "Invalid format %d", out->format);
			cras_fmt_conv_destroy(conv);
			return NULL;
		}
	}

	/* Set up channel number conversion. */
	if (in->num_channels != out->num_channels) {
		conv->num_converters++;
		syslog(LOG_DEBUG, "Convert from %zu to %zu channels.",
		       in->num_channels, out->num_channels);

		/* Populate the conversion matrix base on in/out channel count
		 * and layout. */
		if (in->num_channels == 1 && out->num_channels == 2) {
			conv->channel_converter = s16_mono_to_stereo;
		} else if (in->num_channels == 1 && out->num_channels == 6) {
			conv->channel_converter = s16_mono_to_51;
		} else if (in->num_channels == 2 && out->num_channels == 1) {
			conv->channel_converter = s16_stereo_to_mono;
		} else if (in->num_channels == 2 && out->num_channels == 6) {
			conv->channel_converter = s16_stereo_to_51;
		} else if (in->num_channels == 6 && out->num_channels == 2) {
			int in_channel_layout_set = 0;

			/* Checks if channel_layout is set in the incoming format */
			for (i = 0; i < CRAS_CH_MAX; i++)
				if (in->channel_layout[i] != -1)
					in_channel_layout_set = 1;

			/* Use the conversion matrix based converter when a
			 * channel layout is set, or default to use existing
			 * converter to downmix to stereo */
			if (in_channel_layout_set) {
				conv->ch_conv_mtx = cras_channel_conv_matrix_alloc(
						in->num_channels,
						out->num_channels);
				if (conv->ch_conv_mtx == NULL) {
					cras_fmt_conv_destroy(conv);
					return NULL;
				}
				conv->channel_converter = convert_channels;
				surround51_to_stereo_downmix_mtx(
						conv->ch_conv_mtx,
						conv->in_fmt.channel_layout);
			} else {
				conv->channel_converter = s16_51_to_stereo;
			}
		} else {
			syslog(LOG_WARNING,
			       "Using default channel map for %zu to %zu",
			       in->num_channels, out->num_channels);
			conv->channel_converter = s16_default_all_to_all;
		}
	} else if (in->num_channels > 2 &&
		   !is_channel_layout_equal(in, out)){
		conv->num_converters++;
		conv->ch_conv_mtx = cras_channel_conv_matrix_create(in, out);
		if (conv->ch_conv_mtx == NULL) {
			syslog(LOG_ERR, "Failed to create channel conversion matrix");
			cras_fmt_conv_destroy(conv);
			return NULL;
		}
		conv->channel_converter = convert_channels;
	}
	/* Set up sample rate conversion. */
	if (in->frame_rate != out->frame_rate) {
		conv->num_converters++;
		syslog(LOG_DEBUG, "Convert from %zu to %zu Hz.",
		       in->frame_rate, out->frame_rate);
		conv->speex_state = speex_resampler_init(out->num_channels,
							 in->frame_rate,
							 out->frame_rate,
							 SPEEX_QUALITY_LEVEL,
							 &rc);
		if (conv->speex_state == NULL) {
			syslog(LOG_ERR, "Fail to create speex:%zu %zu %zu %d",
			       out->num_channels,
			       in->frame_rate,
			       out->frame_rate,
			       rc);
			cras_fmt_conv_destroy(conv);
			return NULL;
		}
	}

	/* Set up linear resampler. */
	conv->num_converters++;
	conv->resampler = linear_resampler_create(
			out->num_channels,
			cras_get_format_bytes(out),
			out->frame_rate,
			out->frame_rate);
	if (conv->resampler == NULL) {
		syslog(LOG_ERR, "Fail to create linear resampler");
		cras_fmt_conv_destroy(conv);
		return NULL;
	}

	/* Need num_converters-1 temp buffers, the final converter renders
	 * directly into the output. */
	for (i = 0; i < conv->num_converters - 1; i++) {
		conv->tmp_bufs[i] = malloc(
			max_frames *
			4 * /* width in bytes largest format. */
			MAX(in->num_channels, out->num_channels));
		if (conv->tmp_bufs[i] == NULL) {
			cras_fmt_conv_destroy(conv);
			return NULL;
		}
	}

	assert(conv->num_converters <= MAX_NUM_CONVERTERS);

	return conv;
}

void cras_fmt_conv_destroy(struct cras_fmt_conv *conv)
{
	unsigned i;
	if (conv->ch_conv_mtx)
		cras_channel_conv_matrix_destroy(conv->ch_conv_mtx,
						 conv->out_fmt.num_channels);
	if (conv->speex_state)
		speex_resampler_destroy(conv->speex_state);
	if (conv->resampler)
		linear_resampler_destroy(conv->resampler);
	for (i = 0; i < MAX_NUM_CONVERTERS - 1; i++)
		free(conv->tmp_bufs[i]);
	free(conv);
}

struct cras_fmt_conv *cras_channel_remix_conv_create(
		unsigned int num_channels,
		const float *coefficient)
{
	struct cras_fmt_conv *conv;
	unsigned out_ch, in_ch;

	conv = calloc(1, sizeof(*conv));
	if (conv == NULL)
		return NULL;
	conv->in_fmt.num_channels = num_channels;
	conv->out_fmt.num_channels = num_channels;

	conv->ch_conv_mtx = cras_channel_conv_matrix_alloc(num_channels,
							   num_channels);
	/* Convert the coeffiencnt array to conversion matrix. */
	for (out_ch = 0; out_ch < num_channels; out_ch++)
		for (in_ch = 0; in_ch < num_channels; in_ch++)
			conv->ch_conv_mtx[out_ch][in_ch] =
				coefficient[in_ch + out_ch * num_channels];

	conv->num_converters = 1;
	conv->tmp_bufs[0] = malloc(4 * /* width in bytes largest format. */
				   num_channels);
	return conv;
}

void cras_channel_remix_convert(struct cras_fmt_conv *conv,
				const struct cras_audio_format *fmt,
				uint8_t *in_buf,
				size_t nframes)
{
	unsigned ch, fr;
	int16_t *tmp = (int16_t *)conv->tmp_bufs[0];
	int16_t *buf = (int16_t *)in_buf;

	/* Do remix only when input buffer has the same number of channels. */
	if (fmt->num_channels != conv->in_fmt.num_channels)
		return;

	for (fr = 0; fr < nframes; fr++) {
		for (ch = 0; ch < conv->in_fmt.num_channels; ch++)
			tmp[ch] = multiply_buf_with_coef(
				conv->ch_conv_mtx[ch],
				buf,
				conv->in_fmt.num_channels);
		for (ch = 0; ch < conv->in_fmt.num_channels; ch++)
			buf[ch] = tmp[ch];
		buf += conv->in_fmt.num_channels;
	}
}

const struct cras_audio_format *cras_fmt_conv_in_format(
		const struct cras_fmt_conv *conv)
{
	return &conv->in_fmt;
}

const struct cras_audio_format *cras_fmt_conv_out_format(
		const struct cras_fmt_conv *conv)
{
	return &conv->out_fmt;
}

size_t cras_fmt_conv_in_frames_to_out(struct cras_fmt_conv *conv,
				      size_t in_frames)
{
	if (!conv)
		return in_frames;

	if (conv->pre_linear_resample)
		in_frames = linear_resampler_in_frames_to_out(
				conv->resampler,
				in_frames);
	in_frames = cras_frames_at_rate(conv->in_fmt.frame_rate,
				   in_frames,
				   conv->out_fmt.frame_rate);
	if (!conv->pre_linear_resample)
		in_frames = linear_resampler_in_frames_to_out(
				conv->resampler,
				in_frames);
	return in_frames;
}

size_t cras_fmt_conv_out_frames_to_in(struct cras_fmt_conv *conv,
				      size_t out_frames)
{
	if (!conv)
		return out_frames;
	if (!conv->pre_linear_resample)
		out_frames = linear_resampler_out_frames_to_in(
				conv->resampler,
				out_frames);
	out_frames = cras_frames_at_rate(conv->out_fmt.frame_rate,
				   out_frames,
				   conv->in_fmt.frame_rate);
	if (conv->pre_linear_resample)
		out_frames = linear_resampler_out_frames_to_in(
				conv->resampler,
				out_frames);
	return out_frames;
}

void cras_fmt_conv_set_linear_resample_rates(struct cras_fmt_conv *conv,
					     float from,
					     float to)
{
	linear_resampler_set_rates(conv->resampler, from, to);
}

size_t cras_fmt_conv_convert_frames(struct cras_fmt_conv *conv,
				    const uint8_t *in_buf,
				    uint8_t *out_buf,
				    unsigned int *in_frames,
				    size_t out_frames)
{
	uint32_t fr_in, fr_out;
	uint8_t *buffers[MAX_NUM_CONVERTERS + 1]; /* converters + out buffer. */
	size_t buf_idx = 0;
	static int logged_frames_dont_fit;
	unsigned int used_converters = conv->num_converters;
	unsigned int post_linear_resample = 0;
	unsigned int pre_linear_resample = 0;
	unsigned int linear_resample_fr = 0;

	assert(conv);

	if (linear_resampler_needed(conv->resampler)) {
		post_linear_resample = !conv->pre_linear_resample;
		pre_linear_resample = conv->pre_linear_resample;
	}

	/* If no SRC, then in_frames should = out_frames. */
	if (conv->speex_state == NULL) {
		fr_in = MIN(*in_frames, out_frames);
		if (out_frames < *in_frames && !logged_frames_dont_fit) {
			syslog(LOG_INFO,
			       "fmt_conv: %u to %zu no SRC.",
			       *in_frames,
			       out_frames);
			logged_frames_dont_fit = 1;
		}
	} else {
		fr_in = *in_frames;
	}
	fr_out = fr_in;

	/* Set up a chain of buffers.  The output buffer of the first conversion
	 * is used as input to the second and so forth, ending in the output
	 * buffer. */
	if (!linear_resampler_needed(conv->resampler))
		used_converters--;

	buffers[4] = (uint8_t *)conv->tmp_bufs[3];
	buffers[3] = (uint8_t *)conv->tmp_bufs[2];
	buffers[2] = (uint8_t *)conv->tmp_bufs[1];
	buffers[1] = (uint8_t *)conv->tmp_bufs[0];
	buffers[0] = (uint8_t *)in_buf;
	buffers[used_converters] = out_buf;

	if (pre_linear_resample) {
		linear_resample_fr = fr_in;
		unsigned resample_limit = out_frames;

		/* If there is a 2nd fmt conversion we should convert the
		 * resample limit and round it to the lower bound in order
		 * not to convert too many frames in the pre linear resampler.
		 */
		if (conv->speex_state != NULL)
			resample_limit = resample_limit * conv->in_fmt.frame_rate /
					conv->out_fmt.frame_rate;

		resample_limit = MIN(resample_limit, conv->tmp_buf_frames);
		fr_in = linear_resampler_resample(
				conv->resampler,
				buffers[buf_idx],
				&linear_resample_fr,
				buffers[buf_idx + 1],
				resample_limit);
		buf_idx++;
	}

	/* If the input format isn't S16_LE convert to it. */
	if (conv->in_fmt.format != SND_PCM_FORMAT_S16_LE) {
		conv->in_format_converter(buffers[buf_idx],
					  fr_in * conv->in_fmt.num_channels,
					  (uint8_t *)buffers[buf_idx + 1]);
		buf_idx++;
	}

	/* Then channel conversion. */
	if (conv->channel_converter != NULL) {
		conv->channel_converter(conv,
					(int16_t *)buffers[buf_idx],
					fr_in,
					(int16_t *)buffers[buf_idx + 1]);
		buf_idx++;
	}

	/* Then SRC. */
	if (conv->speex_state != NULL) {
		unsigned int out_limit = out_frames;

		if (post_linear_resample)
			out_limit = linear_resampler_out_frames_to_in(
					conv->resampler, out_limit);
		fr_out = cras_frames_at_rate(conv->in_fmt.frame_rate,
					     fr_in,
					     conv->out_fmt.frame_rate);
		if (fr_out > out_frames + 1 && !logged_frames_dont_fit) {
			syslog(LOG_INFO,
			       "fmt_conv: put %u frames in %zu sized buffer",
			       fr_out,
			       out_frames);
			logged_frames_dont_fit = 1;
		}
		/* limit frames to the output size. */
		fr_out = MIN(fr_out, out_limit);
		speex_resampler_process_interleaved_int(
				conv->speex_state,
				(int16_t *)buffers[buf_idx],
				&fr_in,
				(int16_t *)buffers[buf_idx + 1],
				&fr_out);
		buf_idx++;
	}

	if (post_linear_resample) {
		linear_resample_fr = fr_out;
		unsigned resample_limit = MIN(conv->tmp_buf_frames, out_frames);
		fr_out = linear_resampler_resample(
				conv->resampler,
				buffers[buf_idx],
				&linear_resample_fr,
				buffers[buf_idx + 1],
				resample_limit);
		buf_idx++;
	}

	/* If the output format isn't S16_LE convert to it. */
	if (conv->out_fmt.format != SND_PCM_FORMAT_S16_LE) {
		conv->out_format_converter(buffers[buf_idx],
					   fr_out * conv->out_fmt.num_channels,
					   (uint8_t *)buffers[buf_idx + 1]);
		buf_idx++;
	}

	if (pre_linear_resample)
		*in_frames = linear_resample_fr;
	else
		*in_frames = fr_in;
	return fr_out;
}

int cras_fmt_conversion_needed(const struct cras_fmt_conv *conv)
{
	return linear_resampler_needed(conv->resampler) ||
	       (conv->num_converters > 1);
}

/* If the server cannot provide the requested format, configures an audio format
 * converter that handles transforming the input format to the format used by
 * the server. */
int config_format_converter(struct cras_fmt_conv **conv,
			    enum CRAS_STREAM_DIRECTION dir,
			    const struct cras_audio_format *from,
			    const struct cras_audio_format *to,
			    unsigned int frames)
{
	struct cras_audio_format target;

	/* For input, preserve the channel count and layout of
	 * from format */
	if (dir == CRAS_STREAM_INPUT) {
		target = *from;
		target.format = to->format;
		target.frame_rate = to->frame_rate;
	} else {
		target = *to;
	}

	syslog(LOG_DEBUG,
	       "format convert: from:%d %zu %zu target: %d %zu %zu "
	       "frames = %u",
	       from->format, from->frame_rate, from->num_channels,
	       target.format, target.frame_rate, target.num_channels,
	       frames);
	*conv = cras_fmt_conv_create(from, &target, frames,
				     (dir == CRAS_STREAM_INPUT));
	if (!*conv) {
		syslog(LOG_ERR, "Failed to create format converter");
		return -ENOMEM;
	}

	return 0;
}