Create rate converter plugin SDK

Created a new rate converter plugin SDK.
A rate converter can be replaced as an extra plugin now.
The default rate converter is a built-in linear converter.

You can find a sample external converter in alsa-plugins package.
This commit is contained in:
Takashi Iwai 2006-04-06 18:37:55 +02:00
parent 8f605df24a
commit 33d69ef33b
8 changed files with 879 additions and 461 deletions

View file

@ -10,7 +10,7 @@ alsainclude_HEADERS = asoundlib.h asoundef.h \
seq_event.h seq.h seqmid.h seq_midi_event.h \
conv.h instr.h iatomic.h \
alisp.h pcm_external.h pcm_ioplug.h pcm_extplug.h \
control_external.h
pcm_rate.h control_external.h
noinst_HEADERS = alsa sys.h search.h list.h aserver.h local.h alsa-symbols.h

View file

@ -156,7 +156,7 @@ int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name,
* Rate plugin for linear formats
*/
int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
snd_pcm_format_t sformat, unsigned int srate,
snd_pcm_format_t sformat, unsigned int srate, const char *converter,
snd_pcm_t *slave, int close_slave);
int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
snd_config_t *root, snd_config_t *conf,

117
include/pcm_rate.h Normal file
View file

@ -0,0 +1,117 @@
/**
* \file include/pcm_rate.h
* \brief External Rate-Converter-Plugin SDK
* \author Takashi Iwai <tiwai@suse.de>
* \date 2006
*
* External Rate-Converter-Plugin SDK
*/
/*
* ALSA external PCM rate-converter plugin SDK (draft version)
*
* Copyright (c) 2006 Takashi Iwai <tiwai@suse.de>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#ifndef __ALSA_PCM_RATE_H
#define __ALSA_PCM_RATE_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* Protocol version
*/
#define SND_PCM_RATE_PLUGIN_VERSION 0x010001
/** hw_params information for a single side */
typedef struct snd_pcm_rate_side_info {
snd_pcm_format_t format;
unsigned int rate;
snd_pcm_uframes_t buffer_size;
snd_pcm_uframes_t period_size;
} snd_pcm_rate_side_info_t;
/** hw_params information */
typedef struct snd_pcm_rate_info {
struct snd_pcm_rate_side_info in;
struct snd_pcm_rate_side_info out;
unsigned int channels;
} snd_pcm_rate_info_t;
/** Callback table of rate-converter */
typedef struct snd_pcm_rate_ops {
/**
* close the converter; optional
*/
void (*close)(void *obj);
/**
* initialize the converter, called at hw_params
*/
int (*init)(void *obj, snd_pcm_rate_info_t *info);
/**
* free the converter; optional
*/
void (*free)(void *obj);
/**
* reset the converter, called at prepare; optional
*/
void (*reset)(void *obj);
/**
* adjust the pitch, called at sw_params; optional
*/
int (*adjust_pitch)(void *obj, snd_pcm_rate_info_t *info);
/**
* convert the data
*/
void (*convert)(void *obj,
const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, unsigned int src_frames);
/**
* convert an s16 interleaved-data array; exclusive with convert
*/
void (*convert_s16)(void *obj, int16_t *dst, unsigned int dst_frames,
const int16_t *src, unsigned int src_frames);
/**
* compute the frame size for input
*/
snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames);
/**
* compute the frame size for output
*/
snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames);
} snd_pcm_rate_ops_t;
/** open function type */
typedef int (*snd_pcm_rate_open_func_t)(unsigned int version, void **objp,
snd_pcm_rate_ops_t *opsp);
/**
* Define the object entry for external PCM rate-converter plugins
*/
#define SND_PCM_RATE_PLUGIN_ENTRY(name) _snd_pcm_rate_##name##_open
#ifdef __cplusplus
}
#endif
#endif /* __ALSA_PCM_RATE_H */

View file

@ -29,7 +29,7 @@ if BUILD_PCM_PLUGIN_ADPCM
libpcm_la_SOURCES += pcm_adpcm.c
endif
if BUILD_PCM_PLUGIN_RATE
libpcm_la_SOURCES += pcm_rate.c
libpcm_la_SOURCES += pcm_rate.c pcm_rate_linear.c
endif
if BUILD_PCM_PLUGIN_PLUG
libpcm_la_SOURCES += pcm_plug.c

View file

