| /* 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 <endian.h> |
| #include <limits.h> |
| #include <math.h> |
| |
| #include "cras_fmt_conv.h" |
| #include "cras_fmt_conv_ops.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 uint8_t *in, size_t in_frames, |
| uint8_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. */ |
| }; |
| |
| 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; |
| } |
| |
| /* |
| * Calculates the normalize_factor abs_sum(ci) from given coefficients. |
| * Since sum(ci / abs_sum(ci)) <= 1, this could prevent sample overflow while |
| * upmixing or downmixing. |
| */ |
| static float normalize_factor(float *buf, size_t n) |
| { |
| int i; |
| float abs_sum = 0.0; |
| for (i = 0; i < n; i++) |
| abs_sum += fabs(buf[i]); |
| |
| return 1.0 / abs_sum; |
| } |
| |
| /* |
| * Normalize all channels with the same factor to maintain |
| * the energy ratio between original channels. |
| */ |
| static void normalize(float **mtx, size_t m, size_t n, float factor) |
| { |
| int i, j; |
| for (i = 0; i < m; i++) |
| for (j = 0; j < n; j++) |
| mtx[i][j] *= factor; |
| } |
| |
| /* |
| * 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(mtx, 2, 6, normalize_factor(mtx[STEREO_L], 6)); |
| } |
| |
| /* Populates the down mix matrix by rules: |
| * 1. Front left(right) channel will mix to the front left(right) of |
| * full scale. |
| * 2. Rear and side left(right) channel will mix to the rear left(right) of |
| * full scale. |
| * 3. Center will be split equally to the front left and right. |
| * 4. LFE will be split equally to the other channels. |
| */ |
| static void surround51_to_quad_downmix_mtx(float **mtx, |
| int8_t layout[CRAS_CH_MAX]) |
| { |
| if (layout[CRAS_CH_FL] != -1 && layout[CRAS_CH_FR] != -1) { |
| mtx[CRAS_CH_FL][layout[CRAS_CH_FL]] = 1.0; |
| mtx[CRAS_CH_FR][layout[CRAS_CH_FR]] = 1.0; |
| } |
| if (layout[CRAS_CH_RL] != -1 && layout[CRAS_CH_RR] != -1) { |
| mtx[CRAS_CH_RL][layout[CRAS_CH_RL]] = 1.0; |
| mtx[CRAS_CH_RR][layout[CRAS_CH_RR]] = 1.0; |
| } |
| if (layout[CRAS_CH_SL] != -1 && layout[CRAS_CH_SR] != -1) { |
| mtx[CRAS_CH_RL][layout[CRAS_CH_SL]] = 1.0; |
| mtx[CRAS_CH_RR][layout[CRAS_CH_SR]] = 1.0; |
| } |
| if (layout[CRAS_CH_FC] != -1) { |
| /* Split 1/2 power to the front L/R */ |
| mtx[CRAS_CH_FL][layout[CRAS_CH_FC]] = 0.707; |
| mtx[CRAS_CH_FR][layout[CRAS_CH_FC]] = 0.707; |
| } |
| if (layout[CRAS_CH_LFE] != -1) { |
| /* Split 1/4 power to the other channel */ |
| mtx[CRAS_CH_FL][layout[CRAS_CH_LFE]] = 0.5; |
| mtx[CRAS_CH_FR][layout[CRAS_CH_LFE]] = 0.5; |
| mtx[CRAS_CH_RL][layout[CRAS_CH_LFE]] = 0.5; |
| mtx[CRAS_CH_RR][layout[CRAS_CH_LFE]] = 0.5; |
| } |
| |
| normalize(mtx, 4, 6, normalize_factor(mtx[CRAS_CH_FL], 6)); |
| } |
| |
| static int is_supported_format(const struct cras_audio_format *fmt) |
| { |
| if (!fmt) |
| return 0; |
| |
| switch (fmt->format) { |
| case SND_PCM_FORMAT_U8: |
| case SND_PCM_FORMAT_S16_LE: |
| case SND_PCM_FORMAT_S24_3LE: |
| case SND_PCM_FORMAT_S24_LE: |
| case SND_PCM_FORMAT_S32_LE: |
| return 1; |
| default: |
| return 0; |
| } |
| } |
| |
| static size_t mono_to_stereo(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| return s16_mono_to_stereo(in, in_frames, out); |
| } |
| |
| static size_t stereo_to_mono(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| return s16_stereo_to_mono(in, in_frames, out); |
| } |
| |
| static size_t mono_to_51(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| size_t left, right, center; |
| |
| 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]; |
| |
| return s16_mono_to_51(left, right, center, in, in_frames, out); |
| } |
| |
| static size_t stereo_to_51(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| size_t left, right, center; |
| |
| 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]; |
| |
| return s16_stereo_to_51(left, right, center, in, in_frames, out); |
| } |
| |
| static size_t _51_to_stereo(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| return s16_51_to_stereo(in, in_frames, out); |
| } |
| |
| static size_t _51_to_quad(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| return s16_51_to_quad(in, in_frames, out); |
| } |
| |
| static size_t stereo_to_quad(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| size_t front_left, front_right, rear_left, rear_right; |
| |
| front_left = conv->out_fmt.channel_layout[CRAS_CH_FL]; |
| front_right = conv->out_fmt.channel_layout[CRAS_CH_FR]; |
| rear_left = conv->out_fmt.channel_layout[CRAS_CH_RL]; |
| rear_right = conv->out_fmt.channel_layout[CRAS_CH_RR]; |
| |
| return s16_stereo_to_quad(front_left, front_right, rear_left, |
| rear_right, in, in_frames, out); |
| } |
| |
| static size_t quad_to_stereo(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| size_t front_left, front_right, rear_left, rear_right; |
| |
| front_left = conv->in_fmt.channel_layout[CRAS_CH_FL]; |
| front_right = conv->in_fmt.channel_layout[CRAS_CH_FR]; |
| rear_left = conv->in_fmt.channel_layout[CRAS_CH_RL]; |
| rear_right = conv->in_fmt.channel_layout[CRAS_CH_RR]; |
| |
| return s16_quad_to_stereo(front_left, front_right, rear_left, |
| rear_right, in, in_frames, out); |
| } |
| |
| static size_t default_all_to_all(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| size_t num_in_ch, num_out_ch; |
| |
| num_in_ch = conv->in_fmt.num_channels; |
| num_out_ch = conv->out_fmt.num_channels; |
| |
| return s16_default_all_to_all(&conv->out_fmt, num_in_ch, num_out_ch, in, |
| in_frames, out); |
| } |
| |
| // Fill min(in channels, out_channels), leave the rest 0s. |
| static size_t default_some_to_some(struct cras_fmt_conv *conv, |
| const uint8_t *in, |
| size_t in_frames, |
| uint8_t *out) |
| { |
| size_t num_in_ch, num_out_ch; |
| |
| num_in_ch = conv->in_fmt.num_channels; |
| num_out_ch = conv->out_fmt.num_channels; |
| |
| return s16_some_to_some(&conv->out_fmt, num_in_ch, num_out_ch, in, |
| in_frames, out); |
| } |
| |
| static size_t convert_channels(struct cras_fmt_conv *conv, const uint8_t *in, |
| size_t in_frames, uint8_t *out) |
| { |
| float **ch_conv_mtx; |
| size_t num_in_ch, num_out_ch; |
| |
| ch_conv_mtx = conv->ch_conv_mtx; |
| num_in_ch = conv->in_fmt.num_channels; |
| num_out_ch = conv->out_fmt.num_channels; |
| |
| return s16_convert_channels(ch_conv_mtx, num_in_ch, num_out_ch, in, |
| in_frames, out); |
| } |
| |
| /* |
| * 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; |
| |
| if (!is_supported_format(in)) { |
| syslog(LOG_ERR, "Invalid input format %d", in->format); |
| cras_fmt_conv_destroy(&conv); |
| return NULL; |
| } |
| |
| if (!is_supported_format(out)) { |
| syslog(LOG_ERR, "Invalid output format %d", out->format); |
| cras_fmt_conv_destroy(&conv); |
| return NULL; |
| } |
| |
| /* 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_ERR, "Should never reachable"); |
| break; |
| } |
| } |
| 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_ERR, "Should never reachable"); |
| break; |
| } |
| } |
| |
| /* 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 = mono_to_stereo; |
| } else if (in->num_channels == 1 && out->num_channels == 6) { |
| conv->channel_converter = mono_to_51; |
| } else if (in->num_channels == 2 && out->num_channels == 1) { |
| conv->channel_converter = stereo_to_mono; |
| } else if (in->num_channels == 2 && out->num_channels == 4) { |
| conv->channel_converter = stereo_to_quad; |
| } else if (in->num_channels == 4 && out->num_channels == 2) { |
| conv->channel_converter = quad_to_stereo; |
| } else if (in->num_channels == 2 && out->num_channels == 6) { |
| conv->channel_converter = stereo_to_51; |
| } else if (in->num_channels == 6 && |
| (out->num_channels == 2 || out->num_channels == 4)) { |
| 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; |
| if (out->num_channels == 4) { |
| surround51_to_quad_downmix_mtx( |
| conv->ch_conv_mtx, |
| conv->in_fmt.channel_layout); |
| } else { |
| surround51_to_stereo_downmix_mtx( |
| conv->ch_conv_mtx, |
| conv->in_fmt.channel_layout); |
| } |
| } else { |
| if (out->num_channels == 4) |
| conv->channel_converter = _51_to_quad; |
| else |
| conv->channel_converter = _51_to_stereo; |
| } |
| } else if (in->num_channels <= 8 && out->num_channels <= 8) { |
| // For average channel counts mix from all to all. |
| syslog(LOG_WARNING, |
| "Using all_to_all map for %zu to %zu", |
| in->num_channels, out->num_channels); |
| conv->channel_converter = default_all_to_all; |
| } else { |
| syslog(LOG_WARNING, |
| "Using some_to_some channel map for %zu to %zu", |
| in->num_channels, out->num_channels); |
| conv->channel_converter = default_some_to_some; |
| } |
| } 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." |
| "Fallback to default_all_to_all."); |
| conv->channel_converter = default_all_to_all; |
| } else { |
| 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. |
| * |
| * Note: intended to give both src_rate and dst_rate the same value |
| * (i.e. out->frame_rate). They will be updated in runtime in |
| * update_estimated_rate() when the audio thread wants to adjust the |
| * rate for inaccurate device consumption rate. |
| */ |
| 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 **convp) |
| { |
| unsigned i; |
| struct cras_fmt_conv *conv = *convp; |
| |
| 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); |
| *convp = NULL; |
| } |
| |
| 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; |
| |
| /* |
| * Skip remix for non S16_LE format. |
| * TODO(tzungbi): support 24 bits remix convert. |
| */ |
| if (fmt->format != SND_PCM_FORMAT_S16_LE) |
| return; |
| |
| /* 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] = s16_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); |
| assert(*in_frames <= conv->tmp_buf_frames); |
| |
| 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; |
| /* |
| * However if the limit frames count is less than |
| * |out_rate / in_rate|, the final limit value could be |
| * rounded to zero so it confuses linear resampler to |
| * do nothing. Make sure it's non-zero in that case. |
| */ |
| if (resample_limit == 0) |
| resample_limit = 1; |
| } |
| |
| 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, buffers[buf_idx], fr_in, |
| 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; |
| |
| /* When buffer sizes are small, there's a corner case that |
| * speex library resamples 0 frame to N-1 frames, where N |
| * is the integer ratio of output and input rate. For example, |
| * 16KHz to 48KHz. In this case fmt_conv should claim zero |
| * frames processed, instead of using the linear resampler |
| * processed frames count. Otherwise there will be a frame |
| * leak and, if accumulated, causes delay in multiple devices |
| * use case. |
| */ |
| if (conv->speex_state && (fr_in == 0)) |
| *in_frames = 0; |
| } 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; |
| } |