@ -755,6 +755,8 @@ int snd_pcm_hw_open_fd(snd_pcm_t **pcmp, const char *name, int fd, int mmap_emul
int snd_pcm_wait_nocheck(snd_pcm_t *pcm, int timeout);
const char *snd_pcm_rate_get_default_converter(snd_config_t *root);
#define SND_PCM_HW_PARBIT_ACCESS (1U << SND_PCM_HW_PARAM_ACCESS)
#define SND_PCM_HW_PARBIT_FORMAT (1U << SND_PCM_HW_PARAM_FORMAT)
#define SND_PCM_HW_PARBIT_SUBFORMAT (1U << SND_PCM_HW_PARAM_SUBFORMAT)

View file

@ -50,6 +50,7 @@ typedef struct {
snd_pcm_format_t sformat;
int schannels;
int srate;
const char *rate_converter;
enum snd_pcm_plug_route_policy route_policy;
snd_pcm_route_ttable_entry_t *ttable;
int ttable_ok, ttable_last;
@ -359,7 +360,8 @@ static int snd_pcm_plug_change_rate(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_plu
if (clt->rate == slv->rate)
return 0;
assert(snd_pcm_format_linear(slv->format));
err = snd_pcm_rate_open(new, NULL, slv->format, slv->rate, plug->gen.slave, plug->gen.slave != plug->req_slave);
err = snd_pcm_rate_open(new, NULL, slv->format, slv->rate, plug->rate_converter,
plug->gen.slave, plug->gen.slave != plug->req_slave);
if (err < 0)
return err;
slv->access = clt->access;
@ -1013,6 +1015,7 @@ static snd_pcm_ops_t snd_pcm_plug_ops = {
int snd_pcm_plug_open(snd_pcm_t **pcmp,
const char *name,
snd_pcm_format_t sformat, int schannels, int srate,
const char *rate_converter,
enum snd_pcm_plug_route_policy route_policy,
snd_pcm_route_ttable_entry_t *ttable,
unsigned int tt_ssize,
@ -1030,6 +1033,7 @@ int snd_pcm_plug_open(snd_pcm_t **pcmp,
plug->sformat = sformat;
plug->schannels = schannels;
plug->srate = srate;
plug->rate_converter = rate_converter;
plug->gen.slave = plug->req_slave = slave;
plug->gen.close_slave = close_slave;
plug->route_policy = route_policy;
@ -1087,6 +1091,8 @@ pcm.name {
SCHANNEL REAL # route value (0.0 - 1.0)
}
}
rate_converter STR # type of rate converter
# default value is taken from defaults.pcm.rate_converter
}
\endcode
@ -1127,6 +1133,8 @@ int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name,
unsigned int cused, sused;
snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
int schannels = -1, srate = -1;
const char *rate_converter = NULL;
snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id;
@ -1166,6 +1174,17 @@ int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name,
route_policy = PLUG_ROUTE_POLICY_DUP;
continue;
}
#endif
#ifdef BUILD_PCM_PLUGIN_RATE
if (strcmp(id, "rate_converter") == 0) {
const char *str;
if ((err = snd_config_get_string(n, &str)) < 0) {
SNDERR("Invalid type for %s", id);
return -EINVAL;
}
rate_converter = str;
continue;
}
#endif
SNDERR("Unknown field %s", id);
return -EINVAL;
@ -1200,11 +1219,16 @@ int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name,
}
#endif
#ifdef BUILD_PCM_PLUGIN_RATE
if (! rate_converter)
rate_converter = snd_pcm_rate_get_default_converter(root);
#endif
err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
snd_config_delete(sconf);
if (err < 0)
return err;
err = snd_pcm_plug_open(pcmp, name, sformat, schannels, srate,
err = snd_pcm_plug_open(pcmp, name, sformat, schannels, srate, rate_converter,
route_policy, ttable, ssize, cused, sused, spcm, 1);
if (err < 0)
snd_pcm_close(spcm);

View file

@ -29,8 +29,10 @@
*/
#include <inttypes.h>
#include <byteswap.h>
#include <dlfcn.h>
#include "pcm_local.h"
#include "pcm_plugin.h"
#include "pcm_rate.h"
#include "iatomic.h"
#include "plugin_ops.h"
@ -46,27 +48,8 @@ const char *_snd_module_pcm_rate = "";
#ifndef DOC_HIDDEN
/* LINEAR_DIV needs to be large enough to handle resampling from 192000 -> 8000 */
#define LINEAR_DIV_SHIFT 19
#define LINEAR_DIV (1<<LINEAR_DIV_SHIFT)
enum rate_type {
RATE_TYPE_LINEAR, /* linear interpolation */
RATE_TYPE_BANDLIMIT, /* bandlimited interpolation */
RATE_TYPE_POLYPHASE, /* polyphase resampling */
};
typedef struct _snd_pcm_rate snd_pcm_rate_t;
typedef void (*rate_f)(const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset,
snd_pcm_uframes_t dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset,
snd_pcm_uframes_t src_frames,
unsigned int channels,
snd_pcm_rate_t *rate);
struct _snd_pcm_rate {
snd_pcm_generic_t gen;
snd_atomic_write_t watom;
@ -74,290 +57,21 @@ struct _snd_pcm_rate {
snd_pcm_uframes_t last_commit_ptr;
snd_pcm_uframes_t orig_avail_min;
snd_pcm_sw_params_t sw_params;
enum rate_type type;
unsigned int get_idx;
unsigned int put_idx;
unsigned int pitch;
unsigned int pitch_shift; /* for expand interpolation */
rate_f func;
snd_pcm_format_t sformat;
unsigned int srate;
snd_pcm_channel_area_t *pareas; /* areas for splitted period (rate pcm) */
snd_pcm_channel_area_t *sareas; /* areas for splitted period (slave pcm) */
int16_t *old_sample;
snd_pcm_rate_info_t info;
void *obj;
snd_pcm_rate_ops_t ops;
unsigned int get_idx;
unsigned int put_idx;
int16_t *src_buf;
int16_t *dst_buf;
};
static void snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames,
unsigned int channels,
snd_pcm_rate_t *rate)
{
#define GET16_LABELS
#define PUT16_LABELS
#include "plugin_ops.h"
#undef GET16_LABELS
#undef PUT16_LABELS
void *get = get16_labels[rate->get_idx];
void *put = put16_labels[rate->put_idx];
unsigned int get_threshold = rate->pitch;
unsigned int channel;
snd_pcm_uframes_t src_frames1;
snd_pcm_uframes_t dst_frames1;
int16_t sample = 0;
unsigned int pos;
for (channel = 0; channel < channels; ++channel) {
const snd_pcm_channel_area_t *src_area = &src_areas[channel];
const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
const char *src;
char *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample;
int old_weight, new_weight;
src = snd_pcm_channel_area_addr(src_area, src_offset);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
src_step = snd_pcm_channel_area_step(src_area);
dst_step = snd_pcm_channel_area_step(dst_area);
src_frames1 = 0;
dst_frames1 = 0;
new_sample = rate->old_sample[channel];
pos = get_threshold;
while (dst_frames1 < dst_frames) {
if (pos >= get_threshold) {
pos -= get_threshold;
old_sample = new_sample;
if (src_frames1 < src_frames) {
goto *get;
#define GET16_END after_get
#include "plugin_ops.h"
#undef GET16_END
after_get:
new_sample = sample;
}
}
new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
old_weight = 0x10000 - new_weight;
sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
goto *put;
#define PUT16_END after_put
#include "plugin_ops.h"
#undef PUT16_END
after_put:
dst += dst_step;
dst_frames1++;
pos += LINEAR_DIV;
if (pos >= get_threshold) {
src += src_step;
src_frames1++;
}
}
rate->old_sample[channel] = new_sample;
}
}
/* optimized version for S16 format */
static void snd_pcm_rate_expand_s16(const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames,
unsigned int channels,
snd_pcm_rate_t *rate)
{
unsigned int channel;
snd_pcm_uframes_t src_frames1;
snd_pcm_uframes_t dst_frames1;
unsigned int get_threshold = rate->pitch;
unsigned int pos;
for (channel = 0; channel < channels; ++channel) {
const snd_pcm_channel_area_t *src_area = &src_areas[channel];
const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
const int16_t *src;
int16_t *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample;
int old_weight, new_weight;
src = snd_pcm_channel_area_addr(src_area, src_offset);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
src_step = snd_pcm_channel_area_step(src_area) >> 1;
dst_step = snd_pcm_channel_area_step(dst_area) >> 1;
src_frames1 = 0;
dst_frames1 = 0;
new_sample = rate->old_sample[channel];
pos = get_threshold;
while (dst_frames1 < dst_frames) {
if (pos >= get_threshold) {
pos -= get_threshold;
old_sample = new_sample;
if (src_frames1 < src_frames)
new_sample = *src;
}
new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
old_weight = 0x10000 - new_weight;
*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
dst += dst_step;
dst_frames1++;
pos += LINEAR_DIV;
if (pos >= get_threshold) {
src += src_step;
src_frames1++;
}
}
rate->old_sample[channel] = new_sample;
}
}
static void snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames,
unsigned int channels,
snd_pcm_rate_t *rate)
{
#define GET16_LABELS
#define PUT16_LABELS
#include "plugin_ops.h"
#undef GET16_LABELS
#undef PUT16_LABELS
void *get = get16_labels[rate->get_idx];
void *put = put16_labels[rate->put_idx];
unsigned int get_increment = rate->pitch;
unsigned int channel;
snd_pcm_uframes_t src_frames1;
snd_pcm_uframes_t dst_frames1;
int16_t sample = 0;
unsigned int pos;
for (channel = 0; channel < channels; ++channel) {
const snd_pcm_channel_area_t *src_area = &src_areas[channel];
const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
const char *src;
char *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample = 0;
int old_weight, new_weight;
pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
src = snd_pcm_channel_area_addr(src_area, src_offset);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
src_step = snd_pcm_channel_area_step(src_area);
dst_step = snd_pcm_channel_area_step(dst_area);
src_frames1 = 0;
dst_frames1 = 0;
while (src_frames1 < src_frames) {
goto *get;
#define GET16_END after_get
#include "plugin_ops.h"
#undef GET16_END
after_get:
new_sample = sample;
src += src_step;
src_frames1++;
pos += get_increment;
if (pos >= LINEAR_DIV) {
pos -= LINEAR_DIV;
old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
new_weight = 0x10000 - old_weight;
sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
goto *put;
#define PUT16_END after_put
#include "plugin_ops.h"
#undef PUT16_END
after_put:
dst += dst_step;
dst_frames1++;
if (CHECK_SANITY(dst_frames1 > dst_frames)) {
SNDERR("dst_frames overflow");
break;
}
}
old_sample = new_sample;
}
}
}
/* optimized version for S16 format */
static void snd_pcm_rate_shrink_s16(const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames,
unsigned int channels,
snd_pcm_rate_t *rate)
{
unsigned int get_increment = rate->pitch;
unsigned int channel;
snd_pcm_uframes_t src_frames1;
snd_pcm_uframes_t dst_frames1;
unsigned int pos = 0;
for (channel = 0; channel < channels; ++channel) {
const snd_pcm_channel_area_t *src_area = &src_areas[channel];
const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
const int16_t *src;
int16_t *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample = 0;
int old_weight, new_weight;
pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
src = snd_pcm_channel_area_addr(src_area, src_offset);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
src_step = snd_pcm_channel_area_step(src_area) >> 1;
dst_step = snd_pcm_channel_area_step(dst_area) >> 1 ;
src_frames1 = 0;
dst_frames1 = 0;
while (src_frames1 < src_frames) {
new_sample = *src;
src += src_step;
src_frames1++;
pos += get_increment;
if (pos >= LINEAR_DIV) {
pos -= LINEAR_DIV;
old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
new_weight = 0x10000 - old_weight;
*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
dst += dst_step;
dst_frames1++;
if (CHECK_SANITY(dst_frames1 > dst_frames)) {
SNDERR("dst_frames overflow");
break;
}
}
old_sample = new_sample;
}
}
}
#endif /* DOC_HIDDEN */
static snd_pcm_sframes_t snd_pcm_rate_client_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
{
snd_pcm_rate_t *rate = pcm->private_data;
if (frames == 0)
return 0;
/* Round toward zero */
if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
return muldiv_near(frames, LINEAR_DIV, rate->pitch);
else
return muldiv_near(frames, rate->pitch, LINEAR_DIV);
}
static snd_pcm_sframes_t snd_pcm_rate_slave_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
{
snd_pcm_rate_t *rate = pcm->private_data;
/* Round toward zero */
if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
return muldiv_near(frames, rate->pitch, LINEAR_DIV);
else
return muldiv_near(frames, LINEAR_DIV, rate->pitch);
}
static int snd_pcm_rate_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
{
int err;
@ -516,9 +230,8 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
{
snd_pcm_rate_t *rate = pcm->private_data;
snd_pcm_t *slave = rate->gen.slave;
snd_pcm_format_t src_format, dst_format, pformat, sformat;
unsigned int src_rate, dst_rate, channels, pwidth, swidth, chn;
snd_pcm_uframes_t period_size, buffer_size;
snd_pcm_rate_side_info_t *sinfo, *cinfo;
unsigned int channels, cwidth, swidth, chn;
int err = snd_pcm_hw_params_slave(pcm, params,
snd_pcm_rate_hw_refine_cchange,
snd_pcm_rate_hw_refine_sprepare,
@ -528,85 +241,86 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
return err;
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
err = INTERNAL(snd_pcm_hw_params_get_format)(params, &src_format);
if (err < 0)
return err;
pformat = src_format;
dst_format = slave->format;
sformat = dst_format;
dst_rate = slave->rate;
err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &src_rate, 0);
cinfo = &rate->info.in;
sinfo = &rate->info.out;
} else {
sformat = src_format = slave->format;
err = INTERNAL(snd_pcm_hw_params_get_format)(params, &dst_format);
if (err < 0)
return err;
pformat = dst_format;
src_rate = slave->rate;
err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &dst_rate, 0);
sinfo = &rate->info.in;
cinfo = &rate->info.out;
}
err = INTERNAL(snd_pcm_hw_params_get_format)(params, &cinfo->format);
if (err < 0)
return err;
err = INTERNAL(snd_pcm_hw_params_get_period_size)(params, &period_size, 0);
err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &cinfo->rate, 0);
if (err < 0)
return err;
err = INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &buffer_size);
err = INTERNAL(snd_pcm_hw_params_get_period_size)(params, &cinfo->period_size, 0);
if (err < 0)
return err;
err = INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &cinfo->buffer_size);
if (err < 0)
return err;
err = INTERNAL(snd_pcm_hw_params_get_channels)(params, &channels);
if (err < 0)
return err;
rate->get_idx = snd_pcm_linear_get_index(src_format, SND_PCM_FORMAT_S16);
rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, dst_format);
if (src_rate < dst_rate) {
if (src_format == dst_format && src_format == SND_PCM_FORMAT_S16)
rate->func = snd_pcm_rate_expand_s16;
else
rate->func = snd_pcm_rate_expand;
/* pitch is get_threshold */
} else {
if (src_format == dst_format && src_format == SND_PCM_FORMAT_S16)
rate->func = snd_pcm_rate_shrink_s16;
else
rate->func = snd_pcm_rate_shrink;
/* pitch is get_increment */
}
rate->pitch = (((u_int64_t)dst_rate * LINEAR_DIV) + (src_rate / 2)) / src_rate;
rate->info.channels = channels;
sinfo->format = slave->format;
sinfo->rate = slave->rate;
sinfo->buffer_size = slave->buffer_size;
sinfo->period_size = slave->period_size;
if (CHECK_SANITY(rate->pareas)) {
SNDMSG("rate plugin already in use");
return -EBUSY;
}
#if 0
if ((buffer_size / period_size) * period_size == buffer_size &&
(slave->buffer_size / slave->period_size) * slave->period_size == slave->buffer_size)
return 0;
#endif
err = rate->ops.init(rate->obj, &rate->info);
if (err < 0)
return err;
rate->pareas = malloc(2 * channels * sizeof(*rate->pareas));
if (rate->pareas == NULL)
return -ENOMEM;
free(rate->old_sample);
rate->old_sample = malloc(sizeof(*rate->old_sample) * channels);
if (rate->old_sample == NULL)
return -ENOMEM;
pwidth = snd_pcm_format_physical_width(pformat);
swidth = snd_pcm_format_physical_width(sformat);
rate->pareas[0].addr = malloc(((pwidth * channels * period_size) / 8) +
((swidth * channels * slave->period_size) / 8));
if (rate->pareas[0].addr == NULL) {
free(rate->pareas);
return -ENOMEM;
}
goto error;
cwidth = snd_pcm_format_physical_width(cinfo->format);
swidth = snd_pcm_format_physical_width(sinfo->format);
rate->pareas[0].addr = malloc(((cwidth * channels * cinfo->period_size) / 8) +
((swidth * channels * sinfo->period_size) / 8));
if (rate->pareas[0].addr == NULL)
goto error;
rate->sareas = rate->pareas + channels;
rate->sareas[0].addr = (char *)rate->pareas[0].addr + ((pwidth * channels * period_size) / 8);
rate->sareas[0].addr = (char *)rate->pareas[0].addr + ((cwidth * channels * cinfo->period_size) / 8);
for (chn = 0; chn < channels; chn++) {
rate->pareas[chn].addr = rate->pareas[0].addr + (pwidth * chn * period_size) / 8;
rate->pareas[chn].addr = rate->pareas[0].addr + (cwidth * chn * cinfo->period_size) / 8;
rate->pareas[chn].first = 0;
rate->pareas[chn].step = pwidth;
rate->sareas[chn].addr = rate->sareas[0].addr + (swidth * chn * slave->period_size) / 8;
rate->pareas[chn].step = cwidth;
rate->sareas[chn].addr = rate->sareas[0].addr + (swidth * chn * sinfo->period_size) / 8;
rate->sareas[chn].first = 0;
rate->sareas[chn].step = swidth;
}
if (rate->ops.convert_s16) {
rate->get_idx = snd_pcm_linear_get_index(rate->info.in.format, SND_PCM_FORMAT_S16);
rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, rate->info.out.format);
free(rate->src_buf);
rate->src_buf = malloc(channels * rate->info.in.period_size * 2);
free(rate->dst_buf);
rate->dst_buf = malloc(channels * rate->info.out.period_size * 2);
if (! rate->src_buf || ! rate->dst_buf)
goto error;
}
return 0;
error:
if (rate->pareas) {
free(rate->pareas[0].addr);
free(rate->pareas);
rate->pareas = NULL;
}
if (rate->ops.free)
rate->ops.free(rate->obj);
return -ENOMEM;
}
static int snd_pcm_rate_hw_free(snd_pcm_t *pcm)
@ -618,8 +332,11 @@ static int snd_pcm_rate_hw_free(snd_pcm_t *pcm)
rate->pareas = NULL;
rate->sareas = NULL;
}
free(rate->old_sample);
rate->old_sample = NULL;
if (rate->ops.free)
rate->ops.free(rate->obj);
free(rate->src_buf);
free(rate->dst_buf);
rate->src_buf = rate->dst_buf = NULL;
return snd_pcm_hw_free(rate->gen.slave);
}
@ -649,86 +366,19 @@ static int snd_pcm_rate_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params)
rate->sw_params = *params;
sparams = &rate->sw_params;
if ((rate->pitch >= LINEAR_DIV ? 1 : 0) ^ (pcm->stream == SND_PCM_STREAM_CAPTURE ? 1 : 0)) {
boundary1 = pcm->buffer_size;
boundary2 = slave->buffer_size;
while (boundary2 * 2 <= LONG_MAX - slave->buffer_size) {
boundary1 *= 2;
boundary2 *= 2;
}
} else {
boundary1 = pcm->buffer_size;
boundary2 = slave->buffer_size;
while (boundary1 * 2 <= LONG_MAX - pcm->buffer_size) {
boundary1 *= 2;
boundary2 *= 2;
}
boundary1 = pcm->buffer_size;
boundary2 = slave->buffer_size;
while (boundary1 * 2 <= LONG_MAX - pcm->buffer_size &&
boundary2 * 2 <= LONG_MAX - slave->buffer_size) {
boundary1 *= 2;
boundary2 *= 2;
}
params->boundary = boundary1;
sparams->boundary = boundary2;
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
rate->pitch = (((u_int64_t)slave->period_size * LINEAR_DIV) + (pcm->period_size/2) ) / pcm->period_size;
do {
snd_pcm_uframes_t cframes,cframes_test;
cframes = snd_pcm_rate_client_frames(pcm, slave->period_size );
if (cframes == pcm->period_size )
break;
if (cframes > pcm->period_size ) {
rate->pitch++;
cframes_test = snd_pcm_rate_client_frames(pcm, slave->period_size );
if (cframes_test < pcm->period_size ) {
SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size, pcm->period_size);
return -EIO;
}
} else {
rate->pitch--;
cframes_test = snd_pcm_rate_client_frames(pcm, slave->period_size );
if (cframes_test > pcm->period_size) {
SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size, pcm->period_size);
return -EIO;
}
}
} while (1);
if ((snd_pcm_uframes_t)snd_pcm_rate_client_frames(pcm, slave->period_size ) != pcm->period_size) {
SNDERR("invalid slave period_size %ld for pcm period_size %ld",
slave->period_size, pcm->period_size);
return -EIO;
}
} else {
rate->pitch = (((u_int64_t)pcm->period_size * LINEAR_DIV) + (slave->period_size/2) ) / slave->period_size;
do {
snd_pcm_uframes_t cframes;
cframes = snd_pcm_rate_slave_frames(pcm, pcm->period_size );
if (cframes == slave->period_size )
break;
if (cframes > slave->period_size ) {
rate->pitch++;
if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size ) < slave->period_size ) {
SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size, pcm->period_size);
return -EIO;
}
} else {
rate->pitch--;
if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size) > slave->period_size ) {
SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size , pcm->period_size );
return -EIO;
}
}
} while (1);
if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size ) != slave->period_size) {
SNDERR("invalid pcm period_size %ld for slave period_size",
pcm->period_size, slave->period_size);
return -EIO;
}
}
if (rate->pitch >= LINEAR_DIV) {
/* shift for expand linear interpolation */
rate->pitch_shift = 0;
while ((rate->pitch >> rate->pitch_shift) >= (1 << 16))
rate->pitch_shift++;
}
if (rate->ops.adjust_pitch)
rate->ops.adjust_pitch(rate->obj, &rate->info);
recalc(pcm, &sparams->avail_min);
rate->orig_avail_min = sparams->avail_min;
recalc(pcm, &sparams->xfer_align);
@ -754,20 +404,113 @@ static int snd_pcm_rate_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params)
static int snd_pcm_rate_init(snd_pcm_t *pcm)
{
snd_pcm_rate_t *rate = pcm->private_data;
switch (rate->type) {
case RATE_TYPE_LINEAR:
/* for expand */
if (rate->old_sample)
memset(rate->old_sample, 0, sizeof(*rate->old_sample) * pcm->channels);
break;
default:
assert(0);
}
if (rate->ops.reset)
rate->ops.reset(rate->obj);
rate->last_commit_ptr = 0;
return 0;
}
static inline int
static void convert_to_s16(snd_pcm_rate_t *rate, int16_t *buf,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset, unsigned int frames,
unsigned int channels)
{
#define GET16_LABELS
#include "plugin_ops.h"
#undef GET16_LABELS
void *get = get16_labels[rate->get_idx];
const char *src;
int16_t sample;
const char *srcs[channels];
int src_step[channels];
unsigned int c;
for (c = 0; c < channels; c++) {
srcs[c] = snd_pcm_channel_area_addr(areas + c, offset);
src_step[c] = snd_pcm_channel_area_step(areas + c);
}
while (frames--) {
for (c = 0; c < channels; c++) {
src = srcs[c];
goto *get;
#define GET16_END after_get
#include "plugin_ops.h"
#undef GET16_END
after_get:
*buf++ = sample;
srcs[c] += src_step[c];
}
}
}
static void convert_from_s16(snd_pcm_rate_t *rate, const int16_t *buf,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset, unsigned int frames,
unsigned int channels)
{
#define PUT16_LABELS
#include "plugin_ops.h"
#undef PUT16_LABELS
void *put = put16_labels[rate->put_idx];
char *dst;
int16_t sample;
char *dsts[channels];
int dst_step[channels];
unsigned int c;
for (c = 0; c < channels; c++) {
dsts[c] = snd_pcm_channel_area_addr(areas + c, offset);
dst_step[c] = snd_pcm_channel_area_step(areas + c);
}
while (frames--) {
for (c = 0; c < channels; c++) {
dst = dsts[c];
sample = *buf++;
goto *put;
#define PUT16_END after_put
#include "plugin_ops.h"
#undef PUT16_END
after_put:
dsts[c] += dst_step[c];
}
}
}
static void do_convert(const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, unsigned int src_frames,
unsigned int channels,
snd_pcm_rate_t *rate)
{
if (rate->ops.convert_s16) {
const int16_t *src;
int16_t *dst;
if (! rate->src_buf)
src = src_areas->addr + src_offset * 2 * channels;
else {
convert_to_s16(rate, rate->src_buf, src_areas, src_offset,
src_frames, channels);
src = rate->src_buf;
}
if (! rate->dst_buf)
dst = dst_areas->addr + dst_offset * 2 * channels;
else
dst = rate->dst_buf;
rate->ops.convert_s16(rate->obj, dst, dst_frames, src, src_frames);
if (dst == rate->dst_buf)
convert_from_s16(rate, rate->dst_buf, dst_areas, dst_offset,
dst_frames, channels);
} else {
rate->ops.convert(rate->obj, dst_areas, dst_offset, dst_frames,
src_areas, src_offset, src_frames);
}
}
static inline void
snd_pcm_rate_write_areas1(snd_pcm_t *pcm,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset,
@ -775,13 +518,12 @@ snd_pcm_rate_write_areas1(snd_pcm_t *pcm,
snd_pcm_uframes_t slave_offset)
{
snd_pcm_rate_t *rate = pcm->private_data;
rate->func(slave_areas, slave_offset, rate->gen.slave->period_size,
do_convert(slave_areas, slave_offset, rate->gen.slave->period_size,
areas, offset, pcm->period_size,
pcm->channels, rate);
return 0;
}
static inline int
static inline void
snd_pcm_rate_read_areas1(snd_pcm_t *pcm,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset,
@ -789,10 +531,9 @@ snd_pcm_rate_read_areas1(snd_pcm_t *pcm,
snd_pcm_uframes_t slave_offset)
{
snd_pcm_rate_t *rate = pcm->private_data;
rate->func(areas, offset, pcm->period_size,
do_convert(areas, offset, pcm->period_size,
slave_areas, slave_offset, rate->gen.slave->period_size,
pcm->channels, rate);
return 0;
}
static inline snd_pcm_sframes_t snd_pcm_rate_move_applptr(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
@ -827,8 +568,12 @@ static inline snd_pcm_sframes_t snd_pcm_rate_move_applptr(snd_pcm_t *pcm, snd_pc
slave_appl_ptr = *slave->appl.ptr;
rate->appl_ptr =
(slave_appl_ptr / rate->gen.slave->period_size) * pcm->period_size +
snd_pcm_rate_client_frames(pcm, slave_appl_ptr % rate->gen.slave->period_size) +
orig_appl_ptr % pcm->period_size;
if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
rate->appl_ptr += rate->ops.input_frames(rate->obj, slave_appl_ptr % rate->gen.slave->period_size);
else
rate->appl_ptr += rate->ops.output_frames(rate->obj, slave_appl_ptr % rate->gen.slave->period_size);
diff = orig_appl_ptr - rate->appl_ptr;
if (diff < -(snd_pcm_sframes_t)(slave->boundary / 2)) {
diff = (slave->boundary - rate->appl_ptr) + orig_appl_ptr;
@ -855,7 +600,7 @@ static inline void snd_pcm_rate_sync_hwptr(snd_pcm_t *pcm)
*/
rate->hw_ptr =
(slave_hw_ptr / rate->gen.slave->period_size) * pcm->period_size +
snd_pcm_rate_client_frames(pcm, slave_hw_ptr % rate->gen.slave->period_size);
rate->ops.output_frames(rate->obj, slave_hw_ptr % rate->gen.slave->period_size);
}
static int snd_pcm_rate_hwsync(snd_pcm_t *pcm)
@ -1323,7 +1068,7 @@ static int snd_pcm_rate_drain(snd_pcm_t *pcm)
spsize = rate->gen.slave->period_size;
} else {
psize = size;
spsize = snd_pcm_rate_slave_frames(pcm, size);
spsize = rate->ops.output_frames(rate->obj, size);
if (! spsize)
break;
}
@ -1357,15 +1102,16 @@ static int snd_pcm_rate_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
status->delay = snd_pcm_mmap_playback_hw_avail(pcm);
status->avail = snd_pcm_mmap_playback_avail(pcm);
status->avail_max = rate->ops.input_frames(rate->obj, status->avail_max);
} else {
status->delay = snd_pcm_mmap_capture_hw_avail(pcm);
status->avail = snd_pcm_mmap_capture_avail(pcm);
status->avail_max = rate->ops.output_frames(rate->obj, status->avail_max);
}
if (!snd_atomic_read_ok(&ratom)) {
snd_atomic_read_wait(&ratom);
goto _again;
}
status->avail_max = snd_pcm_rate_client_frames(pcm, (snd_pcm_sframes_t) status->avail_max);
return 0;
}
@ -1387,6 +1133,15 @@ static void snd_pcm_rate_dump(snd_pcm_t *pcm, snd_output_t *out)
snd_pcm_dump(rate->gen.slave, out);
}
static int snd_pcm_rate_close(snd_pcm_t *pcm)
{
snd_pcm_rate_t *rate = pcm->private_data;
if (rate->ops.close)
rate->ops.close(rate->obj);
return snd_pcm_generic_close(pcm);
}
static snd_pcm_fast_ops_t snd_pcm_rate_fast_ops = {
.status = snd_pcm_rate_status,
.state = snd_pcm_generic_state,
@ -1413,7 +1168,7 @@ static snd_pcm_fast_ops_t snd_pcm_rate_fast_ops = {
};
static snd_pcm_ops_t snd_pcm_rate_ops = {
.close = snd_pcm_generic_close,
.close = snd_pcm_rate_close,
.info = snd_pcm_generic_info,
.hw_refine = snd_pcm_rate_hw_refine,
.hw_params = snd_pcm_rate_hw_params,
@ -1427,6 +1182,22 @@ static snd_pcm_ops_t snd_pcm_rate_ops = {
.munmap = snd_pcm_generic_munmap,
};
/**
* \brief Get a default converter string
* \param root Root configuration node
* \retval A const string if found, or NULL
*/
const char *snd_pcm_rate_get_default_converter(snd_config_t *root)
{
snd_config_t *n;
/* look for default definition */
if (snd_config_search(root, "defaults.pcm.rate_converter", &n) >= 0) {
const char *str;
if (snd_config_get_string(n, &str) >= 0)
return str;
}
return NULL;
}
/**
* \brief Creates a new rate PCM
@ -1434,6 +1205,7 @@ static snd_pcm_ops_t snd_pcm_rate_ops = {
* \param name Name of PCM
* \param sformat Slave format
* \param srate Slave rate
* \param type SRC type string
* \param slave Slave PCM handle
* \param close_slave When set, the slave PCM handle is closed with copy PCM
* \retval zero on success otherwise a negative error code
@ -1441,11 +1213,18 @@ static snd_pcm_ops_t snd_pcm_rate_ops = {
* of compatibility reasons. The prototype might be freely
* changed in future.
*/
int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, unsigned int srate, snd_pcm_t *slave, int close_slave)
int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat,
unsigned int srate, const char *type, snd_pcm_t *slave, int close_slave)
{
snd_pcm_t *pcm;
snd_pcm_rate_t *rate;
snd_pcm_rate_open_func_t open_func;
char open_name[64];
int err;
#ifndef PIC
extern int SND_PCM_RATE_PLUGIN_ENTRY(linear) (unsigned int version, void **objp, snd_pcm_rate_ops_t *ops);
#endif
assert(pcmp && slave);
if (sformat != SND_PCM_FORMAT_UNKNOWN &&
snd_pcm_format_linear(sformat) != 1)
@ -1456,16 +1235,60 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sform
}
rate->gen.slave = slave;
rate->gen.close_slave = close_slave;
rate->type = RATE_TYPE_LINEAR;
rate->srate = srate;
rate->sformat = sformat;
snd_atomic_write_init(&rate->watom);
if (! type || ! *type)
type = "linear";
#ifdef PIC
snprintf(open_name, sizeof(open_name), "_snd_pcm_rate_%s_open", type);
open_func = snd_dlobj_cache_lookup(open_name);
if (! open_func) {
void *h;
char lib_name[128], *lib = NULL;
if (strcmp(type, "linear")) {
snprintf(lib_name, sizeof(lib_name),
"%s/libasound_module_rate_%s.so", PKGLIBDIR, type);
lib = lib_name;
}
h = snd_dlopen(lib, RTLD_NOW);
if (! h) {
SNDERR("Cannot open library for type %s", type);
free(rate);
return -ENOENT;
}
open_func = dlsym(h, open_name);
if (! open_func) {
SNDERR("Cannot find function %s", open_name);
snd_dlclose(h);
free(rate);
return -ENOENT;
}
snd_dlobj_cache_add(open_name, h, open_func);
}
#else
open_func = SND_PCM_RATE_PLUGIN_ENTRY(linear);
#endif
err = snd_pcm_new(&pcm, SND_PCM_TYPE_RATE, name, slave->stream, slave->mode);
if (err < 0) {
free(rate);
return err;
}
err = open_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops);
if (err < 0) {
snd_pcm_close(pcm);
return err;
}
if (! rate->ops.init || ! (rate->ops.convert || rate->ops.convert_s16) ||
! rate->ops.input_frames || ! rate->ops.output_frames) {
SNDERR("Inproper rate plugin %s initialization", type);
snd_pcm_close(pcm);
return err;
}
pcm->ops = &snd_pcm_rate_ops;
pcm->fast_ops = &snd_pcm_rate_fast_ops;
pcm->private_data = rate;
@ -1497,6 +1320,8 @@ pcm.name {
rate INT # Slave rate
[format STR] # Slave format
}
[converter STR] # Converter type, default is taken from
# defaults.pcm.rate_converter
}
\endcode
@ -1532,6 +1357,8 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
snd_config_t *slave = NULL, *sconf;
snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
int srate = -1;
const char *type = NULL;
snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id;
@ -1543,6 +1370,15 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
slave = n;
continue;
}
if (strcmp(id, "converter") == 0) {
const char *str;
if ((err = snd_config_get_string(n, &str)) < 0) {
SNDERR("invalid converter string");
return -EINVAL;
}
type = str;
continue;
}
SNDERR("Unknown field %s", id);
return -EINVAL;
}
@ -1550,6 +1386,10 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
SNDERR("slave is not defined");
return -EINVAL;
}
if (! type) {
}
err = snd_pcm_slave_conf(root, slave, &sconf, 2,
SND_PCM_HW_PARAM_FORMAT, 0, &sformat,
SND_PCM_HW_PARAM_RATE, SCONF_MANDATORY, &srate);
@ -1565,8 +1405,8 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
snd_config_delete(sconf);
if (err < 0)
return err;
err = snd_pcm_rate_open(pcmp, name,
sformat, (unsigned int) srate, spcm, 1);
err = snd_pcm_rate_open(pcmp, name, sformat, (unsigned int) srate,
type, spcm, 1);
if (err < 0)
snd_pcm_close(spcm);
return err;

435
src/pcm/pcm_rate_linear.c Normal file
View file

@ -0,0 +1,435 @@
/*
* Linear rate converter plugin
*
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
* 2004 by Jaroslav Kysela <perex@suse.cz>
* 2006 by Takashi Iwai <tiwai@suse.de>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <inttypes.h>
#include <byteswap.h>
#include "pcm_local.h"
#include "pcm_plugin.h"
#include "pcm_rate.h"
#include "plugin_ops.h"
/* LINEAR_DIV needs to be large enough to handle resampling from 192000 -> 8000 */
#define LINEAR_DIV_SHIFT 19
#define LINEAR_DIV (1<<LINEAR_DIV_SHIFT)
struct rate_linear {
unsigned int get_idx;
unsigned int put_idx;
unsigned int pitch;
unsigned int pitch_shift; /* for expand interpolation */
unsigned int channels;
int16_t *old_sample;
void (*func)(struct rate_linear *rate,
const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, unsigned int src_frames);
};
static snd_pcm_uframes_t input_frames(void *obj, snd_pcm_uframes_t frames)
{
struct rate_linear *rate = obj;
if (frames == 0)
return 0;
/* Round toward zero */
return muldiv_near(frames, LINEAR_DIV, rate->pitch);
}
static snd_pcm_uframes_t output_frames(void *obj, snd_pcm_uframes_t frames)
{
struct rate_linear *rate = obj;
if (frames == 0)
return 0;
/* Round toward zero */
return muldiv_near(frames, rate->pitch, LINEAR_DIV);
}
static void linear_expand(struct rate_linear *rate,
const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, unsigned int src_frames)
{
#define GET16_LABELS
#define PUT16_LABELS
#include "plugin_ops.h"
#undef GET16_LABELS
#undef PUT16_LABELS
void *get = get16_labels[rate->get_idx];
void *put = put16_labels[rate->put_idx];
unsigned int get_threshold = rate->pitch;
unsigned int channel;
unsigned int src_frames1;
unsigned int dst_frames1;
int16_t sample = 0;
unsigned int pos;
for (channel = 0; channel < rate->channels; ++channel) {
const snd_pcm_channel_area_t *src_area = &src_areas[channel];
const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
const char *src;
char *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample;
int old_weight, new_weight;
src = snd_pcm_channel_area_addr(src_area, src_offset);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
src_step = snd_pcm_channel_area_step(src_area);
dst_step = snd_pcm_channel_area_step(dst_area);
src_frames1 = 0;
dst_frames1 = 0;
new_sample = rate->old_sample[channel];
pos = get_threshold;
while (dst_frames1 < dst_frames) {
if (pos >= get_threshold) {
pos -= get_threshold;
old_sample = new_sample;
if (src_frames1 < src_frames) {
goto *get;
#define GET16_END after_get
#include "plugin_ops.h"
#undef GET16_END
after_get:
new_sample = sample;
}
}
new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
old_weight = 0x10000 - new_weight;
sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
goto *put;
#define PUT16_END after_put
#include "plugin_ops.h"
#undef PUT16_END
after_put:
dst += dst_step;
dst_frames1++;
pos += LINEAR_DIV;
if (pos >= get_threshold) {
src += src_step;
src_frames1++;
}
}
rate->old_sample[channel] = new_sample;
}
}
/* optimized version for S16 format */
static void linear_expand_s16(struct rate_linear *rate,
const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, unsigned int src_frames)
{
unsigned int channel;
unsigned int src_frames1;
unsigned int dst_frames1;
unsigned int get_threshold = rate->pitch;
unsigned int pos;
for (channel = 0; channel < rate->channels; ++channel) {
const snd_pcm_channel_area_t *src_area = &src_areas[channel];
const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
const int16_t *src;
int16_t *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample;
int old_weight, new_weight;
src = snd_pcm_channel_area_addr(src_area, src_offset);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
src_step = snd_pcm_channel_area_step(src_area) >> 1;
dst_step = snd_pcm_channel_area_step(dst_area) >> 1;
src_frames1 = 0;
dst_frames1 = 0;
new_sample = rate->old_sample[channel];
pos = get_threshold;
while (dst_frames1 < dst_frames) {
if (pos >= get_threshold) {
pos -= get_threshold;
old_sample = new_sample;
if (src_frames1 < src_frames)
new_sample = *src;
}
new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
old_weight = 0x10000 - new_weight;
*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
dst += dst_step;
dst_frames1++;
pos += LINEAR_DIV;
if (pos >= get_threshold) {
src += src_step;
src_frames1++;
}
}
rate->old_sample[channel] = new_sample;
}
}
static void linear_shrink(struct rate_linear *rate,
const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, unsigned int src_frames)
{
#define GET16_LABELS
#define PUT16_LABELS
#include "plugin_ops.h"
#undef GET16_LABELS
#undef PUT16_LABELS
void *get = get16_labels[rate->get_idx];
void *put = put16_labels[rate->put_idx];
unsigned int get_increment = rate->pitch;
unsigned int channel;
unsigned int src_frames1;
unsigned int dst_frames1;
int16_t sample = 0;
unsigned int pos;
for (channel = 0; channel < rate->channels; ++channel) {
const snd_pcm_channel_area_t *src_area = &src_areas[channel];
const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
const char *src;
char *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample = 0;
int old_weight, new_weight;
pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
src = snd_pcm_channel_area_addr(src_area, src_offset);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
src_step = snd_pcm_channel_area_step(src_area);
dst_step = snd_pcm_channel_area_step(dst_area);
src_frames1 = 0;
dst_frames1 = 0;
while (src_frames1 < src_frames) {
goto *get;
#define GET16_END after_get
#include "plugin_ops.h"
#undef GET16_END
after_get:
new_sample = sample;
src += src_step;
src_frames1++;
pos += get_increment;
if (pos >= LINEAR_DIV) {
pos -= LINEAR_DIV;
old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
new_weight = 0x10000 - old_weight;
sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
goto *put;
#define PUT16_END after_put
#include "plugin_ops.h"
#undef PUT16_END
after_put:
dst += dst_step;
dst_frames1++;
if (CHECK_SANITY(dst_frames1 > dst_frames)) {
SNDERR("dst_frames overflow");
break;
}
}
old_sample = new_sample;
}
}
}
/* optimized version for S16 format */
static void linear_shrink_s16(struct rate_linear *rate,
const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, unsigned int src_frames)
{
unsigned int get_increment = rate->pitch;
unsigned int channel;
unsigned int src_frames1;
unsigned int dst_frames1;
unsigned int pos = 0;
for (channel = 0; channel < rate->channels; ++channel) {
const snd_pcm_channel_area_t *src_area = &src_areas[channel];
const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
const int16_t *src;
int16_t *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample = 0;
int old_weight, new_weight;
pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
src = snd_pcm_channel_area_addr(src_area, src_offset);
dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
src_step = snd_pcm_channel_area_step(src_area) >> 1;
dst_step = snd_pcm_channel_area_step(dst_area) >> 1 ;
src_frames1 = 0;
dst_frames1 = 0;
while (src_frames1 < src_frames) {
new_sample = *src;
src += src_step;
src_frames1++;
pos += get_increment;
if (pos >= LINEAR_DIV) {
pos -= LINEAR_DIV;
old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
new_weight = 0x10000 - old_weight;
*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
dst += dst_step;
dst_frames1++;
if (CHECK_SANITY(dst_frames1 > dst_frames)) {
SNDERR("dst_frames overflow");
break;
}
}
old_sample = new_sample;
}
}
}
static void linear_convert(void *obj,
const snd_pcm_channel_area_t *dst_areas,
snd_pcm_uframes_t dst_offset, unsigned int dst_frames,
const snd_pcm_channel_area_t *src_areas,
snd_pcm_uframes_t src_offset, unsigned int src_frames)
{
struct rate_linear *rate = obj;
rate->func(rate, dst_areas, dst_offset, dst_frames,
src_areas, src_offset, src_frames);
}
static void linear_free(void *obj)
{
struct rate_linear *rate = obj;
free(rate->old_sample);
rate->old_sample = NULL;
}
static int linear_init(void *obj, snd_pcm_rate_info_t *info)
{
struct rate_linear *rate = obj;
rate->get_idx = snd_pcm_linear_get_index(info->in.format, SND_PCM_FORMAT_S16);
rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, info->out.format);
if (info->in.rate < info->out.rate) {
if (info->in.format == info->out.format && info->in.format == SND_PCM_FORMAT_S16)
rate->func = linear_expand_s16;
else
rate->func = linear_expand;
/* pitch is get_threshold */
} else {
if (info->in.format == info->out.format && info->in.format == SND_PCM_FORMAT_S16)
rate->func = linear_shrink_s16;
else
rate->func = linear_shrink;
/* pitch is get_increment */
}
rate->pitch = (((u_int64_t)info->out.rate * LINEAR_DIV) +
(info->in.rate / 2)) / info->in.rate;
rate->channels = info->channels;
free(rate->old_sample);
rate->old_sample = malloc(sizeof(*rate->old_sample) * rate->channels);
if (! rate->old_sample)
return -ENOMEM;
return 0;
}
static int linear_adjust_pitch(void *obj, snd_pcm_rate_info_t *info)
{
struct rate_linear *rate = obj;
snd_pcm_uframes_t cframes;
rate->pitch = (((u_int64_t)info->out.period_size * LINEAR_DIV) +
(info->in.period_size/2) ) / info->in.period_size;
cframes = input_frames(rate, info->out.period_size);
while (cframes != info->in.period_size) {
snd_pcm_uframes_t cframes_new;
if (cframes > info->in.period_size)
rate->pitch++;
else
rate->pitch--;
cframes_new = input_frames(rate, info->out.period_size);
if ((cframes > info->in.period_size && cframes_new < info->in.period_size) ||
(cframes < info->in.period_size && cframes_new > info->in.period_size)) {
SNDERR("invalid pcm period_size %ld -> %ld",
info->in.period_size, info->out.period_size);
return -EIO;
}
cframes = cframes_new;
}
if (rate->pitch >= LINEAR_DIV) {
/* shift for expand linear interpolation */
rate->pitch_shift = 0;
while ((rate->pitch >> rate->pitch_shift) >= (1 << 16))
rate->pitch_shift++;
}
return 0;
}
static void linear_reset(void *obj)
{
struct rate_linear *rate = obj;
/* for expand */
if (rate->old_sample)
memset(rate->old_sample, 0, sizeof(*rate->old_sample) * rate->channels);
}
static void linear_close(void *obj)
{
free(obj);
}
static snd_pcm_rate_ops_t linear_ops = {
.close = linear_close,
.init = linear_init,
.free = linear_free,
.reset = linear_reset,
.adjust_pitch = linear_adjust_pitch,
.convert = linear_convert,
.input_frames = input_frames,
.output_frames = output_frames,
};
int SND_PCM_RATE_PLUGIN_ENTRY(linear) (unsigned int version, void **objp, snd_pcm_rate_ops_t *ops)
{
struct rate_linear *rate;
if (version != SND_PCM_RATE_PLUGIN_VERSION) {
SNDERR("Invalid plugin version %x\n", version);
return -EINVAL;
}
rate = calloc(1, sizeof(*rate));
if (! rate)
return -ENOMEM;
*objp = rate;
*ops = linear_ops;
return 0;
}